Match videos: composed VS-card OG preview + richer share metadata

Sports match share links now show the full VS composition (fighters,
flags, event, weight class, referee/court/match chips) in WhatsApp /
Telegram / Facebook / Twitter previews instead of the raw video frame.

- Browsershot + puppeteer-bundled Chromium screenshots a dedicated
  1200x675 (true 16:9) render of the vs-mini composition. Chrome runs
  through a small wrapper (bin/browsershot-node.sh) that raises the fd
  limit and reads its full env, since PHP-FPM's stripped env otherwise
  makes Chromium fail to launch.
- Render is JPEG @ q85 (~100 KB) instead of PNG (~700 KB) — WhatsApp
  silently drops OG images above roughly 300 KB, which is why music
  thumbnails always showed and match cards didn't.
- SportsMatchController@store/update renders synchronously so the
  picture is guaranteed to exist before the user copies the share URL;
  edits regenerate and old stamped variants are cleaned. The og:image
  URL carries a ?v={updated_at} stamp so scrapers recache on edit.
- accessShare() (/s/{token}) now detects scraper user agents and serves
  the video page HTML inline instead of a 302 — some crawlers don't
  follow redirects when building link previews.
- og:description / twitter:description built from headerData(): flag
  emoji + fighter names (blue vs red), event, weight, match#, court,
  referee, view count, upload date.
- Fixed 3-dot menu on video cards being hidden behind the next card
  (:has(.dropdown-menu.show) promotes the active card above siblings
  and lifts the overflow:hidden clip on .yt-video-info).
- vs-mini: skip the entrance animation on touch devices and expose a
  .vs-mini-static class so the OG render always captures the final
  frame instead of a random mid-animation moment.

Also adds an admin gallery at /admin/og-previews for spot-checking the
generated preview JPEGs.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
ghassanandClaude Opus 4.7 committed 2026-08-11 12:34:40 +03:00
1 parent 665bad76bf
commit 3e18bd2150
14 files changed
+3562 -21

No files matched your search

@@ -2,6 +2,7 @@
namespace App\Http\Controllers;
use App\Http\Controllers\VideoController;
use App\Models\SportsMatch;
use App\Services\NasSyncService;
use Illuminate\Http\JsonResponse;
@@ -39,6 +40,18 @@ class SportsMatchController extends Controller
$match->video->save();
}
// Prefetch the OG social-share PNG so WhatsApp/Facebook crawlers
// get the composed VS card on first share, not the fallback frame.
if ($match->video) {
// Blocks the response by ~3–5s while the VS PNG renders. That
// wait is intentional: the user typically shares the URL right
// after saving, and WhatsApp/Facebook cache whatever they see
// on first crawl. If the render hasn't finished when the
// crawler hits, we'd be stuck on the thumbnail fallback in
// every future share.
(new VideoController())->renderMatchOgSync($match->video->fresh(['sportsMatch']));
}
return response()->json([
'ok' => true,
'message' => 'Match saved as draft.',
@@ -64,6 +77,13 @@ class SportsMatchController extends Controller
$sportsMatch->video->save();
}
if ($sportsMatch->video) {
// Synchronous re-render on edit: guarantees the fresh VS card
// exists at the new versioned URL before the JSON response
// returns and the user can share the link. See renderMatchOgSync.
(new VideoController())->renderMatchOgSync($sportsMatch->video->fresh(['sportsMatch']));
}
return response()->json([
'ok' => true,
'message' => 'Match updated.',