
(() => {
  'use strict';

  const layout  = document.getElementById('layout');
  const scrim   = document.getElementById('scrim');
  const reduced = matchMedia('(prefers-reduced-motion: reduce)').matches;
  const isTablet = () => matchMedia('(max-width: 1023px)').matches;

  // ============================================================
  // FLOATING HEADER — transparent over the hero scene; turns solid once the
  // hero has mostly scrolled away so nav stays legible over the game grid.
  // ============================================================
  (() => {
    const hdr = document.querySelector('.header');
    if (!hdr) return;
    const onScroll = () => hdr.classList.toggle('is-solid', window.scrollY > 48);
    onScroll();
    window.addEventListener('scroll', onScroll, { passive: true });
  })();

  // ============================================================
  // SIDEBAR — collapse (desktop) + drawer (mobile/tablet)
  // The .collapsed class is a DESKTOP-ONLY visual state (mini-icon dock).
  // Homepage loads minimized by default; on mobile it would force the drawer
  // into mini-dock mode, so tablet/mobile always strip the class.
  // ============================================================
  let collapsed = true;
  const syncCollapsed = () => {
    if (isTablet()) {
      layout.classList.remove('collapsed');           // mobile: never collapsed-visual
    } else if (collapsed) {
      layout.classList.add('collapsed');              // desktop: honour user preference
    } else {
      layout.classList.remove('collapsed');
    }
  };
  syncCollapsed();

  document.querySelectorAll('.js-fold').forEach(btn => {
    btn.addEventListener('click', () => {
      collapsed = !collapsed;
      syncCollapsed();
    });
  });

  // mobile: the close handle lives outside the scroll container (so it can
  // overhang the rail edge), which left it sitting still while the nav scrolled —
  // it felt detached. Ride its vertical offset with the drawer's scroll so it
  // moves as part of the nav. (Desktop fold-edge is a fixed dock control — unchanged.)
  const sbScroll = document.querySelector('.sb-scroll');
  const drawerClose = document.querySelector('.drawer-close');
  const syncClosePos = () => {
    if (sbScroll && drawerClose) drawerClose.style.transform = 'translateY(' + (-sbScroll.scrollTop) + 'px)';
  };
  if (sbScroll) sbScroll.addEventListener('scroll', syncClosePos, { passive: true });

  const openDrawer  = () => { layout.classList.add('drawer-open'); document.body.style.overflow = 'hidden'; syncClosePos(); };
  const closeDrawer = () => { layout.classList.remove('drawer-open'); document.body.style.overflow = ''; };

  document.querySelectorAll('.js-drawer-open').forEach(b => b.addEventListener('click', openDrawer));
  document.querySelectorAll('.js-drawer-close').forEach(b => b.addEventListener('click', closeDrawer));
  scrim.addEventListener('click', closeDrawer);

  // sidebar sub-nav (Casino / Sports) — click parent to toggle expanded children
  document.querySelectorAll('.js-toggle-sub').forEach(parent => {
    parent.addEventListener('click', e => {
      e.preventDefault();
      parent.classList.toggle('is-open');
    });
  });

  // close drawer if user resizes up to desktop;
  // also re-sync .collapsed across the breakpoint so the mini-icon dock
  // never leaks into the mobile drawer (and is restored when going back to desktop)
  let lastTablet = isTablet();
  window.addEventListener('resize', () => {
    const t = isTablet();
    if (lastTablet && !t) closeDrawer();
    if (lastTablet !== t) syncCollapsed();
    lastTablet = t;
  });

  document.addEventListener('keydown', (e) => {
    if (e.key === 'Escape' && layout.classList.contains('drawer-open')) closeDrawer();
  });

  // ============================================================
  // HERO CAROUSEL — 7 slides with auto-rotate, dots, arrows, swipe
  // ============================================================
  // 8 simulated promotions for structural testing. All bonus descriptions
  // are exactly 53 characters so the cards render at consistent heights.
  // H2 (pct + upTo) and eyebrow labels vary by promo. Art-backed slides use
  // integrated 3D cartoon hero scenes with a safe left text zone.
  // Ordering alternates character-led and motif-led art so the desktop
  // two-card viewport never shows the lion twice at once.
  // Bonus descriptions all locked at 53 characters for visual consistency.
  const heroSlides = [
    {tone:'red',     accent:'#FFCC00', accentRgb:'255,204,0', mobilePos:'72% center', mascot:'mascot-dev/lion-nochain-cutout.webp', art:'hero-v5/hero-v5-01-welcome-bonus-london-lion-v2.webp', eb:'Welcome Bonus Pack',    pct:'450%',    upTo:'€6,000',     bonus:'Claim your fresh bonus across the first four deposits.'},
    {tone:'purple',  accent:'#7C1FBE', accentRgb:'124,31,190', mobilePos:'68% center', mascot:'mascot-dev/lion-nochain-cutout.webp', art:'hero-v5/hero-v5-02-highroller-vip-roulette-london-crown-chip-v2.webp', eb:'Highroller Bonus',      pct:'200%',    upTo:'€3,000',     bonus:'A bigger welcome bonus reserved for VIP-tier players.'},
    {tone:'yellow',  accent:'#00C4D2', accentRgb:'0,196,210', mobilePos:'70% center', mascot:'mascot-dev/lion-nochain-cutout.webp', art:'hero-v5/hero-v5-03-first-deposit-lion-gift-case.webp', eb:'First Deposit Bonus',   pct:'200%',    upTo:'€1,000',     bonus:'Boost your first deposit with this welcome offer pack.'},
    {tone:'cyan',    accent:'#E6007E', accentRgb:'230,0,126', mobilePos:'68% center', mascot:'mascot-dev/lion-nochain-cutout.webp', art:'hero-v5/hero-v5-04-lucky-day-south-bank-map-crown-chip-v2.webp', eb:'Lucky Day',             pct:'Your',    upTo:'Lucky Day',    bonus:'Follow the lucky map today to grab a major top prize.'},
    {tone:'magenta', accent:'#E6007E', accentRgb:'230,0,126', mobilePos:'70% center', mascot:'mascot-dev/lion-nochain-cutout.webp', art:'hero-v5/hero-v5-05-double-cashback-refund-envelopes-lion.webp', eb:'Double Cashback',       pct:'50%',     upTo:'Cashback',     bonus:'Half your weekend losses back, credited every Monday.'},
    {tone:'navy',    accent:'#00C4D2', accentRgb:'0,196,210', mobilePos:'64% center', mascot:'mascot-dev/lion-nochain-cutout.webp', art:'hero-v5/hero-v5-06-sport-wembley-football-crown-chips-v3.webp', eb:'Welcome Sport Bonus',   pct:'100%',    upTo:'€300',         bonus:'Start your sportsbook journey with our welcome boost.'},
    {tone:'yellow',  accent:'#E6007E', accentRgb:'230,0,126', mobilePos:'68% center', mascot:'mascot-dev/lion-nochain-cutout.webp', art:'hero-v5/hero-v5-07-pragmatic-tournament-grounded-lion-crown-chip-v6.webp', eb:'Pragmatic Tournament',  pct:'€30,000', upTo:'Prize Pool', bonus:'Spin daily and rise the leaderboard for the top prize.'},
    {tone:'red',     accent:'#00C4D2', accentRgb:'0,196,210', mobilePos:'68% center', mascot:'mascot-dev/lion-nochain-cutout.webp', art:'hero-v5/hero-v5-08-weekly-cashback-postbox-cashback-crown-chip-v4.webp', eb:'Weekly Cashback',       pct:'25%',     upTo:'Cashback',     bonus:'Earn a quarter of weekly losses back, paid on Mondays.'}
  ];

  const heroTrack = document.getElementById('hero-track');
  const heroDots  = document.getElementById('hero-dots');
  const heroPrev  = document.getElementById('hero-prev');
  const heroNext  = document.getElementById('hero-next');

  if (heroTrack && heroDots && heroPrev && heroNext) {
  heroTrack.innerHTML = heroSlides.map((s, i) => {
    // "up to" only makes sense when the suffix is a currency cap
    const showUpTo = /^[£€$]/.test(s.upTo);
    return `
      <article class="hero-card c-${s.tone}${s.art ? ' has-art' : ''}" ${s.art ? `style="--hero-art:url('assets-new/${s.art}');--hero-accent:${s.accent || '#FFCC00'};--hero-accent-rgb:${s.accentRgb || '255,204,0'};--hero-pos:${s.pos || 'center'};--hero-mobile-pos:${s.mobilePos || '76% center'}"` : ''} aria-roledescription="slide" aria-label="${i+1} of ${heroSlides.length}: ${s.eb}">
        <img class="mascot" src="assets-new/${s.mascot}" alt="" loading="${i < 3 ? 'eager' : 'lazy'}" />
        <div class="hc-eb">${s.eb}</div>
        <h2><span class="pct">${s.pct}</span>${showUpTo ? ' up to<br/>' : ' '}${s.upTo}</h2>
        <p class="bonus">${s.bonus}</p>
        <div class="ctas">
          <button class="cta">Get Bonus <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.8" stroke-linecap="round"><path d="M5 12h14M13 5l7 7-7 7"/></svg></button>
        </div>
      </article>
    `;
  }).join('');

  heroDots.innerHTML = heroSlides.map((s, i) => `
    <button class="d ${i === 0 ? 'active' : ''}" data-i="${i}" role="tab" aria-label="Promotion ${i+1}: ${s.eb}" aria-selected="${i === 0}"></button>
  `).join('');

  let heroIndex = 0;
  let autoTimer = null;
  const AUTO_MS = 6000;

  function visibleHeroCards(){
    const w = innerWidth;
    if (w <= 767) return 1;
    if (w <= 1199) return 2;
    return 3;
  }

  function scrollToHero(i, smooth = true){
    const cards = heroTrack.children;
    if (!cards.length) return;
    const max = Math.max(0, cards.length - visibleHeroCards());
    heroIndex = Math.max(0, Math.min(max, i));
    const card = cards[heroIndex];
    heroTrack.scrollTo({ left: card.offsetLeft - heroTrack.offsetLeft, behavior: smooth && !reduced ? 'smooth' : 'auto' });
    updateHeroUI();
  }

  function updateHeroUI(){
    const dots = heroDots.querySelectorAll('.d');
    dots.forEach((d, i) => {
      const on = i === heroIndex;
      d.classList.toggle('active', on);
      d.setAttribute('aria-selected', on ? 'true' : 'false');
    });
    const max = Math.max(0, heroTrack.children.length - visibleHeroCards());
    heroPrev.disabled = heroIndex === 0;
    heroNext.disabled = heroIndex >= max;
  }

  heroPrev.addEventListener('click', () => { stopAuto(); scrollToHero(heroIndex - 1); });
  heroNext.addEventListener('click', () => { stopAuto(); scrollToHero(heroIndex + 1); });
  heroDots.addEventListener('click', (e) => {
    const dot = e.target.closest('[data-i]');
    if (!dot) return;
    stopAuto();
    scrollToHero(+dot.dataset.i);
  });

  // sync index from native scroll (swipe / touch)
  let scrollDebounce;
  heroTrack.addEventListener('scroll', () => {
    clearTimeout(scrollDebounce);
    scrollDebounce = setTimeout(() => {
      const cards = heroTrack.children;
      const x = heroTrack.scrollLeft;
      let nearest = 0, best = Infinity;
      for (let i = 0; i < cards.length; i++){
        const d = Math.abs(cards[i].offsetLeft - heroTrack.offsetLeft - x);
        if (d < best){ best = d; nearest = i; }
      }
      heroIndex = nearest;
      updateHeroUI();
    }, 90);
  }, {passive: true});

  // keyboard nav when track focused
  heroTrack.addEventListener('keydown', (e) => {
    if (e.key === 'ArrowLeft')  { stopAuto(); scrollToHero(heroIndex - 1); }
    if (e.key === 'ArrowRight') { stopAuto(); scrollToHero(heroIndex + 1); }
  });

  // auto-rotate
  function tickAuto(){
    const max = Math.max(0, heroTrack.children.length - visibleHeroCards());
    const next = heroIndex >= max ? 0 : heroIndex + 1;
    scrollToHero(next);
  }
  function startAuto(){
    if (reduced) return;
    stopAuto();
    autoTimer = setInterval(tickAuto, AUTO_MS);
  }
  function stopAuto(){ if (autoTimer) clearInterval(autoTimer); autoTimer = null; }

  // pause on hover, focus, or hidden tab
  ['mouseenter','focusin'].forEach(ev => heroTrack.addEventListener(ev, stopAuto));
  ['mouseleave','focusout'].forEach(ev => heroTrack.addEventListener(ev, startAuto));
  document.addEventListener('visibilitychange', () => document.hidden ? stopAuto() : startAuto());

  // resize → re-snap so visible count is correct
  let resizeT;
  window.addEventListener('resize', () => {
    clearTimeout(resizeT);
    resizeT = setTimeout(() => scrollToHero(heroIndex, false), 120);
  });

  updateHeroUI();
  startAuto();
  }

  // ============================================================
  // GAME IMAGES — pool of unique landscape thumbnails from
  // assets-new/game-images/ (each used exactly once across all sliders)
  // ============================================================
  const SLOT_IMAGES = [
    "174976-elephants-gold-bonus-combo.jpg",
    "175060-mystery-dragons.jpg",
    "175072-rich-piggies-bonus-combo.jpg",
    "175472-hit-coins-hold-and-spin.jpg",
    "175628-royal-fortunator-hold-and-win.jpg",
    "176268-rip-city.jpg",
    "176368-fist-of-destruction.jpg",
    "199466-big-catch-bonanza-perfect-haul.jpg",
    "200083-raptor-doublemax.jpg",
    "200647-illogicool.jpg",
    "200743-cazino-zeppelin.jpg",
    "200744-valley-of-the-gods-2.jpg",
    "200769-champions-of-rome.jpg",
    "200797-pacific-gold.jpg",
    "200854-vikings-go-berzerk.jpg",
    "200855-valley-of-the-gods.jpg",
    "200866-multifly.jpg",
    "200870-golden-fishtank.jpg",
    "203590-sun-of-egypt-3.jpg",
    "203651-3-hot-chillies.jpg",
    "205166-royalty-of-olympus.jpg",
    "205864-power-of-zeus.jpg",
    "208138-coin-win-hold-the-spin.jpg",
    "217092-big-bass-return-to-the-races.jpg",
    "209413-lady-wolf-moon.jpg",
    "209575-bonanza-billion.jpg",
    "209594-elvis-frog-trueways.jpg",
    "210522-snoop-dogg-dollars.jpg",
    "211961-fortune-three.jpg",
    "211963-fortune-three-xmas.jpg",
    "211979-sea-secret.jpg",
    "211980-max-miner.jpg",
    "211982-raging-lion.jpg",
    "211983-buffalo-trail.jpg",
    "211999-fortune-five-double.jpg",
    "212000-tritons-realm.jpg",
    "212002-throne-of-camelot.jpg",
    "212021-wolf-of-wild-street.jpg",
    "212028-totem-de-oro.jpg",
    "212040-wildstock.jpg",
    "212041-spin4dead.jpg",
    "212160-duel-at-dawn.jpg",
    "214449-sweet-bazaar.jpg",
    "214450-fortune-wild.jpg",
    "216176-brick-house-bonanza.jpg",
    // 6 portrait (400×600) live-casino thumbnails — perfect aspect for the portrait tile shape;
    // these end up in the last 6 RTP cards (Live RTP often features live-casino games)
    "196239-crazy-time_thumbnailx2.jpg",
    "203707-lightning-storm_thumbnailx2.jpg",
    "177693-blackjack-51-ruby_thumbnailx2.jpg",
    "178146-blackjack-11_thumbnailx2.jpg",
    "178147-blackjack-15_thumbnailx2.jpg",
    "178171-speed-roulette-1_thumbnailx2.jpg"
  ];

  const PROVIDERS = ['Pragmatic Play', 'BGaming', "Play'n GO", 'NetEnt', 'Hacksaw', 'Belatra Games', 'Endorphina', 'Platipus', 'Spinomenal', 'Tada Gaming'];

  // parse a friendly title-case name from a filename like "174976-elephants-gold-bonus-combo.jpg"
  function nameFromImg(filename){
    return filename
      .replace(/^\d+-/, '')
      .replace(/\.[^.]+$/, '')
      .replace(/_thumbnailx2$/, '')
      .split('-')
      .map(w => w.charAt(0).toUpperCase() + w.slice(1))
      .join(' ');
  }
  function gameRec(img, i, extras = {}){
    return Object.assign({
      img: 'assets-new/game-images/' + img,
      name: nameFromImg(img),
      pub: PROVIDERS[i % PROVIDERS.length]
    }, extras);
  }

  // partition the pool — no image used twice
  let _imgIdx = 0;
  const take = (n) => SLOT_IMAGES.slice(_imgIdx, _imgIdx += n);

  // 8 National Originals — keep curated names + badges + tones; add an image
  const originalsImgs = take(8);
  const originalsData = [
    {tone:'cyan',    label:'Table Game',   name:'Blackjack', badge:'House Edge · 1%'},
    {tone:'magenta', label:'Skill Game',   name:'Plinko',    badge:'Volatility · High'},
    {tone:'purple',  label:'Crash Game',   name:'Tower',     badge:'Multiplier · 1000x'},
    {tone:'yellow',  label:'Lottery Game', name:'Keno',      badge:'Up to · €10K'},
    {tone:'red',     label:'Dice Game',    name:'Dice',      badge:'Provably Fair'},
    {tone:'green',   label:'Mining Game',  name:'Mines',     badge:'Max Win · 24,750x'},
    {tone:'navy',    label:'Card Game',    name:'Hi-Lo',     badge:'2 Decks · Auto'},
    {tone:'pink',    label:'Crash Game',   name:'Limbo',     badge:'Max Multi · 1,000,000x'}
  ].map((o, i) => Object.assign({}, o, {img: 'assets-new/game-images/' + originalsImgs[i]}));

  // 14 New Releases — game names come from the image filenames
  const newTags = ['new','new','exc','new','hot',null,'new','new','exc','new',null,'hot','exc','new'];
  const newGames = take(14).map((img, i) => gameRec(img, i, {tag: newTags[i]}));

  // 14 Bonus Buy — all tagged 'buy'
  const buyGames = take(14).map((img, i) => gameRec(img, i, {tag: 'buy'}));

  // 14 Live RTP — realistic percentage distribution
  const rtpPcts = ['165.88%','137.33%','123.77%','122.96%','122.96%','122.25%','118.40%','117.10%','115.20%','114.85%','112.50%','109.40%','107.80%','104.20%'];
  const rtpData = take(14).map((img, i) => gameRec(img, i, {pct: rtpPcts[i]}));

  // ============================================================
  // RENDER game rows
  // ============================================================
  const tagLbl = {hot:'HOT', new:'NEW', exc:'EXCLUSIVE', buy:'BUY BONUS'};

  function gameTile(g){
    return `
      <div class="game" role="button" tabindex="0" aria-label="${g.name} by ${g.pub}">
        <img class="game-img" src="${g.img}" alt="${g.name}" loading="lazy" />
        ${g.tag ? `<span class="tag ${g.tag}">${tagLbl[g.tag]}</span>` : ''}
        <div class="game-overlay">
          <button class="fav" aria-label="Add to favourites"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"><path d="M12 21s-7-4.5-9-9C1.5 8 4 4 8 4c2 0 3 1 4 3 1-2 2-3 4-3 4 0 6.5 4 5 8-2 4.5-9 9-9 9z"/></svg></button>
          <div class="g-mid">
            <button class="g-play" aria-label="Play ${g.name}"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg></button>
            <a class="g-demo">Demo</a>
            <div class="g-title">${g.name}</div>
          </div>
          <div class="g-bot">${g.pub}</div>
        </div>
      </div>
    `;
  }

  {const _e=document.getElementById('new-row'); if(_e) _e.innerHTML = newGames.map(gameTile).join('');}
  {const _e=document.getElementById('buy-row'); if(_e) _e.innerHTML = buyGames.map(gameTile).join('');}

  // ============================================================
  // LOBBY GAMES — active result set below the search/filter row.
  // 18 small landscape cards in a static grid, mixed tags for variety.
  // Reuses image filenames from the directory directly (no overlap
  // concern with the curated rows — players expect popular games to
  // show up in filter results too).
  // ============================================================
  const lobbyGrid = document.getElementById('lg-grid');
  if (lobbyGrid) {
    const lobbyImages = (window.PAGE && PAGE.lobbyImages) || [];
    // distribute tags so the grid feels curated, not random — TOP dominates,
    // with NEW / HOT / JACKPOT sprinkled through for tonal variety
    const lobbyTags = [
      {l:'TOP',c:'t-yellow'},  {l:'TOP',c:'t-yellow'}, {l:'NEW',c:'t-red'},
      {l:'TOP',c:'t-yellow'},  {l:'HOT',c:'t-red'},    {l:'TOP',c:'t-yellow'},
      {l:'JACKPOT',c:'t-magenta'}, {l:'TOP',c:'t-yellow'}, {l:'TOP',c:'t-yellow'},
      {l:'NEW',c:'t-red'},     {l:'TOP',c:'t-yellow'}, {l:'HOT',c:'t-red'},
      {l:'TOP',c:'t-yellow'},  {l:'TOP',c:'t-yellow'}, {l:'NEW',c:'t-red'},
      {l:'TOP',c:'t-yellow'},  {l:'JACKPOT',c:'t-magenta'}, {l:'TOP',c:'t-yellow'}
    ];
    lobbyGrid.innerHTML = lobbyImages.map((img, i) => {
      const name = nameFromImg(img);
      const tag  = lobbyTags[i % lobbyTags.length];
      const pub  = PROVIDERS[i % PROVIDERS.length];
      return `
        <div class="lg-card" role="button" tabindex="0" aria-label="${name} by ${pub}" data-name="${name.toLowerCase()}" data-pub="${pub.toLowerCase()}">
          <img class="lg-img" src="assets-new/game-images/${img}" alt="${name}" loading="lazy" />
          <span class="lg-tag ${tag.c}">${tag.l}</span>
          <div class="game-overlay">
            <button class="fav" aria-label="Add to favourites"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"><path d="M12 21s-7-4.5-9-9C1.5 8 4 4 8 4c2 0 3 1 4 3 1-2 2-3 4-3 4 0 6.5 4 5 8-2 4.5-9 9-9 9z"/></svg></button>
            <div class="g-mid">
              <button class="g-play" aria-label="Play ${name}"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg></button>
              <a class="g-demo">Demo</a>
              <div class="g-title">${name}</div>
            </div>
            <div class="g-bot">${pub}</div>
          </div>
        </div>
      `;
    }).join('');
    // keep the "X of N" hint honest — current screen shows 18 of 2,418
    const loadMeta = document.querySelector('.lg-load .lg-load-meta');
    if (loadMeta) loadMeta.textContent = (window.PAGE && PAGE.loadMeta) || '';

    // ============================================================
    // LIVE SEARCH — filter the lobby grid by game name + provider
    // as the user types in the search input. Empty query restores
    // the full grid. Meta + Load more update to match.
    // ============================================================
    const searchInput = document.querySelector('.big-search-wrap input');
    const lgMeta      = document.getElementById('lg-meta');
    const lgLoad      = document.querySelector('.lg-load');
    const lgGrid      = lobbyGrid;
    const totalCards  = lobbyImages.length;
    const totalCatalogue = 2418;

    // empty-state row, injected once and toggled
    const emptyState = document.createElement('div');
    emptyState.className = 'lg-empty';
    emptyState.hidden = true;
    emptyState.innerHTML = `
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.5-4.5"/><path d="M8 11h6"/></svg>
      <div class="lg-empty-title">No games match that search</div>
      <div class="lg-empty-sub">Try a shorter term or a different spelling.</div>
    `;
    lobbyGrid.after(emptyState);

    function filterLobby(q){
      const query = q.trim().toLowerCase();
      const cards = lgGrid.querySelectorAll('.lg-card');
      let shown = 0;
      cards.forEach(c => {
        const match = !query
          || c.dataset.name.includes(query)
          || c.dataset.pub.includes(query);
        c.style.display = match ? '' : 'none';
        if (match) shown++;
      });
      // meta + load-more text reflect the filter state.
      // lgMeta may be null (header strip removed on Home Dark) — guard each write
      if (query){
        if (lgMeta) lgMeta.textContent = shown + (shown === 1 ? ' match' : ' matches');
        if (lgLoad) lgLoad.style.display = 'none';
        emptyState.hidden = shown !== 0;
        lgGrid.style.display = shown === 0 ? 'none' : '';
      } else {
        if (lgMeta) lgMeta.textContent = totalCatalogue.toLocaleString() + ' games';
        if (lgLoad) lgLoad.style.display = '';
        emptyState.hidden = true;
        lgGrid.style.display = '';
      }
    }
    if (searchInput) searchInput.addEventListener('input', e => filterLobby(e.target.value));

    // Payment rail now lives in the footer; lobby filtering no longer moves it.
  }

  const _rtpEl=document.getElementById('rtp-row'); if(_rtpEl) _rtpEl.innerHTML = rtpData.map(g => `
    <div class="game rtp-card" role="button" tabindex="0" aria-label="${g.name} live RTP ${g.pct}">
      <img class="game-img" src="${g.img}" alt="${g.name}" loading="lazy" />
      <div class="rtp-pct">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round"><path d="M5 17l6-6 4 4 8-8"/></svg>
        ${g.pct}
      </div>
      <div class="game-overlay">
        <button class="fav" aria-label="Add to favourites"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"><path d="M12 21s-7-4.5-9-9C1.5 8 4 4 8 4c2 0 3 1 4 3 1-2 2-3 4-3 4 0 6.5 4 5 8-2 4.5-9 9-9 9z"/></svg></button>
        <div class="g-mid">
          <button class="g-play" aria-label="Play ${g.name}"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg></button>
          <a class="g-demo">Demo</a>
          <div class="g-title">${g.name}</div>
        </div>
        <div class="g-bot">${g.pub}</div>
      </div>
    </div>
  `).join('');

  const _origEl=document.getElementById('originals'); if(_origEl) _origEl.innerHTML = originalsData.map(o => `
    <div class="orig-card" role="button" tabindex="0" aria-label="${o.name} — ${o.label}">
      <img class="game-img" src="${o.img}" alt="${o.name}" loading="lazy" />
      <div class="badge">${o.badge}</div>
      <div class="game-overlay orig-overlay">
        <button class="fav" aria-label="Add to favourites"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"><path d="M12 21s-7-4.5-9-9C1.5 8 4 4 8 4c2 0 3 1 4 3 1-2 2-3 4-3 4 0 6.5 4 5 8-2 4.5-9 9-9 9z"/></svg></button>
        <div class="g-mid">
          <button class="g-play" aria-label="Play ${o.name}"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg></button>
          <a class="g-demo">Demo</a>
          <div class="g-title">${o.name}</div>
        </div>
        <div class="g-bot">${o.label}</div>
      </div>
    </div>
  `).join('');

  // ============================================================
  // GAME ROW arrows — scroll by one viewport width
  // ============================================================
  document.querySelectorAll('.nav-arrows[data-rail]').forEach(group => {
    const rail = document.getElementById(group.dataset.rail);
    if (!rail) return;
    const wrap = rail.parentElement; // .row-wrap
    const buttons = group.querySelectorAll('button');

    const updateState = () => {
      const max = rail.scrollWidth - rail.clientWidth - 2;
      const atStart = rail.scrollLeft <= 2;
      const atEnd = rail.scrollLeft >= max;
      wrap.classList.toggle('at-start', atStart);
      wrap.classList.toggle('at-end', atEnd);
      buttons[0].disabled = atStart;
      buttons[1].disabled = atEnd;
    };

    buttons.forEach(b => {
      b.addEventListener('click', () => {
        const dir = +b.dataset.dir;
        const step = rail.clientWidth * 0.9;
        rail.scrollBy({ left: dir * step, behavior: reduced ? 'auto' : 'smooth' });
      });
    });

    let t;
    rail.addEventListener('scroll', () => { clearTimeout(t); t = setTimeout(updateState, 80); }, {passive: true});
    new ResizeObserver(updateState).observe(rail);
    updateState();
  });

  // ============================================================
  // FAVOURITES — toggle
  // ============================================================
  document.addEventListener('click', (e) => {
    const fav = e.target.closest('.fav');
    if (!fav) return;
    e.stopPropagation();
    fav.classList.toggle('on');
  });

  // ============================================================
  // CATEGORY TABS — single-select
  // ============================================================
  document.querySelectorAll('.cat-tabs').forEach(strip => {
    strip.addEventListener('click', (e) => {
      const tab = e.target.closest('.cat-tab');
      if (!tab) return;
      if (tab.id === 'cat-search-tab') return;   // search trigger, not a category
      strip.querySelectorAll('.cat-tab').forEach(t => t.classList.remove('active'));
      tab.classList.add('active');
    });
  });

  // ============================================================
  // VERTICAL / MARKET — Casino vs Sports
  //   The sidebar market tabs (.sb-market-tab) are real navigation LINKS —
  //   each points at its product's URL, so switching products is a page load,
  //   NOT a JS swap of the menu. setVertical() only keeps the visual "active"
  //   state in sync (used by the mobile bottom nav as a prototype affordance).
  // ============================================================
  function setVertical(vert){
    document.querySelectorAll('.sb-market-tab').forEach(i => {
      const on = i.dataset.vert === vert;
      i.classList.toggle('active', on);
      if (on) i.setAttribute('aria-current', 'page');
      else i.removeAttribute('aria-current');
    });
    document.querySelectorAll('.mb-item[data-vert]').forEach(i => {
      i.classList.toggle('active', i.dataset.vert === vert);
    });
  }

  // The sidebar product toggle is a DEMO affordance only: clicking switches
  // the active state visually but does NOT navigate (Sports has no page yet).
  document.querySelectorAll('.sb-market-tab').forEach(tab => {
    tab.addEventListener('click', (e) => {
      e.preventDefault();
      setVertical(tab.dataset.vert);
    });
  });

  // ============================================================
  // MOBILE BOTTOM NAV — wire up vertical, chat, and bonuses actions
  //   (Menu reuses .js-drawer-open via its class, no extra wiring.)
  // ============================================================
  document.querySelectorAll('.mb-nav').forEach(nav => {
    nav.addEventListener('click', (e) => {
      const item = e.target.closest('.mb-item');
      if (!item) return;
      const vert = item.dataset.vert;
      if (vert) {
        if (!item.classList.contains('active')) setVertical(vert);
        return;
      }
      const action = item.dataset.action;
      if (action === 'live') {
        // hook for future routing — jump to the Live Casino category.
        // for now we just sync the category tab to "Live Casino" if present.
        const liveTab = Array.from(document.querySelectorAll('.cat-tab'))
          .find(t => t.textContent.trim().toLowerCase().startsWith('live casino'));
        if (liveTab) liveTab.click();
      }
      // 'bonuses' and 'menu' are stubs / handled by existing class hooks
    });
  });

  // ============================================================
  // PROVIDER DROPDOWN — toggle panel, select option, close on outside / ESC
  // ============================================================
  document.querySelectorAll('.provider-dd').forEach(dd => {
    const btn   = dd.querySelector('.provider-select');
    const panel = dd.querySelector('.provider-panel');
    if (!btn || !panel) return;
    const label = btn.querySelector('.ps-label');
    const count = btn.querySelector('.ps-count');

    const open  = () => { panel.hidden = false; btn.setAttribute('aria-expanded','true'); };
    const close = () => { panel.hidden = true;  btn.setAttribute('aria-expanded','false'); };

    btn.addEventListener('click', (e) => {
      e.stopPropagation();
      panel.hidden ? open() : close();
    });

    panel.addEventListener('click', (e) => {
      const opt = e.target.closest('.provider-opt');
      if (!opt) return;
      panel.querySelectorAll('.provider-opt').forEach(o => o.classList.remove('selected'));
      opt.classList.add('selected');
      if (label) label.textContent = opt.dataset.label || opt.textContent.trim();
      if (count) count.textContent = opt.dataset.count || '';
      close();
      btn.focus();
    });

    document.addEventListener('click', (e) => {
      if (!panel.hidden && !dd.contains(e.target)) close();
    });
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape' && !panel.hidden) { close(); btn.focus(); }
    });
  });

  // ============================================================
  // LIVE BETS — 3 feeds (All Bets, Latest Wins, Big Wins) with tab switching
  // ============================================================
  const lbAll = [
    {img:'200647-illogicool.jpg',                   game:'Space Gems Rockblocks', time:'11:35 PM', user:'JOSEP*****', bet:'€0.32', mult:'x0',     pay:'€0',     win:false},
    {img:'200083-raptor-doublemax.jpg',             game:'Aviator',                time:'11:35 PM', user:'CAETA*****', bet:'€5.00', mult:'x0',     pay:'€0',     win:false},
    {img:'175628-royal-fortunator-hold-and-win.jpg',game:'Royal Dice',             time:'11:35 PM', user:'MzS*******', bet:'€1.20', mult:'34.83x', pay:'€41.80', win:true},
    {img:'200854-vikings-go-berzerk.jpg',           game:'Vikings Go Berzerk',     time:'11:34 PM', user:'PL*****',    bet:'€0.40', mult:'x0',     pay:'€0',     win:false},
    {img:'214449-sweet-bazaar.jpg',                 game:'Sweet Bazaar',           time:'11:34 PM', user:'GEO*****',   bet:'€0.20', mult:'1.60x',  pay:'€0.32',  win:true},
    {img:'209575-bonanza-billion.jpg',              game:'Bonanza Billion',        time:'11:33 PM', user:'RID*****',   bet:'€1.00', mult:'13.20x', pay:'€13.20', win:true},
    {img:'205864-power-of-zeus.jpg',                game:'Power of Zeus',          time:'11:33 PM', user:'ANNA*****',  bet:'€0.90', mult:'8.27x',  pay:'€7.44',  win:true},
    {img:'230426-raptor-2.jpg',                     game:'Raptor 2',               time:'11:32 PM', user:'SAM*****',   bet:'€2.00', mult:'x0',     pay:'€0',     win:false},
    {img:'212021-wolf-of-wild-street.jpg',          game:'Wolf of Wild Street',    time:'11:32 PM', user:'LIZ*****',   bet:'€2.50', mult:'18.80x', pay:'€47.00', win:true},
    {img:'196239-crazy-time_thumbnailx2.jpg',       game:'Crazy Time',             time:'11:31 PM', user:'TOM*****',   bet:'€0.50', mult:'x0',     pay:'€0',     win:false}
  ];

  const lbWins = [
    {img:'175628-royal-fortunator-hold-and-win.jpg',game:'Royal Dice',             time:'11:35 PM', user:'MzS*******', bet:'€1.20', mult:'34.83x', pay:'€41.80', win:true},
    {img:'214449-sweet-bazaar.jpg',                 game:'Sweet Bazaar',           time:'11:34 PM', user:'GEO*****',   bet:'€0.20', mult:'1.60x',  pay:'€0.32',  win:true},
    {img:'209575-bonanza-billion.jpg',              game:'Bonanza Billion',        time:'11:33 PM', user:'RID*****',   bet:'€1.00', mult:'13.20x', pay:'€13.20', win:true},
    {img:'205864-power-of-zeus.jpg',                game:'Power of Zeus',          time:'11:33 PM', user:'ANNA*****',  bet:'€0.90', mult:'8.27x',  pay:'€7.44',  win:true},
    {img:'205166-royalty-of-olympus.jpg',           game:'Royalty of Olympus',     time:'11:32 PM', user:'PL*****',    bet:'€1.00', mult:'3.00x',  pay:'€3.00',  win:true},
    {img:'212021-wolf-of-wild-street.jpg',          game:'Wolf of Wild Street',    time:'11:32 PM', user:'LIZ*****',   bet:'€2.50', mult:'18.80x', pay:'€47.00', win:true},
    {img:'199466-big-catch-bonanza-perfect-haul.jpg',game:'Big Catch Bonanza',     time:'11:31 PM', user:'EVA*****',   bet:'€0.40', mult:'9.75x',  pay:'€3.90',  win:true},
    {img:'203707-lightning-storm_thumbnailx2.jpg',  game:'Lightning Storm',        time:'11:31 PM', user:'KIM*****',   bet:'€1.50', mult:'4.40x',  pay:'€6.60',  win:true},
    {img:'218186-crown-coins.jpg',                  game:'Crown Coins',            time:'11:30 PM', user:'BEN*****',   bet:'€0.60', mult:'7.10x',  pay:'€4.26',  win:true},
    {img:'178171-speed-roulette-1_thumbnailx2.jpg', game:'Speed Roulette',         time:'11:30 PM', user:'NIA*****',   bet:'€3.00', mult:'2.00x',  pay:'€6.00',  win:true}
  ];

  const lbBig = [
    {img:'209594-elvis-frog-trueways.jpg',          game:'Elvis Frog Trueways',    time:'11:28 PM', user:'MzS*******', bet:'€5.00',  mult:'1240.00x', pay:'€6,200', win:true},
    {img:'212021-wolf-of-wild-street.jpg',          game:'Wolf of Wild Street',    time:'11:24 PM', user:'LIZ*****',   bet:'€2.50',  mult:'1880.00x', pay:'€4,700', win:true},
    {img:'225568-888-bonus-combo.jpg',              game:'888 Bonus Combo',        time:'11:18 PM', user:'GEO*****',   bet:'€10.00', mult:'412.50x',  pay:'€4,125', win:true},
    {img:'205864-power-of-zeus.jpg',                game:'Power of Zeus',          time:'11:12 PM', user:'ANNA*****',  bet:'€1.50',  mult:'2150.00x', pay:'€3,225', win:true},
    {img:'212000-tritons-realm.jpg',                game:'Tritons Realm',          time:'11:08 PM', user:'TOM*****',   bet:'€3.00',  mult:'960.00x',  pay:'€2,880', win:true},
    {img:'199466-big-catch-bonanza-perfect-haul.jpg',game:'Big Catch Bonanza',     time:'11:03 PM', user:'EVA*****',   bet:'€4.00',  mult:'620.00x',  pay:'€2,480', win:true},
    {img:'203707-lightning-storm_thumbnailx2.jpg',  game:'Lightning Storm',        time:'10:58 PM', user:'SAM*****',   bet:'€2.00',  mult:'1010.00x', pay:'€2,020', win:true},
    {img:'231599-bonanza-billion-xtreme.jpg',       game:'Bonanza Billion Xtreme', time:'10:53 PM', user:'RID*****',   bet:'€1.25',  mult:'1460.00x', pay:'€1,825', win:true},
    {img:'220610-royalty-of-olympus-extreme.jpg',   game:'Olympus Extreme',        time:'10:48 PM', user:'PL*****',    bet:'€0.80',  mult:'1900.00x', pay:'€1,520', win:true},
    {img:'200743-cazino-zeppelin.jpg',              game:'Cazino Zeppelin',        time:'10:42 PM', user:'BEN*****',   bet:'€3.50',  mult:'380.00x',  pay:'€1,330', win:true}
  ];

  const feeds = {all: lbAll, wins: lbWins, big: lbBig};

  function renderLB(rows){
    const lbRowsEl = document.getElementById('lb-rows');
    const lbScrollEl = document.querySelector('.lb-scroll');
    if (!lbRowsEl) return;
    lbRowsEl.innerHTML = rows.slice(0, 10).map(r => `
      <tr>
        <td><div class="lb-game">
          <div class="thm"><img src="assets-new/game-images/${r.img}" alt="${r.game}" loading="lazy" /></div>
          <a class="nm">${r.game}</a>
        </div></td>
        <td><span class="lb-time">${r.time}</span></td>
        <td><span class="lb-user">${r.user}</span></td>
        <td class="num">${r.bet}</td>
        <td class="num"><span class="lb-mult ${r.win ? '' : 'zero'}">${r.mult}</span></td>
        <td class="num"><span class="lb-pay ${r.win ? '' : 'zero'}">${r.pay} <span class="coin">€</span></span></td>
      </tr>
    `).join('');
    if (lbScrollEl) lbScrollEl.scrollTop = 0;
  }
  renderLB(lbAll);

  document.querySelectorAll('.lb-tab').forEach(btn => {
    btn.addEventListener('click', () => {
      document.querySelectorAll('.lb-tab').forEach(b => b.classList.remove('on'));
      btn.classList.add('on');
      const feed = feeds[btn.dataset.feed] || lbAll;
      renderLB(feed);
    });
  });

  // ============================================================
  // TOURNAMENT COUNTDOWN — days / hrs / mins to end date
  // Spin Win Quest ends 30 June 2026 23:59:59 UTC
  // ============================================================
  const dTime = document.getElementById('d-time');
  if (dTime){
    const tournamentEnd = Date.UTC(2026, 5, 30, 23, 59, 59);   // month is 0-indexed (5 = June)
    const elD = dTime.querySelector('[data-unit="d"]');
    const elH = dTime.querySelector('[data-unit="h"]');
    const elM = dTime.querySelector('[data-unit="m"]');
    const elS = dTime.querySelector('[data-unit="s"]');
    function tick(){
      let s = Math.max(0, Math.floor((tournamentEnd - Date.now()) / 1000));
      const d = Math.floor(s / 86400); s %= 86400;
      const h = Math.floor(s / 3600);  s %= 3600;
      const m = Math.floor(s / 60);
      const sec = s % 60;
      elD.textContent = String(d).padStart(2,'0');
      elH.textContent = String(h).padStart(2,'0');
      elM.textContent = String(m).padStart(2,'0');
      elS.textContent = String(sec).padStart(2,'0');
    }
    tick();
    setInterval(tick, 1000);   // live ticking now that seconds are shown
  }

  // ============================================================
  // LATEST WINS — live winners ticker (scrolling feed)
  // Uses authentic game names sampled from the SLOT_IMAGES pool.
  // ============================================================
  const lwTrack = document.getElementById('lw-track');
  if (lwTrack){
    // realistic recent-win mix — amounts skew to the lower end with
    // occasional bigger wins for visual rhythm
    const wins = [
      {pl:'MzS*****',  amt:'€12,400', gm:'Sweet Bonanza CandyLand'},
      {pl:'Anna*****', amt:'€5,516',  gm:'Big Catch Bonanza'},
      {pl:'Geo*****',  amt:'€3,820',  gm:'Royal Crown Cup'},
      {pl:'Rid*****',  amt:'€2,140',  gm:'Sugar Rush 1000'},
      {pl:'Tom*****',  amt:'€1,820',  gm:'Gates of Olympus'},
      {pl:'Liz*****',  amt:'€1,560',  gm:'Wolf of Wild Street'},
      {pl:'Pl*****',   amt:'€940',    gm:'Crazy Time'},
      {pl:'Sam*****',  amt:'€780',    gm:'Speed Roulette'},
      {pl:'Kim*****',  amt:'€620',    gm:'Lightning Storm'},
      {pl:'J*****',    amt:'€420',    gm:'Champions of Rome'},
      {pl:'Ben*****',  amt:'€340',    gm:'Royal Fortunator'},
      {pl:'Eva*****',  amt:'€220',    gm:'Bonanza Billion'}
    ];
    const html = wins.map(w => `
      <span class="lt-item">
        <span class="pl">${w.pl}</span><span class="won"> won </span><span class="amt">${w.amt}</span><span class="on"> on </span><a class="gm" href="#" tabindex="0">${w.gm}</a>
      </span>
    `).join('');
    lwTrack.innerHTML = html + html;   // duplicate for seamless loop
  }

  // ============================================================
  // NATIONAL TOURNAMENT — countdown (DD/HH/MM/SS) + leaderboard
  // Crown Cup · June runs 1 June 2026 00:00 → 30 June 2026 23:59:59 UTC
  // ============================================================
  const nptTime = document.getElementById('npt-time');
  if (nptTime){
    const npEnd = Date.UTC(2026, 5, 30, 23, 59, 59);
    const nD = nptTime.querySelector('[data-unit="d"]');
    const nH = nptTime.querySelector('[data-unit="h"]');
    const nM = nptTime.querySelector('[data-unit="m"]');
    const nS = nptTime.querySelector('[data-unit="s"]');
    function nptTick(){
      let s = Math.max(0, Math.floor((npEnd - Date.now()) / 1000));
      const d = Math.floor(s / 86400); s %= 86400;
      const h = Math.floor(s / 3600);  s %= 3600;
      const m = Math.floor(s / 60);
      const sec = s % 60;
      nD.textContent = String(d).padStart(2,'0');
      nH.textContent = String(h).padStart(2,'0');
      nM.textContent = String(m).padStart(2,'0');
      nS.textContent = String(sec).padStart(2,'0');
    }
    nptTick();
    setInterval(nptTick, 1000);
  }

  // Crown Cup · June leaderboard — top 10 players
  const nptLb = [
    {player:'MzS*****',  points:74192, prize:'€15,000'},
    {player:'Geo*****',  points:64946, prize:'€7,500'},
    {player:'Rid*****',  points:51820, prize:'€5,000'},
    {player:'Anna*****', points:38915, prize:'€2,500'},
    {player:'Liz*****',  points:28802, prize:'€1,500'},
    {player:'Sam*****',  points:24380, prize:'€1,000'},
    {player:'Eva*****',  points:21940, prize:'€750'},
    {player:'Tom*****',  points:18425, prize:'€500'},
    {player:'Kim*****',  points:15108, prize:'€300'},
    {player:'Ben*****',  points:12664, prize:'€200'}
  ];
  const nptLbEl = document.getElementById('npt-lb-rows');
  if (nptLbEl){
    nptLbEl.innerHTML = nptLb.slice(0, 10).map((r, i) => `
      <li class="r${i+1}">
        <span class="rank">${i+1}</span>
        <span class="player">${r.player}</span>
        <span class="points">${r.points.toLocaleString()}</span>
        <span class="prize">${r.prize}</span>
      </li>
    `).join('');
  }

  document.querySelectorAll('.npt-lb-rows, .lb-scroll').forEach(scroller => {
    let fadeTimer;
    const revealScrollbar = () => {
      scroller.classList.add('is-scrolling');
      clearTimeout(fadeTimer);
      fadeTimer = setTimeout(() => scroller.classList.remove('is-scrolling'), 900);
    };
    scroller.addEventListener('scroll', revealScrollbar, {passive:true});
    scroller.addEventListener('wheel', revealScrollbar, {passive:true});
    scroller.addEventListener('touchmove', revealScrollbar, {passive:true});
  });

  // ============================================================
  // LANGUAGE PICKER — wires up BOTH triggers (sidebar pill + header pill)
  // to flag-based popovers. Single source of truth for selected language;
  // picking in either popover updates both triggers in sync.
  // ============================================================
  (function initLangPicker(){
    const LANGS = [
      ['gb','English','EN'],     ['de','Deutsch','DE'],     ['fr','Français','FR'],    ['es','Español','ES'],
      ['it','Italiano','IT'],    ['pt','Português','PT'],   ['br','Português (BR)','BR'], ['nl','Nederlands','NL'],
      ['se','Svenska','SE'],     ['no','Norsk','NO'],       ['fi','Suomi','FI'],       ['dk','Dansk','DK'],
      ['pl','Polski','PL'],      ['cz','Čeština','CZ'],     ['hu','Magyar','HU'],      ['jp','日本語','JP']
    ];
    let current = 'gb';

    // [trigger element, popover element, local flag-symbol selector, label selector, label-format-fn]
    const triggers = [
      { trigger: document.getElementById('lang-trigger'),
        pop:     document.getElementById('lang-pop'),
        flagSel: '.sb-lang-flag .flag-use',
        lblSel:  '.sb-lang-label',
        fmtLbl:  (entry) => entry[1] },                       // sidebar: full label
      { trigger: document.getElementById('lang-trigger-header'),
        pop:     document.getElementById('lang-pop-header'),
        flagSel: '.lang-flag .flag-use',
        lblSel:  '.lang-code',
        fmtLbl:  (entry) => entry[2] }                        // header: short code
    ].filter(t => t.trigger && t.pop);

    if(!triggers.length) return;

    // build the flag grid once per popover (shared markup)
    const gridHTML = LANGS.map(([iso, label]) => `
      <button class="lang-opt ${iso===current?'is-on':''}"
              type="button" role="option" data-iso="${iso}"
              aria-selected="${iso===current}" title="${label}">
        <span class="lang-opt-img"><svg class="flag-svg" aria-hidden="true"><use href="#np-flag-${iso}"></use></svg></span>
        <span class="lang-check" aria-hidden="true">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 13l4 4L19 7"/></svg>
        </span>
      </button>
    `).join('');

    triggers.forEach(t => {
      t.grid = t.pop.querySelector('.lang-grid');
      t.grid.innerHTML = gridHTML;
      t.flagEl = t.trigger.querySelector(t.flagSel);
      t.lblEl  = t.trigger.querySelector(t.lblSel);
    });

    function setOpen(target, open){
      target.pop.classList.toggle('is-open', open);
      target.trigger.setAttribute('aria-expanded', open ? 'true' : 'false');
      // close the OTHER popover when one opens (mutual exclusivity)
      if(open) triggers.filter(o => o !== target).forEach(o => setOpen(o, false));
    }

    function closeAll(){ triggers.forEach(t => setOpen(t, false)); }

    function selectLang(iso){
      const entry = LANGS.find(l => l[0] === iso);
      if(!entry) return;
      current = iso;
      // update BOTH triggers in sync
      triggers.forEach(t => {
        if(t.flagEl){
          t.flagEl.setAttribute('href', `#np-flag-${iso}`);
        }
        if(t.lblEl) t.lblEl.textContent = t.fmtLbl(entry);
        // update grid selection in this popover
        t.grid.querySelectorAll('.lang-opt').forEach(opt => {
          const isOn = opt.dataset.iso === iso;
          opt.classList.toggle('is-on', isOn);
          opt.setAttribute('aria-selected', isOn);
        });
      });
      closeAll();
    }

    triggers.forEach(t => {
      t.trigger.addEventListener('click', (e) => {
        e.stopPropagation();
        setOpen(t, !t.pop.classList.contains('is-open'));
      });
      t.grid.addEventListener('click', (e) => {
        const opt = e.target.closest('.lang-opt');
        if(opt) selectLang(opt.dataset.iso);
      });
    });

    // click outside any popover/trigger → close all
    document.addEventListener('click', (e) => {
      const inside = triggers.some(t => t.pop.contains(e.target) || t.trigger.contains(e.target));
      if(!inside) closeAll();
    });

    // ESC → close all
    document.addEventListener('keydown', (e) => {
      if(e.key === 'Escape') closeAll();
    });
  })();

})();


  /* ===== search cat-tab: toggles the lobby search; lit while open, focuses on open ===== */
  (function(){
    var tab=document.getElementById('cat-search-tab'); if(!tab) return;
    var prevActive=null;
    tab.addEventListener('click', function(){
      var open=document.body.classList.toggle('search-open');
      document.body.classList.toggle('search-folded', !open);   /* unhide BEFORE focusing */
      var strip=tab.closest('.cat-tabs');
      var inp=document.querySelector('.big-search-wrap input');
      if(open){
        prevActive=strip?strip.querySelector('.cat-tab.active'):null;
        if(prevActive) prevActive.classList.remove('active');
        tab.classList.add('active');
        if(inp) inp.focus();
        if(window.innerWidth<768){
          var ls=document.querySelector('.lobby-search');
          if(ls) ls.scrollIntoView({behavior:'smooth', block:'center'});
        }
      } else {
        tab.classList.remove('active');
        if(inp) inp.blur();
        if(prevActive && strip && !strip.querySelector('.cat-tab.active')) prevActive.classList.add('active');
        prevActive=null;
      }
    });
  })();
  /* ===== game tile idle effects — 2 per row/section, varied spots (page self-detects) ===== */
  (function(){
    var FXCROWN='<svg viewBox="0 0 24 24" fill="currentColor"><path d="M3.2 17.5 2 7.6l5 3.6L12 4l5 7.2 5-3.6-1.2 9.9H3.2zm.3 2.1h17c.4 0 .7.3.7.7v.5c0 .4-.3.7-.7.7h-17c-.4 0-.7-.3-.7-.7v-.5c0-.4.3-.7.7-.7z"/></svg>';
    function fxApply(sel, idx, fx){
      var els=document.querySelectorAll(sel); var el=els[idx]; if(!el) return;
      if(fx==='fx-bb'){ var j=idx; while(j<els.length && !els[j].querySelector('.tag,.badge,.lg-tag')) j++; el=els[j]||el; }
      el.classList.add(fx);
      if(fx==='fx-atrace'||fx==='fx-ntrace'||fx==='fx-ptrace'||fx==='fx-utrace'||fx==='fx-ctrace'){
        var sp=document.createElement('span'); sp.className='fxring'; sp.setAttribute('aria-hidden','true'); el.appendChild(sp);
      }
      var FXLBL={'fx-excl':[FXCROWN,'Exclusive'],
        'fx-pink':['<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 21s-7-4.5-9-9C1.5 8 4 4 8 4c2 0 3 1 4 3 1-2 2-3 4-3 4 0 6.5 4 5 8-2 4.5-9 9-9 9z"/></svg>','Fan Favourite'],
        'fx-purp':['<svg viewBox="0 0 24 24" fill="currentColor"><path d="M6 3h12l4 6-10 12L2 9l4-6zm2.2 2L6 8.2 9.6 9 8.2 5zm7.6 0L14.4 9l3.6-.8L15.8 5zM12 5.6 10.6 9h2.8L12 5.6zM4.6 10.6 10 17l-1.8-6.4H4.6zm11.2 0L14 17l5.4-6.4h-3.6z"/></svg>','VIP Pick'],
        'fx-cyanl':['<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M3 17l6-6 4 4 8-8"/><path d="M14 7h7v7"/></svg>','Trending']};
      if(FXLBL[fx]){
        if(el.parentElement) el.parentElement.classList.add('fx-lblrow');
        el.querySelectorAll('.tag,.lg-tag,.badge').forEach(function(t){t.remove();});
        var b=document.createElement('span'); b.className='fx-xbadge'; b.setAttribute('aria-hidden','true');
        b.innerHTML=FXLBL[fx][0]+'<span class="lb">'+FXLBL[fx][1]+'</span>'; el.appendChild(b);
      }
    }
    var FX_PLAN = document.getElementById('k1-slider') ? [
      ['#lg-grid .lg-card',1,'fx-excl'],  ['#lg-grid .lg-card',3,'fx-pulse'],  ['#lg-grid .lg-card',5,'fx-atrace'],
      ['#lg-grid .lg-card',9,'fx-shine'],  ['#lg-grid .lg-card',11,'fx-foil'],
      ['#lg-grid .lg-card',12,'fx-ntrace'],['#lg-grid .lg-card',15,'fx-flip'], ['#lg-grid .lg-card',16,'fx-pop'],
      ['#lg-grid .lg-card',21,'fx-shake'], ['#lg-grid .lg-card',23,'fx-ptrace'],
      ['#lg-grid .lg-card',24,'fx-bb'],   ['#lg-grid .lg-card',29,'fx-utrace'],
      ['#lg-grid .lg-card',31,'fx-lev'],  ['#lg-grid .lg-card',34,'fx-ctrace']
    ] : [
      ['#lg-grid .lg-card',0,'fx-pink'],  ['#lg-grid .lg-card',1,'fx-excl'],
      ['#lg-grid .lg-card',3,'fx-pulse'], ['#lg-grid .lg-card',4,'fx-ctrace'],['#lg-grid .lg-card',5,'fx-shine'],
      ['#lg-grid .lg-card',8,'fx-bb'],    ['#lg-grid .lg-card',10,'fx-foil'],
      ['#lg-grid .lg-card',14,'fx-lev'],  ['#lg-grid .lg-card',16,'fx-ptrace'],
      ['#originals .orig-card',0,'fx-flip'],['#originals .orig-card',1,'fx-purp'],['#originals .orig-card',2,'fx-ntrace'],
      ['#new-row .game',1,'fx-pop'],      ['#new-row .game',3,'fx-atrace'],
      ['#rtp-row .game',0,'fx-shake'],    ['#rtp-row .game',1,'fx-cyanl'],
      ['#buy-row .game',0,'fx-flip'],     ['#buy-row .game',2,'fx-utrace']
    ];
    FX_PLAN.forEach(function(p){ fxApply(p[0], p[1], p[2]); });
  })();
