Initial: folxplay.tv clone with JW promos, no AdSense

This commit is contained in:
OpenClaw Agent 2026-04-29 11:17:21 +02:00
commit d148152e0b
46 changed files with 1051 additions and 0 deletions

6
.dockerignore Normal file
View File

@ -0,0 +1,6 @@
.git
.gitignore
README.md
node_modules
*.log
.DS_Store

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
node_modules/
*.log
.DS_Store
.env

13
Dockerfile Normal file
View File

@ -0,0 +1,13 @@
FROM nginx:1.27-alpine
# Replace default nginx config
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Copy site
COPY public/ /usr/share/nginx/html/
EXPOSE 80
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD wget -qO- http://127.0.0.1/health || exit 1

69
README.md Normal file
View File

@ -0,0 +1,69 @@
# folxplay-clone
Clean rebuild of folxplay.tv — same look as the original, but **without
Google AdSense, Google Analytics, Funding Choices, or any third-party
tracking**. Channel videos play promo clips served from JW Player Cloud.
- **Live:** https://folxplay.biba.live
- **Original:** https://folxplay.tv
## Stack
- Vanilla JS SPA (no build step, no framework)
- Shaka Player 4.x for HLS playback (loaded from jsDelivr)
- nginx static hosting (Docker)
- JW Player Cloud (site `KR6L8XQG`) for the channel promos
## Channels and JW media IDs
| # | Channel | JW media ID | Duration |
|---|---|---|---|
| 1 | One Music TV | `CBM2fSnV` | 71 s |
| 2 | Zwei Music TV | `LZVUn3Qy` | 76 s |
| 3 | Folx Music TV | `UYU6dx3Q` | 60 s |
| 4 | Adria Music TV | `lI4SC7Do` | 53 s |
| 5 | Folx Slovenija | `aoCWwwSg` | — |
| 6 | One Adria Music TV | `IGOSgmvW` | 71 s |
To swap a promo for a live stream later, edit
`public/app.js → CHANNELS[].jw` (or replace the `JW_HLS()` helper with
the production stream URL).
## Local dev
```bash
cd public
python3 -m http.server 8080
# open http://localhost:8080
```
## Build & run
```bash
docker build -t folxplay-clone .
docker run -p 8080:80 folxplay-clone
```
## Deploy
Coolify reads this repo, builds the Docker image, and serves it on
`folxplay.biba.live` via the wildcard `*.biba.live` (already pointed at
openclaw1).
## What was removed compared to the original
- `<script src="googletagmanager.com/gtag/js?id=G-1HF0NT0FBR">` — GA4 tag
- `<script src="pagead2.googlesyndication.com/.../adsbygoogle.js">` — AdSense
- The `<ins class="adsbygoogle">` ad slot (slot id `8928661853`)
- Funding Choices / TCF consent dialog
- Cloudflare Insights beacon
- The `Top 10 this week` widget (depended on `console.folxplay.tv`)
- The Artists list (depended on `console.folxplay.tv`)
- The Channel PlayLog (depended on `console.folxplay.tv`)
## Notes
- The site has `Channels` widget instead of `Top 10 this week` — it lists
the six channels and clicking any of them plays its promo.
- The Channel PlayLog page shows a small note pointing back to the
production site.

52
nginx.conf Normal file
View File

@ -0,0 +1,52 @@
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
root /usr/share/nginx/html;
index index.html;
# Gzip
gzip on;
gzip_types text/plain text/css application/javascript application/json image/svg+xml;
gzip_min_length 256;
# Long cache for static assets
location ~* \.(?:png|jpg|jpeg|gif|ico|webp|svg|woff2?|ttf|eot|otf)$ {
expires 30d;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
location ~* \.(?:js|css)$ {
expires 7d;
add_header Cache-Control "public";
try_files $uri =404;
}
# Don't cache HTML / app shell
location = / {
add_header Cache-Control "no-store" always;
try_files /index.html =404;
}
location = /index.html {
add_header Cache-Control "no-store" always;
}
# SPA fallback: any unknown path -> index.html
location / {
try_files $uri $uri/ /index.html;
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Healthcheck
location = /health {
access_log off;
return 200 "ok\n";
add_header Content-Type text/plain;
}
}

348
public/app.js Normal file
View File

@ -0,0 +1,348 @@
// FolxPlay clone — vanilla JS SPA
// Each channel plays its JW Player promo (HLS from cdn.jwplayer.com).
// No tracking, no AdSense, no external API.
const JW_HLS = id => `https://cdn.jwplayer.com/manifests/${id}.m3u8`;
const JW_POSTER = id => `https://cdn.jwplayer.com/v2/media/${id}/poster.jpg?width=1280`;
const CHANNELS = [
{ key: 'one', name: 'One Music TV', color: '#a84d7e', logo: '/logos/one_mt_neg_b.png', alt: 'logo of One', jw: 'CBM2fSnV' },
{ key: 'zwei', name: 'Zwei Music TV', color: '#f36700', logo: '/logos/zwei_neg_b.png', alt: 'logo of Zwei', jw: 'LZVUn3Qy' },
{ key: 'mt', name: 'Folx Music TV', color: '#dc1d1d', logo: '/logos/mt_neg_b.png', alt: 'logo of Folx Music',jw: 'UYU6dx3Q' },
{ key: 'adria', name: 'Adria Music TV', color: '#30a6d1', logo: '/logos/adria_neg_b.png', alt: 'logo of Adria', jw: 'lI4SC7Do' },
{ key: 'folx-slo', name: 'Folx Slovenija', color: '#a8b539', logo: '/logos/folx_slo_neg_b.png', alt: 'logo of Folx Slovenija', jw: 'aoCWwwSg' },
{ key: 'one-adria', name: 'One Adria Music TV', color: '#a84d7e', logo: '/logos/one_neg_b.png', alt: 'logo of One Adria', jw: 'IGOSgmvW' },
];
const ABOUT = [
{ title: 'FOLX MUSIC TELEVISION', text: 'the whole area of folk music is covered with exclusive content. Over 90% of the content is created by our own FOLX NETWORK production.' },
{ title: 'ONE MUSIC TELEVISION', text: 'covers the area of the new German-speaking and world-famous pop and rock sound production and is additionally enriched with old German and world-famous classics of the 80s, 90s and 00s.' },
{ title: 'ZWEI MUSIC TELEVISION', text: 'covers the area of current pop music as well as old, well-known hits. Daily playlists are enriched with the current pop hits, Latin American music with a touch of reggaeton provides daily variety.' },
{ title: 'ADRIA MUSIC TELEVISION', text: 'is aimed at residents of Germany, Austria and Switzerland with Slovenian, Croatian, Bosnian, Herzegovinian, Serbian, Macedonian or Montenegrin roots. The traditional Dalmatian music, Klape, the most famous pop and rock hits are able to inspire everyone from the Adriatic region.' },
{ title: 'FOLX SLOVENIJA MUSIC TELEVISION',text: 'covers the area of European folk music with exclusive content, 90% of the broadcast content was created or is created in our production FOLX NETWORK.' },
{ title: 'ONE ADRIA MUSIC TELEVISION', text: 'covers the area of new fresh music from the Adriatic (SLO, CRO, BIH, SRB, MKD, MG), world pop and rock production and adds world hits of the 80s, 90s and 00s.' },
];
const PLAY_SVG = `<svg aria-hidden="true" viewBox="0 0 448 512"><path fill="currentColor" d="M424.4 214.7L72.4 6.6C43.8-10.3 0 6.1 0 47.9V464c0 37.5 40.7 60.1 72.4 41.3l352-208c31.4-18.5 31.5-64.1 0-82.6z"/></svg>`;
// pick a random background on each load
const bgs = ['bg_01','bg_02','bg_03','bg_04','bg_05','bg_06','bg_07'];
const BG_URL = `/backgrounds/${bgs[Math.floor(Math.random() * bgs.length)]}.jpg`;
// ---------- helpers ----------
function el(html) {
const t = document.createElement('template');
t.innerHTML = html.trim();
return t.content.firstElementChild;
}
function escapeHtml(s) {
return String(s ?? '').replace(/[&<>"']/g, c => ({
'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'
}[c]));
}
async function api() { return null; /* no backend */ }
// ---------- shell ----------
function renderShell() {
document.getElementById('root').innerHTML = `
<div class="mainBackground" style="background-image: url('${BG_URL}');">
<div class="nav">
<div class="nav__width">
<div class="logo">
<a href="/" data-link><img alt="Folx.network logo" src="/logos/folxnetwork.png"></a>
</div>
<div class="links">
<a href="/" data-link data-route="/">Home</a>
<a href="/artists" data-link data-route="/artists">Artists</a>
<a href="/folxnetwork" data-link data-route="/folxnetwork">About</a>
<a href="/policies" data-link data-route="/policies">Terms of service</a>
</div>
</div>
</div>
<main id="main"></main>
<footer class="siteFooter">© FOLX NETWORK · folxplay.biba.live</footer>
</div>
`;
// intercept <a data-link> for client-side navigation
document.body.addEventListener('click', (e) => {
const a = e.target.closest('a[data-link]');
if (!a) return;
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button !== 0) return;
e.preventDefault();
navigate(a.getAttribute('href'));
});
window.addEventListener('popstate', () => render(location.pathname));
}
function navigate(path) {
if (location.pathname !== path) history.pushState({}, '', path);
render(path);
}
// ---------- pages ----------
const pages = {
'/': renderHome,
'/artists': renderArtists,
'/folxnetwork': renderAbout,
'/policies': renderPolicies,
'/log': renderLog,
};
function render(path) {
const handler = pages[path] || renderNotFound;
// mark active link
document.querySelectorAll('.nav .links a').forEach(a => {
a.classList.toggle('active', a.getAttribute('data-route') === path);
});
handler();
}
// ===== HOME =====
let shakaInstance = null;
let currentChannel = null;
function renderHome() {
const main = document.getElementById('main');
main.innerHTML = `
<div class="pageHome">
<div class="split">
<div class="split__video">
<div id="player-area"></div>
<div id="currently-playing" class="currentlyPlaying" style="display:none;"></div>
<div class="playingStats">
<div class="playingStats__title">Channels</div>
<ol id="channel-list">
${CHANNELS.map(c => `
<li><a href="#" data-channel-link="${c.key}" style="color:#eee;border:none;">
${escapeHtml(c.name)}
</a></li>
`).join('')}
</ol>
</div>
</div>
<div class="split__channels">
<div class="channelList">
${CHANNELS.map(c => `
<a class="channelList__channel" href="#" data-channel="${c.key}">
<div class="channelList__channelIcon" style="--var-bgColor: ${c.color};">
${PLAY_SVG}
</div>
<div class="channelList__channelLogo">
<img src="${c.logo}" alt="${escapeHtml(c.alt)}">
</div>
</a>
`).join('')}
</div>
</div>
</div>
</div>
`;
showSelectChannelBox();
// channel click handlers (sidebar + bottom list)
main.querySelectorAll('[data-channel], [data-channel-link]').forEach(a => {
a.addEventListener('click', (e) => {
e.preventDefault();
const key = a.getAttribute('data-channel') || a.getAttribute('data-channel-link');
playChannel(key);
});
});
}
function showSelectChannelBox() {
document.getElementById('player-area').innerHTML = `
<div class="selectChannelBox">
<div>
<div class="lineHeader">FolxPlay Live TV</div>
<div class="lineSelectChannel">Select channel on right</div>
<div class="lineRandom" id="random-btn">Or ${PLAY_SVG} random channel</div>
</div>
</div>
`;
document.getElementById('random-btn')?.addEventListener('click', () => {
const ch = CHANNELS[Math.floor(Math.random() * CHANNELS.length)];
playChannel(ch.key);
});
}
function showVideoPlayer(ch) {
const poster = ch ? ` poster="${JW_POSTER(ch.jw)}"` : '';
document.getElementById('player-area').innerHTML = `
<div class="split__video_ratio">
<video id="video" controls autoplay playsinline${poster}></video>
</div>
`;
}
async function playChannel(key) {
const ch = CHANNELS.find(c => c.key === key);
if (!ch) return;
currentChannel = ch;
// mark active in sidebar
document.querySelectorAll('.channelList__channelIcon').forEach(el => {
el.classList.remove('channelList__channelIconActive');
});
const link = document.querySelector(`.channelList__channel[data-channel="${key}"] .channelList__channelIcon`);
if (link) link.classList.add('channelList__channelIconActive');
showVideoPlayer(ch);
// Try Shaka first (HLS); fall back to native <video src=mp4-fallback>
await waitForShaka();
const video = document.getElementById('video');
const hlsUrl = JW_HLS(ch.jw);
try {
if (shakaInstance) {
try { await shakaInstance.destroy(); } catch (_) {}
shakaInstance = null;
}
if (window.shaka && shaka.Player.isBrowserSupported()) {
shakaInstance = new shaka.Player(video);
await shakaInstance.load(hlsUrl);
} else {
// Safari / native HLS
video.src = hlsUrl;
}
video.play().catch(() => { /* autoplay may be blocked, user can press play */ });
} catch (e) {
console.warn('HLS load failed, falling back to native src:', e?.message);
video.src = hlsUrl;
video.play().catch(() => {});
}
// Show now playing
const now = document.getElementById('currently-playing');
if (now) {
now.style.display = '';
now.innerHTML = `Now playing: <b>${escapeHtml(ch.name)}</b> — promo`;
}
}
function waitForShaka() {
return new Promise(resolve => {
if (window.shaka) return resolve();
const i = setInterval(() => {
if (window.shaka) { clearInterval(i); resolve(); }
}, 50);
setTimeout(() => { clearInterval(i); resolve(); }, 4000);
});
}
async function loadCurrentlyPlaying() { /* no-op (no backend) */ }
async function loadTop10() { /* no-op (no backend) */ }
// ===== ARTISTS =====
function renderArtists() {
const main = document.getElementById('main');
main.innerHTML = `
<div class="box">
<h1>Artists on FolxPlay</h1>
<p>Six channels of original folk, schlager and pop production from FOLX NETWORK. Pick a channel below to watch the promo.</p>
<div class="artistsList">
${CHANNELS.map(c => `
<a href="#" data-channel-link="${c.key}">
<strong style="color:${c.color};"></strong> ${escapeHtml(c.name)}
</a>
`).join('')}
</div>
</div>
`;
main.querySelectorAll('[data-channel-link]').forEach(a => {
a.addEventListener('click', (e) => {
e.preventDefault();
navigate('/');
// wait one tick so home renders, then play
setTimeout(() => playChannel(a.getAttribute('data-channel-link')), 50);
});
});
}
// ===== ABOUT (folxnetwork) =====
function renderAbout() {
const main = document.getElementById('main');
main.innerHTML = `
<div class="box">
<div>
${ABOUT.map(c => `
<div class="channel">
<div class="channel__title">${escapeHtml(c.title)}</div>
<p>${escapeHtml(c.text)}</p>
</div>
`).join('')}
<hr>
<a href="/log" data-link>Channel PlayLog</a>
</div>
</div>
`;
}
// ===== POLICIES =====
function renderPolicies() {
const main = document.getElementById('main');
main.innerHTML = `
<div class="box">
<h1>Terms of service</h1>
<p>These Terms of Service ("Account Terms") govern your use of the FolxPlay platform and services, and certain purchases and rentals made through the FolxPlay Channel Store, including videos, audio, graphics, photographs, text and product information ("Original Content") provided by publishers ("Publishers").</p>
<h3>Definitions</h3>
<p><strong>"Channel"</strong> constitutes the entirety of content published, marketed and made available to you by Publishers or on a Publisher's behalf.</p>
<p><strong>"Original Content"</strong> includes video, audio, graphics, photographs, text and product information.</p>
<p><strong>"Publisher Service"</strong> means a service Publishers make available through the FolxPlay platform.</p>
<p><strong>"Member"</strong> refers to anyone that uses the service, also referred to herein as "you".</p>
<h3>Changes to Terms of Service</h3>
<p>FolxPlay may amend the Terms of Service at any time by posting the amended Terms on the FolxPlay website or via the FolxPlay platform, whichever occurs first. By continuing to use the service after the amended Terms are posted, you agree to the amended Terms.</p>
<h3>Privacy Policy and Use of Data</h3>
<p>The FolxPlay Privacy Policy explains how the service collects, uses, transmits, and discloses information you provide.</p>
<h3>Fees and Charges</h3>
<p>FolxPlay does not take part in any commercial activity between Members and Publishers. Fees required to access content will be charged to the payment method on file (credit card or PayPal).</p>
<h3>Content Availability</h3>
<p>The content viewed through the FolxPlay service is solely for your personal and non-commercial enjoyment. Content is protected by copyright and other intellectual property laws and treaties.</p>
<p>You only have access to channels and content authorized for the country of your residence. Channels and content will vary by geographic location or country.</p>
<h3>Children and Age-restricted Content</h3>
<p>The service contains content which may not be appropriate for children. You are responsible for ensuring that any age-restricted content is not viewed by any person not meeting the applicable age limits without consent from a parent or guardian.</p>
<p>Age ratings consist of five categories: Kids (All), Older Kids (7+), Teens (13+), Young Adults (16+), Adults (18+).</p>
<h3>Service Updates</h3>
<p>FolxPlay reserves the right to update the service, including bug fixes and updates, at any time and without notice.</p>
<h3>Limitation of Liability</h3>
<p>The FolxPlay service is provided "as is" and "as available" with all faults and without warranty of any kind. To the maximum extent permissible by law, FolxPlay does not guarantee, represent, or warrant that the service will be uninterrupted, secure, virus-free or error-free.</p>
<h3>Contact Information</h3>
<p>If you wish to contact us, please send your correspondence to <a href="mailto:office@folx.tv">office@folx.tv</a>.</p>
</div>
`;
}
// ===== PLAYLOG =====
function renderLog() {
const main = document.getElementById('main');
main.innerHTML = `
<div class="box">
<h1>Channel PlayLog</h1>
<p>The full per-channel play history is available on the production site at
<a href="https://folxplay.tv/log">folxplay.tv/log</a>.</p>
<p>This preview at <strong>folxplay.biba.live</strong> ships the look and feel of the site together with the channel promos served from JW Player Cloud.</p>
<hr>
<p><a href="/" data-link> Back to Home</a></p>
</div>
`;
}
function renderNotFound() {
document.getElementById('main').innerHTML = `
<div class="box"><h1>Page not found</h1><p>That page does not exist.</p></div>
`;
}
// ---------- boot ----------
renderShell();
render(location.pathname);

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -0,0 +1,55 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<link rel="shortcut icon" type="image/ico" href="/assets/favicon-fNqEDBCF.ico" />
<title>FolxPlay.tv</title>
<meta name="description" content="Folx Music | Zwei Music | One music | Adria Music | One Adria Music | Folx Slovenija" />
<script async src="https://www.googletagmanager.com/gtag/js?id=G-1HF0NT0FBR"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-1HF0NT0FBR');
</script>
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-5738844327989815"
crossorigin="anonymous"></script>
<!-- <script defer src="js/mux.6.0.0.js"></script>-->
<!-- <script defer src="js/shaka-3.2.1/shaka-player.ui.min.js"></script>-->
<!-- <link rel="stylesheet" href="js/shaka-3.2.1/controls.min.css">-->
<link rel="apple-touch-icon" sizes="57x57" href="icons/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="icons/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="icons/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="icons/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="icons/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="icons/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="icons/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="icons/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="icons/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="icons/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="icons/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="icons/favicon-16x16.png">
<link rel="manifest" href="/manifest.json">
<meta name="msapplication-TileColor" content="#000000">
<meta name="msapplication-TileImage" content="icons/ms-icon-144x144.png">
<meta name="theme-color" content="#000000">
<script type="module" crossorigin src="/assets/index-BHECMraK.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-XemGjmsU.css">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script>
</script>
</body>
</html>

View File

@ -0,0 +1,55 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<link rel="shortcut icon" type="image/ico" href="/assets/favicon-fNqEDBCF.ico" />
<title>FolxPlay.tv</title>
<meta name="description" content="Folx Music | Zwei Music | One music | Adria Music | One Adria Music | Folx Slovenija" />
<script async src="https://www.googletagmanager.com/gtag/js?id=G-1HF0NT0FBR"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-1HF0NT0FBR');
</script>
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-5738844327989815"
crossorigin="anonymous"></script>
<!-- <script defer src="js/mux.6.0.0.js"></script>-->
<!-- <script defer src="js/shaka-3.2.1/shaka-player.ui.min.js"></script>-->
<!-- <link rel="stylesheet" href="js/shaka-3.2.1/controls.min.css">-->
<link rel="apple-touch-icon" sizes="57x57" href="icons/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="icons/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="icons/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="icons/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="icons/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="icons/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="icons/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="icons/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="icons/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="icons/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="icons/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="icons/favicon-16x16.png">
<link rel="manifest" href="/manifest.json">
<meta name="msapplication-TileColor" content="#000000">
<meta name="msapplication-TileImage" content="icons/ms-icon-144x144.png">
<meta name="theme-color" content="#000000">
<script type="module" crossorigin src="/assets/index-BHECMraK.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-XemGjmsU.css">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script>
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,55 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<link rel="shortcut icon" type="image/ico" href="/assets/favicon-fNqEDBCF.ico" />
<title>FolxPlay.tv</title>
<meta name="description" content="Folx Music | Zwei Music | One music | Adria Music | One Adria Music | Folx Slovenija" />
<script async src="https://www.googletagmanager.com/gtag/js?id=G-1HF0NT0FBR"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-1HF0NT0FBR');
</script>
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-5738844327989815"
crossorigin="anonymous"></script>
<!-- <script defer src="js/mux.6.0.0.js"></script>-->
<!-- <script defer src="js/shaka-3.2.1/shaka-player.ui.min.js"></script>-->
<!-- <link rel="stylesheet" href="js/shaka-3.2.1/controls.min.css">-->
<link rel="apple-touch-icon" sizes="57x57" href="icons/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="icons/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="icons/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="icons/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="icons/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="icons/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="icons/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="icons/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="icons/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="icons/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="icons/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="icons/favicon-16x16.png">
<link rel="manifest" href="/manifest.json">
<meta name="msapplication-TileColor" content="#000000">
<meta name="msapplication-TileImage" content="icons/ms-icon-144x144.png">
<meta name="theme-color" content="#000000">
<script type="module" crossorigin src="/assets/index-BHECMraK.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-XemGjmsU.css">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script>
</script>
</body>
</html>

View File

@ -0,0 +1,55 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<link rel="shortcut icon" type="image/ico" href="/assets/favicon-fNqEDBCF.ico" />
<title>FolxPlay.tv</title>
<meta name="description" content="Folx Music | Zwei Music | One music | Adria Music | One Adria Music | Folx Slovenija" />
<script async src="https://www.googletagmanager.com/gtag/js?id=G-1HF0NT0FBR"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-1HF0NT0FBR');
</script>
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-5738844327989815"
crossorigin="anonymous"></script>
<!-- <script defer src="js/mux.6.0.0.js"></script>-->
<!-- <script defer src="js/shaka-3.2.1/shaka-player.ui.min.js"></script>-->
<!-- <link rel="stylesheet" href="js/shaka-3.2.1/controls.min.css">-->
<link rel="apple-touch-icon" sizes="57x57" href="icons/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="icons/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="icons/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="icons/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="icons/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="icons/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="icons/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="icons/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="icons/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="icons/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="icons/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="icons/favicon-16x16.png">
<link rel="manifest" href="/manifest.json">
<meta name="msapplication-TileColor" content="#000000">
<meta name="msapplication-TileImage" content="icons/ms-icon-144x144.png">
<meta name="theme-color" content="#000000">
<script type="module" crossorigin src="/assets/index-BHECMraK.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-XemGjmsU.css">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script>
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

42
public/index.html Normal file
View File

@ -0,0 +1,42 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<link rel="shortcut icon" type="image/x-icon" href="/icons/favicon-32x32.png" />
<title>FolxPlay.tv</title>
<meta name="description" content="Folx Music | Zwei Music | One music | Adria Music | One Adria Music | Folx Slovenija" />
<link rel="apple-touch-icon" sizes="57x57" href="/icons/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/icons/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/icons/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/icons/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/icons/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/icons/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/icons/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/icons/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/icons/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="/icons/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="/icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="/icons/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="/icons/favicon-16x16.png">
<link rel="manifest" href="/manifest.json">
<meta name="msapplication-TileColor" content="#000000">
<meta name="msapplication-TileImage" content="/icons/ms-icon-144x144.png">
<!-- Shaka Player for HLS playback -->
<script defer src="https://cdn.jsdelivr.net/npm/shaka-player@4.7.11/dist/shaka-player.compiled.js"></script>
<link rel="stylesheet" href="/styles.css">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<!-- background loader fallback -->
<div class="mainBackground" style="background-image: url('/backgrounds/bg_01.jpg');"></div>
</div>
<script type="module" src="/app.js"></script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
public/logos/mt_neg_b.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
public/logos/one_neg_b.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
public/logos/zwei_neg_b.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

16
public/manifest.json Normal file
View File

@ -0,0 +1,16 @@
{
"name": "FolxPlay.tv",
"short_name": "FolxPlay",
"start_url": "/",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#000000",
"icons": [
{ "src": "/icons/android-icon-36x36.png", "sizes": "36x36", "type": "image/png", "density": "0.75" },
{ "src": "/icons/android-icon-48x48.png", "sizes": "48x48", "type": "image/png", "density": "1.0" },
{ "src": "/icons/android-icon-72x72.png", "sizes": "72x72", "type": "image/png", "density": "1.5" },
{ "src": "/icons/android-icon-96x96.png", "sizes": "96x96", "type": "image/png", "density": "2.0" },
{ "src": "/icons/android-icon-144x144.png","sizes": "144x144","type": "image/png", "density": "3.0" },
{ "src": "/icons/android-icon-192x192.png","sizes": "192x192","type": "image/png", "density": "4.0" }
]
}

2
public/robots.txt Normal file
View File

@ -0,0 +1,2 @@
User-agent: *
Disallow:

279
public/styles.css Normal file
View File

@ -0,0 +1,279 @@
/* ---------- base ---------- */
html, body {
background: #333;
color: #fff;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
}
html { height: 100%; }
body { min-height: 100%; margin: 0; }
html, body { box-sizing: border-box; }
*, *:before, *:after { box-sizing: inherit; }
a { color: #000; text-decoration: none; border-bottom: 1px dotted #313131; }
a:hover { border-bottom: 1px solid #313131; }
.box {
margin: 20px 50px;
padding: 20px;
background: #eee;
color: #333;
border-radius: 15px;
}
/* ---------- navigation ---------- */
.nav {
background: #0000004d;
-webkit-backdrop-filter: blur(15px);
backdrop-filter: blur(15px);
}
.nav__width {
padding: 20px 50px;
display: flex;
max-width: 1500px;
margin: 0 auto;
}
.nav .logo { margin-right: 20px; }
.nav .logo img { height: 15px; width: auto; display: block; }
.nav .links {
display: flex;
list-style-type: none;
padding: 0;
margin: 0;
border-radius: 10px;
align-items: center;
}
.nav .links a {
color: #eee;
text-decoration: none;
border: none;
margin-right: 20px;
font-size: 14px;
}
.nav .links a:hover { text-decoration: underline; }
.nav .links a.active { font-weight: 700; }
/* ---------- main background ---------- */
.mainBackground {
min-height: 100vh;
background-size: cover;
background-position: left top;
}
/* ---------- home page ---------- */
.pageHome {
padding: 20px 50px;
max-width: 1500px;
margin: 0 auto;
}
.split {
--channelsLeftMargin: 20px;
--channelsTopMargin: 0;
--channelsWidth: 200px;
display: flex;
}
.split__video { flex: 1; }
.split__video_ratio {
position: relative;
padding-bottom: 56.25%;
height: 0;
}
.split__video_ratio video {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #000;
border-radius: 15px;
}
.split__channels {
width: var(--channelsWidth);
margin-left: var(--channelsLeftMargin);
}
@media all and (max-width: 800px) {
.split {
flex-direction: column;
--channelsLeftMargin: 0;
--channelsTopMargin: 20px;
--channelsWidth: 100%;
}
}
/* ---------- currently playing / select ---------- */
.currentlyPlaying {
background: #0009;
padding: 15px;
margin-top: 10px;
border-radius: 15px;
font-size: 14px;
}
.currentlyPlaying b { font-weight: 700; }
.selectChannelBox {
background: #000;
height: 400px;
display: flex;
text-align: center;
justify-content: center;
border-radius: 15px;
color: #b4b4b4;
align-items: center;
background-color: #000000e6;
font-size: 18px;
}
@media all and (max-width: 800px) {
.selectChannelBox { height: 150px; }
}
.selectChannelBox svg { width: 10px; height: 10px; }
.selectChannelBox .lineHeader { font-weight: 700; }
.selectChannelBox .lineSelectChannel { margin: 5px 0; font-size: 20px; }
.selectChannelBox .lineRandom {
--initialScale: 1;
--endScale: 1.2;
cursor: pointer;
animation: pulseScale 3s ease infinite;
}
/* ---------- playing stats / Top10 ---------- */
.playingStats {
color: #eee;
background: #0009;
padding: 15px;
margin-top: 10px;
border-radius: 15px;
}
.playingStats__title {
font-size: 18px;
border-bottom: 1px solid #727272;
padding-bottom: 10px;
margin-bottom: 10px;
}
.playingStats ol {
list-style-position: inside;
padding: 0;
margin: 0;
font-size: 14px;
}
.playingStats li {
padding: 6px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.playingStats li:last-child { border-bottom: none; }
/* ---------- channel list ---------- */
.channelList { margin-top: var(--channelsTopMargin); }
.channelList__channel {
cursor: pointer;
height: 64px;
display: flex;
margin-bottom: 5px;
text-decoration: none;
border: none;
}
.channelList__channel:hover { transform: scale(1.1); }
.channelList__channelIcon {
--initialWidth: 20px;
--endWidth: 25px;
width: 64px;
background-color: var(--var-bgColor);
text-align: center;
display: flex;
justify-content: center;
align-items: center;
border-bottom-left-radius: 10px;
border-top-left-radius: 10px;
}
.channelList__channelIcon svg {
opacity: .8;
width: var(--initialWidth);
height: auto;
fill: #fff;
}
.channelList__channelIconActive svg {
opacity: 1;
animation: pulseWidth 1s linear infinite;
}
.channelList__channelLogo {
flex: 1;
background: #333;
border-bottom-right-radius: 10px;
border-top-right-radius: 10px;
display: flex;
align-items: center;
}
.channelList__channelLogo img {
margin-left: 10px;
height: 30px;
width: auto;
}
@keyframes pulseWidth {
0% { width: var(--initialWidth); }
50% { width: var(--endWidth); }
100% { width: var(--initialWidth); }
}
@keyframes pulseScale {
0% { transform: scale(var(--initialScale)); }
50% { transform: scale(var(--endScale)); }
100% { transform: scale(var(--initialScale)); }
}
/* ---------- about / channel descriptions ---------- */
.channel { margin-bottom: 15px; }
.channel:last-child { margin-bottom: 0; }
.channel__title { font-weight: 700; }
.channel p { margin: 0; }
/* ---------- artists & playlog ---------- */
.artistsList {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 8px 24px;
margin-top: 15px;
font-size: 14px;
}
.artistsList a {
color: #ddd;
border: none;
padding: 4px 0;
}
.artistsList a:hover { color: #fff; text-decoration: underline; }
.playlogControls {
display: flex;
gap: 10px;
flex-wrap: wrap;
align-items: center;
margin-bottom: 15px;
}
.playlogControls select,
.playlogControls input[type="date"] {
padding: 6px 10px;
border-radius: 6px;
border: 1px solid #ccc;
background: #fff;
color: #000;
font: inherit;
}
.playlogList { font-size: 14px; }
.playlogList .row {
display: flex;
padding: 6px 0;
border-bottom: 1px solid rgba(0, 0, 0, .07);
}
.playlogList .row:last-child { border-bottom: none; }
.playlogList .time { width: 80px; color: #666; flex-shrink: 0; }
.playlogList .title { flex: 1; font-weight: 500; }
.playlogList .artist { width: 240px; color: #666; text-align: right; }
@media all and (max-width: 700px) {
.playlogList .artist { display: none; }
}
/* ---------- footer ---------- */
.siteFooter {
text-align: center;
font-size: 12px;
color: #888;
padding: 30px 50px;
}