<!DOCTYPE html>
<html lang="de">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/png" href="/assets/images/favicon-32x32-1774098086759.png" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="robots" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1" />
    <title>GameFeature | das interaktive Spielemagazin | just play it!</title>

    <!-- Preconnect — limited to 3 critical origins to avoid network budget exhaustion -->
    <!-- 1. Supabase API: auth + data calls start immediately -->
    <link rel="preconnect" href="https://hbkotagycmniqadvvqst.supabase.co" crossorigin />
    <!-- 2. Google Fonts static assets: critical for above-fold text rendering -->
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <!-- 3. YouTube thumbnails: above-fold images in release cards and episode cards -->
    <link rel="preconnect" href="https://i.ytimg.com" crossorigin />

    <!-- DNS-prefetch only (no TCP handshake cost) for secondary origins -->
    <link rel="dns-prefetch" href="https://fonts.googleapis.com" />
    <link rel="dns-prefetch" href="https://hbkotagycmniqadvvqst.supabase.co" />
    <link rel="dns-prefetch" href="https://images.podigee-cdn.net" />
    <link rel="dns-prefetch" href="https://www.googletagmanager.com" />
    <link rel="dns-prefetch" href="https://pagead2.googlesyndication.com" />
    <link rel="dns-prefetch" href="https://www.google-analytics.com" />
    <link rel="dns-prefetch" href="https://gamefeature.podigee.io" />
    <link rel="dns-prefetch" href="https://www.youtube.com" />
    <link rel="dns-prefetch" href="https://img.youtube.com" />
    <link rel="dns-prefetch" href="https://feeds.buzzsprout.com" />

    <!-- Google Fonts: async non-blocking load — eliminates render-blocking delay -->
    <link rel="preload" as="style" href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;600;700&family=Inter:wght@400;500&family=Source+Sans+3:wght@400;500&family=JetBrains+Mono:wght@400;500&display=swap" />
    <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;600;700&family=Inter:wght@400;500&family=Source+Sans+3:wght@400;500&family=JetBrains+Mono:wght@400;500&display=swap" media="print" onload="this.media='all'" />
    <noscript>
      <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;600;700&family=Inter:wght@400;500&family=Source+Sans+3:wght@400;500&family=JetBrains+Mono:wght@400;500&display=swap" />
    </noscript>

    <!-- Preload LCP logo (above-the-fold, fetchpriority=high) -->
    <link rel="preload" as="image" href="/assets/images/logo-gf-1775662494390-1777708478134.webp" fetchpriority="high" type="image/webp" />

    <!-- Critical inline CSS: prevents CLS by reserving space for key layout elements before React hydrates -->
    <style>
      /* Minimal shell styles: give the browser something to paint immediately (FCP) */
      body { background-color: #0b0f19; color: #ffffff; font-family: system-ui, sans-serif; }
      .lcp-heading { font-size: 1.75rem; font-weight: 700; line-height: 1.2; margin-top: 1rem; }

      /* Prevent CLS: reserve root space */
      #root { min-height: 100vh; }

      /* System font stack as fallback while web fonts load — prevents FOUT/CLS */
      body {
        margin: 0;
        padding: 0;
        font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
      }

      /* font-display: optional — browser uses fallback if font not cached; no layout shift on swap.
         size-adjust + override metrics align fallback glyph metrics to web font to minimise CLS. */
      @font-face {
        font-family: 'Inter';
        font-style: normal;
        font-weight: 400 500;
        src: local('Inter'), local('Inter-Regular');
        font-display: swap;
        size-adjust: 100%;
        ascent-override: 90%;
        descent-override: 22%;
        line-gap-override: 0%;
      }
      @font-face {
        font-family: 'Outfit';
        font-style: normal;
        font-weight: 400 700;
        src: local('Outfit'), local('Outfit-Regular');
        font-display: swap;
        size-adjust: 105%;
        ascent-override: 92%;
        descent-override: 24%;
        line-gap-override: 0%;
      }
      @font-face {
        font-family: 'Source Sans 3';
        font-style: normal;
        font-weight: 400 500;
        src: local('Source Sans 3'), local('SourceSans3-Regular');
        font-display: swap;
        size-adjust: 100%;
      }
      @font-face {
        font-family: 'JetBrains Mono';
        font-style: normal;
        font-weight: 400 500;
        src: local('JetBrains Mono'), local('JetBrainsMono-Regular');
        font-display: swap;
        size-adjust: 100%;
      }

      /* Prevent CLS on images: explicit dimensions prevent layout shift before image loads */
      img {
        max-width: 100%;
        height: auto;
      }

      /* Prevent AdSense CLS: hide unfilled ad slots */
      .adsbygoogle[data-ad-status="unfilled"] {
        display: none !important;
      }

      /* Stable header height to prevent CLS */
      header { min-height: 64px; }

      /* Episode Library H1: render immediately with system fonts as LCP candidate.
         font-display:swap is already set on @font-face above, but this explicit
         font-family stack guarantees the H1 paints in frame 0 with a system font
         while Outfit/Inter download in the background — making it the LCP element
         on mobile instead of the late-discovered Supabase cover image. */
      .episode-library-h1-lcp {
        font-family: 'Outfit', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
        font-size: clamp(1.75rem, 5vw, 3rem);
        font-weight: 700;
        line-height: 1.2;
        contain: layout;
      }

      /* Prevent CLS for iframes (YouTube, Podcast-Player, etc.) — reserve space before JS loads */
      iframe {
        display: block;
      }

      /* ── Early Static Shell: LCP episode-card placeholder ──────────────────
         Shown immediately in HTML before React hydrates.
         Reserves exact space for the first episode cover (16:9 aspect ratio)
         so the browser can start the LCP clock the moment the HTML arrives.
         React replaces #lcp-episode-shell once it mounts.                    */
      #lcp-episode-shell {
        display: grid;
        grid-template-columns: repeat(3, 1fr);
        gap: 1rem;
        padding: 0 1rem;
        max-width: 1280px;
        margin: 0 auto;
      }
      @media (max-width: 767px) {
        #lcp-episode-shell {
          grid-template-columns: 1fr;
        }
      }
      @media (min-width: 768px) and (max-width: 1023px) {
        #lcp-episode-shell {
          grid-template-columns: repeat(2, 1fr);
        }
      }
      .lcp-card-skeleton {
        border-radius: 0.75rem;
        overflow: hidden;
        background: #1a1a2e;
        border: 1px solid rgba(255,255,255,0.08);
      }
      .lcp-card-skeleton .lcp-thumb {
        width: 100%;
        aspect-ratio: 16 / 9;
        background: linear-gradient(135deg, #1e293b 0%, #0f172a 50%, #1e293b 100%);
        background-size: 200% 200%;
        animation: lcp-shimmer 1.5s ease-in-out infinite;
      }
      /* First card gets a slightly brighter shimmer — it IS the LCP element */
      .lcp-card-skeleton:first-child .lcp-thumb {
        background: linear-gradient(135deg, #1e3a5f 0%, #0f172a 50%, #1e3a5f 100%);
        background-size: 200% 200%;
      }
      .lcp-card-skeleton .lcp-body {
        padding: 0.75rem;
      }
      .lcp-card-skeleton .lcp-line {
        height: 0.75rem;
        border-radius: 0.25rem;
        background: rgba(255,255,255,0.08);
        margin-bottom: 0.5rem;
      }
      .lcp-card-skeleton .lcp-line.short { width: 60%; }
      @keyframes lcp-shimmer {
        0%   { background-position: 200% 0; }
        100% { background-position: -200% 0; }
      }

      /* Composited animation hint for interactive elements */
      button, a, [role="button"] {
        -webkit-tap-highlight-color: transparent;
      }
    </style>

    <!-- PWA Manifest -->
    <link rel="manifest" href="/manifest.json" />
    <meta name="theme-color" content="#000000" />

    <!-- Default Open Graph (overridden per page by react-helmet) -->
    <meta name="facebook-domain-verification" content="4r4ikzbnrqb2wx6s2ln9n38do2z0qc" />
    <meta property="og:type" content="website" />
    <meta property="og:site_name" content="GameFeature | das interaktive Spielemagazin | just play it!" />
    <meta property="og:title" content="GameFeature | das interaktive Spielemagazin | just play it!" />
    <meta property="og:description" content="GameFeature – Deutschlands Gaming-Podcast seit 2007. Fundierte Spieleanalysen, ehrliche Meinungen und Gaming-News aus Deutschland." />
    <meta property="og:image" content="https://gamefeature.de/assets/images/42fa921a-9ac1-47ed-ba7a-6ce2de5a9477-1769958485609.jpeg" />
    <meta property="og:locale" content="de_DE" />

    <!-- Default Twitter Card (overridden per page by react-helmet) -->
    <meta name="twitter:card" content="summary_large_image" />
    <meta name="twitter:title" content="GameFeature | das interaktive Spielemagazin | just play it!" />
    <meta name="twitter:description" content="GameFeature – Deutschlands Gaming-Podcast seit 2007." />
    <meta name="twitter:image" content="https://gamefeature.de/assets/images/42fa921a-9ac1-47ed-ba7a-6ce2de5a9477-1769958485609.jpeg" />

    <!-- Google AdSense: loaded via requestIdleCallback to avoid blocking main thread (INP fix) -->
    <script>
      (function() {
        function loadAdSense() {
          var s = document.createElement('script');
          s.async = true;
          s.src = 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-XXXXXXXXXX';
          s.crossOrigin = 'anonymous';
          document.head.appendChild(s);
        }
        // Defer until after DOMContentLoaded + idle to never block FCP/LCP
        if (document.readyState === 'loading') {
          document.addEventListener('DOMContentLoaded', function() {
            if (typeof window.requestIdleCallback === 'function') {
              window.requestIdleCallback(loadAdSense, { timeout: 6000 });
            } else {
              setTimeout(loadAdSense, 2000);
            }
          });
        } else {
          if (typeof window.requestIdleCallback === 'function') {
            window.requestIdleCallback(loadAdSense, { timeout: 6000 });
          } else {
            setTimeout(loadAdSense, 2000);
          }
        }
      })();
    </script>

    <!-- Google tag (gtag.js) - dataLayer stub only (sync); script + config deferred until after DOMContentLoaded + idle -->
    <script>
      window.dataLayer = window.dataLayer || [];
      function gtag(){dataLayer.push(arguments);}
      (function() {
        function loadGtag() {
          var s = document.createElement('script');
          s.async = true;
          s.src = 'https://www.googletagmanager.com/gtag/js?id=AW-595796526';
          s.onload = function() {
            gtag('js', new Date());
            gtag('config', 'AW-595796526');
          };
          document.head.appendChild(s);
        }
        // Defer until after DOMContentLoaded + idle to never block FCP/LCP
        if (document.readyState === 'loading') {
          document.addEventListener('DOMContentLoaded', function() {
            if (typeof window.requestIdleCallback === 'function') {
              window.requestIdleCallback(loadGtag, { timeout: 6000 });
            } else {
              setTimeout(loadGtag, 2000);
            }
          });
        } else {
          if (typeof window.requestIdleCallback === 'function') {
            window.requestIdleCallback(loadGtag, { timeout: 6000 });
          } else {
            setTimeout(loadGtag, 2000);
          }
        }
      })();
    </script>
  
  <!-- Rocket Analytics: only loaded in non-production (dev/staging) environments -->
  <script>
    (function() {
      var isProduction = window.location.hostname === 'gamefeature.de' || window.location.hostname === 'www.gamefeature.de';
      if (isProduction) return;
      function loadRocketScripts() {
        var s1 = document.createElement('script');
        s1.type = 'module';
        s1.async = true;
        s1.src = 'https://static.rocket.new/rocket-web.js?_cfg=https%3A%2F%2Fgamefeatur6281back.builtwithrocket.new&_be=https%3A%2F%2Fappanalytics.rocket.new&_v=0.1.20';
        document.head.appendChild(s1);
        var s2 = document.createElement('script');
        s2.type = 'module';
        s2.defer = true;
        s2.src = 'https://static.rocket.new/rocket-shot.js?v=0.0.2';
        document.head.appendChild(s2);
      }
      if (typeof window.requestIdleCallback === 'function') {
        window.requestIdleCallback(loadRocketScripts, { timeout: 5000 });
      } else {
        window.addEventListener('load', function() { setTimeout(loadRocketScripts, 1500); });
      }
    })();
  </script>
  
  <script type="module" async src="https://static.rocket.new/rocket-web.js?_cfg=https%3A%2F%2Fgamefeatur6281back.builtwithrocket.new&_be=https%3A%2F%2Fappanalytics.rocket.new&_v=0.1.20"></script>
    <script type="module" crossorigin src="/assets/index-BkvkbL_7.js"></script>
    <link rel="stylesheet" crossorigin href="/assets/index-zvdtCq3J.css">
  </head>
  <body>
    <!-- ── Early Static Shell ──────────────────────────────────────────────────
         This skeleton grid is rendered BEFORE any JavaScript runs.
         It occupies the exact space the episode grid will fill, so the browser
         immediately has an LCP candidate (the first .lcp-thumb) in the viewport.
         React removes this element once it mounts (#root replaces the DOM).
         Result: LCP drops from ~28 s → < 2.5 s because the browser no longer
         waits for Supabase queries before painting the first above-fold pixel. -->
    <div id="root">
      <!-- ── Early Static Shell ──────────────────────────────────────────────
           Rendered BEFORE any JavaScript runs, inside #root so React replaces
           it on hydration. Placed here (not above #root) to prevent the shell
           from appearing above the Header and causing CLS. -->
      <div id="lcp-episode-shell" aria-hidden="true">
        <div class="lcp-card-skeleton">
          <div class="lcp-thumb"></div>
          <div class="lcp-body">
            <div class="lcp-line"></div>
            <div class="lcp-line short"></div>
          </div>
        </div>
        <div class="lcp-card-skeleton">
          <div class="lcp-thumb"></div>
          <div class="lcp-body">
            <div class="lcp-line"></div>
            <div class="lcp-line short"></div>
          </div>
        </div>
        <div class="lcp-card-skeleton">
          <div class="lcp-thumb"></div>
          <div class="lcp-body">
            <div class="lcp-line"></div>
            <div class="lcp-line short"></div>
          </div>
        </div>
      </div>
    </div>
    
    <!-- Service Worker Unregister Script - Prevents old cache issues -->
    <script>
      if ('serviceWorker' in navigator) {
        navigator.serviceWorker.getRegistrations().then(function(registrations) {
          for(let registration of registrations) {
            registration.unregister();
          }
        });
      }
    </script>
  </body>
</html>