Guymy97 commited on
Commit
3612828
·
verified ·
1 Parent(s): 00f585e

Update via AnyCoder

Browse files
Files changed (1) hide show
  1. index.html +256 -138
index.html CHANGED
@@ -3,7 +3,7 @@
3
  <head>
4
  <meta charset="UTF-8" />
5
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
6
- <title>IPTV - Connexion par Code MAC</title>
7
  <meta name="color-scheme" content="dark light" />
8
  <link rel="preconnect" href="https://fonts.googleapis.com" />
9
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
@@ -186,6 +186,103 @@
186
  .tag { border: 1px solid color-mix(in oklab, var(--txt) 10%, transparent); border-radius: 999px; padding: 6px 10px; background: color-mix(in oklab, var(--bg-soft) 60%, transparent); }
187
 
188
  .hidden { display: none !important; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
  </style>
190
  </head>
191
  <body>
@@ -195,7 +292,7 @@
195
  <div class="logo" aria-hidden="true"></div>
196
  <div>
197
  <h1>NeoIPTV</h1>
198
- <small style="display:none">Connexion avec code MAC</small>
199
  </div>
200
  </div>
201
 
@@ -212,24 +309,25 @@
212
  </header>
213
 
214
  <main>
215
- <!-- Section de connexion supprimée comme demandé -->
216
  <section class="content">
217
  <div class="hero">
218
  <div class="hero-inner">
219
  <h2>Vos chaînes, films et séries en un clic</h2>
220
- <p>Explorez le guide TV, la VOD et les séries. Le lecteur est intégré et supporte HLS (m3u8).</p>
221
  </div>
222
  </div>
223
 
224
  <div class="tabs" role="tablist">
225
- <button class="tab active" data-tab="live"><i class='bx bx-play-circle'></i> Direct</button>
 
226
  <button class="tab" data-tab="vod"><i class='bx bx-movie-play'></i> Films</button>
227
  <button class="tab" data-tab="series"><i class='bx bx-collection'></i> Séries</button>
228
  <button class="tab" data-tab="favs"><i class='bx bx-star'></i> Favoris</button>
229
  <button class="tab" data-tab="recent"><i class='bx bx-time-five'></i> Récents</button>
230
  </div>
231
 
232
- <div class="toolbar">
 
233
  <div class="searchbar">
234
  <i class='bx bx-search' aria-hidden="true"></i>
235
  <input id="searchInput" type="search" placeholder="Rechercher dans tous les onglets…" />
@@ -242,7 +340,153 @@
242
  </div>
243
  </div>
244
 
245
- <div id="grid-live" class="grid" role="region" aria-label="Chaînes en direct"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
246
  <div id="grid-vod" class="grid hidden" role="region" aria-label="Films VOD"></div>
247
  <div id="grid-series" class="grid hidden" role="region" aria-label="Séries TV"></div>
248
  <div id="grid-favs" class="grid hidden" role="region" aria-label="Favoris">
@@ -330,12 +574,14 @@
330
 
331
  const tabs = document.querySelectorAll('.tab');
332
  const grids = {
 
333
  live: document.getElementById('grid-live'),
334
  vod: document.getElementById('grid-vod'),
335
  series: document.getElementById('grid-series'),
336
  favs: document.getElementById('grid-favs'),
337
  recent: document.getElementById('grid-recent')
338
  };
 
339
 
340
  // Tabs
341
  tabs.forEach(tab => {
@@ -344,6 +590,8 @@
344
  tab.classList.add('active');
345
  const chosen = tab.dataset.tab;
346
  Object.keys(grids).forEach(k => grids[k].classList.toggle('hidden', k !== chosen));
 
 
347
  localStorage.setItem('iptv_tab', chosen);
348
  applySearchAndFilters();
349
  });
@@ -395,134 +643,4 @@
395
  function saveFavs(f) { localStorage.setItem('iptv_favs', JSON.stringify(f)); }
396
  function favKey(item, type){ return `${type}:${item.id}`; }
397
 
398
- function toggleFav(item, type){
399
- const key = favKey(item,type);
400
- let favs = getFavs();
401
- if (favs.includes(key)) favs = favs.filter(k => k !== key);
402
- else favs.push(key);
403
- saveFavs(favs);
404
- renderFavs();
405
- }
406
-
407
- function renderFavs(){
408
- const favs = getFavs();
409
- const container = grids.favs;
410
- container.innerHTML = '';
411
- if (favs.length === 0) {
412
- const empty = document.createElement('div');
413
- empty.className = 'empty';
414
- empty.innerHTML = "<i class='bx bx-star' style='font-size:32px;'></i><div>Aucun favori pour le moment.</div>";
415
- container.appendChild(empty);
416
- return;
417
- }
418
- const addFrom = (arr, type) => arr.forEach(it => {
419
- if (favs.includes(favKey(it,type))) container.appendChild(createCard(it,type));
420
- });
421
- addFrom(data.live,'live'); addFrom(data.vod,'vod'); addFrom(data.series,'series');
422
- }
423
- renderFavs();
424
-
425
- function pushRecent(item, type){
426
- const recents = JSON.parse(localStorage.getItem('iptv_recent') || '[]').filter(r => r.id !== item.id || r.type !== type);
427
- recents.unshift({ id:item.id, type, title:item.title, poster:item.poster||item.logo, when:Date.now(), tag:item.tag||'' });
428
- localStorage.setItem('iptv_recent', JSON.stringify(recents.slice(0,30)));
429
- renderRecent();
430
- }
431
-
432
- function renderRecent(){
433
- const recents = JSON.parse(localStorage.getItem('iptv_recent') || '[]');
434
- const container = grids.recent;
435
- container.innerHTML = '';
436
- if (recents.length === 0) {
437
- const empty = document.createElement('div');
438
- empty.className = 'empty';
439
- empty.innerHTML = "<i class='bx bx-time-five' style='font-size:32px;'></i><div>Votre historique de lecture apparaîtra ici.</div>";
440
- container.appendChild(empty);
441
- return;
442
- }
443
- recents.forEach(r => {
444
- const item = { id:r.id, title:r.title, poster:r.poster, logo:r.poster, url:'#', tag:r.tag };
445
- container.appendChild(createCard(item, r.type));
446
- });
447
- }
448
- renderRecent();
449
-
450
- // Search and filters
451
- const searchInput = document.getElementById('searchInput');
452
- const clearSearch = document.getElementById('clearSearch');
453
- const filterBtns = document.querySelectorAll('.filter-chip');
454
-
455
- function applySearchAndFilters(){
456
- const q = (searchInput.value || '').toLowerCase().trim();
457
- const activeTab = document.querySelector('.tab.active').dataset.tab;
458
- const activeFilter = document.querySelector('.filter-chip.active').dataset.filter;
459
- const container = grids[activeTab];
460
- if (!container) return;
461
- [...container.children].forEach(card => {
462
- if (card.classList.contains('empty')) return;
463
- const matchesQ = card.dataset.search?.includes(q) || q === '';
464
- let matchesF = true;
465
- if (activeFilter === 'hd') matchesF = card.dataset.quality === 'hd';
466
- if (activeFilter === 'recent') matchesF = card.dataset.recent === '1';
467
- card.style.display = matchesQ && matchesF ? '' : 'none';
468
- });
469
- }
470
- searchInput.addEventListener('input', applySearchAndFilters);
471
- clearSearch.addEventListener('click', () => { searchInput.value=''; applySearchAndFilters(); });
472
- filterBtns.forEach(b => b.addEventListener('click', () => {
473
- filterBtns.forEach(x => x.classList.remove('active'));
474
- b.classList.add('active');
475
- applySearchAndFilters();
476
- }));
477
-
478
- // Restore selected tab
479
- (function restoreTab(){
480
- const t = localStorage.getItem('iptv_tab');
481
- if (!t) return;
482
- const btn = document.querySelector(`.tab[data-tab="${t}"]`);
483
- if (btn) btn.click();
484
- })();
485
-
486
- // Player
487
- const playerDialog = document.getElementById('playerDialog');
488
- const video = document.getElementById('video');
489
- const chipTitle = document.getElementById('chipTitle');
490
- const chipType = document.getElementById('chipType');
491
- const chipRes = document.getElementById('chipRes');
492
- const closePlayer = document.getElementById('closePlayer');
493
- const pipBtn = document.getElementById('pipBtn');
494
- const muteBtn = document.getElementById('muteBtn');
495
- const castBtn = document.getElementById('castBtn');
496
-
497
- function openPlayer(item, type){
498
- chipTitle.textContent = item.title;
499
- chipType.textContent = type.toUpperCase();
500
- chipRes.textContent = 'Auto';
501
- video.src = item.url;
502
- video.play().catch(()=>{});
503
- playerDialog.showModal();
504
- pushRecent(item, type);
505
- }
506
- closePlayer.addEventListener('click', () => {
507
- video.pause();
508
- video.src = '';
509
- playerDialog.close();
510
- });
511
- pipBtn.addEventListener('click', async () => {
512
- if (document.pictureInPictureElement) document.exitPictureInPicture();
513
- else if (video.requestPictureInPicture) await video.requestPictureInPicture();
514
- });
515
- muteBtn.addEventListener('click', () => {
516
- video.muted = !video.muted;
517
- muteBtn.innerHTML = video.muted ? "<i class='bx bx-volume-mute'></i> Unmute" : "<i class='bx bx-volume-full'></i> Mute";
518
- });
519
- castBtn.addEventListener('click', () => {
520
- alert('Casting non implémenté dans cette démo.');
521
- });
522
-
523
- // Small helpers
524
- document.getElementById('scanBtn').addEventListener('click', (e)=>{ e.preventDefault(); alert('Scanner QR non disponible dans cette démo.'); });
525
- document.getElementById('helpBtn').addEventListener('click', (e)=>{ e.preventDefault(); alert('Aide: utilisez la recherche, les filtres et cliquez sur Lire pour démarrer.'); });
526
- </script>
527
- </body>
528
- </html>
 
3
  <head>
4
  <meta charset="UTF-8" />
5
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
6
+ <title>NeoIPTV - Connexion & Catalogue</title>
7
  <meta name="color-scheme" content="dark light" />
8
  <link rel="preconnect" href="https://fonts.googleapis.com" />
9
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
 
186
  .tag { border: 1px solid color-mix(in oklab, var(--txt) 10%, transparent); border-radius: 999px; padding: 6px 10px; background: color-mix(in oklab, var(--bg-soft) 60%, transparent); }
187
 
188
  .hidden { display: none !important; }
189
+
190
+ /* Connexion styles */
191
+ .connect-wrap {
192
+ display: grid;
193
+ gap: var(--gap);
194
+ grid-template-columns: 1.1fr .9fr;
195
+ }
196
+ @media (max-width: 900px) {
197
+ .connect-wrap { grid-template-columns: 1fr; }
198
+ }
199
+ .connect-panel {
200
+ padding: clamp(16px, 2.5vw, 22px);
201
+ border-radius: var(--radius-xl);
202
+ background: linear-gradient(180deg, color-mix(in oklab, var(--surface) 86%, transparent), transparent);
203
+ border: 1px solid color-mix(in oklab, var(--txt) 8%, transparent);
204
+ box-shadow: var(--shadow);
205
+ }
206
+ .connect-tabs {
207
+ display: grid;
208
+ grid-template-columns: repeat(4, 1fr);
209
+ gap: 8px;
210
+ margin-bottom: 12px;
211
+ }
212
+ .connect-tab {
213
+ border: 1px solid color-mix(in oklab, var(--txt) 10%, transparent);
214
+ background: color-mix(in oklab, var(--bg-soft) 60%, transparent);
215
+ border-radius: 12px;
216
+ padding: 10px;
217
+ font-weight: 800;
218
+ color: var(--muted);
219
+ cursor: pointer;
220
+ display: flex; align-items: center; justify-content: center; gap: 8px;
221
+ }
222
+ .connect-tab.active {
223
+ color: var(--txt);
224
+ border-color: color-mix(in oklab, var(--primary) 50%, transparent);
225
+ background: color-mix(in oklab, var(--primary) 14%, transparent);
226
+ box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--primary) 22%, transparent);
227
+ }
228
+ .form {
229
+ display: grid; gap: 12px;
230
+ }
231
+ .field {
232
+ display: grid; gap: 8px;
233
+ }
234
+ .field label {
235
+ font-weight: 700; color: var(--muted); font-size: 13px;
236
+ }
237
+ .input {
238
+ display: grid; grid-template-columns: 28px 1fr; align-items: center;
239
+ gap: 10px; padding: 12px; border-radius: 12px;
240
+ border: 1px solid color-mix(in oklab, var(--txt) 10%, transparent);
241
+ background: color-mix(in oklab, var(--bg-soft) 60%, transparent);
242
+ color: var(--txt);
243
+ }
244
+ .input input, .input select {
245
+ border: none; outline: none; background: transparent; color: var(--txt);
246
+ font-weight: 700;
247
+ }
248
+ .row-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
249
+ @media (max-width: 600px){ .row-2 { grid-template-columns: 1fr; } }
250
+
251
+ .cta {
252
+ display: flex; gap: 10px; flex-wrap: wrap; align-items: center;
253
+ }
254
+ .btn {
255
+ padding: 10px 14px; border-radius: 12px; font-weight: 800; cursor: pointer;
256
+ border: 1px solid color-mix(in oklab, var(--primary) 45%, transparent);
257
+ background: linear-gradient(180deg, color-mix(in oklab, var(--primary) 22%, transparent), transparent);
258
+ color: #fff;
259
+ display: inline-flex; align-items: center; gap: 8px;
260
+ }
261
+ .btn.secondary {
262
+ border-color: color-mix(in oklab, var(--txt) 14%, transparent);
263
+ background: color-mix(in oklab, var(--bg-soft) 60%, transparent);
264
+ color: var(--txt);
265
+ }
266
+ .helper {
267
+ font-size: 12px; color: var(--muted);
268
+ }
269
+ .qr-box {
270
+ display: grid; place-items: center; aspect-ratio: 1/1;
271
+ border-radius: 16px; border: 1px dashed color-mix(in oklab, var(--txt) 14%, transparent);
272
+ background: repeating-linear-gradient(45deg, color-mix(in oklab, var(--glass) 100%, transparent) 0 10px, transparent 10px 20px);
273
+ }
274
+ .success {
275
+ display: none; margin-top: 10px; padding: 10px 12px; border-radius: 10px;
276
+ background: color-mix(in oklab, var(--accent) 18%, transparent);
277
+ border: 1px solid color-mix(in oklab, var(--accent) 48%, transparent);
278
+ color: white; font-weight: 800;
279
+ }
280
+ .error {
281
+ display: none; margin-top: 10px; padding: 10px 12px; border-radius: 10px;
282
+ background: color-mix(in oklab, var(--danger) 18%, transparent);
283
+ border: 1px solid color-mix(in oklab, var(--danger) 48%, transparent);
284
+ color: white; font-weight: 800;
285
+ }
286
  </style>
287
  </head>
288
  <body>
 
292
  <div class="logo" aria-hidden="true"></div>
293
  <div>
294
  <h1>NeoIPTV</h1>
295
+ <small>Connexion & Catalogue</small>
296
  </div>
297
  </div>
298
 
 
309
  </header>
310
 
311
  <main>
 
312
  <section class="content">
313
  <div class="hero">
314
  <div class="hero-inner">
315
  <h2>Vos chaînes, films et séries en un clic</h2>
316
+ <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>
317
  </div>
318
  </div>
319
 
320
  <div class="tabs" role="tablist">
321
+ <button class="tab active" data-tab="connect"><i class='bx bx-log-in-circle'></i> Connexion</button>
322
+ <button class="tab" data-tab="live"><i class='bx bx-play-circle'></i> Direct</button>
323
  <button class="tab" data-tab="vod"><i class='bx bx-movie-play'></i> Films</button>
324
  <button class="tab" data-tab="series"><i class='bx bx-collection'></i> Séries</button>
325
  <button class="tab" data-tab="favs"><i class='bx bx-star'></i> Favoris</button>
326
  <button class="tab" data-tab="recent"><i class='bx bx-time-five'></i> Récents</button>
327
  </div>
328
 
329
+ <!-- Toolbar only for catalogue tabs -->
330
+ <div class="toolbar" id="catalogToolbar">
331
  <div class="searchbar">
332
  <i class='bx bx-search' aria-hidden="true"></i>
333
  <input id="searchInput" type="search" placeholder="Rechercher dans tous les onglets…" />
 
340
  </div>
341
  </div>
342
 
343
+ <!-- Connexion tab -->
344
+ <section id="grid-connect" class="card" aria-label="Connexion" style="padding: clamp(16px, 2.5vw, 22px);">
345
+ <div class="connect-wrap">
346
+ <div class="connect-panel">
347
+ <div class="connect-tabs" role="tablist" aria-label="Modes de connexion">
348
+ <button class="connect-tab active" data-mode="mac"><i class='bx bx-barcode'></i> Code MAC</button>
349
+ <button class="connect-tab" data-mode="xtreme"><i class='bx bx-link'></i> Xtream Codes</button>
350
+ <button class="connect-tab" data-mode="m3u"><i class='bx bx-file'></i> M3U/M3U8</button>
351
+ <button class="connect-tab" data-mode="qr"><i class='bx bx-qr'></i> QR Code</button>
352
+ </div>
353
+
354
+ <!-- MAC -->
355
+ <form id="form-mac" class="form" autocomplete="on">
356
+ <div class="field">
357
+ <label for="macAddress">Adresse MAC</label>
358
+ <div class="input">
359
+ <i class='bx bx-chip'></i>
360
+ <input id="macAddress" name="mac" inputmode="text" placeholder="AB:CD:EF:12:34:56" maxlength="17" />
361
+ </div>
362
+ <div class="helper">Format accepté: XX:XX:XX:XX:XX:XX (lettres et chiffres)</div>
363
+ </div>
364
+ <div class="field">
365
+ <label for="portalMac">Portail</label>
366
+ <div class="input">
367
+ <i class='bx bx-globe'></i>
368
+ <input id="portalMac" name="portal" placeholder="https://mon-portail.example.com" />
369
+ </div>
370
+ </div>
371
+ <div class="cta">
372
+ <button type="button" class="btn" id="loginMac"><i class='bx bx-log-in'></i> Se connecter</button>
373
+ <button type="button" class="btn secondary" id="genMac"><i class='bx bx-dice-6'></i> Générer MAC</button>
374
+ </div>
375
+ <div class="success" id="macSuccess"><i class='bx bx-check-shield'></i> Connecté (démo) — vos playlists seront chargées.</div>
376
+ <div class="error" id="macError"><i class='bx bx-error'></i> MAC invalide. Vérifiez le format.</div>
377
+ </form>
378
+
379
+ <!-- Xtream -->
380
+ <form id="form-xtreme" class="form hidden" autocomplete="on">
381
+ <div class="row-2">
382
+ <div class="field">
383
+ <label for="xtHost">Hôte/URL</label>
384
+ <div class="input">
385
+ <i class='bx bx-globe'></i>
386
+ <input id="xtHost" placeholder="ex: http://example.com:8080" />
387
+ </div>
388
+ </div>
389
+ <div class="field">
390
+ <label for="xtProtocol">Protocole</label>
391
+ <div class="input">
392
+ <i class='bx bx-traffic-barrier'></i>
393
+ <select id="xtProtocol">
394
+ <option value="http">HTTP</option>
395
+ <option value="https">HTTPS</option>
396
+ </select>
397
+ </div>
398
+ </div>
399
+ </div>
400
+ <div class="row-2">
401
+ <div class="field">
402
+ <label for="xtUser">Utilisateur</label>
403
+ <div class="input">
404
+ <i class='bx bx-user'></i>
405
+ <input id="xtUser" placeholder="username" />
406
+ </div>
407
+ </div>
408
+ <div class="field">
409
+ <label for="xtPass">Mot de passe</label>
410
+ <div class="input">
411
+ <i class='bx bx-lock-alt'></i>
412
+ <input id="xtPass" type="password" placeholder="password" />
413
+ </div>
414
+ </div>
415
+ </div>
416
+ <div class="cta">
417
+ <button type="button" class="btn" id="loginXtream"><i class='bx bx-log-in'></i> Se connecter</button>
418
+ <button type="button" class="btn secondary" id="testXtream"><i class='bx bx-plug'></i> Tester</button>
419
+ </div>
420
+ <div class="success" id="xtSuccess"><i class='bx bx-check-shield'></i> Authentification réussie (démo).</div>
421
+ <div class="error" id="xtError"><i class='bx bx-error'></i> Paramètres Xtream incomplets.</div>
422
+ </form>
423
+
424
+ <!-- M3U -->
425
+ <form id="form-m3u" class="form hidden" autocomplete="on">
426
+ <div class="field">
427
+ <label for="m3uUrl">URL de la playlist M3U/M3U8</label>
428
+ <div class="input">
429
+ <i class='bx bx-link-external'></i>
430
+ <input id="m3uUrl" placeholder="https://.../playlist.m3u8" />
431
+ </div>
432
+ </div>
433
+ <div class="field">
434
+ <label for="m3uEpg">URL EPG (optionnel)</label>
435
+ <div class="input">
436
+ <i class='bx bx-calendar-event'></i>
437
+ <input id="m3uEpg" placeholder="https://.../guide.xml" />
438
+ </div>
439
+ </div>
440
+ <div class="cta">
441
+ <button type="button" class="btn" id="loadM3u"><i class='bx bx-cloud-download'></i> Charger</button>
442
+ <button type="button" class="btn secondary" id="demoM3u"><i class='bx bx-line-chart'></i> Utiliser démo</button>
443
+ </div>
444
+ <div class="success" id="m3uSuccess"><i class='bx bx-check-shield'></i> Playlist chargée (démo).</div>
445
+ <div class="error" id="m3uError"><i class='bx bx-error'></i> URL non valide.</div>
446
+ </form>
447
+
448
+ <!-- QR -->
449
+ <div id="form-qr" class="form hidden">
450
+ <div class="field">
451
+ <label>Scanner un QR Code</label>
452
+ <div class="qr-box" id="qrBox">
453
+ <i class='bx bx-qr' style="font-size: 56px; color: var(--muted)"></i>
454
+ </div>
455
+ <div class="helper">Autorisez l’accès à la caméra pour scanner (démo non connectée au flux caméra).</div>
456
+ </div>
457
+ <div class="cta">
458
+ <button type="button" class="btn" id="startQr"><i class='bx bx-camera'></i> Démarrer</button>
459
+ <button type="button" class="btn secondary" id="pasteQr"><i class='bx bx-clipboard'></i> Coller un contenu</button>
460
+ </div>
461
+ <div class="success" id="qrSuccess"><i class='bx bx-check-shield'></i> QR lu (démo) — paramètres appliqués.</div>
462
+ <div class="error" id="qrError"><i class='bx bx-error'></i> Lecture QR impossible.</div>
463
+ </div>
464
+ </div>
465
+
466
+ <div class="connect-panel">
467
+ <h3 style="margin:0 0 10px;font-size:16px">Aperçu de session</h3>
468
+ <div class="empty" id="sessionEmpty">
469
+ <i class='bx bx-id-card' style="font-size: 28px;"></i>
470
+ <div>Non connecté. Choisissez un mode de connexion à gauche.</div>
471
+ </div>
472
+ <div id="sessionInfo" class="hidden">
473
+ <div class="tag" id="sessionMode">Mode: -</div>
474
+ <div style="height:8px"></div>
475
+ <div class="grid" style="--card-w: 180px;" id="sessionPreview">
476
+ <!-- quelques cartes de démo chargées après connexion -->
477
+ </div>
478
+ <div style="height:10px"></div>
479
+ <div class="cta">
480
+ <button class="btn" id="goToLive"><i class='bx bx-television'></i> Aller au Direct</button>
481
+ <button class="btn secondary" id="disconnect"><i class='bx bx-power-off'></i> Se déconnecter</button>
482
+ </div>
483
+ </div>
484
+ </div>
485
+ </div>
486
+ </section>
487
+
488
+ <!-- Catalogue grids -->
489
+ <div id="grid-live" class="grid hidden" role="region" aria-label="Chaînes en direct"></div>
490
  <div id="grid-vod" class="grid hidden" role="region" aria-label="Films VOD"></div>
491
  <div id="grid-series" class="grid hidden" role="region" aria-label="Séries TV"></div>
492
  <div id="grid-favs" class="grid hidden" role="region" aria-label="Favoris">
 
574
 
575
  const tabs = document.querySelectorAll('.tab');
576
  const grids = {
577
+ connect: document.getElementById('grid-connect'),
578
  live: document.getElementById('grid-live'),
579
  vod: document.getElementById('grid-vod'),
580
  series: document.getElementById('grid-series'),
581
  favs: document.getElementById('grid-favs'),
582
  recent: document.getElementById('grid-recent')
583
  };
584
+ const catalogToolbar = document.getElementById('catalogToolbar');
585
 
586
  // Tabs
587
  tabs.forEach(tab => {
 
590
  tab.classList.add('active');
591
  const chosen = tab.dataset.tab;
592
  Object.keys(grids).forEach(k => grids[k].classList.toggle('hidden', k !== chosen));
593
+ // hide toolbar on connection tab
594
+ catalogToolbar.style.display = chosen === 'connect' ? 'none' : '';
595
  localStorage.setItem('iptv_tab', chosen);
596
  applySearchAndFilters();
597
  });
 
643
  function saveFavs(f) { localStorage.setItem('iptv_favs', JSON.stringify(f)); }
644
  function favKey(item, type){ return `${type}:${item.id}`; }
645
 
646
+ function