8a39bc02fa
It now recalculates video sort_order from created_at newest-first, so videos with corrected/original publish dates appear first in the homepage and public catalogue. I also updated catalogue/admin/latest-video ordering to use publish date as a tie-breaker after stored sort order.
653 lines
29 KiB
PHP
653 lines
29 KiB
PHP
<?php
|
|
require_once __DIR__ . '/includes/db.php';
|
|
require_once __DIR__ . '/includes/layout.php';
|
|
|
|
// Determine which video to play
|
|
$slug = public_video_slug_from_request();
|
|
if (url_rewrite_enabled() && $slug !== '' && isset($_GET['v']) && ((public_route_segments()[0] ?? '') !== 'v')) {
|
|
$target = public_video_url($slug);
|
|
$redirect_page = max(1, (int)($_GET['page'] ?? 1));
|
|
if ($redirect_page > 1) $target = media_append_query($target, ['page' => (string)$redirect_page]);
|
|
header('Location: ' . $target, true, 302);
|
|
exit;
|
|
}
|
|
$video = $slug ? get_video_by_slug($slug) : null;
|
|
|
|
// Latest video as default if none specified
|
|
if (!$video) {
|
|
$db = get_db();
|
|
$latest = $db->query("SELECT slug FROM videos WHERE published=1 ORDER BY sort_order ASC, datetime(created_at) DESC, created_at DESC, id DESC LIMIT 1")->fetch();
|
|
if ($latest) $video = get_video_by_slug($latest['slug']);
|
|
}
|
|
|
|
$live_enabled = live_enabled();
|
|
$live_url = $live_enabled ? live_embed_url() : '';
|
|
$live_title = live_embed_title();
|
|
$requested_land = strtolower(trim(is_scalar($_GET['land'] ?? '') ? (string)$_GET['land'] : ''));
|
|
$home_live_first = $live_enabled && $requested_land === 'live';
|
|
|
|
if ($video) {
|
|
$video['view_count'] = $home_live_first
|
|
? (int)($video['view_count'] ?? 0)
|
|
: increment_video_view_count((int)$video['id']);
|
|
}
|
|
|
|
$show_video_views = setting_bool('public_show_video_views', true);
|
|
$show_total_views = setting_bool('public_show_total_views', true);
|
|
$show_page_stats = setting_bool('public_show_page_stats', true);
|
|
$total_public_views = total_video_views(true);
|
|
$public_stats = $show_page_stats ? public_page_stats() : [
|
|
'videos' => 0,
|
|
'sources' => 0,
|
|
'views' => $total_public_views,
|
|
];
|
|
|
|
$per_page = (int)setting('catalogue_per_page', '10');
|
|
$page = max(1, (int)($_GET['page'] ?? 1));
|
|
$paged = get_videos_paginated($page, $per_page);
|
|
|
|
$player_sources = [];
|
|
if ($video && !empty($video['sources'])) {
|
|
foreach ($video['sources'] as $source) {
|
|
$player_source = source_player_data($source, public_media_url());
|
|
if ($player_source) $player_sources[] = $player_source;
|
|
}
|
|
}
|
|
$primary_player_source = $player_sources[0] ?? null;
|
|
$native_player_sources = [];
|
|
foreach ($player_sources as $player_source) {
|
|
if (($player_source['kind'] ?? '') !== 'iframe') $native_player_sources[] = $player_source;
|
|
}
|
|
|
|
$site_title = setting('site_title', 'Ty Clifford');
|
|
if ($home_live_first) {
|
|
$share_title = $live_title . ' — ' . $site_title;
|
|
$share_description = setting('site_sub', 'tyclifford.com / media');
|
|
$share_url = public_absolute_url(media_append_query(public_home_url(), ['land' => 'live']));
|
|
$share_image = '';
|
|
} else {
|
|
$share_title = $video ? $video['title'] . ' — ' . $site_title : 'Media — ' . $site_title;
|
|
$share_description = $video && trim($video['description'] ?? '') !== ''
|
|
? trim($video['description'])
|
|
: setting('site_sub', 'tyclifford.com / media');
|
|
$share_url = $video ? public_absolute_url(public_video_url($video['slug'])) : public_absolute_url(public_home_url());
|
|
$share_image = ($video && !empty($video['thumbnail'])) ? public_absolute_url(public_media_url($video['thumbnail'])) : '';
|
|
}
|
|
$share_image_width = 0;
|
|
$share_image_height = 0;
|
|
if (!$home_live_first && $video && !empty($video['thumbnail']) && function_exists('getimagesize')) {
|
|
$share_image_file = MEDIA_DIR . ltrim($video['thumbnail'], '/');
|
|
if (is_file($share_image_file)) {
|
|
$share_image_size = @getimagesize($share_image_file);
|
|
if (is_array($share_image_size)) {
|
|
$share_image_width = (int)($share_image_size[0] ?? 0);
|
|
$share_image_height = (int)($share_image_size[1] ?? 0);
|
|
}
|
|
}
|
|
}
|
|
$share_meta = [
|
|
'description' => $share_description,
|
|
'url' => $share_url,
|
|
'type' => ($video && !$home_live_first) ? 'video.other' : 'website',
|
|
'image' => $share_image,
|
|
'image_alt' => $home_live_first ? $live_title : ($video ? $video['title'] : $site_title),
|
|
'image_width' => $share_image_width,
|
|
'image_height' => $share_image_height,
|
|
];
|
|
|
|
render_head($share_title, '
|
|
/* ── Catalogue grid ── */
|
|
.cat-section { display:flex; flex-direction:column; gap:1rem; }
|
|
.cat-header { display:flex; align-items:center; justify-content:space-between; gap:1rem; flex-wrap:wrap; }
|
|
.cat-search { display:flex; gap:.5rem; flex:1; min-width:240px; max-width:430px; }
|
|
.cat-search input { flex:1; min-width:0; }
|
|
@media(max-width:560px) { .cat-search { min-width:100%; max-width:none; } }
|
|
.cat-grid {
|
|
display:grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
|
gap:.85rem;
|
|
}
|
|
@media(min-width:600px) { .cat-grid { grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); } }
|
|
@media(min-width:1000px) { .cat-grid { grid-template-columns: repeat(5, 1fr); } }
|
|
|
|
.cat-thumb {
|
|
background:var(--surface-2);
|
|
border:1px solid var(--border);
|
|
border-radius:calc(var(--r)+2px);
|
|
overflow:hidden;
|
|
text-decoration:none;
|
|
color:var(--text);
|
|
display:flex; flex-direction:column;
|
|
transition:border-color .2s, transform .15s, box-shadow .2s;
|
|
position:relative;
|
|
cursor:pointer;
|
|
}
|
|
.cat-thumb:hover { border-color:var(--purple); transform:translateY(-2px); box-shadow:0 6px 24px rgba(176,96,255,.18); }
|
|
.cat-thumb.active { border-color:var(--green); box-shadow:0 0 0 1px var(--green), 0 4px 20px rgba(0,232,122,.2); }
|
|
.cat-thumb-img {
|
|
position:relative; width:100%; padding-top:56.25%; background:#000;
|
|
overflow:hidden;
|
|
}
|
|
.cat-thumb-img img {
|
|
position:absolute; inset:0; width:100%; height:100%; object-fit:cover;
|
|
transition:transform .3s;
|
|
}
|
|
.cat-thumb:hover .cat-thumb-img img { transform:scale(1.04); }
|
|
.cat-thumb-img .no-thumb {
|
|
position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
|
|
font-family:"Share Tech Mono",monospace; font-size:.6rem; color:var(--text-3); letter-spacing:.1em;
|
|
background:var(--surface);
|
|
}
|
|
.cat-thumb-dur {
|
|
position:absolute; bottom:4px; right:6px;
|
|
background:rgba(0,0,0,.75); backdrop-filter:blur(4px);
|
|
font-family:"Share Tech Mono",monospace; font-size:.55rem; letter-spacing:.05em;
|
|
color:#fff; padding:.15em .45em; border-radius:3px;
|
|
}
|
|
.play-overlay {
|
|
position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
|
|
background:rgba(0,0,0,.35); opacity:0; transition:opacity .2s;
|
|
}
|
|
.cat-thumb:hover .play-overlay { opacity:1; }
|
|
.play-overlay svg { filter:drop-shadow(0 2px 8px rgba(0,0,0,.5)); }
|
|
.cat-thumb-body { padding:.55rem .65rem; display:flex; flex-direction:column; gap:.2rem; flex:1; }
|
|
.cat-thumb-title { font-size:.78rem; font-weight:600; color:var(--text); line-height:1.35; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
|
|
.cat-thumb-meta { font-family:"Share Tech Mono",monospace; font-size:.58rem; color:var(--text-3); letter-spacing:.06em; margin-top:auto; padding-top:.35rem; }
|
|
.cat-active-indicator {
|
|
position:absolute; top:0; left:0; right:0; height:2px;
|
|
background:var(--green); opacity:0; transition:opacity .2s;
|
|
}
|
|
.cat-thumb.active .cat-active-indicator { opacity:1; }
|
|
|
|
/* ── Video.js custom skin ── */
|
|
.video-js {
|
|
width:100% !important;
|
|
height:100% !important;
|
|
position:absolute !important;
|
|
inset:0 !important;
|
|
}
|
|
.video-js .vjs-big-play-button {
|
|
background:rgba(176,96,255,.75) !important;
|
|
border:2px solid rgba(176,96,255,.9) !important;
|
|
border-radius:50% !important;
|
|
width:64px !important; height:64px !important;
|
|
top:50% !important; left:50% !important;
|
|
transform:translate(-50%,-50%) !important;
|
|
margin:0 !important;
|
|
line-height:64px !important;
|
|
transition:background .2s !important;
|
|
}
|
|
.video-js:hover .vjs-big-play-button { background:rgba(176,96,255,.95) !important; }
|
|
.video-js .vjs-control-bar {
|
|
background:linear-gradient(transparent,rgba(0,0,0,.85)) !important;
|
|
height:3.2em !important;
|
|
}
|
|
.video-js .vjs-play-progress { background:var(--green) !important; }
|
|
.video-js .vjs-volume-level { background:var(--green) !important; }
|
|
.video-js .vjs-slider { background:rgba(255,255,255,.15) !important; }
|
|
.video-js .vjs-load-progress { background:rgba(255,255,255,.1) !important; }
|
|
.video-js .vjs-current-time,
|
|
.video-js .vjs-duration,
|
|
.video-js .vjs-time-divider { color:rgba(255,255,255,.8) !important; font-size:.7em !important; }
|
|
.provider-embed-frame {
|
|
position:absolute; inset:0;
|
|
display:block; width:100%; height:100%;
|
|
border:0; background:#000;
|
|
}
|
|
.home-mode-toggle {
|
|
display:inline-flex; align-items:center; gap:.4rem;
|
|
align-self:flex-start;
|
|
padding:.25rem; border:1px solid var(--border-2); border-radius:var(--r);
|
|
background:rgba(255,255,255,.03);
|
|
}
|
|
.home-mode-toggle button {
|
|
border:0; border-radius:calc(var(--r) - 1px); cursor:pointer;
|
|
padding:.38rem .75rem;
|
|
font-family:"Share Tech Mono",monospace; font-size:.65rem; letter-spacing:.08em;
|
|
color:var(--text-3); background:transparent;
|
|
transition:color .2s, background .2s;
|
|
}
|
|
.home-mode-toggle button:hover { color:var(--text-2); }
|
|
.home-mode-toggle button.active { color:var(--green); background:rgba(0,232,122,.12); }
|
|
.home-mode-panel[hidden] { display:none; }
|
|
.live-footer { justify-content:space-between; }
|
|
.live-footer .stream-meta { max-width:60ch; line-height:1.5; letter-spacing:.06em; }
|
|
|
|
/* ── Lower grid ── */
|
|
.lower-grid { display:grid; grid-template-columns:1fr; gap:1rem; }
|
|
@media(min-width:640px) { .lower-grid { grid-template-columns:1fr 1fr; } }
|
|
@media(min-width:900px) { .lower-grid { grid-template-columns:2fr 1fr 1fr; } }
|
|
.card-about { background:var(--surface-2); }
|
|
.card-social { background:var(--surface-2); }
|
|
.stats-list { list-style:none; display:flex; flex-direction:column; gap:.65rem; }
|
|
.stats-list li {
|
|
display:flex; align-items:center; justify-content:space-between; gap:1rem;
|
|
border-bottom:1px solid var(--border-2); padding-bottom:.55rem;
|
|
}
|
|
.stats-list li:last-child { border-bottom:0; padding-bottom:0; }
|
|
.stats-label { font-family:"Share Tech Mono",monospace; font-size:.62rem; letter-spacing:.12em; text-transform:uppercase; color:var(--text-3); }
|
|
.stats-value { font-family:"Share Tech Mono",monospace; font-size:.88rem; color:var(--text); }
|
|
.social-list { list-style:none; display:flex; flex-direction:column; gap:.5rem; }
|
|
.social-list a {
|
|
display:flex; align-items:center; gap:.65rem;
|
|
padding:.6rem .8rem; border:1px solid var(--border-2); border-radius:var(--r);
|
|
font-family:"Share Tech Mono",monospace; font-size:.78rem; color:var(--text-2);
|
|
text-decoration:none; transition:border-color .2s, color .2s, background .2s;
|
|
}
|
|
.social-list a:hover { border-color:var(--green); color:var(--text); background:rgba(0,232,122,.04); }
|
|
.social-list a svg { flex-shrink:0; color:var(--text-3); transition:color .2s; }
|
|
.social-list a:hover svg { color:var(--green); }
|
|
|
|
/* empty state */
|
|
.empty-state {
|
|
padding:3rem 1rem; text-align:center; display:flex; flex-direction:column;
|
|
align-items:center; gap:1rem;
|
|
}
|
|
.empty-state svg { color:var(--text-3); opacity:.4; }
|
|
.empty-state p { font-family:"Share Tech Mono",monospace; font-size:.75rem; color:var(--text-3); letter-spacing:.1em; }
|
|
|
|
/* no-video placeholder */
|
|
.no-video { display:flex; flex-direction:column; align-items:center; justify-content:center; gap:.75rem; }
|
|
.no-video p { font-family:"Share Tech Mono",monospace; font-size:.7rem; color:var(--text-3); letter-spacing:.1em; }
|
|
|
|
/* pagination row */
|
|
.cat-footer { display:flex; align-items:center; justify-content:space-between; gap:1rem; flex-wrap:wrap; }
|
|
.cat-count { font-family:"Share Tech Mono",monospace; font-size:.65rem; color:var(--text-3); letter-spacing:.06em; }
|
|
|
|
/* ── Embed panel ── */
|
|
.embed-panel {
|
|
border-top:1px solid var(--border);
|
|
overflow:hidden;
|
|
max-height:0;
|
|
transition:max-height .35s cubic-bezier(.4,0,.2,1), padding .35s;
|
|
padding:0 1.1rem;
|
|
}
|
|
.embed-panel.open { max-height:320px; padding:.9rem 1.1rem; }
|
|
.embed-panel-inner { display:flex; flex-direction:column; gap:.75rem; }
|
|
.embed-label { font-family:"Share Tech Mono",monospace; font-size:.6rem; letter-spacing:.14em; text-transform:uppercase; color:var(--text-3); }
|
|
.embed-code-wrap { position:relative; }
|
|
.embed-code {
|
|
display:block; width:100%;
|
|
background:var(--surface-2); border:1px solid var(--border-2); border-radius:var(--r);
|
|
padding:.65rem .85rem; padding-right:5.5rem;
|
|
font-family:"Share Tech Mono",monospace; font-size:.68rem; color:var(--text-2);
|
|
white-space:nowrap; overflow-x:auto; line-height:1.6;
|
|
resize:none; cursor:text;
|
|
scrollbar-width:thin;
|
|
}
|
|
.embed-copy-btn {
|
|
position:absolute; right:.4rem; top:50%; transform:translateY(-50%);
|
|
padding:.32rem .7rem; font-size:.65rem; font-weight:600;
|
|
background:rgba(176,96,255,.15); color:var(--purple);
|
|
border:1px solid rgba(176,96,255,.35); border-radius:calc(var(--r) - 1px);
|
|
cursor:pointer; font-family:"Share Tech Mono",monospace; letter-spacing:.06em;
|
|
transition:background .2s, color .2s;
|
|
white-space:nowrap;
|
|
}
|
|
.embed-copy-btn:hover { background:rgba(176,96,255,.28); }
|
|
.embed-copy-btn.copied { background:rgba(0,232,122,.15); color:var(--green); border-color:rgba(0,232,122,.35); }
|
|
.embed-row { display:flex; gap:.6rem; align-items:center; flex-wrap:wrap; }
|
|
.embed-direct-link {
|
|
font-family:"Share Tech Mono",monospace; font-size:.65rem; color:var(--text-3);
|
|
text-decoration:none; border-bottom:1px solid rgba(255,255,255,.1);
|
|
transition:color .15s, border-color .15s;
|
|
}
|
|
.embed-direct-link:hover { color:var(--green); border-color:rgba(0,232,122,.4); }
|
|
.embed-preview-link {
|
|
font-family:"Share Tech Mono",monospace; font-size:.65rem; color:var(--purple);
|
|
text-decoration:none; border-bottom:1px solid rgba(176,96,255,.2);
|
|
transition:color .15s;
|
|
}
|
|
.embed-preview-link:hover { color:#c880ff; }
|
|
.embed-toggle-btn {
|
|
display:inline-flex; align-items:center; gap:.4rem;
|
|
font-family:"Share Tech Mono",monospace; font-size:.65rem; letter-spacing:.08em;
|
|
color:var(--text-3); background:transparent; border:1px solid var(--border-2);
|
|
border-radius:var(--r); padding:.35rem .7rem; cursor:pointer;
|
|
transition:color .2s, border-color .2s, background .2s;
|
|
}
|
|
.embed-toggle-btn:hover { color:var(--text-2); border-color:var(--purple); background:rgba(176,96,255,.07); }
|
|
.embed-toggle-btn.active { color:var(--purple); border-color:var(--purple); background:rgba(176,96,255,.1); }
|
|
', $share_meta);
|
|
?>
|
|
<main class="page" role="main">
|
|
|
|
<?php render_topbar('home'); ?>
|
|
|
|
<?php if ($live_enabled): ?>
|
|
<div class="home-mode-toggle" role="tablist" aria-label="Homepage media mode">
|
|
<button type="button" class="<?= $home_live_first ? '' : 'active' ?>" data-home-mode="videos" role="tab" aria-selected="<?= $home_live_first ? 'false' : 'true' ?>" aria-controls="home-video-panel">Videos</button>
|
|
<button type="button" class="<?= $home_live_first ? 'active' : '' ?>" data-home-mode="live" role="tab" aria-selected="<?= $home_live_first ? 'true' : 'false' ?>" aria-controls="home-live-panel">Live</button>
|
|
</div>
|
|
|
|
<section class="stream-card home-mode-panel" id="home-live-panel" aria-label="Live stream" <?= $home_live_first ? '' : 'hidden' ?>>
|
|
<div class="stream-ratio">
|
|
<iframe class="provider-embed-frame"
|
|
src="<?= h($live_url) ?>"
|
|
allowfullscreen
|
|
allow="autoplay; fullscreen"
|
|
title="<?= h($live_title) ?>"
|
|
referrerpolicy="no-referrer-when-downgrade"></iframe>
|
|
</div>
|
|
<div class="stream-footer live-footer">
|
|
<p class="eyebrow eyebrow-green"><?= h($live_title) ?></p>
|
|
<p class="stream-meta"><span>live embed</span></p>
|
|
</div>
|
|
</section>
|
|
<?php endif; ?>
|
|
|
|
<!-- ═══════════════════════════ PLAYER CARD ════════════════════════════════ -->
|
|
<section class="stream-card home-mode-panel" id="home-video-panel" aria-label="Media player" <?= $home_live_first ? 'hidden' : '' ?>>
|
|
<div class="stream-ratio">
|
|
<?php if ($primary_player_source && ($primary_player_source['kind'] ?? '') === 'iframe'): ?>
|
|
<iframe class="provider-embed-frame"
|
|
src="<?= h($primary_player_source['embed']) ?>"
|
|
title="<?= h($video['title']) ?>"
|
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; fullscreen; gyroscope; picture-in-picture; web-share"
|
|
allowfullscreen
|
|
referrerpolicy="no-referrer-when-downgrade"></iframe>
|
|
<?php elseif ($video && !empty($native_player_sources)): ?>
|
|
<video id="main-player"
|
|
class="video-js vjs-default-skin vjs-big-play-centered"
|
|
controls preload="metadata"
|
|
<?php if ($video['thumbnail']): ?>
|
|
poster="<?= h(public_media_url($video['thumbnail'])) ?>"
|
|
<?php endif; ?>
|
|
data-setup='{"fluid":false,"responsive":true,"playbackRates":[0.5,1,1.25,1.5,2]}'>
|
|
<?php foreach ($native_player_sources as $src): ?>
|
|
<source src="<?= h($src['url']) ?>" type="<?= h($src['mime']) ?>" label="<?= h($src['label']) ?>">
|
|
<?php endforeach; ?>
|
|
<p class="vjs-no-js">Your browser does not support HTML video.</p>
|
|
</video>
|
|
<?php else: ?>
|
|
<div class="no-video">
|
|
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" opacity=".25"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
|
|
<p>no video selected</p>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<div class="stream-footer">
|
|
<p class="eyebrow eyebrow-green"><?= $video ? h($video['title']) : 'Media Player' ?></p>
|
|
<?php if ($video && $video['description']): ?>
|
|
<p class="stream-meta" style="font-family:Inter,sans-serif;font-size:.78rem;color:var(--text-2);letter-spacing:0;max-width:60ch;line-height:1.5"><?= h($video['description']) ?></p>
|
|
<?php endif; ?>
|
|
<?php
|
|
$stream_meta = [];
|
|
if ($video && $video['duration']) $stream_meta[] = format_duration((int)$video['duration']);
|
|
if ($video && $show_video_views) $stream_meta[] = view_count_label(video_total_views($video));
|
|
?>
|
|
<?php if ($stream_meta): ?>
|
|
<p class="stream-meta"><span><?= h(implode(' · ', $stream_meta)) ?></span></p>
|
|
<?php endif; ?>
|
|
<?php if ($video): ?>
|
|
<button class="embed-toggle-btn" id="embed-toggle" aria-expanded="false" aria-controls="embed-panel">
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
|
|
Embed
|
|
</button>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<?php if ($video):
|
|
$embed_url = public_absolute_url('embed.php?v=' . rawurlencode($video['slug']));
|
|
$iframe_code = '<iframe src="' . $embed_url . '" width="560" height="315" frameborder="0" allowfullscreen allow="fullscreen"></iframe>';
|
|
?>
|
|
<div class="embed-panel" id="embed-panel" role="region" aria-label="Embed code">
|
|
<div class="embed-panel-inner">
|
|
<div class="form-group" style="gap:.35rem;">
|
|
<span class="embed-label">iframe embed code</span>
|
|
<div class="embed-code-wrap">
|
|
<code class="embed-code" id="embed-code-iframe"><?= h($iframe_code) ?></code>
|
|
<button class="embed-copy-btn" data-target="embed-code-iframe" onclick="copyEmbed(this)">Copy</button>
|
|
</div>
|
|
</div>
|
|
<div class="embed-row">
|
|
<span class="embed-label">Direct player URL —</span>
|
|
<a class="embed-direct-link" href="<?= h($embed_url) ?>" target="_blank" rel="noopener">
|
|
<?= h($embed_url) ?>
|
|
</a>
|
|
<a class="embed-preview-link" href="<?= h($embed_url) ?>" target="_blank" rel="noopener">
|
|
↗ Preview
|
|
</a>
|
|
</div>
|
|
<div>
|
|
<span class="embed-label" style="color:var(--text-3);font-size:.58rem;">
|
|
Recommended: width="560" height="315" — any 16:9 size works. Add style="border:0;border-radius:6px" for no border.
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
</section>
|
|
|
|
<!-- ═══════════════════════════ VIDEO CATALOGUE ═══════════════════════════ -->
|
|
<section class="cat-section">
|
|
<div class="cat-header">
|
|
<p class="eyebrow eyebrow-purple">Video Catalogue</p>
|
|
<?php if ($show_total_views): ?>
|
|
<span class="cat-count"><?= h(view_count_label($total_public_views)) ?> total</span>
|
|
<?php endif; ?>
|
|
<form class="cat-search" method="get" action="<?= h(public_catalogue_url()) ?>" role="search">
|
|
<input class="form-input" type="search" name="q" placeholder="Search public videos…">
|
|
<button class="btn btn-secondary btn-sm" type="submit">
|
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
|
Search
|
|
</button>
|
|
</form>
|
|
<a href="<?= h(public_catalogue_url()) ?>" class="btn btn-secondary btn-sm">Browse all →</a>
|
|
</div>
|
|
|
|
<?php if (empty($paged['videos'])): ?>
|
|
<div class="card">
|
|
<div class="empty-state">
|
|
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14,2 14,8 20,8"/></svg>
|
|
<p>no videos yet</p>
|
|
</div>
|
|
</div>
|
|
<?php else: ?>
|
|
<div class="cat-grid">
|
|
<?php foreach ($paged['videos'] as $v):
|
|
$active = $video && $video['id'] == $v['id'];
|
|
$vslug = $v['slug'];
|
|
$vtitle = $v['title'];
|
|
$vthumb = $v['thumbnail'];
|
|
$vdur = (int)$v['duration'];
|
|
$created = substr($v['created_at'], 0, 10);
|
|
$meta_parts = [$created];
|
|
if ($show_video_views) $meta_parts[] = view_count_label(video_total_views($v));
|
|
?>
|
|
<a class="cat-thumb <?= $active ? 'active' : '' ?>"
|
|
href="<?= h(public_video_url($vslug)) ?>"
|
|
title="<?= h($vtitle) ?>">
|
|
<div class="cat-active-indicator"></div>
|
|
<div class="cat-thumb-img">
|
|
<?php if ($vthumb): ?>
|
|
<img src="<?= h(public_media_url($vthumb)) ?>" alt="<?= h($vtitle) ?>" loading="lazy">
|
|
<?php else: ?>
|
|
<div class="no-thumb">NO THUMB</div>
|
|
<?php endif; ?>
|
|
<?php if ($vdur): ?>
|
|
<span class="cat-thumb-dur"><?= format_duration($vdur) ?></span>
|
|
<?php endif; ?>
|
|
<div class="play-overlay">
|
|
<svg width="28" height="28" viewBox="0 0 24 24" fill="white"><circle cx="12" cy="12" r="12" fill="rgba(0,0,0,.4)"/><polygon points="10,8 18,12 10,16" fill="white"/></svg>
|
|
</div>
|
|
</div>
|
|
<div class="cat-thumb-body">
|
|
<span class="cat-thumb-title"><?= h($vtitle) ?></span>
|
|
<span class="cat-thumb-meta"><?= h(implode(' · ', $meta_parts)) ?></span>
|
|
</div>
|
|
</a>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
|
|
<?php if ($paged['total_pages'] > 1): ?>
|
|
<div class="cat-footer">
|
|
<span class="cat-count">
|
|
<?= $paged['total'] ?> video<?= $paged['total'] != 1 ? 's' : '' ?>
|
|
· page <?= $paged['page'] ?> of <?= $paged['total_pages'] ?>
|
|
</span>
|
|
<?php
|
|
$player_page_base = ($slug !== '' && $video) ? public_video_url($video['slug']) : public_home_url();
|
|
if ($home_live_first) $player_page_base = media_append_query($player_page_base, ['land' => 'live']);
|
|
render_pagination($paged['page'], $paged['total_pages'], public_page_url_pattern($player_page_base));
|
|
?>
|
|
</div>
|
|
<?php endif; ?>
|
|
<?php endif; ?>
|
|
</section>
|
|
|
|
<!-- ═══════════════════════════ LOWER GRID ════════════════════════════════ -->
|
|
<div class="lower-grid">
|
|
|
|
<div class="card card-about">
|
|
<p class="eyebrow eyebrow-green" style="margin-bottom:.1rem">About</p>
|
|
<h2 class="card-title">Ty Clifford</h2>
|
|
<p class="card-body">
|
|
Based in <strong>Keyser, West Virginia</strong>. Nerd, tinkerer, and creator.
|
|
Hobbies are my hobby — from tech and podcasting to whatever rabbit hole I fell into this week.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="card card-social">
|
|
<p class="eyebrow eyebrow-green" style="margin-bottom:.1rem">Links</p>
|
|
<h2 class="card-title">Places</h2>
|
|
<ul class="social-list" aria-label="Social media links">
|
|
<li>
|
|
<a href="mailto:ty@tyclifford.com">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="4" width="20" height="16" rx="2"/><polyline points="2,4 12,13 22,4"/></svg>
|
|
ty@tyclifford.com
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="https://go.tyclifford.com/patreon" target="_blank" rel="noopener">
|
|
<svg width="14" height="14" viewBox="0 0 12 14" fill="currentColor"><ellipse cx="7.5" cy="4.8" rx="4.5" ry="4.5"/><rect x="0" y="0" width="2.8" height="14" rx="1.2"/></svg>
|
|
patreon.com/tyclifford
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<?php if ($show_page_stats): ?>
|
|
<div class="card card-social">
|
|
<p class="eyebrow eyebrow-green" style="margin-bottom:.1rem">Stats</p>
|
|
<h2 class="card-title">Public Library</h2>
|
|
<ul class="stats-list" aria-label="Public library statistics">
|
|
<li>
|
|
<span class="stats-label">Videos</span>
|
|
<strong class="stats-value"><?= h(format_count((int)$public_stats['videos'])) ?></strong>
|
|
</li>
|
|
<?php if ($show_total_views): ?>
|
|
<li>
|
|
<span class="stats-label">Total Views</span>
|
|
<strong class="stats-value"><?= h(format_count((int)$public_stats['views'])) ?></strong>
|
|
</li>
|
|
<?php endif; ?>
|
|
<li>
|
|
<span class="stats-label">Sources</span>
|
|
<strong class="stats-value"><?= h(format_count((int)$public_stats['sources'])) ?></strong>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
</div>
|
|
|
|
<?php render_footer(); ?>
|
|
</main>
|
|
|
|
<script>
|
|
// Init Video.js if player exists
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
var el = document.getElementById('main-player');
|
|
if (el) {
|
|
var player = videojs('main-player', {
|
|
controls: true,
|
|
autoplay: false,
|
|
preload: 'metadata',
|
|
fluid: false,
|
|
responsive: true,
|
|
playbackRates: [0.5, 1, 1.25, 1.5, 2],
|
|
controlBar: {
|
|
children: [
|
|
'playToggle','volumePanel','currentTimeDisplay',
|
|
'timeDivider','durationDisplay','progressControl',
|
|
'playbackRateMenuButton','fullscreenToggle'
|
|
]
|
|
}
|
|
});
|
|
}
|
|
|
|
// Embed panel toggle
|
|
var toggleBtn = document.getElementById('embed-toggle');
|
|
var panel = document.getElementById('embed-panel');
|
|
if (toggleBtn && panel) {
|
|
toggleBtn.addEventListener('click', function() {
|
|
var open = panel.classList.toggle('open');
|
|
toggleBtn.classList.toggle('active', open);
|
|
toggleBtn.setAttribute('aria-expanded', open ? 'true' : 'false');
|
|
});
|
|
}
|
|
|
|
var modeButtons = document.querySelectorAll('[data-home-mode]');
|
|
var livePanel = document.getElementById('home-live-panel');
|
|
var videoPanel = document.getElementById('home-video-panel');
|
|
|
|
function setHomeMode(mode, updateUrl) {
|
|
modeButtons.forEach(function(other) {
|
|
var active = other.getAttribute('data-home-mode') === mode;
|
|
other.classList.toggle('active', active);
|
|
other.setAttribute('aria-selected', active ? 'true' : 'false');
|
|
});
|
|
if (livePanel) livePanel.hidden = mode !== 'live';
|
|
if (videoPanel) videoPanel.hidden = mode === 'live';
|
|
if (updateUrl && window.history && typeof window.URL !== 'undefined') {
|
|
var url = new URL(window.location.href);
|
|
if (mode === 'live') url.searchParams.set('land', 'live');
|
|
else url.searchParams.delete('land');
|
|
window.history.replaceState({}, '', url.pathname + url.search + url.hash);
|
|
}
|
|
}
|
|
|
|
modeButtons.forEach(function(btn) {
|
|
btn.addEventListener('click', function() {
|
|
setHomeMode(btn.getAttribute('data-home-mode'), true);
|
|
});
|
|
});
|
|
});
|
|
|
|
// Copy embed code to clipboard
|
|
function copyEmbed(btn) {
|
|
var targetId = btn.getAttribute('data-target');
|
|
var el = document.getElementById(targetId);
|
|
if (!el) return;
|
|
var text = el.textContent || el.innerText;
|
|
navigator.clipboard.writeText(text).then(function() {
|
|
btn.textContent = 'Copied!';
|
|
btn.classList.add('copied');
|
|
setTimeout(function() {
|
|
btn.textContent = 'Copy';
|
|
btn.classList.remove('copied');
|
|
}, 2000);
|
|
}).catch(function() {
|
|
// Fallback for older browsers
|
|
var ta = document.createElement('textarea');
|
|
ta.value = text;
|
|
ta.style.position = 'fixed';
|
|
ta.style.opacity = '0';
|
|
document.body.appendChild(ta);
|
|
ta.select();
|
|
document.execCommand('copy');
|
|
document.body.removeChild(ta);
|
|
btn.textContent = 'Copied!';
|
|
btn.classList.add('copied');
|
|
setTimeout(function() {
|
|
btn.textContent = 'Copy';
|
|
btn.classList.remove('copied');
|
|
}, 2000);
|
|
});
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|