| | === index.html === |
| | <!DOCTYPE html> |
| | <html lang="fr"> |
| | <head> |
| | <meta charset="UTF-8" /> |
| | <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" /> |
| | <title>NeoIPTV - Connexion & Catalogue + Assistant IA</title> |
| | <meta name="color-scheme" content="dark light" /> |
| | <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=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" /> |
| | <link href="https://unpkg.com/boxicons@2.1.4/css/boxicons.min.css" rel="stylesheet"> |
| | <link rel="stylesheet" href="style.css" /> |
| | <script type="module"> |
| | import { pipeline } from 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.7.3'; |
| | window.__TRANSFORMERS_PIPELINE__ = pipeline; |
| | </script> |
| | </head> |
| | <body> |
| | <header class="appbar"> |
| | <div class="appbar-inner"> |
| | <a class="brand" href="#" id="brandHome"> |
| | <div class="logo" aria-hidden="true"></div> |
| | <div> |
| | <h1>NeoIPTV</h1> |
| | <small>Connexion & Catalogue</small> |
| | </div> |
| | </a> |
| |
|
| | <div class="top-actions"> |
| | <a href="#" id="scanBtn"><i class='bx bx-qr-scan'></i> Scanner QR</a> |
| | <a href="#" id="helpBtn"><i class='bx bx-help-circle'></i> Aide</a> |
| | </div> |
| |
|
| | <div class="header-right"> |
| | <a class="anycoder" href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" rel="noopener">Built with anycoder</a> |
| | <div class="gpu-toggle"> |
| | <label class="switch"> |
| | <input type="checkbox" id="gpuToggle" aria-label="Activer l'exécution GPU (WebGPU)"> |
| | <span class="slider"></span> |
| | </label> |
| | <span id="gpuLabel" class="gpu-label" aria-live="polite">CPU</span> |
| | </div> |
| | <button class="theme-toggle" id="themeToggle" title="Basculer le thème" aria-label="Basculer le thème clair/sombre"><i class='bx bx-moon'></i></button> |
| | </div> |
| | </div> |
| | </header> |
| |
|
| | <main> |
| | <section class="content"> |
| | <div class="hero"> |
| | <div class="hero-inner"> |
| | <h2>Vos chaînes, films et séries en un clic</h2> |
| | <p>Connectez-vous avec votre méthode préférée puis profitez du direct, de la VOD et des séries. Lecteur HLS intégré (m3u8).</p> |
| | </div> |
| | </div> |
| |
|
| | <div class="tabs" role="tablist"> |
| | <button class="tab active" data-tab="connect" role="tab" aria-selected="true"><i class='bx bx-log-in-circle'></i> Connexion</button> |
| | <button class="tab" data-tab="live" role="tab" aria-selected="false"><i class='bx bx-play-circle'></i> Direct</button> |
| | <button class="tab" data-tab="vod" role="tab" aria-selected="false"><i class='bx bx-movie-play'></i> Films</button> |
| | <button class="tab" data-tab="series" role="tab" aria-selected="false"><i class='bx bx-collection'></i> Séries</button> |
| | <button class="tab" data-tab="favs" role="tab" aria-selected="false"><i class='bx bx-star'></i> Favoris</button> |
| | <button class="tab" data-tab="recent" role="tab" aria-selected="false"><i class='bx bx-time-five'></i> Récents</button> |
| | </div> |
| |
|
| | <div class="toolbar" id="catalogToolbar"> |
| | <div class="searchbar"> |
| | <i class='bx bx-search' aria-hidden="true"></i> |
| | <input id="searchInput" type="search" placeholder="Rechercher dans tous les onglets…" aria-label="Rechercher"/> |
| | <button id="clearSearch" title="Effacer" class="btn ghost" aria-label="Effacer la recherche" style="border:none;background:transparent;color:var(--muted);padding:0;display:grid;place-items:center"><i class='bx bx-x'></i></button> |
| | </div> |
| | <div class="filters" role="group" aria-label="Filtres"> |
| | <button class="filter-chip active" data-filter="all" aria-pressed="true"><i class='bx bx-layer'></i> Tous</button> |
| | <button class="filter-chip" data-filter="hd" aria-pressed="false"><i class='bx bx-video'></i> HD/4K</button> |
| | <button class="filter-chip" data-filter="recent" aria-pressed="false"><i class='bx bx-bolt-circle'></i> Nouveautés</button> |
| | </div> |
| | </div> |
| |
|
| | <section id="grid-connect" class="card page" aria-label="Connexion" style="padding: clamp(16px, 2.5vw, 22px);"> |
| | <div class="connect-wrap"> |
| | <div class="connect-panel"> |
| | <div class="connect-tabs" role="tablist" aria-label="Modes de connexion"> |
| | <button class="connect-tab active" data-mode="mac"><i class='bx bx-barcode'></i> Code MAC</button> |
| | <button class="connect-tab" data-mode="xtreme"><i class='bx bx-link'></i> Xtream Codes</button> |
| | <button class="connect-tab" data-mode="m3u"><i class='bx bx-file'></i> M3U/M3U8</button> |
| | <button class="connect-tab" data-mode="qr"><i class='bx bx-qr'></i> QR Code</button> |
| | </div> |
| |
|
| | <form id="form-mac" class="form" autocomplete="on" novalidate> |
| | <div class="field"> |
| | <label for="macAddress">Adresse MAC</label> |
| | <div class="input"> |
| | <i class='bx bx-chip' aria-hidden="true"></i> |
| | <input id="macAddress" name="mac" inputmode="text" placeholder="AB:CD:EF:12:34:56" maxlength="17" aria-describedby="macHelp" /> |
| | </div> |
| | <div class="helper" id="macHelp">Format accepté: XX:XX:XX:XX:XX:XX (lettres et chiffres)</div> |
| | </div> |
| | <div class="field"> |
| | <label for="portalMac">Portail</label> |
| | <div class="input"> |
| | <i class='bx bx-globe' aria-hidden="true"></i> |
| | <input id="portalMac" name="portal" placeholder="https://mon-portail.example.com" /> |
| | </div> |
| | </div> |
| | <div class="cta"> |
| | <button type="button" class="btn" id="loginMac"><i class='bx bx-log-in'></i> Se connecter</button> |
| | <button type="button" class="btn secondary" id="genMac"><i class='bx bx-dice-6'></i> Générer MAC</button> |
| | </div> |
| | <div class="success" id="macSuccess" role="status"><i class='bx bx-check-shield'></i> Connecté (démo) — vos playlists seront chargées.</div> |
| | <div class="error" id="macError" role="alert"><i class='bx bx-error'></i> MAC invalide. Vérifiez le format.</div> |
| | </form> |
| |
|
| | <form id="form-xtreme" class="form hidden" autocomplete="on" novalidate> |
| | <div class="row-2"> |
| | <div class="field"> |
| | <label for="xtHost">Hôte/URL</label> |
| | <div class="input"> |
| | <i class='bx bx-globe' aria-hidden="true"></i> |
| | <input id="xtHost" placeholder="ex: http://example.com:8080" /> |
| | </div> |
| | </div> |
| | <div class="field"> |
| | <label for="xtProtocol">Protocole</label> |
| | <div class="input"> |
| | <i class='bx bx-traffic-barrier' aria-hidden="true"></i> |
| | <select id="xtProtocol"> |
| | <option value="http">HTTP</option> |
| | <option value="https">HTTPS</option> |
| | </select> |
| | </div> |
| | </div> |
| | </div> |
| | <div class="row-2"> |
| | <div class="field"> |
| | <label for="xtUser">Utilisateur</label> |
| | <div class="input"> |
| | <i class='bx bx-user' aria-hidden="true"></i> |
| | <input id="xtUser" placeholder="username" /> |
| | </div> |
| | </div> |
| | <div class="field"> |
| | <label for="xtPass">Mot de passe</label> |
| | <div class="input"> |
| | <i class='bx bx-lock-alt' aria-hidden="true"></i> |
| | <input id="xtPass" type="password" placeholder="password" /> |
| | </div> |
| | </div> |
| | </div> |
| | <div class="cta"> |
| | <button type="button" class="btn" id="loginXtream"><i class='bx bx-log-in'></i> Se connecter</button> |
| | <button type="button" class="btn secondary" id="testXtream"><i class='bx bx-plug'></i> Tester</button> |
| | </div> |
| | <div class="success" id="xtSuccess" role="status"><i class='bx bx-check-shield'></i> Authentification réussie (démo).</div> |
| | <div class="error" id="xtError" role="alert"><i class='bx bx-error'></i> Paramètres Xtream incomplets.</div> |
| | </form> |
| |
|
| | <form id="form-m3u" class="form hidden" autocomplete="on" novalidate> |
| | <div class="field"> |
| | <label for="m3uUrl">URL de la playlist M3U/M3U8</label> |
| | <div class="input"> |
| | <i class='bx bx-link-external' aria-hidden="true"></i> |
| | <input id="m3uUrl" placeholder="https://.../playlist.m3u8" /> |
| | </div> |
| | </div> |
| | <div class="field"> |
| | <label for="m3uEpg">URL EPG (optionnel)</label> |
| | <div class="input"> |
| | <i class='bx bx-calendar-event' aria-hidden="true"></i> |
| | <input id="m3uEpg" placeholder="https://.../guide.xml" /> |
| | </div> |
| | </div> |
| | <div class="cta"> |
| | <button type="button" class="btn" id="loadM3u"><i class='bx bx-cloud-download'></i> Charger</button> |
| | <button type="button" class="btn secondary" id="demoM3u"><i class='bx bx-line-chart'></i> Utiliser démo</button> |
| | </div> |
| | <div class="success" id="m3uSuccess" role="status"><i class='bx bx-check-shield'></i> Playlist chargée (démo).</div> |
| | <div class="error" id="m3uError" role="alert"><i class='bx bx-error'></i> URL non valide.</div> |
| | </form> |
| |
|
| | <div id="form-qr" class="form hidden"> |
| | <div class="field"> |
| | <label>Scanner un QR Code</label> |
| | <div class="qr-box" id="qrBox"> |
| | <i class='bx bx-qr' style="font-size: 56px; color: var(--muted)"></i> |
| | </div> |
| | <div class="helper">Autorisez l’accès à la caméra pour scanner (démo non connectée au flux caméra).</div> |
| | </div> |
| | <div class="cta"> |
| | <button type="button" class="btn" id="startQr"><i class='bx bx-camera'></i> Démarrer</button> |
| | <button type="button" class="btn secondary" id="pasteQr"><i class='bx bx-clipboard'></i> Coller un contenu</button> |
| | </div> |
| | <div class="success" id="qrSuccess" role="status"><i class='bx bx-check-shield'></i> QR lu (démo) — paramètres appliqués.</div> |
| | <div class="error" id="qrError" role="alert"><i class='bx bx-error'></i> Lecture QR impossible.</div> |
| | </div> |
| | </div> |
| |
|
| | <div class="connect-panel"> |
| | <h3 style="margin:0 0 10px;font-size:16px">Aperçu de session</h3> |
| | <div class="empty" id="sessionEmpty"> |
| | <i class='bx bx-id-card' style="font-size: 28px;"></i> |
| | <div>Non connecté. Choisissez un mode de connexion à gauche.</div> |
| | </div> |
| | <div id="sessionInfo" class="hidden" aria-live="polite"> |
| | <div class="tag" id="sessionMode">Mode: -</div> |
| | <div style="height:8px"></div> |
| | <div class="grid" style="--card-w: 180px;" id="sessionPreview"></div> |
| | <div style="height:10px"></div> |
| | <div class="cta"> |
| | <button class="btn" id="goToLive"><i class='bx bx-television'></i> Aller au Direct</button> |
| | <button class="btn secondary" id="disconnect"><i class='bx bx-power-off'></i> Se déconnecter</button> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| | </section> |
| |
|
| | <section id="page-live" class="page hidden" aria-label="Direct"> |
| | <div class="card" style="padding:14px; margin-bottom:12px;"> |
| | <div style="display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap"> |
| | <div style="display:flex;align-items:center;gap:10px"> |
| | <i class='bx bx-broadcast' style="color:var(--accent)"></i> |
| | <strong>Direct — Chaînes TV</strong> |
| | </div> |
| | <div class="pager"> |
| | <button class="page-btn" id="livePrev" aria-label="Page précédente"><i class='bx bx-left-arrow'></i></button> |
| | <span class="pill" id="livePageInfo">Page 1</span> |
| | <button class="page-btn" id="liveNext" aria-label="Page suivante"><i class='bx bx-right-arrow'></i></button> |
| | </div> |
| | </div> |
| | </div> |
| | <div id="grid-live" class="grid" role="region" aria-label="Chaînes en direct"></div> |
| | </section> |
| |
|
| | <section id="page-vod" class="page hidden" aria-label="Films"> |
| | <div class="card" style="padding:14px; margin-bottom:12px;"> |
| | <div style="display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap"> |
| | <div style="display:flex;align-items:center;gap:10px"> |
| | <i class='bx bx-film' style="color:var(--primary)"></i> |
| | <strong>Films — VOD</strong> |
| | </div> |
| | <div class="pager"> |
| | <button class="page-btn" id="vodPrev" aria-label="Page précédente"><i class='bx bx-left-arrow'></i></button> |
| | <span class="pill" id="vodPageInfo">Page 1</span> |
| | <button class="page-btn" id="vodNext" aria-label="Page suivante"><i class='bx bx-right-arrow'></i></button> |
| | </div> |
| | </div> |
| | </div> |
| | <div id="grid-vod" class="grid" role="region" aria-label="Films VOD"></div> |
| | </section> |
| |
|
| | <section id="page-series" class="page hidden" aria-label="Séries"> |
| | <div class="card" style="padding:14px; margin-bottom:12px;"> |
| | <div style="display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap"> |
| | <div style="display:flex;align-items:center;gap:10px"> |
| | <i class='bx bx-collection' style="color:var(--warn)"></i> |
| | <strong>Séries TV</strong> |
| | </div> |
| | <div class="pager"> |
| | <button class="page-btn" id="seriesPrev" aria-label="Page précédente"><i class='bx bx-left-arrow'></i></button> |
| | <span class="pill" id="seriesPageInfo">Page 1</span> |
| | <button class="page-btn" id="seriesNext" aria-label="Page suivante"><i class='bx bx-right-arrow'></i></button> |
| | </div> |
| | </div> |
| | </div> |
| | <div id="grid-series" class="grid" role="region" aria-label="Séries"></div> |
| | </section> |
| |
|
| | <section id="page-favs" class="page hidden" aria-label="Favoris"> |
| | <div class="empty"><i class='bx bx-star'></i> Aucun favori pour le moment.</div> |
| | </section> |
| |
|
| | <section id="page-recent" class="page hidden" aria-label="Récents"> |
| | <div class="empty"><i class='bx bx-time-five'></i> Historique vide.</div> |
| | </section> |
| |
|
| | </section> |
| | </main> |
| |
|
| | <dialog class="player" id="playerDialog" aria-label="Lecteur vidéo"> |
| | <div class="player-card"> |
| | <div class="player-top"> |
| | <video id="video" playsinline controls></video> |
| | <div id="videoFallback" class="video-fallback" hidden> |
| | <i class='bx bx-error-circle'></i> |
| | <span>Votre navigateur ne supporte pas HLS natif. Utilisez un lien externe.</span> |
| | </div> |
| | </div> |
| | <div class="player-bottom"> |
| | <div class="player-actions"> |
| | <span class="chip" id="currentTitle">Titre</span> |
| | <span class="chip" id="currentQuality">HLS</span> |
| | <button class="close" id="closePlayer" aria-label="Fermer le lecteur"><i class='bx bx-x'></i> Fermer</button> |
| | </div> |
| | <div class="helper">Astuce: double-clic pour plein écran.</div> |
| | </div> |
| | </div> |
| | </dialog> |
| |
|
| | <div class="overlay" id="sheetOverlay" aria-hidden="true"></div> |
| |
|
| | <div class="fab" id="fabIa" aria-label="Assistant IA"> |
| | <i class='bx bx-bot'></i> |
| | </div> |
| |
|
| | <section class="ia-panel hidden" id="iaPanel" aria-label="Assistant IA"> |
| | <div class="ia-header"> |
| | <i class='bx bx-bot' style="color:var(--primary)"></i> |
| | <div> |
| | <div class="title">Assistant IA</div> |
| | <div class="muted">Recherche sémantique pour vos contenus</div> |
| | </div> |
| | <div class="spacer" style="flex:1"></div> |
| | <span class="pill" id="modelBadge">Chargement…</span> |
| | </div> |
| |
|
| | <div class="ia-prompt"> |
| | <input id="iaInput" type="text" placeholder="Demandez un film d'action récent, une chaîne sport, etc." aria-label="Entrer une requête" /> |
| | <button id="iaSearchBtn" class="btn" aria-label="Rechercher"><i class='bx bx-search-alt-2'></i> Rechercher</button> |
| | </div> |
| |
|
| | <div class="ia-actions"> |
| | <button class="ia-chip" data-quick="match de foot en direct">Foot en direct</button> |
| | <button class="ia-chip" data-quick="comédie familiale">Comédie familiale</button> |
| | <button class="ia-chip" data-quick="série policière">Série policière</button> |
| | <button class="ia-chip" data-quick="film action 4K récent">Action 4K</button> |
| | </div> |
| |
|
| | <div class="ia-results" id="iaResults" role="list"></div> |
| |
|
| | <div class="ia-footer"> |
| | <div class="ia-status" id="iaStatus">Modèle non chargé</div> |
| | <div class="row"> |
| | <span class="kbd">GPU</span> |
| | <span class="kbd" id="gpuState">off</span> |
| | </div> |
| | </div> |
| | </section> |
| |
|
| | <footer> |
| | <div class="credits"> |
| | <span class="tag">NeoIPTV démo</span> |
| | <span class="tag">HLS</span> |
| | <span class="tag" id="webgpuTag">WebGPU: test…</span> |
| | </div> |
| | <small class="muted">Ceci est une démo front-end. Aucun service externe n'est contacté sauf pour charger le modèle localement via transformers.js CDN.</small> |
| | </footer> |
| |
|
| | <script src="index.js" type="module"></script> |
| | </body> |
| | </html> |
| |
|
| | === index.js === |
| | /* NeoIPTV - Application front démo avec Assistant IA (transformers.js) |
| | - Grilles de contenus simulées |
| | - Lecteur HLS basique (m3u8), avec fallback message si non supporté |
| | - Assistant IA: text-classification pour requêtes et recommandation simple |
| | - WebGPU toggle si supporté |
| | */ |
| |
|
| | const $ = (sel, root = document) => root.querySelector(sel); |
| | const $$ = (sel, root = document) => Array.from(root.querySelectorAll(sel)); |
| |
|
| | /* THEME */ |
| | const themeToggle = $("#themeToggle"); |
| | const prefersDark = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches; |
| | function applyTheme(theme) { |
| | document.documentElement.dataset.theme = theme; |
| | themeToggle.innerHTML = theme |
| |
|
| | === style.css === |
| | :root { |
| | --bg: #0c0f14; |
| | --bg-soft: #121621; |
| | --surface: #141927; |
| | --elev: #192033; |
| | --txt: #e7ebf3; |
| | --muted: #9aa7c7; |
| | --primary: # |