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) <noreply@anthropic.com>
41 lines
2.0 KiB
PHP
41 lines
2.0 KiB
PHP
@extends('admin.layout')
|
||
|
||
@section('title', 'OG Previews')
|
||
|
||
@section('content')
|
||
<div style="padding: 24px;">
|
||
<h1 style="margin: 0 0 8px; color: #fff;">Generated VS Cards</h1>
|
||
<p style="color: #aaa; margin: 0 0 20px;">
|
||
These are the 1200×630 PNGs that WhatsApp, Facebook, and Twitter show
|
||
when a sports match video is shared. Generated on save; regenerated on
|
||
edit. Filename is <code>match-{video_id}-{updated_at}.png</code>.
|
||
</p>
|
||
|
||
@if($files->isEmpty())
|
||
<p style="color: #aaa;">No previews cached yet. Save a match video to generate one.</p>
|
||
@else
|
||
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); gap: 20px;">
|
||
@foreach($files as $f)
|
||
<div style="background: #1a1a1a; border: 1px solid #333; border-radius: 12px; overflow: hidden;">
|
||
<a href="{{ route('admin.og-previews.file', $f['name']) }}" target="_blank" style="display: block; background: #000;">
|
||
<img src="{{ route('admin.og-previews.file', $f['name']) }}"
|
||
alt="{{ $f['name'] }}"
|
||
style="display: block; width: 100%; height: auto;">
|
||
</a>
|
||
<div style="padding: 12px 14px; color: #ccc; font-size: 13px;">
|
||
<div style="font-weight: 600; color: #fff;">Video #{{ $f['id'] }}</div>
|
||
<div style="color: #888; margin-top: 4px; font-family: monospace; font-size: 11px; word-break: break-all;">
|
||
{{ $f['name'] }}
|
||
</div>
|
||
<div style="color: #888; margin-top: 4px;">
|
||
{{ number_format($f['size'] / 1024, 1) }} KB ·
|
||
{{ \Carbon\Carbon::createFromTimestamp($f['mtime'])->diffForHumans() }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
@endforeach
|
||
</div>
|
||
@endif
|
||
</div>
|
||
@endsection
|