- SportsMatchController syncs Video.title with SportsMatch.title on
store/update so the "Match title" field drives the video's display
title everywhere.
- headerData(): "championship" now prefers event_name (event title)
and adds a dedicated "stage" key sourced from SportsMatch.title
(shown between the yellow lines above the weight class in both the
in-player VS overlay and the vs-mini gallery card).
- Video cards render match titles with flags + corner colours
("FINALS – 🇧🇭 Blue vs 🇧🇭 Red (-61 kg)"), matching the video
page header, and clip long titles to one line.
- Video gallery grid uses repeat(N, minmax(0, 1fr)) + grid-auto-rows:1fr
so every card thumbnail renders at exactly the same size; hover-preview
videos use object-fit:cover to stop portrait sources looking smaller.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
537 lines
28 KiB
PHP
537 lines
28 KiB
PHP
{{-- ══════════════════════════════════════════════════════════════════════
|
||
VS MINI — the arena intro card rendered INSIDE a video-card thumbnail.
|
||
No countdown / no skip button; identical layout to the full-page VS
|
||
intro (fixed 1920×1080 stage, transform-scaled to fit the thumb).
|
||
|
||
Behavior:
|
||
- Sits at z-index 1 (above the still image, BELOW the hover video
|
||
which uses z-index 2 + .active { opacity: 1 }).
|
||
- On mouseleave from the thumb, animations restart so the intro
|
||
plays again the next time the user's mouse is off the card.
|
||
════════════════════════════════════════════════════════════════════════ --}}
|
||
@php
|
||
$vsm_hd = $video->sportsMatch?->headerData();
|
||
$vsm_red = $vsm_hd['red'] ?? null;
|
||
$vsm_blue = $vsm_hd['blue'] ?? null;
|
||
$vsm_show = $vsm_hd && (($vsm_red['name'] ?? null) || ($vsm_blue['name'] ?? null));
|
||
@endphp
|
||
|
||
@if ($vsm_show)
|
||
@php
|
||
$vsm_flag = fn(?string $c) => (($c = strtolower(trim((string) $c))) !== '' ? $c : 'xx');
|
||
$vsm_cname = fn (?string $c) => \App\Data\Countries::name($c);
|
||
$vsm_img = fn(?string $p) => $p ? route('media.thumbnail', $p) : null;
|
||
$vsm_str = fn ($x) => (is_string($x) && trim($x) !== '') ? trim($x) : '';
|
||
|
||
// Every placeholder from the design is always in the DOM; empty ones
|
||
// are hidden by `.vsm-nullable:empty` / `.vsm-nullable-hide` in the
|
||
// CSS below. Same treatment as the full-page VS.
|
||
$vsm_event = $vsm_str($vsm_hd['championship'] ?? '');
|
||
$vsm_stage = $vsm_str($vsm_hd['stage'] ?? '');
|
||
$vsm_weight = $vsm_str($vsm_hd['weight_category'] ?? '');
|
||
$vsm_matchNo = $vsm_str($vsm_hd['match_number'] ?? '');
|
||
$vsm_court = $vsm_str($vsm_hd['court'] ?? '');
|
||
$vsm_ref = $vsm_str($vsm_hd['referee']['name'] ?? '');
|
||
|
||
// Per-fighter chips (Record / Rank / Stats) — fields aren't in
|
||
// headerData() today, so fall back to raw participants JSON.
|
||
$vsm_parts = $video->sportsMatch?->participants ?? [];
|
||
$vsm_redRecord = $vsm_str($vsm_parts['p2_record'] ?? '');
|
||
$vsm_redRank = $vsm_str($vsm_parts['p2_rank'] ?? '');
|
||
$vsm_redStats = $vsm_str($vsm_parts['p2_stats'] ?? '');
|
||
$vsm_blueRecord = $vsm_str($vsm_parts['p1_record'] ?? '');
|
||
$vsm_blueRank = $vsm_str($vsm_parts['p1_rank'] ?? '');
|
||
$vsm_blueStats = $vsm_str($vsm_parts['p1_stats'] ?? '');
|
||
|
||
$vsm_redName = $vsm_str($vsm_red['name'] ?? '');
|
||
$vsm_blueName = $vsm_str($vsm_blue['name'] ?? '');
|
||
$vsm_redClub = $vsm_str($vsm_red['club'] ?? '');
|
||
$vsm_blueClub = $vsm_str($vsm_blue['club'] ?? '');
|
||
$vsm_redFlag = $vsm_str($vsm_red['flag'] ?? '');
|
||
$vsm_blueFlag = $vsm_str($vsm_blue['flag'] ?? '');
|
||
$vsm_redLogo = $vsm_img($vsm_red['club_logo'] ?? null);
|
||
$vsm_blueLogo = $vsm_img($vsm_blue['club_logo'] ?? null);
|
||
@endphp
|
||
|
||
<div class="vs-mini vs-mini-run" data-vs-mini>
|
||
<div class="vs-mini-stage">
|
||
{{-- RED panel --}}
|
||
<div class="vs-mini-panel vs-mini-panel-red">
|
||
<div class="vs-mini-photo"
|
||
@if($vsm_img($vsm_red['headshot'] ?? null)) style="background-image:url('{{ $vsm_img($vsm_red['headshot']) }}')"@endif></div>
|
||
<div class="vs-mini-scrim vs-mini-scrim-red"></div>
|
||
<div class="vs-mini-fade"></div>
|
||
</div>
|
||
{{-- BLUE panel --}}
|
||
<div class="vs-mini-panel vs-mini-panel-blue">
|
||
<div class="vs-mini-photo vs-mini-photo-rev"
|
||
@if($vsm_img($vsm_blue['headshot'] ?? null)) style="background-image:url('{{ $vsm_img($vsm_blue['headshot']) }}')"@endif></div>
|
||
<div class="vs-mini-scrim vs-mini-scrim-blue"></div>
|
||
<div class="vs-mini-fade"></div>
|
||
</div>
|
||
|
||
<div class="vs-mini-divider"></div>
|
||
|
||
{{-- RED identity (left) — every placeholder always in DOM ── --}}
|
||
<div class="vs-mini-info vs-mini-info-red">
|
||
<div class="vs-mini-tag vs-mini-tag-red">AKA · RED</div>
|
||
<div class="vs-mini-flag-row">
|
||
<span class="vs-mini-flag fi fi-{{ $vsm_flag($vsm_redFlag) }} {{ $vsm_redFlag === '' ? 'vsm-nullable-hide' : '' }}"></span>
|
||
<div class="vs-mini-country vs-mini-country-red vsm-nullable">{{ $vsm_redFlag !== '' ? strtoupper($vsm_cname($vsm_redFlag) ?? $vsm_redFlag) : '' }}</div>
|
||
</div>
|
||
<div class="vs-mini-name vsm-nullable">{{ $vsm_redName }}</div>
|
||
<div class="vs-mini-club-row">
|
||
<div class="vs-mini-logo {{ $vsm_redLogo ? '' : 'vsm-nullable-hide' }}"
|
||
@if($vsm_redLogo) style="background-image:url('{{ $vsm_redLogo }}')"@endif></div>
|
||
<div class="vs-mini-club vsm-nullable">{{ $vsm_redClub }}</div>
|
||
</div>
|
||
<div class="vs-mini-chips-row">
|
||
<div class="vs-mini-chip-fighter vsm-nullable">{{ $vsm_redRecord }}</div>
|
||
<div class="vs-mini-chip-fighter vs-mini-chip-fighter-gold vsm-nullable">{{ $vsm_redRank }}</div>
|
||
<div class="vs-mini-chip-fighter vsm-nullable">{{ $vsm_redStats }}</div>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- BLUE identity (right) ── --}}
|
||
<div class="vs-mini-info vs-mini-info-blue">
|
||
<div class="vs-mini-tag vs-mini-tag-blue">AO · BLUE</div>
|
||
<div class="vs-mini-flag-row vs-mini-flag-row-r">
|
||
<span class="vs-mini-flag fi fi-{{ $vsm_flag($vsm_blueFlag) }} {{ $vsm_blueFlag === '' ? 'vsm-nullable-hide' : '' }}"></span>
|
||
<div class="vs-mini-country vs-mini-country-blue vsm-nullable">{{ $vsm_blueFlag !== '' ? strtoupper($vsm_cname($vsm_blueFlag) ?? $vsm_blueFlag) : '' }}</div>
|
||
</div>
|
||
<div class="vs-mini-name vsm-nullable">{{ $vsm_blueName }}</div>
|
||
<div class="vs-mini-club-row vs-mini-club-row-r">
|
||
<div class="vs-mini-logo {{ $vsm_blueLogo ? '' : 'vsm-nullable-hide' }}"
|
||
@if($vsm_blueLogo) style="background-image:url('{{ $vsm_blueLogo }}')"@endif></div>
|
||
<div class="vs-mini-club vsm-nullable">{{ $vsm_blueClub }}</div>
|
||
</div>
|
||
<div class="vs-mini-chips-row">
|
||
<div class="vs-mini-chip-fighter vsm-nullable">{{ $vsm_blueRecord }}</div>
|
||
<div class="vs-mini-chip-fighter vs-mini-chip-fighter-gold vsm-nullable">{{ $vsm_blueRank }}</div>
|
||
<div class="vs-mini-chip-fighter vsm-nullable">{{ $vsm_blueStats }}</div>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- Top: event + stage + weight (all in DOM) --}}
|
||
<div class="vs-mini-top">
|
||
<div class="vs-mini-top-event vsm-nullable">{{ $vsm_event }}</div>
|
||
<div class="vs-mini-top-stage-row {{ $vsm_stage === '' ? 'vsm-nullable-hide' : '' }}">
|
||
<div class="vs-mini-top-line vs-mini-top-line-l"></div>
|
||
<div class="vs-mini-top-stage">{{ $vsm_stage }}</div>
|
||
<div class="vs-mini-top-line vs-mini-top-line-r"></div>
|
||
</div>
|
||
<div class="vs-mini-top-weight vsm-nullable">{{ $vsm_weight }}</div>
|
||
</div>
|
||
|
||
{{-- Center VS --}}
|
||
<div class="vs-mini-center">
|
||
<div class="vs-mini-word">VS
|
||
<div class="vs-mini-shine-wrap"><div class="vs-mini-shine"></div></div>
|
||
</div>
|
||
</div>
|
||
{{-- .vs-mini-flash removed — the intro's full-thumb white flash at
|
||
t=1.5s was firing on every mouseleave-triggered replay, which
|
||
read as strobing on the left edge of the card. --}}
|
||
|
||
{{-- Bottom chips — Match / Court / Referee always in DOM; each hides
|
||
via data-vsm-empty and the flanking diamonds collapse via :has(). --}}
|
||
<div class="vs-mini-bottom">
|
||
<div class="vs-mini-chip vsm-nullable-chip" data-vsm-empty="{{ $vsm_matchNo === '' ? '1' : '0' }}">
|
||
<span class="vs-mini-chip-lbl">Match</span><span class="vs-mini-chip-val vsm-nullable">{{ $vsm_matchNo }}</span>
|
||
</div>
|
||
<div class="vs-mini-diamond vs-mini-diamond-mc"></div>
|
||
<div class="vs-mini-chip vsm-nullable-chip" data-vsm-empty="{{ $vsm_court === '' ? '1' : '0' }}">
|
||
<span class="vs-mini-chip-lbl">Court</span><span class="vs-mini-chip-val vsm-nullable">{{ $vsm_court }}</span>
|
||
</div>
|
||
<div class="vs-mini-diamond vs-mini-diamond-cr"></div>
|
||
<div class="vs-mini-chip vsm-nullable-chip" data-vsm-empty="{{ $vsm_ref === '' ? '1' : '0' }}">
|
||
<span class="vs-mini-chip-lbl">Referee</span><span class="vs-mini-chip-val vsm-nullable">{{ $vsm_ref }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
@endif
|
||
|
||
@once
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Anton&family=Barlow+Condensed:wght@400;600;700;800&display=swap">
|
||
|
||
<style>
|
||
/* ══ VS MINI — arena intro inside a video-card thumbnail.
|
||
Same 1920×1080 canvas as the full-page overlay, transform-scaled
|
||
to fit the thumb. Every child in ABSOLUTE PIXELS on that canvas.
|
||
z-index 1: below the hover <video> (which we bump to 5 for match
|
||
cards), above the still <img>. ══ */
|
||
.yt-video-thumb .vs-mini {
|
||
position: absolute; inset: 0; z-index: 1;
|
||
background: #050507;
|
||
font-family: 'Barlow Condensed', sans-serif;
|
||
color: #e8e6e0;
|
||
overflow: hidden;
|
||
pointer-events: none;
|
||
--vs-mini-scale: 0.5;
|
||
--vs-mini-w: 1920px;
|
||
--vs-mini-h: 1080px;
|
||
}
|
||
|
||
/* When a match card is hovered, the VS mini EXPLODES outward — the
|
||
inverse of its implode entrance. Each part flies away in the direction
|
||
opposite to where it came from, clearing the stage for the video.
|
||
The whole overlay fades to fully hidden by the time the panels are
|
||
off-screen (~0.6s), so the video takes over cleanly. */
|
||
.yt-video-card:hover .yt-video-thumb .vs-mini {
|
||
animation: vsmFadeOut .55s .25s ease forwards;
|
||
}
|
||
.yt-video-card:hover .yt-video-thumb .vs-mini .vs-mini-panel-red { animation: vsmExplodeL .55s cubic-bezier(.55,0,.7,.2) forwards !important; }
|
||
.yt-video-card:hover .yt-video-thumb .vs-mini .vs-mini-panel-blue { animation: vsmExplodeR .55s cubic-bezier(.55,0,.7,.2) forwards !important; }
|
||
.yt-video-card:hover .yt-video-thumb .vs-mini .vs-mini-info-red { animation: vsmExplodeInfoL .5s cubic-bezier(.55,0,.7,.2) forwards !important; }
|
||
.yt-video-card:hover .yt-video-thumb .vs-mini .vs-mini-info-blue { animation: vsmExplodeInfoR .5s cubic-bezier(.55,0,.7,.2) forwards !important; }
|
||
.yt-video-card:hover .yt-video-thumb .vs-mini .vs-mini-top { animation: vsmExplodeTop .5s cubic-bezier(.55,0,.7,.2) forwards !important; }
|
||
.yt-video-card:hover .yt-video-thumb .vs-mini .vs-mini-bottom { animation: vsmExplodeBot .5s cubic-bezier(.55,0,.7,.2) forwards !important; }
|
||
.yt-video-card:hover .yt-video-thumb .vs-mini .vs-mini-center { animation: vsmExplodeVS .6s cubic-bezier(.34,1.56,.64,1) forwards !important; }
|
||
.yt-video-card:hover .yt-video-thumb .vs-mini .vs-mini-divider { animation: vsmDividerOut .35s ease forwards !important; }
|
||
|
||
/* Force the hover-preview video above the VS mini's stacking context.
|
||
The default rule sets video { z-index: 2 } but the VS mini itself
|
||
is z-index 1 + a stacking context, so we bump the video to be safe. */
|
||
.yt-video-thumb:has(.vs-mini) video { z-index: 5 !important; background: transparent !important; }
|
||
.vs-mini .vs-mini-stage {
|
||
position: absolute;
|
||
left: 50%; top: 50%;
|
||
width: var(--vs-mini-w); height: var(--vs-mini-h);
|
||
transform: translate(-50%, -50%) scale(var(--vs-mini-scale));
|
||
transform-origin: center center;
|
||
background: radial-gradient(120% 90% at 50% 40%, #16161f 0%, #0a0a0e 65%, #050507 100%);
|
||
overflow: hidden;
|
||
will-change: transform;
|
||
}
|
||
|
||
/* Animations only run while the .vs-mini-run class is set (removed +
|
||
re-added on mouseleave to force a replay from the intro). */
|
||
|
||
/* ── Panels ── */
|
||
.vs-mini .vs-mini-panel { position: absolute; overflow: hidden; opacity: 0; }
|
||
.vs-mini .vs-mini-panel-red {
|
||
inset: 0 auto 0 0; width: 56%;
|
||
background: oklch(0.28 0.09 25);
|
||
clip-path: polygon(0 0, 100% 0, 82% 100%, 0 100%);
|
||
}
|
||
.vs-mini .vs-mini-panel-blue {
|
||
inset: 0 0 0 auto; width: 56%;
|
||
background: oklch(0.28 0.09 255);
|
||
clip-path: polygon(18% 0, 100% 0, 100% 100%, 0 100%);
|
||
}
|
||
.vs-mini.vs-mini-run .vs-mini-panel-red { animation: vsmPanelL .9s cubic-bezier(.22,1,.36,1) both; }
|
||
.vs-mini.vs-mini-run .vs-mini-panel-blue { animation: vsmPanelR .9s cubic-bezier(.22,1,.36,1) both; }
|
||
|
||
.vs-mini .vs-mini-photo {
|
||
position: absolute; inset: 0;
|
||
background-size: cover; background-position: center;
|
||
animation: vsmDrift 18s ease-in-out infinite;
|
||
}
|
||
.vs-mini .vs-mini-photo-rev { animation-direction: reverse; }
|
||
.vs-mini .vs-mini-scrim-red { position: absolute; inset: 0; pointer-events: none;
|
||
background: linear-gradient(115deg, oklch(0.45 0.18 25 / 0.55) 0%, transparent 55%); }
|
||
.vs-mini .vs-mini-scrim-blue { position: absolute; inset: 0; pointer-events: none;
|
||
background: linear-gradient(245deg, oklch(0.45 0.15 255 / 0.55) 0%, transparent 55%); }
|
||
.vs-mini .vs-mini-fade {
|
||
position: absolute; inset: 0; pointer-events: none;
|
||
background:
|
||
linear-gradient(to top, rgba(5,5,7,.95) 0%, rgba(5,5,7,.72) 22%, rgba(5,5,7,.30) 42%, transparent 62%),
|
||
linear-gradient(to bottom, rgba(5,5,7,.82) 0%, rgba(5,5,7,.35) 18%, transparent 32%);
|
||
}
|
||
|
||
/* ── Divider ── */
|
||
.vs-mini .vs-mini-divider {
|
||
position: absolute; top: -6%; bottom: -6%; left: 50%;
|
||
width: 3px; margin-left: -1.5px;
|
||
transform: rotate(10.15deg);
|
||
background: linear-gradient(to bottom, transparent, oklch(0.85 0.16 85 / .9) 20%, oklch(0.85 0.16 85 / .9) 80%, transparent);
|
||
pointer-events: none; filter: blur(1px);
|
||
}
|
||
|
||
/* ── Fighter identity (fixed px on 1920×1080 canvas) ── */
|
||
.vs-mini .vs-mini-info {
|
||
position: absolute; z-index: 6; max-width: 44%;
|
||
display: flex; flex-direction: column; gap: 13px;
|
||
opacity: 0;
|
||
}
|
||
.vs-mini .vs-mini-info-red { left: 43.2px; bottom: 118.8px; align-items: flex-start; }
|
||
.vs-mini .vs-mini-info-blue { right: 43.2px; bottom: 118.8px; align-items: flex-end; text-align: right; }
|
||
.vs-mini.vs-mini-run .vs-mini-info-red { animation: vsmRiseUp .8s .7s cubic-bezier(.22,1,.36,1) both; }
|
||
.vs-mini.vs-mini-run .vs-mini-info-blue { animation: vsmRiseUp .8s .85s cubic-bezier(.22,1,.36,1) both; }
|
||
|
||
.vs-mini .vs-mini-tag {
|
||
font: 800 21.6px/1 'Barlow Condensed', sans-serif;
|
||
letter-spacing: .35em; color: #fff;
|
||
padding: 5.4px 15.1px 5.4px 18.9px;
|
||
}
|
||
.vs-mini .vs-mini-tag-red { background: oklch(0.55 0.20 25); }
|
||
.vs-mini .vs-mini-tag-blue { background: oklch(0.50 0.16 255); }
|
||
|
||
.vs-mini .vs-mini-flag-row { display: flex; align-items: center; gap: 15.1px; }
|
||
.vs-mini .vs-mini-flag-row-r { flex-direction: row-reverse; }
|
||
.vs-mini .vs-mini-flag {
|
||
width: 56.2px; height: auto; aspect-ratio: 4/3;
|
||
background-size: cover !important; background-position: center !important;
|
||
border: 1px solid rgba(255,255,255,.35);
|
||
box-shadow: 0 4px 18px rgba(0,0,0,.6);
|
||
display: inline-block; line-height: 0;
|
||
}
|
||
.vs-mini .vs-mini-country { font: 700 32.4px/1 'Barlow Condensed', sans-serif; letter-spacing: .28em; }
|
||
.vs-mini .vs-mini-country-red { color: oklch(0.85 0.05 25); }
|
||
.vs-mini .vs-mini-country-blue { color: oklch(0.85 0.05 255); }
|
||
|
||
.vs-mini .vs-mini-name {
|
||
font-family: 'Anton', 'Barlow Condensed', sans-serif;
|
||
font-size: 71.3px; line-height: .95;
|
||
text-transform: uppercase; color: #fff;
|
||
text-shadow: 0 6px 30px rgba(0,0,0,.8);
|
||
}
|
||
|
||
.vs-mini .vs-mini-club-row { display: flex; align-items: center; gap: 13px; margin-top: 4.3px; }
|
||
.vs-mini .vs-mini-club-row-r { flex-direction: row-reverse; }
|
||
.vs-mini .vs-mini-logo {
|
||
width: 69.1px; height: 69.1px;
|
||
border-radius: 50%;
|
||
background: rgba(255,255,255,.06);
|
||
background-size: cover; background-position: center;
|
||
border: 1px solid rgba(255,255,255,.2);
|
||
}
|
||
.vs-mini .vs-mini-club {
|
||
font: 600 30.2px/1.1 'Barlow Condensed', sans-serif;
|
||
letter-spacing: .12em; text-transform: uppercase;
|
||
color: rgba(232,230,224,.9);
|
||
}
|
||
|
||
/* Fighter chips row (Record / Rank / Stats) — matches full-page VS. */
|
||
.vs-mini .vs-mini-chips-row { display: flex; flex-wrap: wrap; gap: 9.7px; margin-top: 5.4px; }
|
||
.vs-mini .vs-mini-info-blue .vs-mini-chips-row { justify-content: flex-end; }
|
||
.vs-mini .vs-mini-chip-fighter {
|
||
font: 700 23.8px/1 'Barlow Condensed', sans-serif;
|
||
letter-spacing: .12em; text-transform: uppercase;
|
||
padding: 5.4px 14px;
|
||
background: rgba(10,10,14,.62);
|
||
border: 1px solid rgba(255,255,255,.25);
|
||
color: #fff;
|
||
}
|
||
.vs-mini .vs-mini-chip-fighter-gold {
|
||
border-color: oklch(0.85 0.16 85 / .6);
|
||
color: oklch(0.87 0.14 85);
|
||
}
|
||
|
||
/* ══ Placeholder hiding — parity with the full-page VS. ══ */
|
||
.vs-mini .vsm-nullable:empty { display: none; }
|
||
.vs-mini .vsm-nullable-hide { display: none; }
|
||
.vs-mini .vs-mini-chip.vsm-nullable-chip[data-vsm-empty="1"] { display: none; }
|
||
.vs-mini .vs-mini-bottom .vs-mini-diamond-mc:has(+ .vs-mini-chip[data-vsm-empty="1"]),
|
||
.vs-mini .vs-mini-bottom .vs-mini-chip[data-vsm-empty="1"] + .vs-mini-diamond-mc,
|
||
.vs-mini .vs-mini-bottom .vs-mini-chip[data-vsm-empty="1"] + .vs-mini-diamond-cr,
|
||
.vs-mini .vs-mini-bottom .vs-mini-diamond-cr:has(+ .vs-mini-chip[data-vsm-empty="1"]) { display: none; }
|
||
|
||
/* ── Top block ── */
|
||
.vs-mini .vs-mini-top {
|
||
position: absolute; top: 34.6px; left: 50%; transform: translateX(-50%);
|
||
display: flex; flex-direction: column; align-items: center; gap: 10.8px;
|
||
z-index: 8; width: 92%; pointer-events: none; opacity: 0;
|
||
}
|
||
.vs-mini.vs-mini-run .vs-mini-top { animation: vsmDropIn .8s .5s cubic-bezier(.22,1,.36,1) both; }
|
||
.vs-mini .vs-mini-top-event {
|
||
font: 700 30.2px/1.05 'Barlow Condensed', sans-serif;
|
||
letter-spacing: .42em; text-transform: uppercase;
|
||
color: rgba(232,230,224,.92); text-align: center;
|
||
text-shadow: 0 2px 14px rgba(0,0,0,.9);
|
||
}
|
||
.vs-mini .vs-mini-top-stage-row { display: flex; align-items: center; gap: 17.3px; }
|
||
.vs-mini .vs-mini-top-line { height: 2px; width: 64.8px; }
|
||
.vs-mini .vs-mini-top-line-l { background: linear-gradient(to left, oklch(0.85 0.16 85), transparent); }
|
||
.vs-mini .vs-mini-top-line-r { background: linear-gradient(to right, oklch(0.85 0.16 85), transparent); }
|
||
.vs-mini .vs-mini-top-stage {
|
||
font-family: 'Anton', 'Barlow Condensed', sans-serif;
|
||
font-size: 38.9px;
|
||
letter-spacing: .30em; padding-left: .3em;
|
||
color: oklch(0.85 0.16 85); text-transform: uppercase;
|
||
}
|
||
.vs-mini .vs-mini-top-weight {
|
||
font: 700 42px/1.1 'Anton', 'Barlow Condensed', sans-serif;
|
||
letter-spacing: .28em; padding-left: .28em; text-transform: uppercase;
|
||
color: #fff; text-shadow: 0 2px 14px rgba(0,0,0,.9);
|
||
}
|
||
|
||
/* ── Center VS ── */
|
||
.vs-mini .vs-mini-center {
|
||
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -52%);
|
||
z-index: 7; pointer-events: none;
|
||
display: flex; align-items: center; justify-content: center;
|
||
opacity: 0;
|
||
}
|
||
.vs-mini.vs-mini-run .vs-mini-center { animation: vsmSlam .7s 1.1s cubic-bezier(.22,1,.36,1) both; }
|
||
.vs-mini .vs-mini-word {
|
||
position: relative;
|
||
font-family: 'Anton', 'Barlow Condensed', sans-serif;
|
||
font-size: 183.6px; font-style: italic;
|
||
color: #fffdf5; line-height: 1;
|
||
-webkit-text-stroke: 2px oklch(0.85 0.16 85 / 0.6);
|
||
animation: vsmPulse 2.4s ease-in-out infinite;
|
||
overflow: visible;
|
||
}
|
||
.vs-mini .vs-mini-shine-wrap { position: absolute; inset: -10% -20%; overflow: hidden; pointer-events: none; }
|
||
.vs-mini .vs-mini-shine {
|
||
position: absolute; top: 0; bottom: 0; width: 34%;
|
||
background: linear-gradient(to right, transparent, rgba(255,255,255,.16), transparent);
|
||
animation: vsmShine 5s ease-in-out infinite;
|
||
}
|
||
|
||
/* .vs-mini-flash rules removed with the element itself. */
|
||
|
||
/* ── Bottom chips ── */
|
||
.vs-mini .vs-mini-bottom {
|
||
position: absolute; bottom: 32.4px; left: 50%; transform: translateX(-50%);
|
||
display: flex; gap: 17.3px; z-index: 8; align-items: center;
|
||
flex-wrap: wrap; justify-content: center; max-width: 94%; opacity: 0;
|
||
}
|
||
.vs-mini.vs-mini-run .vs-mini-bottom { animation: vsmRiseC .8s 1.3s cubic-bezier(.22,1,.36,1) both; }
|
||
.vs-mini .vs-mini-chip {
|
||
display: flex; align-items: baseline; gap: 8.6px;
|
||
background: rgba(10,10,14,.72);
|
||
border: 1px solid oklch(0.85 0.16 85 / .45);
|
||
padding: 10.8px 23.8px; backdrop-filter: blur(6px);
|
||
}
|
||
.vs-mini .vs-mini-chip-lbl {
|
||
font: 600 23.8px/1 'Barlow Condensed', sans-serif;
|
||
letter-spacing: .30em; text-transform: uppercase;
|
||
color: rgba(232,230,224,.65);
|
||
}
|
||
.vs-mini .vs-mini-chip-val {
|
||
font-family: 'Anton', 'Barlow Condensed', sans-serif;
|
||
font-size: 34.6px; color: #fff;
|
||
}
|
||
.vs-mini .vs-mini-diamond {
|
||
width: 6px; height: 6px; transform: rotate(45deg);
|
||
background: oklch(0.85 0.16 85);
|
||
}
|
||
|
||
/* ── Portrait canvas (rare — vertical match cards) ── */
|
||
.vs-mini.vs-mini-portrait { --vs-mini-w: 1080px; --vs-mini-h: 1920px; }
|
||
.vs-mini.vs-mini-portrait .vs-mini-panel-red { inset: 0 0 auto 0; width: 100%; height: 56%;
|
||
clip-path: polygon(0 0, 100% 0, 100% 82%, 0 96%); }
|
||
.vs-mini.vs-mini-portrait .vs-mini-panel-blue { inset: auto 0 0 0; width: 100%; height: 56%;
|
||
clip-path: polygon(0 18%, 100% 4%, 100% 100%, 0 100%); }
|
||
.vs-mini.vs-mini-portrait .vs-mini-divider {
|
||
left: -4%; right: -4%; top: 50%; bottom: auto;
|
||
width: auto; height: 3px; margin-top: -1.5px; margin-left: 0;
|
||
transform: rotate(-4.48deg);
|
||
background: linear-gradient(to right, transparent, oklch(0.85 0.16 85 / .9) 20%, oklch(0.85 0.16 85 / .9) 80%, transparent);
|
||
}
|
||
.vs-mini.vs-mini-portrait .vs-mini-info-red { left: 43.2px; top: 129.6px; bottom: auto; }
|
||
.vs-mini.vs-mini-portrait .vs-mini-info-blue { right: 43.2px; bottom: 129.6px; top: auto; }
|
||
.vs-mini.vs-mini-portrait.vs-mini-run .vs-mini-panel-red { animation-name: vsmPanelT; }
|
||
.vs-mini.vs-mini-portrait.vs-mini-run .vs-mini-panel-blue { animation-name: vsmPanelB; }
|
||
|
||
/* Hide the shimmer skeleton on match cards — the VS mini fills the thumb. */
|
||
.yt-video-thumb:has(.vs-mini)::before { display: none; }
|
||
|
||
/* Animations */
|
||
@keyframes vsmPulse {
|
||
0%,100% { text-shadow: 0 0 30px rgba(255,215,120,.55), 0 0 90px rgba(255,170,60,.3); transform: scale(1); }
|
||
50% { text-shadow: 0 0 65px rgba(255,220,130,1), 0 0 160px rgba(255,170,60,.7); transform: scale(1.045); }
|
||
}
|
||
@keyframes vsmShine { 0% { transform: translateX(-130%) skewX(-18deg); } 60%,100% { transform: translateX(230%) skewX(-18deg); } }
|
||
@keyframes vsmDrift { 0% { transform: translate3d(0,0,0) scale(1.02); } 50% { transform: translate3d(0,-1.2%,0) scale(1.05); } 100% { transform: translate3d(0,0,0) scale(1.02); } }
|
||
@keyframes vsmPanelL { from { opacity: 1; transform: translateX(-105%); } to { opacity: 1; transform: translateX(0); } }
|
||
@keyframes vsmPanelR { from { opacity: 1; transform: translateX( 105%); } to { opacity: 1; transform: translateX(0); } }
|
||
@keyframes vsmPanelT { from { opacity: 1; transform: translateY(-105%); } to { opacity: 1; transform: translateY(0); } }
|
||
@keyframes vsmPanelB { from { opacity: 1; transform: translateY( 105%); } to { opacity: 1; transform: translateY(0); } }
|
||
@keyframes vsmRiseUp { from { opacity: 0; transform: translateY(43.2px); } to { opacity: 1; transform: translateY(0); } }
|
||
@keyframes vsmRiseC { from { opacity: 0; transform: translate(-50%, 43.2px); } to { opacity: 1; transform: translate(-50%, 0); } }
|
||
@keyframes vsmDropIn { from { opacity: 0; transform: translate(-50%, -32.4px); } to { opacity: 1; transform: translate(-50%, 0); } }
|
||
@keyframes vsmSlam {
|
||
0% { opacity: 0; transform: translate(-50%,-52%) scale(3.4) rotate(-6deg); }
|
||
60% { opacity: 1; transform: translate(-50%,-52%) scale(.92) rotate(1deg); }
|
||
80% { transform: translate(-50%,-52%) scale(1.06); }
|
||
100% { opacity: 1; transform: translate(-50%,-52%) scale(1) rotate(0deg); }
|
||
}
|
||
|
||
/* ══ EXPLODE — the inverse of the implode entrance. Each piece flies
|
||
OUT in the direction opposite to where it came from, then the
|
||
whole mini fades to zero opacity while the video takes the stage. ══ */
|
||
@keyframes vsmFadeOut { to { opacity: 0; } }
|
||
@keyframes vsmExplodeL { from { opacity: 1; transform: translateX(0) scale(1); } to { opacity: 0; transform: translateX(-140%) scale(1.05); } }
|
||
@keyframes vsmExplodeR { from { opacity: 1; transform: translateX(0) scale(1); } to { opacity: 0; transform: translateX( 140%) scale(1.05); } }
|
||
@keyframes vsmExplodeInfoL { from { opacity: 1; transform: translate(0,0) scale(1); } to { opacity: 0; transform: translate(-70%, 40%) scale(.85); } }
|
||
@keyframes vsmExplodeInfoR { from { opacity: 1; transform: translate(0,0) scale(1); } to { opacity: 0; transform: translate( 70%, 40%) scale(.85); } }
|
||
@keyframes vsmExplodeTop { from { opacity: 1; transform: translate(-50%,0) scale(1); } to { opacity: 0; transform: translate(-50%,-120%) scale(.9); } }
|
||
@keyframes vsmExplodeBot { from { opacity: 1; transform: translate(-50%,0) scale(1); } to { opacity: 0; transform: translate(-50%, 120%) scale(.9); } }
|
||
@keyframes vsmExplodeVS {
|
||
0% { opacity: 1; transform: translate(-50%,-52%) scale(1) rotate(0deg); filter: blur(0); }
|
||
40% { opacity: 1; transform: translate(-50%,-52%) scale(1.3) rotate(-2deg); filter: blur(1px); }
|
||
100% { opacity: 0; transform: translate(-50%,-52%) scale(4) rotate(6deg); filter: blur(8px); }
|
||
}
|
||
@keyframes vsmDividerOut { to { opacity: 0; transform: rotate(10.15deg) scaleY(0); } }
|
||
</style>
|
||
|
||
<script>
|
||
(function () {
|
||
if (window._vsMiniBound) return;
|
||
window._vsMiniBound = true;
|
||
|
||
// Fit the 1920×1080 (or 1080×1920) stage to whatever thumb size the
|
||
// card resolves to. One ResizeObserver watches every current + future
|
||
// thumb — cheap because the callback is a couple of arithmetic ops.
|
||
function fit(el) {
|
||
const host = el.parentElement;
|
||
if (!host) return;
|
||
const w = host.clientWidth, h = host.clientHeight;
|
||
if (!w || !h) return;
|
||
const portrait = h > w * 1.05;
|
||
el.classList.toggle('vs-mini-portrait', portrait);
|
||
const sw = portrait ? 1080 : 1920;
|
||
const sh = portrait ? 1920 : 1080;
|
||
el.style.setProperty('--vs-mini-scale', Math.min(w / sw, h / sh));
|
||
}
|
||
|
||
const ro = ('ResizeObserver' in window) ? new ResizeObserver(entries => {
|
||
for (const e of entries) {
|
||
const el = e.target.querySelector(':scope > .vs-mini');
|
||
if (el) fit(el);
|
||
}
|
||
}) : null;
|
||
|
||
function register(el) {
|
||
fit(el);
|
||
if (ro) ro.observe(el.parentElement);
|
||
}
|
||
|
||
// Register everything already in the DOM.
|
||
document.querySelectorAll('.vs-mini').forEach(register);
|
||
|
||
// Register anything added later (SPA nav, infinite-scroll loads).
|
||
new MutationObserver(muts => {
|
||
for (const m of muts) {
|
||
for (const n of m.addedNodes) {
|
||
if (!(n instanceof Element)) continue;
|
||
if (n.matches?.('.vs-mini')) register(n);
|
||
n.querySelectorAll?.('.vs-mini').forEach(register);
|
||
}
|
||
}
|
||
}).observe(document.body, { childList: true, subtree: true });
|
||
|
||
// Restart entrance animations whenever the mouse leaves a card thumb
|
||
// that has a VS mini in it. removing + re-adding the class in the
|
||
// next frame forces the CSS animation to play from the start.
|
||
document.addEventListener('mouseleave', (e) => {
|
||
const thumb = e.target?.classList?.contains('yt-video-thumb') ? e.target : null;
|
||
if (!thumb) return;
|
||
const vsm = thumb.querySelector(':scope > .vs-mini');
|
||
if (!vsm) return;
|
||
vsm.classList.remove('vs-mini-run');
|
||
// Reflow-then-reapply — CSS animations only restart when the
|
||
// running class is removed and reintroduced across a frame.
|
||
void vsm.offsetWidth;
|
||
vsm.classList.add('vs-mini-run');
|
||
}, true);
|
||
})();
|
||
</script>
|
||
@endonce
|