feat(home): Shorts shelf above the main feed
The home grid used to interleave shorts with regular videos ordered by
upload date, so a cluster of 60-second portrait clips could fill the
first row and push the non-shorts off the fold. Split them:
- The main feed grid now excludes shorts (notShorts() filter).
- A horizontally-scrolling Shorts shelf renders above the grid with
up to 12 latest shorts, each a real <a href> to /shorts/{id} so
tapping it deep-links the vertical feed to that short.
- "See all" link in the shelf header jumps to the full /shorts feed.
- Shelf is skipped entirely when there are no shorts to show.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
1 parent
9e613c05d2
commit
c3955c4a7c
2 files changed
+61
-2
No files matched your search
@@ -176,7 +176,12 @@ class VideoController extends Controller
|
||||
}
|
||||
|
||||
// ── All (home) — mixed chronological feed ─────────────────
|
||||
$videos = Video::public()->latest()->limit(60)->get();
|
||||
// Videos shown in the grid are the newest non-shorts. Shorts render
|
||||
// in their own horizontally-scrolling shelf injected after the first
|
||||
// row of cards (see videos/index.blade.php), so the home grid isn't
|
||||
// diluted by 60-second portrait clips.
|
||||
$videos = Video::public()->notShorts()->latest()->limit(60)->get();
|
||||
$homeShorts = Video::public()->where('is_shorts', true)->latest()->limit(12)->get();
|
||||
|
||||
$playlistQuery = Playlist::withCount('videos')
|
||||
->with(['user', 'videos' => fn($q) => $q->orderBy('playlist_videos.position')->limit(1)])
|
||||
@@ -193,7 +198,7 @@ class VideoController extends Controller
|
||||
->sortByDesc('date')
|
||||
->values();
|
||||
|
||||
return view('videos.index', compact('feedItems', 'filter', 'langChips') + [
|
||||
return view('videos.index', compact('feedItems', 'filter', 'langChips', 'homeShorts') + [
|
||||
'videos' => collect(), 'shorts' => collect(), 'matches' => collect(), 'playlists' => collect(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -237,6 +237,60 @@
|
||||
══════════════════════════════════════════════ --}}
|
||||
@elseif(!isset($query) && (!isset($filter) || $filter === 'all'))
|
||||
|
||||
@if(!empty($homeShorts) && $homeShorts->count())
|
||||
{{-- Horizontally-scrolling Shorts shelf. Sits above the main feed grid
|
||||
so the user sees fresh short-form content without the feed being
|
||||
drowned in 60-second portrait clips. --}}
|
||||
<section class="home-shorts-shelf" aria-labelledby="home-shorts-heading">
|
||||
<div class="home-shorts-shelf-head">
|
||||
<h2 id="home-shorts-heading"><i class="bi bi-lightning-charge-fill" aria-hidden="true"></i> Shorts</h2>
|
||||
<a href="{{ route('videos.shorts') }}" class="home-shorts-see-all">See all</a>
|
||||
</div>
|
||||
<div class="home-shorts-shelf-track">
|
||||
@foreach($homeShorts as $s)
|
||||
<a class="home-short-card" href="{{ route('videos.shorts.focused', $s) }}"
|
||||
aria-label="Open short: {{ $s->title }}">
|
||||
<img src="{{ $s->thumbnail ? route('media.thumbnail', $s->thumbnail) : 'https://picsum.photos/seed/'.$s->id.'/180/320' }}"
|
||||
alt="" width="150" height="266" loading="lazy" decoding="async">
|
||||
<div class="home-short-card-overlay">
|
||||
<div class="home-short-card-title">{{ Str::limit($s->title, 40) }}</div>
|
||||
<div class="home-short-card-views">{{ number_format($s->view_count) }} views</div>
|
||||
</div>
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</section>
|
||||
<style>
|
||||
.home-shorts-shelf { margin: 8px 0 24px; }
|
||||
.home-shorts-shelf-head { display:flex; justify-content:space-between; align-items:baseline; margin-bottom:12px; }
|
||||
.home-shorts-shelf-head h2 { font-size:18px; font-weight:600; margin:0; display:flex; align-items:center; gap:8px; }
|
||||
.home-shorts-shelf-head h2 i { color:#ef4444; }
|
||||
.home-shorts-see-all { color: var(--text-secondary); font-size:13px; text-decoration:none; }
|
||||
.home-shorts-see-all:hover { color: var(--text-primary); text-decoration: underline; }
|
||||
.home-shorts-shelf-track {
|
||||
display:flex; gap:12px; overflow-x:auto; scroll-snap-type: x proximity;
|
||||
scrollbar-width: none; -ms-overflow-style: none;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
.home-shorts-shelf-track::-webkit-scrollbar { display:none; }
|
||||
.home-short-card {
|
||||
flex: 0 0 150px; aspect-ratio: 9/16; border-radius: 10px;
|
||||
overflow: hidden; position: relative; scroll-snap-align: start;
|
||||
text-decoration: none; color: inherit; background: #1a1a1a;
|
||||
transition: transform .15s ease;
|
||||
}
|
||||
.home-short-card:hover { transform: translateY(-2px); }
|
||||
.home-short-card img { width:100%; height:100%; object-fit: cover; display:block; }
|
||||
.home-short-card-overlay {
|
||||
position:absolute; left:0; right:0; bottom:0; padding: 20px 8px 8px;
|
||||
background: linear-gradient(transparent, rgba(0,0,0,.75));
|
||||
color: #fff;
|
||||
}
|
||||
.home-short-card-title { font-size:12px; font-weight:600; line-height:1.3; overflow:hidden; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; }
|
||||
.home-short-card-views { font-size:11px; opacity:.8; margin-top:2px; }
|
||||
</style>
|
||||
@endif
|
||||
|
||||
@if(isset($feedItems) && $feedItems->count())
|
||||
<div class="yt-video-grid">
|
||||
@foreach($feedItems as $entry)
|
||||
|
||||
Reference in new issue
Block a user