<?php
/**
 * SteamOS-style HTML launcher
 * - Put this PHP file in a folder full of .html/.htm files
 * - For each HTML file, it reads the <title> tag for the display name
 * - Looks for a poster image with the same base name:
 *   example: wart.html -> wart.png|jpg|jpeg|webp|svg|gif|avif
 */

declare(strict_types=1);

function h(string $s): string {
  return htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

function getTitleFromHtml(string $path): string {
  // Read a chunk (enough for head/title in most cases)
  $maxBytes = 262144; // 256KB
  $fh = @fopen($path, 'rb');
  if (!$fh) return '';
  $chunk = @fread($fh, $maxBytes);
  @fclose($fh);
  if ($chunk === false || $chunk === '') return '';

  // Try to find <title>...</title> robustly
  if (preg_match('~<title\b[^>]*>(.*?)</title>~is', $chunk, $m)) {
    $title = trim(html_entity_decode(strip_tags($m[1]), ENT_QUOTES | ENT_HTML5, 'UTF-8'));
    // Collapse whitespace
    $title = preg_replace('/\s+/u', ' ', $title ?? '') ?? '';
    return trim($title);
  }
  return '';
}

function findPoster(string $dir, string $baseName): ?string {
  // Allowed poster extensions (common + modern)
  $exts = ['png','jpg','jpeg','webp','svg','gif','avif'];
  foreach ($exts as $ext) {
    $candidate = $dir . DIRECTORY_SEPARATOR . $baseName . '.' . $ext;
    if (is_file($candidate)) return $baseName . '.' . $ext;
  }

  // Case-insensitive fallback scan (for odd casing)
  $pattern = $dir . DIRECTORY_SEPARATOR . $baseName . '.*';
  foreach (glob($pattern, GLOB_NOSORT) ?: [] as $file) {
    if (!is_file($file)) continue;
    $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
    if (in_array($ext, $exts, true)) {
      return basename($file);
    }
  }
  return null;
}

$dir = __DIR__;
$self = basename(__FILE__);

// Collect HTML files
$htmlFiles = [];
foreach (glob($dir . DIRECTORY_SEPARATOR . '*.{html,htm}', GLOB_BRACE) ?: [] as $path) {
  if (!is_file($path)) continue;
  $bn = basename($path);
  // If someone names this file .html, ignore; otherwise include all.
  $htmlFiles[] = $bn;
}

// Build game entries
$games = [];
foreach ($htmlFiles as $file) {
  $path = $dir . DIRECTORY_SEPARATOR . $file;
  $base = pathinfo($file, PATHINFO_FILENAME);

  $title = getTitleFromHtml($path);
  if ($title === '') $title = $base;

  $poster = findPoster($dir, $base);
  $posterMtime = null;
  if ($poster) {
    $posterPath = $dir . DIRECTORY_SEPARATOR . $poster;
    $posterMtime = @filemtime($posterPath) ?: null;
  }

  $games[] = [
    'file' => $file,
    'base' => $base,
    'title' => $title,
    'poster' => $poster,
    'poster_mtime' => $posterMtime,
    'mtime' => @filemtime($path) ?: 0,
  ];
}

// Sort by title (natural, case-insensitive)
usort($games, function($a, $b) {
  return strnatcasecmp((string)$a['title'], (string)$b['title']);
});

$total = count($games);
?><!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <meta name="color-scheme" content="dark" />
  <title>Library (<?= $total ?>)</title>
  <style>
    :root{
      --bg0:#070a12;
      --bg1:#0b1020;
      --card: rgba(255,255,255,.06);
      --card2: rgba(255,255,255,.09);
      --stroke: rgba(255,255,255,.10);
      --stroke2: rgba(255,255,255,.18);
      --text:#eaf0ff;
      --muted: rgba(234,240,255,.72);
      --accent:#6ee7ff;
      --good:#46f6a8;
      --shadow: 0 12px 40px rgba(0,0,0,.45);
      --radius: 22px;
    }

    *{box-sizing:border-box}
    html,body{height:100%}
    body{
      margin:0;
      font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
      background:
        radial-gradient(1200px 800px at 20% 10%, rgba(110,231,255,.12), transparent 60%),
        radial-gradient(900px 700px at 80% 30%, rgba(70,246,168,.10), transparent 55%),
        linear-gradient(180deg, var(--bg0), var(--bg1));
      color:var(--text);
      overflow-x:hidden;
    }

    .topbar{
      position: sticky;
      top: 0;
      z-index: 50;
      backdrop-filter: blur(14px);
      background: linear-gradient(180deg, rgba(7,10,18,.92), rgba(11,16,32,.72));
      border-bottom: 1px solid var(--stroke);
    }

    .topbar-inner{
      max-width: 1400px;
      margin: 0 auto;
      padding: 16px 18px;
      display:flex;
      align-items:center;
      gap:14px;
    }

    .brand{
      display:flex;
      align-items:center;
      gap:12px;
      min-width: 200px;
    }

    .logo{
      width:42px;height:42px;
      border-radius: 14px;
      background: radial-gradient(circle at 30% 25%, rgba(110,231,255,.85), rgba(110,231,255,.12) 55%, rgba(255,255,255,.06));
      border: 1px solid var(--stroke2);
      box-shadow: var(--shadow);
      position:relative;
      overflow:hidden;
    }
    .logo:after{
      content:"";
      position:absolute; inset:-40% -40%;
      background: conic-gradient(from 180deg, transparent, rgba(110,231,255,.22), transparent, rgba(70,246,168,.18), transparent);
      animation: spin 6s linear infinite;
    }
    @keyframes spin { to { transform: rotate(360deg); } }

    .brand h1{
      margin:0;
      font-size: 16px;
      letter-spacing: .3px;
      font-weight: 800;
      line-height: 1.2;
    }
    .brand .sub{
      font-size: 12px;
      color: var(--muted);
      margin-top: 2px;
    }

    .search{
      flex: 1;
      display:flex;
      align-items:center;
      gap:10px;
    }

    .search input{
      width:100%;
      padding: 12px 14px;
      border-radius: 16px;
      border: 1px solid var(--stroke);
      background: rgba(255,255,255,.05);
      color: var(--text);
      outline: none;
      font-size: 14px;
    }
    .search input:focus{
      border-color: rgba(110,231,255,.45);
      box-shadow: 0 0 0 4px rgba(110,231,255,.12);
    }

    .pill{
      padding: 10px 12px;
      border-radius: 999px;
      border: 1px solid var(--stroke);
      background: rgba(255,255,255,.04);
      color: var(--muted);
      font-size: 12px;
      white-space: nowrap;
    }

    .wrap{
      max-width: 1400px;
      margin: 0 auto;
      padding: 18px;
    }

    .section-title{
      display:flex;
      align-items:baseline;
      justify-content:space-between;
      gap: 12px;
      margin: 18px 4px 14px;
    }

    .section-title h2{
      margin:0;
      font-size: 18px;
      font-weight: 800;
      letter-spacing: .2px;
    }

    .grid{
      display:grid;
      grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
      gap: 14px;
    }

    /* SteamOS-ish poster cards */
    .card{
      display:flex;
      flex-direction:column;
      gap: 10px;
      text-decoration:none;
      color: inherit;
      background: var(--card);
      border: 1px solid var(--stroke);
      border-radius: var(--radius);
      padding: 12px;
      box-shadow: 0 10px 28px rgba(0,0,0,.25);
      transition: transform .14s ease, background .14s ease, border-color .14s ease;
      position:relative;
      overflow:hidden;
      min-height: 280px;
    }
    .card:hover{
      transform: translateY(-3px);
      background: var(--card2);
      border-color: rgba(110,231,255,.25);
    }
    .card:focus{
      outline:none;
      box-shadow: 0 0 0 4px rgba(110,231,255,.16), 0 10px 28px rgba(0,0,0,.32);
      border-color: rgba(110,231,255,.35);
    }

    .poster{
      width: 100%;
      aspect-ratio: 2 / 3;
      border-radius: 18px;
      overflow:hidden;
      border: 1px solid rgba(255,255,255,.10);
      background:
        radial-gradient(500px 260px at 40% 30%, rgba(110,231,255,.16), transparent 65%),
        linear-gradient(140deg, rgba(255,255,255,.10), rgba(255,255,255,.02));
      display:flex;
      align-items:center;
      justify-content:center;
      position:relative;
    }

    .poster img{
      width:100%;
      height:100%;
      object-fit: cover;
      display:block;
      transform: scale(1.02);
    }

    .fallback{
      padding: 12px;
      text-align:center;
      color: rgba(234,240,255,.82);
      font-weight: 800;
      line-height: 1.2;
    }
    .fallback small{
      display:block;
      color: var(--muted);
      font-weight: 600;
      margin-top: 8px;
    }

    .title{
      font-weight: 850;
      font-size: 14px;
      letter-spacing: .2px;
      display:flex;
      align-items:flex-start;
      justify-content:space-between;
      gap: 10px;
      margin-top: 2px;
    }
    .title span{
      display:-webkit-box;
      -webkit-line-clamp: 2;
      -webkit-box-orient: vertical;
      overflow:hidden;
    }

    .meta{
      color: var(--muted);
      font-size: 12px;
      display:flex;
      gap: 10px;
      align-items:center;
      justify-content:space-between;
      margin-top: -2px;
    }

    .badge{
      font-size: 11px;
      color: rgba(7,10,18,.92);
      background: rgba(70,246,168,.92);
      padding: 6px 10px;
      border-radius: 999px;
      font-weight: 900;
      letter-spacing: .2px;
      box-shadow: 0 10px 28px rgba(0,0,0,.22);
    }
    .badge.missing{
      background: rgba(255,255,255,.15);
      color: rgba(234,240,255,.88);
      font-weight: 800;
      border: 1px solid rgba(255,255,255,.14);
    }

    .empty{
      margin-top: 20px;
      padding: 18px;
      border-radius: var(--radius);
      border: 1px dashed var(--stroke2);
      background: rgba(255,255,255,.04);
      color: var(--muted);
    }

    /* Make it feel like a console UI on touch */
    @media (hover:none){
      .card:hover{ transform:none; }
    }

    @media (max-width: 520px){
      .brand{min-width:unset}
      .pill{display:none}
      .grid{grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));}
      .card{min-height: 260px;}
    }
  </style>
</head>
<body>
  <div class="topbar">
    <div class="topbar-inner">
      <div class="brand" aria-label="Library">
        <div class="logo" aria-hidden="true"></div>
        <div>
          <h1>Library</h1>
          <div class="sub"><?= $total ?> HTML <?= $total === 1 ? 'game' : 'games' ?> found</div>
        </div>
      </div>

      <div class="search">
        <input id="q" type="search" placeholder="Search titles…" autocomplete="off" />
      </div>

      <div class="pill" id="countPill"><?= $total ?> shown</div>
    </div>
  </div>

  <div class="wrap">
    <div class="section-title">
      <h2>All Games</h2>
      <div style="color:var(--muted);font-size:12px;">Tip: add <strong>poster images</strong> named the same as the HTML file.</div>
    </div>

    <?php if ($total === 0): ?>
      <div class="empty">
        No <code>.html</code> / <code>.htm</code> files found in this folder.
      </div>
    <?php else: ?>
      <div class="grid" id="grid">
        <?php foreach ($games as $g): ?>
          <?php
            $file = $g['file'];
            $title = $g['title'];
            $poster = $g['poster'];
            $hasPoster = (bool)$poster;

            $href = rawurlencode($file);
            $posterUrl = '';
            if ($poster) {
              $posterUrl = rawurlencode($poster);
              if ($g['poster_mtime']) $posterUrl .= '?v=' . (int)$g['poster_mtime'];
            }
          ?>
          <a class="card" href="<?= h($href) ?>" data-title="<?= h(mb_strtolower($title, 'UTF-8')) ?>" tabindex="0">
            <div class="poster">
              <?php if ($hasPoster): ?>
                <img loading="lazy" decoding="async" src="<?= h($posterUrl) ?>" alt="<?= h($title) ?> poster" />
              <?php else: ?>
                <div class="fallback">
                  <?= h($title) ?>
                  <small>No poster found</small>
                </div>
              <?php endif; ?>
            </div>

            <div class="title">
              <span><?= h($title) ?></span>
            </div>

            <div class="meta">
              <span><?= h($file) ?></span>
              <span class="badge <?= $hasPoster ? '' : 'missing' ?>"><?= $hasPoster ? 'Poster' : 'No Poster' ?></span>
            </div>
          </a>
        <?php endforeach; ?>
      </div>
    <?php endif; ?>
  </div>

  <script>
    (function(){
      const q = document.getElementById('q');
      const grid = document.getElementById('grid');
      const pill = document.getElementById('countPill');
      if (!q || !grid) return;

      const cards = Array.from(grid.querySelectorAll('.card'));
      const total = cards.length;

      function update(){
        const term = (q.value || '').trim().toLowerCase();
        let shown = 0;

        for (const c of cards){
          const t = c.getAttribute('data-title') || '';
          const ok = !term || t.includes(term);
          c.style.display = ok ? '' : 'none';
          if (ok) shown++;
        }
        if (pill) pill.textContent = shown + ' shown';
      }

      q.addEventListener('input', update);

      // Keyboard: press "/" to focus search (Steam-ish)
      window.addEventListener('keydown', (e) => {
        if (e.key === '/' && document.activeElement !== q) {
          e.preventDefault();
          q.focus();
        }
        if (e.key === 'Escape' && document.activeElement === q) {
          q.value = '';
          q.blur();
          update();
        }
      });

      update();
    })();
  </script>
</body>
</html>