feat(watch): weighted Up Next + real <a> cards

- VideoController@show now scores the newest 200 public ready videos
  against the one being watched: +4 same type, +3 same language, +2
  any tag overlap, +1 same channel. Sort by score desc then by newest.
  Replaces the plain "latest 20 excluding self" list that was often
  dominated by whatever happened to be uploaded last, with no regard
  to what the viewer actually chose.
- Sidebar cards are real <a href> elements again, so middle-click,
  right-click, cmd-click and screen readers all work. The SPA click
  interceptors in types/{music,generic}.blade.php still fire via
  data-rec-url for a soft-swap when the user left-clicks normally.
- loading="lazy" + width/height on the sidebar thumbnails so a long
  Up Next list doesn't blow the critical path.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
ghassanandClaude Opus 4.7 committed 2026-10-21 10:18:37 +03:00
1 parent d1fd76c278
commit 36061d1561
4 files changed
+77 -13

No files matched your search

+16 -8
View File
@@ -836,30 +836,38 @@
@if ($recommendedVideos && $recommendedVideos->count() > 0)
<div class="recommended-videos-list">
@foreach ($recommendedVideos as $recVideo)
<div class="sidebar-video-card"
onclick="window.location.href='{{ route('videos.show', $recVideo) }}'">
{{-- Real <a href> cards so right-click / open-in-new-tab /
middle-click and screen readers all work. The SPA
transition hooks in types/*.blade.php still intercept
these clicks via data-rec-url for a soft load. --}}
<a class="sidebar-video-card"
href="{{ route('videos.show', $recVideo) }}"
data-rec-url="{{ route('videos.show', $recVideo) }}">
<div class="sidebar-thumb" style="position: relative;">
@if ($recVideo->thumbnail)
<img src="{{ route('media.thumbnail', $recVideo->thumbnail) }}"
alt="{{ $recVideo->title }}">
alt="{{ $recVideo->title }}" width="168" height="94"
loading="lazy" decoding="async">
@else
<img src="https://picsum.photos/seed/{{ $recVideo->id }}/320/180"
alt="{{ $recVideo->title }}">
alt="{{ $recVideo->title }}" width="168" height="94"
loading="lazy" decoding="async">
@endif
@if ($recVideo->duration)
<span class="yt-video-duration">{{ gmdate('i:s', $recVideo->duration) }}</span>
@endif
@if ($recVideo->is_shorts)
<span class="yt-shorts-badge"
style="position: absolute; top: 8px; left: 8px; font-size: 10px; padding: 2px 6px;">
<i class="bi bi-collection-play-fill"></i> SHORTS
style="position: absolute; top: 8px; left: 8px; font-size: 10px; padding: 2px 6px;">
<i class="bi bi-collection-play-fill" aria-hidden="true"></i> SHORTS
</span>
@endif
</div>
<div class="sidebar-info">
<div class="sidebar-title">
<i class="bi {{ match ($recVideo->type) {'music' => 'bi-music-note','match' => 'bi-trophy',default => 'bi-film'} }}"
style="color: #ef4444; margin-right: 4px; font-size: 12px;"></i>
style="color: #ef4444; margin-right: 4px; font-size: 12px;"
aria-hidden="true"></i>
{{ Str::limit($recVideo->title, 60) }}
</div>
<div class="sidebar-meta">
@@ -868,7 +876,7 @@
{{ $recVideo->created_at->diffForHumans() }}</div>
</div>
</div>
</div>
</a>
@endforeach
</div>
@else
@@ -307,7 +307,10 @@
border-radius: 8px;
padding: 4px;
transition: background .15s;
color: inherit;
text-decoration: none;
}
.sidebar-video-card:hover { background: rgba(255,255,255,.05); }
.sidebar-video-card.current-video {
background: rgba(239,68,68,.12);
@@ -307,7 +307,12 @@
border-radius: 8px;
padding: 4px;
transition: background .15s;
/* Card is now an <a href> for real right-click / open-in-new-tab
/ middle-click support. Strip the default link styling. */
color: inherit;
text-decoration: none;
}
.sidebar-video-card:hover { background: rgba(255,255,255,.05); }
.sidebar-video-card.current-video {
background: rgba(239,68,68,.12);