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

@@ -0,0 +1,47 @@
<?php
namespace App\Console\Commands;
use App\Http\Controllers\VideoController;
use App\Models\Video;
use Illuminate\Console\Command;
/**
* Renders the 1200×630 VS composition PNG for a match video's social-share
* preview. Runs the same helper used by VideoController@ogImage but from a
* CLI process context, because Chromium reliably fails to launch under
* PHP-FPM ("Failed to launch the browser process: Code: null") on this
* host. Invoke detached via `nohup` from either the ogImage endpoint (on
* cache miss) or SportsMatchController on save.
*/
class RenderMatchOgImage extends Command
{
protected $signature = 'og:render-match {video}';
protected $description = 'Render the OG social-share PNG for a match video';
public function handle(): int
{
$video = Video::find($this->argument('video'));
if (! $video) {
$this->error("Video {$this->argument('video')} not found.");
return 1;
}
if ($video->type !== 'match' || ! $video->sportsMatch) {
$this->error("Video {$video->id} is not a match with a sports record.");
return 1;
}
$controller = new VideoController();
$r = new \ReflectionMethod($controller, 'renderMatchOgImage');
$r->setAccessible(true);
$png = $r->invoke($controller, $video);
if ($png === null) {
$this->error("Render failed. See laravel.log.");
return 1;
}
$this->info("Rendered " . strlen($png) . " bytes for video {$video->id}.");
return 0;
}
}