<?php
/**
 * Sony-Ericsson-style "phone" launcher (single-file)
 * - Lists .html, .htm, .php from the same folder as this PHP file
 * - If base.html and base.htm both exist -> show ONLY base.html
 * - If base.html doesn't exist -> show base.htm
 * - Always include .php (except this launcher file)
 * - Display name comes from <title> tag inside the file (source scan)
 * - Poster image shares same base name: base.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 getTitleFromSourceFile(string $path): string {
  $maxBytes = 524288; // 512KB
  $fh = @fopen($path, 'rb');
  if (!$fh) return '';
  $chunk = @fread($fh, $maxBytes);
  @fclose($fh);
  if ($chunk === false || $chunk === '') return '';

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

function findPoster(string $dir, string $baseName): ?array {
  $exts = ['png','jpg','jpeg','webp','svg','gif','avif'];

  foreach ($exts as $ext) {
    $candidate = $dir . DIRECTORY_SEPARATOR . $baseName . '.' . $ext;
    if (is_file($candidate)) {
      return ['file' => $baseName . '.' . $ext, 'mtime' => (@filemtime($candidate) ?: null)];
    }
  }

  // Case-insensitive fallback scan (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 ['file' => basename($file), 'mtime' => (@filemtime($file) ?: null)];
    }
  }

  return null;
}

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

// Scan candidates
$html = []; // base => filename
$htm  = []; // base => filename
$php  = []; // list

foreach (glob($dir . DIRECTORY_SEPARATOR . '*.{html,htm,php}', GLOB_BRACE) ?: [] as $path) {
  if (!is_file($path)) continue;

  $file = basename($path);
  if (strcasecmp($file, $self) === 0) continue; // don't list this file

  $ext  = strtolower(pathinfo($file, PATHINFO_EXTENSION));
  $base = pathinfo($file, PATHINFO_FILENAME);

  if ($ext === 'html') $html[$base] = $file;
  elseif ($ext === 'htm') $htm[$base] = $file;
  elseif ($ext === 'php') $php[] = $file;
}

// Final list with html>htm preference
$final = [];

// Include all html
foreach ($html as $base => $file) $final[] = $file;

// Include htm only if html missing for same base
foreach ($htm as $base => $file) {
  if (!isset($html[$base])) $final[] = $file;
}

// Include all php
foreach ($php as $file) $final[] = $file;

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

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

  $posterInfo = findPoster($dir, $base);
  $poster = $posterInfo['file'] ?? null;
  $posterMtime = $posterInfo['mtime'] ?? null;

  $href = rawurlencode($file);

  $posterUrl = null;
  if ($poster) {
    $posterUrl = rawurlencode($poster);
    if ($posterMtime) $posterUrl .= '?v=' . (int)$posterMtime;
  }

  $items[] = [
    'file' => $file,
    'href' => $href,
    'base' => $base,
    'title' => $title,
    'title_lc' => mb_strtolower($title, 'UTF-8'),
    'poster' => $posterUrl,
  ];
}

// Sort by title natural (case-insensitive)
usort($items, fn($a, $b) => strnatcasecmp((string)$a['title'], (string)$b['title'));

$total = count($items);
?><!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>Phone Launcher (<?= (int)$total ?>)</title>
  <style>
    :root{
      --bg:#0b0f1a;
      --phone-silver1:#d9dde2;
      --phone-silver2:#aeb6c2;
      --phone-dark:#0a0d12;
      --screen:#e7eefc;
      --screen2:#cfe2ff;
      --screen-bezel:#0a0a0c;
      --ink:#0b1428;
      --muted: rgba(11,20,40,.72);
      --accent:#2fd0ff;
      --accent2:#42f7b1;
      --shadow: 0 25px 70px rgba(0,0,0,.55);
      --radius: 42px;
    }

    *{box-sizing:border-box}
    html,body{height:100%}
    body{
      margin:0;
      background:
        radial-gradient(1200px 800px at 20% 10%, rgba(47,208,255,.18), transparent 60%),
        radial-gradient(900px 700px at 80% 35%, rgba(66,247,177,.14), transparent 55%),
        linear-gradient(180deg, #060913, var(--bg));
      font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
      display:grid;
      place-items:center;
      padding: 20px;
    }

    /* Phone shell */
    .phone{
      width: min(380px, 92vw);
      aspect-ratio: 9 / 16;
      border-radius: var(--radius);
      background: linear-gradient(180deg, var(--phone-silver1), var(--phone-silver2));
      box-shadow: var(--shadow);
      border: 1px solid rgba(255,255,255,.16);
      position:relative;
      overflow:hidden;
    }

    /* top black bezel */
    .top-bezel{
      position:absolute;
      left:0; right:0;
      top:0;
      height: 18%;
      background: linear-gradient(180deg, #0a0c10, #0b0e15 55%, #0a0d12);
    }
    .earpiece{
      position:absolute;
      top: 5.2%;
      left: 50%;
      transform: translateX(-50%);
      width: 34%;
      height: 8px;
      border-radius: 999px;
      background: linear-gradient(180deg, rgba(255,255,255,.12), rgba(255,255,255,.05));
      box-shadow: inset 0 1px 2px rgba(0,0,0,.65);
      opacity:.9;
    }
    .brand{
      position:absolute;
      top: 8.5%;
      left: 50%;
      transform: translateX(-50%);
      font-size: 12px;
      letter-spacing: .5px;
      color: rgba(255,255,255,.78);
      font-weight: 700;
      user-select:none;
    }

    /* screen */
    .screen{
      position:absolute;
      top: 13.5%;
      left: 9%;
      right: 9%;
      height: 40%;
      border-radius: 16px;
      background: linear-gradient(180deg, var(--screen), var(--screen2));
      border: 8px solid var(--screen-bezel);
      box-shadow: inset 0 2px 14px rgba(0,0,0,.35);
      overflow:hidden;
      color: var(--ink);
    }

    .status{
      display:flex;
      align-items:center;
      justify-content:space-between;
      padding: 8px 10px 6px;
      font-weight: 800;
      font-size: 12px;
      border-bottom: 1px solid rgba(0,0,0,.12);
      background: linear-gradient(180deg, rgba(255,255,255,.65), rgba(255,255,255,.18));
    }
    .status .right{
      display:flex; gap:8px; align-items:center;
      font-weight:900;
      color: rgba(11,20,40,.75);
    }
    .dot{width:6px;height:6px;border-radius:999px;background:rgba(11,20,40,.55);}
    .sig{display:flex;gap:2px;align-items:flex-end;}
    .sig i{display:block;width:3px;background:rgba(11,20,40,.55);border-radius:2px;}
    .sig i:nth-child(1){height:4px}
    .sig i:nth-child(2){height:6px}
    .sig i:nth-child(3){height:8px}
    .sig i:nth-child(4){height:10px}

    .menu-title{
      padding: 8px 10px 6px;
      font-weight: 900;
      font-size: 14px;
      letter-spacing:.2px;
    }

    /* icon grid in screen */
    .grid{
      padding: 6px 10px 8px;
      display:grid;
      grid-template-columns: repeat(3, 1fr);
      gap: 8px;
      height: calc(100% - 82px);
      overflow:auto;
      scrollbar-width: none;
    }
    .grid::-webkit-scrollbar{display:none}

    .app{
      display:flex;
      flex-direction:column;
      gap: 6px;
      align-items:center;
      justify-content:flex-start;
      text-decoration:none;
      color: inherit;
      border-radius: 10px;
      padding: 6px 6px 8px;
      transition: transform .16s ease, box-shadow .16s ease, background .16s ease;
      user-select:none;
      -webkit-tap-highlight-color: transparent;
      outline:none;
    }

    /* Hover animation for mouse + keyboard focus */
    .app:hover,
    .app:focus-visible,
    .app.is-hover{
      transform: translateY(-2px) scale(1.04);
      background: rgba(47,208,255,.16);
      box-shadow: 0 10px 18px rgba(0,0,0,.18);
    }

    /* Touch press */
    .app:active{
      transform: translateY(0px) scale(1.02);
      background: rgba(66,247,177,.16);
    }

    .icon{
      width: 54px;
      height: 54px;
      border-radius: 12px;
      overflow:hidden;
      background:
        radial-gradient(60px 60px at 30% 20%, rgba(255,255,255,.85), rgba(255,255,255,.18) 55%, rgba(0,0,0,.06)),
        linear-gradient(140deg, rgba(47,208,255,.20), rgba(66,247,177,.14));
      border: 1px solid rgba(0,0,0,.12);
      box-shadow: inset 0 1px 8px rgba(255,255,255,.38);
      display:grid;
      place-items:center;
      position:relative;
    }

    /* Poster image inside icon (enlarge on hover) */
    .icon img{
      width: 100%;
      height: 100%;
      object-fit: cover;
      display:block;
      transform: scale(1.02);
      transition: transform .18s ease;
      will-change: transform;
    }
    .app:hover .icon img,
    .app:focus-visible .icon img,
    .app.is-hover .icon img{
      transform: scale(1.14);
    }

    /* If no poster: show a simple colorful glyph */
    .glyph{
      font-weight: 1000;
      font-size: 16px;
      letter-spacing:.3px;
      color: rgba(11,20,40,.78);
      text-shadow: 0 1px 0 rgba(255,255,255,.35);
    }

    .label{
      font-size: 11px;
      font-weight: 900;
      text-align:center;
      line-height: 1.15;
      padding: 0 2px;
      display:-webkit-box;
      -webkit-line-clamp: 2;
      -webkit-box-orient: vertical;
      overflow:hidden;
      min-height: 26px;
    }

    /* Softkeys bar at bottom of screen */
    .softkeys{
      position:absolute;
      left: 0; right: 0; bottom: 0;
      height: 26px;
      display:flex;
      justify-content:space-between;
      align-items:center;
      padding: 0 10px;
      font-size: 11px;
      font-weight: 900;
      color: rgba(11,20,40,.78);
      border-top: 1px solid rgba(0,0,0,.12);
      background: linear-gradient(180deg, rgba(255,255,255,.14), rgba(255,255,255,.42));
    }

    /* Lower body (keypad hint only) */
    .lower{
      position:absolute;
      left:0; right:0;
      top: 58%;
      bottom: 0;
      padding: 16px 18px;
    }

    .nav-pad{
      width: 56%;
      margin: 0 auto;
      height: 92px;
      border-radius: 26px;
      background: linear-gradient(180deg, rgba(255,255,255,.62), rgba(0,0,0,.06));
      border: 1px solid rgba(0,0,0,.10);
      box-shadow: inset 0 10px 22px rgba(255,255,255,.28);
      position:relative;
    }
    .nav-pad:before{
      content:"";
      position:absolute; inset: 18px;
      border-radius: 20px;
      border: 2px solid rgba(0,0,0,.12);
      opacity:.9;
    }
    .nav-dot{
      position:absolute;
      left:50%; top:50%;
      transform: translate(-50%,-50%);
      width: 22px; height: 22px;
      border-radius: 999px;
      background: radial-gradient(circle at 30% 30%, rgba(255,255,255,.9), rgba(0,0,0,.08));
      border: 1px solid rgba(0,0,0,.12);
    }

    .hint{
      margin-top: 14px;
      text-align:center;
      font-size: 12px;
      font-weight: 800;
      color: rgba(0,0,0,.55);
      user-select:none;
    }

    /* Search overlay inside screen */
    .searchWrap{
      position:absolute;
      right: 10px;
      top: 34px;
      width: calc(100% - 20px);
      display:flex;
      gap: 8px;
      align-items:center;
      padding: 6px 0 4px;
    }
    .searchWrap input{
      width:100%;
      padding: 7px 10px;
      border-radius: 10px;
      border: 1px solid rgba(0,0,0,.18);
      background: rgba(255,255,255,.55);
      outline:none;
      font-size: 12px;
      font-weight: 800;
      color: rgba(11,20,40,.86);
    }
    .searchWrap input:focus{
      border-color: rgba(47,208,255,.55);
      box-shadow: 0 0 0 3px rgba(47,208,255,.18);
    }

    @media (prefers-reduced-motion: reduce){
      .app, .icon img { transition: none !important; }
    }
  </style>
</head>
<body>

<div class="phone" role="application" aria-label="Phone Launcher">
  <div class="top-bezel">
    <div class="earpiece" aria-hidden="true"></div>
    <div class="brand" aria-hidden="true">Sony Ericsson</div>
  </div>

  <div class="screen" aria-label="Screen">
    <div class="status">
      <div>Menu</div>
      <div class="right">
        <span class="sig" aria-hidden="true"><i></i><i></i><i></i><i></i></span>
        <span class="dot" aria-hidden="true"></span>
        <span><?= (int)$total ?></span>
      </div>
    </div>

    <div class="menu-title">Apps</div>

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

    <div class="grid" id="grid">
      <?php foreach ($items as $it): ?>
        <?php
          $title = $it['title'];
          $href  = $it['href'];
          $poster = $it['poster'];

          // Make a stable 2-letter glyph from title for fallback
          $glyph = mb_strtoupper(mb_substr(preg_replace('/\s+/u','', $title), 0, 2, 'UTF-8'), 'UTF-8');
          if ($glyph === '') $glyph = 'AP';
        ?>
        <a class="app" href="<?= h($href) ?>"
           data-title="<?= h($it['title_lc']) ?>"
           aria-label="<?= h($title) ?>">
          <div class="icon">
            <?php if ($poster): ?>
              <img src="<?= h($poster) ?>" alt="<?= h($title) ?> poster" loading="lazy">
            <?php else: ?>
              <div class="glyph"><?= h($glyph) ?></div>
            <?php endif; ?>
          </div>
          <div class="label"><?= h($title) ?></div>
        </a>
      <?php endforeach; ?>
    </div>

    <div class="softkeys" aria-hidden="true">
      <span>Select</span>
      <span>Back</span>
    </div>
  </div>

  <div class="lower" aria-hidden="true">
    <div class="nav-pad">
      <div class="nav-dot"></div>
    </div>
    <div class="hint">Tip: posters match filenames (game.html → game.png/jpg/webp…)</div>
  </div>
</div>

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

  const apps = Array.from(grid.querySelectorAll('.app'));

  function filter(){
    const term = (q.value || '').trim().toLowerCase();
    for(const a of apps){
      const t = a.getAttribute('data-title') || '';
      a.style.display = (!term || t.includes(term)) ? '' : 'none';
    }
  }
  q.addEventListener('input', filter);

  // "/" focuses search, ESC clears (nice on keyboard)
  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(); filter(); }
  });

  // Touch "hover" effect: briefly apply .is-hover on tap so it feels alive
  let timer = null;
  grid.addEventListener('touchstart', (e) => {
    const a = e.target.closest && e.target.closest('.app');
    if(!a) return;

    apps.forEach(x => x.classList.remove('is-hover'));
    a.classList.add('is-hover');

    clearTimeout(timer);
    timer = setTimeout(() => a.classList.remove('is-hover'), 650);
  }, {passive:true});

  // Remove hover state while scrolling
  window.addEventListener('touchmove', () => {
    apps.forEach(x => x.classList.remove('is-hover'));
  }, {passive:true});

  filter();
})();
</script>

</body>
</html>
``