<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

<!-- ── PRIMARY SEO ── -->
<title>MSTV Network | Free Live Indian, Punjabi &amp; Religious TV Channels</title>
<meta name="description" content="Watch MSTV Network for free live Indian, Punjabi, and religious TV channels. High-speed HD streaming for cricket and entertainment available on all devices.">
<meta name="keywords" content="MSTV, live Indian TV, Punjabi channels, religious channels, HD streaming, free live TV, cricket streaming, Indian entertainment, live stream">
<meta name="author" content="MSTV Network">
<meta name="robots" content="index, follow">
<meta name="googlebot" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1">
<meta name="theme-color" content="#0C0E14">
<meta name="application-name" content="MSTV Network">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-title" content="MSTV Network">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">

<!-- ── CANONICAL ── -->
<link id="canonical-link" rel="canonical" href="https://mstvnet.netlify.app/">

<!-- ── FAVICON ── -->
<link rel="icon" href="/favicon.ico" sizes="any">
<link rel="icon" href="/favicon.ico" type="image/x-icon">

<!-- ── OPEN GRAPH ── -->
<meta property="og:type" content="website">
<meta property="og:site_name" content="Apna Tv">
<meta property="og:url" content="https://apnatv.netlify.app/">
<meta property="og:title" content="MSTV Network | Free Live Indian, Pakistani &amp; Religious TV Channels">
<meta property="og:description" content="Watch 100+ Live Indian, Pakistani, and Religious channels for free in HD. Cricket, entertainment, devotional and more — all on one platform.">
<meta property="og:image" content="https://apnatv.netlify.app/og-image.webp">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="Apna Tv — Live Indian TV Streaming">
<meta property="og:locale" content="en_IN">

<!-- ── TWITTER / X CARD ── -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@MStvNetwork">
<meta name="twitter:url" content="https://apnatv.netlify.app/">
<meta name="twitter:title" content="MSTV Network | Free Live Indian &amp; Punjabi TV">
<meta name="twitter:description" content="Watch 100+ Live Indian, Punjabi, and Religious channels for free in HD. Available on all devices.">
<meta name="twitter:image" content="https://apnatv.netlify.app/og-image.webp">
<meta name="twitter:image:alt" content="MSTV Network — Live Indian TV Streaming">

<!-- ── STRUCTURED DATA ── -->
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "Organization",
      "@id": "https://apnatv.netlify.app/#organization",
      "name": "Apna Tv",
      "url": "https://apnatv.netlify.app",
      "logo": {
        "@type": "ImageObject",
        "url": "https://apnatv.netlify.app/og-image.png",
        "width": 1200,
        "height": 630
      },
      "description": "Watch 100+ Live Indian and Punjabi TV channels in HD for free.",
      "sameAs": []
    },
    {
      "@type": "WebSite",
      "@id": "https://apnatv.netlify.app/#website",
      "url": "https://apnatv.netlify.app",
      "name": "Apna Tv",
      "description": "Free live Indian, Punjabi and religious TV channels in HD.",
      "publisher": { "@id": "https://apnatv.netlify.app/#organization" },
      "potentialAction": {
        "@type": "SearchAction",
        "target": {
          "@type": "EntryPoint",
          "urlTemplate": "https://apnatv.netlify.app/?q={search_term_string}"
        },
        "query-input": "required name=search_term_string"
      },
      "inLanguage": ["en", "pa", "hi"]
    },
    {
      "@type": "WebPage",
      "@id": "https://apnatv.netlify.app/#webpage",
      "url": "https://apnatv.netlify.app",
      "name": "MSTV Network | Free Live Indian, Punjabi &amp; Religious TV",
      "isPartOf": { "@id": "https://apnatv.netlify.app/#website" },
      "about": { "@id": "https://apnatv.netlify.app/#organization" },
      "description": "Watch free live Indian, Punjabi, and religious TV channels in HD on MSTV Network.",
      "breadcrumb": {
        "@type": "BreadcrumbList",
        "itemListElement": [{ "@type": "ListItem", "position": 1, "name": "Home", "item": "https://mstvnet.netlify.app/" }]
      }
    }
  ]
}
</script>

<!-- ── PRECONNECTS ── -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin>

<!-- ── FONTS ── -->
<link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=DM+Sans:wght@300;400;500;600&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">

<!-- ── GOATCOUNTER ANALYTICS ── -->
<script data-goatcounter="https://mstv.goatcounter.com/count" async src="//gc.zgo.at/count.js"></script>

<!-- ── PLAYER LIBS ── -->
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest/dist/hls.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/shaka-player/4.7.6/shaka-player.compiled.min.js"></script>

<style>
:root {
  --bg:       #0b0d14;
  --bg2:      #0f1219;
  --bg3:      #151923;
  --surface:  #1c2235;
  --border:   rgba(255,255,255,0.08);
  --accent:   #e63e6d;
  --accent2:  #ff6b35;
  --gold:     #f5c842;
  --text:     #e8ecf4;
  --muted:    #6b7694;
  --live:     #22c55e;
  --radius:   12px;
  --sidebar:  290px;
  --header-h: 62px;
  --player-h: clamp(200px, 44vw, 560px);
}

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html { scroll-behavior: smooth; }
body {
  min-height: 100vh;
  background: var(--bg);
  color: var(--text);
  font-family: 'DM Sans', sans-serif;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

::-webkit-scrollbar { width: 4px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--surface); border-radius: 2px; }

/* ── HEADER: sticky, hides on scroll-down, returns on scroll-up ── */
#header {
  position: sticky; top: 0; z-index: 200;
  height: var(--header-h);
  background: rgba(11,13,20,0.94);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border-bottom: 1px solid var(--border);
  display: flex; align-items: center;
  padding: 0 18px; gap: 14px;
  transition: transform .3s cubic-bezier(.4,0,.2,1);
  will-change: transform;
}
#header.hide { transform: translateY(-100%); }

.logo-mark { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
.logo-icon {
  width: 34px; height: 34px;
  background: linear-gradient(135deg, var(--accent), var(--accent2));
  border-radius: 8px; display: grid; place-items: center; font-size: 17px; flex-shrink: 0;
}
.logo-text {
  font-family: 'Bebas Neue', sans-serif; font-size: 24px; letter-spacing: 2px;
  background: linear-gradient(90deg, var(--accent), var(--accent2));
  -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;
}
.logo-sub {
  font-family: 'JetBrains Mono', monospace; font-size: 8px;
  color: var(--muted); letter-spacing: 3px; text-transform: uppercase; margin-top: -5px;
}
.live-badge {
  display: flex; align-items: center; gap: 6px;
  background: rgba(34,197,94,0.1); border: 1px solid rgba(34,197,94,0.28);
  border-radius: 20px; padding: 4px 10px;
  font-family: 'JetBrains Mono', monospace; font-size: 10px;
  font-weight: 600; color: var(--live); letter-spacing: 1.5px; flex-shrink: 0;
}
.live-dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--live); box-shadow: 0 0 6px var(--live);
  animation: blink 1.4s ease-in-out infinite;
}
@keyframes blink { 0%,100%{opacity:1;transform:scale(1)} 50%{opacity:.4;transform:scale(.7)} }

.search-wrap { flex: 1; max-width: 400px; margin: 0 auto; position: relative; }
.search-wrap svg {
  position: absolute; left: 11px; top: 50%; transform: translateY(-50%);
  color: var(--muted); pointer-events: none;
}
#searchInput {
  width: 100%; height: 36px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 18px; color: var(--text);
  font-family: 'DM Sans', sans-serif; font-size: 13px;
  padding: 0 14px 0 34px; outline: none;
  transition: border-color .2s, box-shadow .2s;
}
#searchInput::placeholder { color: var(--muted); }
#searchInput:focus { border-color: rgba(230,62,109,.45); box-shadow: 0 0 0 3px rgba(230,62,109,.07); }

#channelCount { font-family: 'JetBrains Mono', monospace; font-size: 10px; color: var(--muted); flex-shrink: 0; }
#menuToggle {
  display: none; align-items: center; justify-content: center;
  width: 34px; height: 34px; background: var(--surface);
  border: 1px solid var(--border); border-radius: 8px; cursor: pointer; color: var(--text); flex-shrink: 0;
}

/* ── LAYOUT ── */
#pageBody { display: flex; min-height: calc(100vh - var(--header-h)); align-items: flex-start; }

/* ── SIDEBAR ── */
#sidebar {
  width: var(--sidebar); flex-shrink: 0;
  background: var(--bg2); border-right: 1px solid var(--border);
  position: sticky; top: var(--header-h);
  height: calc(100vh - var(--header-h));
  display: flex; flex-direction: column; overflow: hidden;
  transition: transform .3s cubic-bezier(.4,0,.2,1);
}
.sidebar-top {
  padding: 14px 14px 12px; border-bottom: 1px solid var(--border); flex-shrink: 0;
}
.section-label {
  font-family: 'JetBrains Mono', monospace; font-size: 9px;
  letter-spacing: 2.5px; color: var(--muted); text-transform: uppercase; padding-left: 2px;
}
#channelList { flex: 1; overflow-y: auto; padding: 6px; }

.channel-item {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 10px; border-radius: 10px; cursor: pointer;
  transition: background .15s; position: relative; border: 1px solid transparent;
}
.channel-item:hover { background: var(--surface); }
.channel-item.active { background: var(--surface); border-color: rgba(230,62,109,.3); }
.channel-item.active::before {
  content: ''; position: absolute; left: 0; top: 22%; bottom: 22%;
  width: 3px; border-radius: 2px;
  background: linear-gradient(var(--accent), var(--accent2));
}
.ch-logo {
  width: 40px; height: 40px; border-radius: 8px; object-fit: contain;
  background: var(--bg3); flex-shrink: 0; border: 1px solid var(--border);
}
.ch-logo-fallback {
  width: 40px; height: 40px; border-radius: 8px; background: var(--bg3);
  flex-shrink: 0; border: 1px solid var(--border);
  display: grid; place-items: center; font-size: 15px; color: var(--muted);
}
.ch-info { flex: 1; min-width: 0; }
.ch-name {
  font-size: 12.5px; font-weight: 500; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ch-arrow { color: var(--muted); flex-shrink: 0; opacity: 0; transition: opacity .15s, color .15s; }
.channel-item:hover .ch-arrow, .channel-item.active .ch-arrow { opacity: 1; color: var(--accent); }

.empty-state { text-align: center; padding: 40px 20px; color: var(--muted); }
.empty-state svg { margin-bottom: 12px; opacity: .35; display: block; margin-left: auto; margin-right: auto; }
.empty-state p { font-size: 13px; }

/* ── MAIN ── */
#main { flex: 1; min-width: 0; display: flex; flex-direction: column; }

/* ── PLAYER ── */
#playerArea {
  background: #000; position: relative;
  width: 100%; height: var(--player-h); flex-shrink: 0; overflow: hidden;
}

/* VIVID PLACEHOLDER */
#playerPlaceholder {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 18px;
  background: linear-gradient(135deg, #0f1a2e 0%, #1a0f2e 40%, #2a0f1a 80%, #0d1a22 100%);
  z-index: 2; transition: opacity .35s; overflow: hidden;
}
#playerPlaceholder::before {
  content: ''; position: absolute; inset: 0;
  background-image:
    linear-gradient(rgba(230,62,109,.07) 1px, transparent 1px),
    linear-gradient(90deg, rgba(230,62,109,.07) 1px, transparent 1px);
  background-size: 48px 48px;
  animation: gridDrift 14s linear infinite;
}
@keyframes gridDrift { from{background-position:0 0} to{background-position:48px 48px} }
#playerPlaceholder::after {
  content: ''; position: absolute; inset: 0;
  background: radial-gradient(ellipse 65% 55% at 50% 50%,
    rgba(230,62,109,.2) 0%, rgba(255,107,53,.09) 40%, transparent 70%);
}
.placeholder-inner {
  position: relative; z-index: 1;
  display: flex; flex-direction: column; align-items: center; gap: 14px;
  text-align: center; padding: 0 24px;
}
.ph-ring {
  width: 80px; height: 80px; border-radius: 50%;
  border: 2px solid rgba(230,62,109,.4);
  display: grid; place-items: center;
  box-shadow: 0 0 40px rgba(230,62,109,.22), inset 0 0 20px rgba(230,62,109,.06);
  animation: ringPulse 3s ease-in-out infinite;
}
@keyframes ringPulse {
  0%,100%{box-shadow:0 0 30px rgba(230,62,109,.18),inset 0 0 15px rgba(230,62,109,.04)}
  50%{box-shadow:0 0 64px rgba(230,62,109,.38),inset 0 0 30px rgba(230,62,109,.1)}
}
.ph-icon { font-size: 36px; filter: drop-shadow(0 0 14px rgba(230,62,109,.55)); }
.ph-title {
  font-family: 'Bebas Neue', sans-serif; font-size: 22px; letter-spacing: 3px;
  background: linear-gradient(90deg, var(--accent), var(--accent2));
  -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;
}
.ph-sub { font-size: 13px; color: var(--muted); line-height: 1.6; max-width: 260px; }
.ph-cue {
  display: flex; align-items: center; gap: 7px;
  font-family: 'JetBrains Mono', monospace; font-size: 10px;
  color: rgba(107,118,148,.7); letter-spacing: 1.5px; text-transform: uppercase;
  animation: nudge 2.2s ease-in-out infinite;
}
@keyframes nudge { 0%,100%{transform:translateX(0)} 50%{transform:translateX(-6px)} }
.ph-cue svg { color: var(--accent); }
#playerPlaceholder.hidden { opacity: 0; pointer-events: none; }

/* loader */
#playerLoader {
  position: absolute; inset: 0; z-index: 5;
  display: grid; place-items: center; background: rgba(0,0,0,.8);
  opacity: 0; pointer-events: none; transition: opacity .2s;
}
#playerLoader.show { opacity: 1; pointer-events: all; }
.spinner {
  width: 44px; height: 44px;
  border: 3px solid rgba(255,255,255,.1); border-top-color: var(--accent);
  border-radius: 50%; animation: spin .7s linear infinite;
}
@keyframes spin { to{transform:rotate(360deg)} }

/* error */
#playerError {
  position: absolute; inset: 0; z-index: 6;
  display: none; flex-direction: column; align-items: center; justify-content: center;
  gap: 12px; background: rgba(0,0,0,.88); color: var(--muted);
  font-size: 14px; text-align: center; padding: 24px;
}
#playerError.show { display: flex; }
#playerError .err-icon { font-size: 36px; color: var(--accent); opacity: .7; }

#videoEl {
  width: 100%; height: 100%; object-fit: contain;
  display: none; position: absolute; inset: 0; z-index: 1; background: #000;
}
#iframeEl {
  width: 100%; height: 100%; border: none;
  display: none; position: absolute; inset: 0; z-index: 1; background: #000;
  pointer-events: auto;
}


/* ── NOW PLAYING ── */
#nowPlayingBar {
  display: none; align-items: center; gap: 12px;
  padding: 10px 16px; background: var(--bg3); border-bottom: 1px solid var(--border); flex-shrink: 0;
}
#nowPlayingBar.visible { display: flex; }
.np-logo { width: 30px; height: 30px; border-radius: 6px; object-fit: contain; background: var(--bg2); border: 1px solid var(--border); }
.np-logo-fallback { width: 30px; height: 30px; border-radius: 6px; background: var(--bg2); border: 1px solid var(--border); display: grid; place-items: center; font-size: 13px; }
.np-name { font-size: 13px; font-weight: 600; color: var(--text); }
.np-type { font-family: 'JetBrains Mono', monospace; font-size: 9px; letter-spacing: 1.5px; color: var(--muted); text-transform: uppercase; }
.np-live { margin-left: auto; display: flex; align-items: center; gap: 6px; font-family: 'JetBrains Mono', monospace; font-size: 10px; color: var(--live); letter-spacing: 1px; }
.np-live-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--live); animation: blink 1.4s infinite; }

/* ── GRID ── */
#gridWrap { padding: 16px; }
#gridTitle { font-family: 'Bebas Neue', sans-serif; font-size: 19px; letter-spacing: 1.5px; color: var(--text); margin-bottom: 12px; display: flex; align-items: center; gap: 10px; }
#gridTitle span.count { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: var(--muted); font-weight: 400; letter-spacing: 1px; }
#channelGrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); gap: 10px; }

.grid-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 12px 8px;
  cursor: pointer; text-align: center;
  transition: border-color .18s, transform .18s, box-shadow .18s;
  position: relative; overflow: hidden;
}
.grid-card::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(135deg, var(--accent), var(--accent2));
  opacity: 0; transition: opacity .2s;
}
.grid-card:hover { border-color: rgba(230,62,109,.4); transform: translateY(-2px); box-shadow: 0 8px 24px rgba(0,0,0,.3); }
.grid-card:hover::after { opacity: .04; }
.grid-card.active { border-color: var(--accent); }
.grid-card.active::after { opacity: .08; }
.grid-logo { width: 54px; height: 54px; object-fit: contain; border-radius: 8px; margin: 0 auto 8px; background: var(--bg3); display: block; }
.grid-logo-fallback { width: 54px; height: 54px; border-radius: 8px; margin: 0 auto 8px; background: var(--bg3); display: grid; place-items: center; font-size: 20px; }
.grid-name { font-size: 11px; font-weight: 500; color: var(--text); line-height: 1.3; word-break: break-word; }


/* ── FOOTER ── */
#footer {
  background: rgba(8,10,15,0.98); border-top: 1px solid var(--border);
  padding: 16px 20px 14px;
}
.footer-top {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-bottom: 12px; padding-bottom: 12px;
  border-bottom: 1px solid var(--border);
}
.footer-left { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.footer-brand {
  font-family: 'Bebas Neue', sans-serif; font-size: 16px; letter-spacing: 2px;
  background: linear-gradient(90deg, var(--accent), var(--accent2));
  -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;
}
.footer-sep { color: var(--border); }
.footer-copy { font-size: 11px; color: var(--muted); }
.footer-right { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.footer-stat { font-family: 'JetBrains Mono', monospace; font-size: 10px; color: var(--muted); display: flex; align-items: center; gap: 5px; }
.footer-stat strong { color: var(--text); }
#updateStatus { font-family: 'JetBrains Mono', monospace; font-size: 10px; color: var(--muted); display: flex; align-items: center; gap: 5px; }
.update-dot { width: 5px; height: 5px; border-radius: 50%; background: var(--gold); animation: blink 2s infinite; }

/* DISCLAIMER */
.footer-disclaimer {
  font-size: 10.5px; color: var(--muted); line-height: 1.7;
  text-align: center; max-width: 860px; margin: 0 auto;
}
.footer-disclaimer strong { color: rgba(245,200,66,.8); font-weight: 600; }
.footer-disclaimer a { color: var(--muted); text-decoration: underline; text-underline-offset: 2px; }

/* ── SIDEBAR OVERLAY ── */
#sidebarOverlay {
  display: none; position: fixed; inset: 0;
  background: rgba(0,0,0,.65); z-index: 150; backdrop-filter: blur(2px);
}
#sidebarOverlay.show { display: block; }

/* ── RESPONSIVE ── */
@media (max-width: 768px) {
  :root { --sidebar: 260px; --player-h: 56vw; }
  #menuToggle { display: flex; }
  #sidebar {
    position: fixed; top: var(--header-h); bottom: 0; left: 0;
    z-index: 160; height: auto; transform: translateX(-100%);
    box-shadow: 4px 0 28px rgba(0,0,0,.5);
  }
  #sidebar.open { transform: translateX(0); }
  #channelCount { display: none; }
  #channelGrid { grid-template-columns: repeat(auto-fill, minmax(90px, 1fr)); }
  .footer-top { flex-direction: column; align-items: flex-start; gap: 8px; }
}
@media (max-width: 480px) {
  :root { --player-h: 56vw; }
  .logo-sub { display: none; }
  #gridWrap { padding: 10px; }
  #channelGrid { grid-template-columns: repeat(auto-fill, minmax(74px, 1fr)); gap: 8px; }
  .footer-right { display: none; }
}


/* ── CATEGORY PILLS ── */
#categoryWrap {
  padding: 8px 8px 4px; border-bottom: 1px solid var(--border); flex-shrink: 0;
  overflow-x: auto; white-space: nowrap;
  scrollbar-width: none;
}
#categoryWrap::-webkit-scrollbar { display: none; }
.cat-pill {
  display: inline-flex; align-items: center; gap: 5px;
  font-family: 'DM Sans', sans-serif; font-size: 11px; font-weight: 500;
  padding: 5px 11px; border-radius: 20px; cursor: pointer;
  border: 1px solid var(--border); background: transparent; color: var(--muted);
  transition: all .17s; white-space: nowrap; margin-right: 5px; margin-bottom: 4px;
  flex-shrink: 0;
}
.cat-pill:hover { color: var(--text); border-color: rgba(255,255,255,.18); }
.cat-pill.active {
  background: linear-gradient(90deg, var(--accent), var(--accent2));
  border-color: transparent; color: #fff;
}
.cat-pill .cat-icon { font-size: 12px; line-height: 1; }



@keyframes fadeSlide { from{opacity:0;transform:translateY(8px)} to{opacity:1;transform:none} }
.channel-item, .grid-card { animation: fadeSlide .22s ease both; }
</style>
</head>
<body>

<!-- HEADER -->
<header id="header">
  <div class="logo-mark">
    <div class="logo-icon">📺</div>
    <div>
      <div class="logo-text">MSTV</div>
      <div class="logo-sub">Live Network</div>
    </div>
  </div>

  <div class="live-badge"><span class="live-dot"></span>LIVE</div>

  <div class="search-wrap">
    <svg width="14" height="14" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
      <circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
    </svg>
    <input id="searchInput" type="text" placeholder="Search channels…" autocomplete="off" spellcheck="false">
  </div>

  <span id="channelCount">— channels</span>

  <button id="menuToggle" aria-label="Toggle channels">
    <svg width="17" height="17" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
      <line x1="3" y1="6" x2="21" y2="6"/>
      <line x1="3" y1="12" x2="21" y2="12"/>
      <line x1="3" y1="18" x2="21" y2="18"/>
    </svg>
  </button>
</header>

<!-- SIDEBAR OVERLAY -->
<div id="sidebarOverlay"></div>

<!-- PAGE BODY -->
<div id="pageBody">

  <!-- SIDEBAR -->
  <aside id="sidebar">
    <div class="sidebar-top">
      <div class="section-label">Channels List</div>
    </div>
    <div id="categoryWrap">
      <!-- Category pills injected by JS -->
    </div>
    <div id="channelList"></div>
  </aside>

  <!-- MAIN -->
  <main id="main">

    <!-- PLAYER -->
    <div id="playerArea">
      <!-- Vivid animated placeholder -->
      <div id="playerPlaceholder">
        <div class="placeholder-inner">
          <div class="ph-ring"><span class="ph-icon">📡</span></div>
          <div class="ph-title">Select a Channel</div>
          <div class="ph-sub">Choose any channel from the list to start watching live.</div>
          <div class="ph-cue">
            <svg width="13" height="13" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
              <polyline points="15 18 9 12 15 6"/>
            </svg>
            Pick a channel to watch
          </div>
        </div>
      </div>

      <div id="playerLoader"><div class="spinner"></div></div>

      <div id="playerError">
        <div class="err-icon">⚠️</div>
        <p id="playerErrorMsg">Stream could not be loaded.</p>
      </div>

      <video id="videoEl" controls playsinline></video>

      <iframe id="iframeEl"
        sandbox="allow-scripts allow-same-origin allow-presentation allow-forms"
        allow="autoplay; fullscreen; encrypted-media; picture-in-picture"
        allowfullscreen></iframe>
    </div>

    <!-- NOW PLAYING -->
    <div id="nowPlayingBar">
      <div id="npLogoWrap"></div>
      <div>
        <div class="np-name" id="npName">—</div>
      </div>
      <div class="np-live"><span class="np-live-dot"></span>LIVE</div>
    </div>

    <!-- GRID -->
    <div id="gridWrap">
      <div id="gridTitle">ALL CHANNELS <span class="count" id="gridCount"></span></div>
      <div id="channelGrid"></div>
    </div>

    <!-- FOOTER (inside main — scrolls with content) -->
    <footer id="footer">
      <div class="footer-top">
        <div class="footer-left">
          <span class="footer-brand">MSTV</span>
          <span class="footer-sep">|</span>
          <span class="footer-copy">© 2025 MSTV Live Network. All rights reserved.</span>
        </div>
        <div class="footer-right">
          <div class="footer-stat">Channels: <strong id="footerCount">—</strong></div>
          <div id="updateStatus">
            <span class="update-dot"></span>
            <span id="updateText">Syncing…</span>
          </div>
        </div>
      </div>
      <div class="footer-disclaimer">
        <strong>⚠ Disclaimer:</strong>&nbsp; All content displayed on MSTV is sourced exclusively from streams
        that are publicly available on the internet. <strong>We do not host, upload, store, or transmit any media files on our servers.</strong>
        MSTV acts solely as an aggregator of publicly accessible links and assumes no responsibility for the
        availability, accuracy, legality, or suitability of any third-party content. Viewers access all streams
        entirely at their own discretion and risk. Copyrights remain with the respective content owners and broadcasters.
        If you believe any content linked here infringes your intellectual property rights, please contact the
        originating stream provider directly.
      </div>
    </footer>

  </main>
</div>

<script>
// ── CONFIG ──
const JSON_URL      = 'channels2.json';
const POLL_INTERVAL = 8000;

// ── STATE ──
let channels         = [];
let filteredChannels = [];
let currentChannel   = null;
let searchQuery      = '';
let activeCategory   = 'All';
let hlsInstance      = null;
let shakaPlayer      = null;
let pollTimer        = null;
let lastJsonHash     = '';

// ── DOM REFS ──
const $searchInput       = document.getElementById('searchInput');
const $channelList       = document.getElementById('channelList');
const $channelGrid       = document.getElementById('channelGrid');
const $channelCount      = document.getElementById('channelCount');
const $gridCount         = document.getElementById('gridCount');
const $footerCount       = document.getElementById('footerCount');
const $videoEl           = document.getElementById('videoEl');
const $iframeEl          = document.getElementById('iframeEl');
const $playerPlaceholder = document.getElementById('playerPlaceholder');
const $playerLoader      = document.getElementById('playerLoader');
const $playerError       = document.getElementById('playerError');
const $playerErrorMsg    = document.getElementById('playerErrorMsg');
const $nowPlayingBar     = document.getElementById('nowPlayingBar');
const $npLogoWrap        = document.getElementById('npLogoWrap');
const $npName            = document.getElementById('npName');
const $npType            = document.getElementById('npType');
const $updateText        = document.getElementById('updateText');
const $sidebar           = document.getElementById('sidebar');
const $sidebarOverlay    = document.getElementById('sidebarOverlay');
const $menuToggle        = document.getElementById('menuToggle');
const $header            = document.getElementById('header');

// ── HEADER HIDE-ON-SCROLL ──
// Scrolling down hides the header so viewers get a larger player area.
// Scrolling back up or reaching the top reveals it again.
let lastY = 0;
window.addEventListener('scroll', () => {
  const y = window.scrollY;
  $header.classList.toggle('hide', y > lastY && y > 80);
  lastY = y;
}, { passive: true });

// ── INIT ──
async function init() {
  shaka.polyfill.installAll();
  shakaPlayer = new shaka.Player();
  shakaPlayer.configure({ streaming: { bufferingGoal: 10, rebufferingGoal: 2 } });
  await loadChannels();
  startPolling();
  bindEvents();
}

// ── FETCH & POLL ──
async function loadChannels(silent = false) {
  try {
    const res  = await fetch(JSON_URL + '?_=' + Date.now());
    if (!res.ok) throw new Error('HTTP ' + res.status);
    const text = await res.text();
    const hash = simpleHash(text);
    if (hash === lastJsonHash) { updateTimestamp(); return; }
    lastJsonHash = hash;

    let data = JSON.parse(text);
    if (!Array.isArray(data)) data = data.channels || Object.values(data);
    channels = data.filter(c => c && c.name && c.url);

    silent ? renderSilent() : render();
    updateTimestamp();
  } catch (e) {
    $updateText.textContent = 'Error: ' + e.message;
  }
}

function startPolling() {
  clearInterval(pollTimer);
  pollTimer = setInterval(() => loadChannels(true), POLL_INTERVAL);
}

function updateTimestamp() {
  $updateText.textContent = 'Updated ' + new Date().toLocaleTimeString([], { hour:'2-digit', minute:'2-digit', second:'2-digit' });
}

function simpleHash(str) {
  let h = 0;
  for (let i = 0; i < str.length; i++) h = (Math.imul(31, h) + str.charCodeAt(i)) | 0;
  return h;
}

// ── FILTER ──
function applyFilters() {
  const q = searchQuery.toLowerCase().trim();
  filteredChannels = channels.filter(ch => {
    const nameOk = !q || ch.name.toLowerCase().includes(q);
    const catOk  = activeCategory === 'All' || (ch.category || '').toLowerCase() === activeCategory.toLowerCase();
    return nameOk && catOk;
  });
}

// ── CATEGORIES ──
function getCategories() {
  const cats = ['All'];
  channels.forEach(ch => {
    if (ch.category && !cats.includes(ch.category)) cats.push(ch.category);
  });
  return cats;
}

const CAT_ICONS = {
  'All':'🌐','Entertainment':'🎬','News':'📰','Sports':'⚽','Cricket':'🏏',
  'Punjabi':'🎵','Religious':'🕌','Kids':'🧸','Movies':'🎥','Music':'🎶',
  'Devotional':'🪔','Infotainment':'💡','Regional':'📡','Comedy':'😄'
};

function renderCategories() {
  const cats = getCategories();
  const wrap = document.getElementById('categoryWrap');
  wrap.innerHTML = cats.map(cat => `
    <button class="cat-pill${activeCategory === cat ? ' active' : ''}" data-cat="${esc(cat)}">
      <span class="cat-icon">${CAT_ICONS[cat] || '📺'}</span>${esc(cat)}
    </button>`).join('');
  wrap.querySelectorAll('.cat-pill').forEach(btn =>
    btn.addEventListener('click', () => {
      activeCategory = btn.dataset.cat;
      renderCategories();
      renderSidebar();
      renderGrid();
      updateCounts();
    })
  );
  // Hide wrap if only "All" exists (no categories in json yet)
  wrap.style.display = cats.length > 1 ? 'block' : 'none';
}

// ── RENDER ──
function render()       { applyFilters(); renderCategories(); renderSidebar(); renderGrid(); updateCounts(); }
function renderSilent() { applyFilters(); renderCategories(); renderSidebar(); renderGrid(); updateCounts(); }

function renderSidebar() {
  if (!filteredChannels.length) {
    $channelList.innerHTML = `<div class="empty-state">
      <svg width="28" height="28" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
        <circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
      </svg><p>No channels found</p></div>`;
    return;
  }
  $channelList.innerHTML = filteredChannels.map((ch, i) => `
    <div class="channel-item${currentChannel?.url === ch.url ? ' active' : ''}"
         data-idx="${i}" style="animation-delay:${Math.min(i*16,320)}ms">
      ${logo(ch, 40, 'ch-logo', 'ch-logo-fallback')}
      <div class="ch-info">
        <div class="ch-name" title="${esc(ch.name)}">${esc(ch.name)}</div>
      </div>
      <svg class="ch-arrow" width="13" height="13" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
        <polyline points="9 18 15 12 9 6"/>
      </svg>
    </div>`).join('');

  $channelList.querySelectorAll('.channel-item').forEach(el =>
    el.addEventListener('click', () => { playChannel(filteredChannels[+el.dataset.idx]); closeSidebar(); })
  );
}

function renderGrid() {
  $channelGrid.innerHTML = filteredChannels.map((ch, i) => `
    <div class="grid-card${currentChannel?.url === ch.url ? ' active' : ''}"
         data-idx="${i}" style="animation-delay:${Math.min(i*10,400)}ms">
      ${logo(ch, 54, 'grid-logo', 'grid-logo-fallback')}
      <div class="grid-name">${esc(ch.name)}</div>
    </div>`).join('');

  $channelGrid.querySelectorAll('.grid-card').forEach(el =>
    el.addEventListener('click', () => playChannel(filteredChannels[+el.dataset.idx]))
  );
}

function updateCounts() {
  const n = filteredChannels.length;
  $channelCount.textContent = n + ' channel' + (n !== 1 ? 's' : '');
  $gridCount.textContent    = '(' + n + ')';
  $footerCount.textContent  = channels.length;
}

// ── LOGO HELPER ──
function logo(ch, size, imgCls, fbCls) {
  const s = `width:${size}px;height:${size}px`;
  return ch.logo
    ? `<img class="${imgCls}" src="${esc(ch.logo)}" alt="${esc(ch.name)}" style="${s}"
         onerror="this.style.display='none';this.nextElementSibling.style.display='grid'">
       <div class="${fbCls}" style="${s};display:none">📺</div>`
    : `<div class="${fbCls}" style="${s}">📺</div>`;
}

function esc(s) {
  return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}

// ── PLAY ──
async function playChannel(ch) {
  if (!ch) return;
  currentChannel = ch;

  document.querySelectorAll('.channel-item').forEach(el =>
    el.classList.toggle('active', filteredChannels[+el.dataset.idx]?.url === ch.url));
  document.querySelectorAll('.grid-card').forEach(el =>
    el.classList.toggle('active', filteredChannels[+el.dataset.idx]?.url === ch.url));

  $playerPlaceholder.classList.add('hidden');
  $playerError.classList.remove('show');
  showLoader(true);

  await teardown();

  const type = (ch.type || '').toLowerCase();

  if (type === 'iframe') {
    $videoEl.style.display  = 'none';
    $iframeEl.style.display = 'block';
    $iframeEl.src           = ch.url;
    $iframeEl.onload        = () => showLoader(false);
    $iframeEl.onerror       = () => showError('iFrame could not be loaded.');
  } else if (type === 'dash') {
    $iframeEl.style.display = 'none';
    $videoEl.style.display  = 'block';
    await playDASH(ch.url);
  } else {
    $iframeEl.style.display = 'none';
    $videoEl.style.display  = 'block';
    if (ch.url.includes('.mpd')) await playDASH(ch.url);
    else await playHLS(ch.url);
  }

  updateNowPlaying(ch);

  // Scroll player into view on all screens, offset by header height so the
  // player top edge is never obscured behind the sticky header.
  const playerArea = document.getElementById('playerArea');
  const headerH    = $header.offsetHeight || 62;
  const playerTop  = playerArea.getBoundingClientRect().top + window.scrollY;
  window.scrollTo({ top: playerTop - headerH, behavior: 'smooth' });
}

async function playHLS(url) {
  if (Hls.isSupported()) {
    hlsInstance = new Hls({ enableWorker: true, lowLatencyMode: true, backBufferLength: 30 });
    hlsInstance.on(Hls.Events.ERROR, (e, d) => { if (d.fatal) showError('HLS error: ' + d.type); });
    hlsInstance.on(Hls.Events.MANIFEST_PARSED, () => { showLoader(false); $videoEl.play().catch(()=>{}); });
    hlsInstance.loadSource(url);
    hlsInstance.attachMedia($videoEl);
  } else if ($videoEl.canPlayType('application/vnd.apple.mpegurl')) {
    $videoEl.src = url;
    $videoEl.addEventListener('loadedmetadata', () => { showLoader(false); $videoEl.play().catch(()=>{}); }, { once: true });
    $videoEl.addEventListener('error', () => showError('Stream could not be loaded.'), { once: true });
  } else {
    showError('HLS is not supported on this browser.');
  }
}

async function playDASH(url) {
  try {
    await shakaPlayer.attach($videoEl);
    await shakaPlayer.load(url);
    showLoader(false);
    $videoEl.play().catch(()=>{});
  } catch (e) {
    showError('DASH error: ' + e.message);
  }
}

async function teardown() {
  if (hlsInstance) { hlsInstance.destroy(); hlsInstance = null; }
  try { await shakaPlayer.detach(); } catch {}
  $videoEl.pause();
  $videoEl.removeAttribute('src');
  $videoEl.load();
  $iframeEl.src = 'about:blank';
}



function updateNowPlaying(ch) {
  $nowPlayingBar.classList.add('visible');
  $npName.textContent = ch.name;
  $npLogoWrap.innerHTML = ch.logo
    ? `<img class="np-logo" src="${esc(ch.logo)}" alt="${esc(ch.name)}"
         onerror="this.style.display='none';this.nextElementSibling.style.display='grid'">
       <div class="np-logo-fallback" style="display:none">📺</div>`
    : `<div class="np-logo-fallback">📺</div>`;
}

function showLoader(s) { $playerLoader.classList.toggle('show', s); }
function showError(msg) {
  showLoader(false);
  $playerErrorMsg.textContent = msg || 'Stream could not be loaded.';
  $playerError.classList.add('show');
}

// ── EVENTS ──
function bindEvents() {
  $searchInput.addEventListener('input', e => { searchQuery = e.target.value; render(); });
  $menuToggle.addEventListener('click', () => {
    $sidebar.classList.toggle('open');
    $sidebarOverlay.classList.toggle('show', $sidebar.classList.contains('open'));
  });
  $sidebarOverlay.addEventListener('click', closeSidebar);
}
function closeSidebar() {
  $sidebar.classList.remove('open');
  $sidebarOverlay.classList.remove('show');
}

// ── TOP-NAVIGATION BLOCKER ──
(function() {
  const _open = window.open.bind(window);
  window.open = (url, target, f) => {
    if (target === '_blank' || target === '_top' || target === '_parent') return null;
    return _open(url, target, f);
  };
  window.addEventListener('beforeunload', e => {
    if (currentChannel && (currentChannel.type||'').toLowerCase() === 'iframe') {
      e.preventDefault(); e.returnValue = ''; return '';
    }
  });
  window.addEventListener('message', e => {
    if (e.data && typeof e.data === 'object' && (e.data.type === 'navigate' || e.data.href || e.data.redirect))
      e.stopImmediatePropagation();
  }, true);
})();

init();
</script>
</body>
</html>
