{"id":4221,"date":"2025-06-04T09:40:08","date_gmt":"2025-06-04T04:10:08","guid":{"rendered":"https:\/\/tbspanthers.british-school.org\/?page_id=4221"},"modified":"2025-10-06T10:34:05","modified_gmt":"2025-10-06T05:04:05","slug":"panthers-on-youtube","status":"publish","type":"page","link":"https:\/\/tbspanthers.british-school.org\/index.php\/panthers-on-youtube\/","title":{"rendered":"Panthers on YouTube"},"content":{"rendered":"\n<!-- Tailwind CSS -->\n<script src=\"https:\/\/cdn.tailwindcss.com\"><\/script>\n\n<!-- Even-gutter wide container (matches Player of the Month pattern) -->\n<style>\n  .yt-wrap{\n    --wide: 1500px; \/* change to 1600px if you want wider *\/\n    max-width: none !important;\n    width: min(var(--wide), 94vw) !important;\n    margin-left: calc(50% - (min(var(--wide), 94vw) \/ 2)) !important;\n    margin-right: calc(50% - (min(var(--wide), 94vw) \/ 2)) !important;\n    padding-left: 16px;  \/* same as px-4 *\/\n    padding-right: 16px; \/* same as px-4 *\/\n    padding-top: 24px;   \/* same as py-6 *\/\n    padding-bottom: 24px;\n  }\n<\/style>\n\n<div class=\"yt-wrap font-sans\">\n  <!-- Banner -->\n  <div class=\"bg-white p-5 rounded-xl flex flex-wrap items-center justify-between shadow-md mb-6\">\n    <div class=\"flex items-center gap-4\">\n      <img decoding=\"async\" src=\"https:\/\/spcdn.shortpixel.ai\/spio\/ret_img,q_cdnize,to_auto,s_webp:avif\/tbspanthers.british-school.org\/wp-content\/uploads\/2025\/04\/Panther-With-Moto-Logo.png\" class=\"w-14 h-14 rounded-full\" alt=\"TBS Logo\" \/>\n      <div>\n        <h2 class=\"text-xl sm:text-2xl font-bold text-gray-900\">TBS Panthers YouTube Channel<\/h2>\n        <p class=\"text-sm text-gray-600\">Watch, learn, and celebrate student sports action!<\/p>\n      <\/div>\n    <\/div>\n    <div class=\"flex gap-2 mt-3 sm:mt-0\">\n      <a href=\"https:\/\/www.youtube.com\/@TBSPanthers?sub_confirmation=1\" target=\"_blank\" rel=\"noopener\" class=\"inline-flex items-center px-5 py-2 bg-red-600 text-white font-semibold rounded-full hover:bg-blue-700 transition\">\n        <svg class=\"w-5 h-5 mr-2 fill-current\" viewBox=\"0 0 24 24\"><path d=\"M10 15l5.19-3L10 9v6zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z\"\/><\/svg>\n        Subscribe\n      <\/a>\n      <a href=\"https:\/\/www.youtube.com\/@TBSPanthers?sub_confirmation=1\" target=\"_blank\" rel=\"noopener\" class=\"inline-flex items-center px-3 py-2 bg-gray-100 text-gray-600 rounded-full hover:text-gray-700 transition\" title=\"Subscribe Notifications\">\n        <svg class=\"w-5 h-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\"><path d=\"M12 22c1.1 0 2-.9 2-2h-4a2 2 0 002 2zm6-6v-5c0-3.1-1.6-5.7-4.5-6.3V4a1.5 1.5 0 10-3 0v.7C7.6 5.3 6 7.9 6 11v5l-1.7 1.7a1 1 0 00.7 1.7h14a1 1 0 00.7-1.7L18 16z\"\/><\/svg>\n      <\/a>\n    <\/div>\n  <\/div>\n\n  <!-- Video Gallery -->\n  <div id=\"youtubeGallery\" class=\"grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6\"><\/div>\n<\/div>\n\n<!-- Video Modal (unchanged) -->\n<div id=\"videoModal\" class=\"fixed inset-0 bg-black bg-opacity-90 z-50 hidden flex items-center justify-center\">\n  <div class=\"relative w-full max-w-[90vw] h-[90vh]\">\n    <button id=\"closeModal\" class=\"absolute top-3 left-3 z-50 text-white hover:text-gray-300\">\n      <svg class=\"w-6 h-6\" fill=\"currentColor\" viewBox=\"0 0 20 20\"><path fill-rule=\"evenodd\" d=\"M10 8.586l4.95-4.95 1.414 1.414L11.414 10l4.95 4.95-1.414 1.414L10 11.414l-4.95 4.95-1.414-1.414L8.586 10 3.636 5.05l1.414-1.414L10 8.586z\" clip-rule=\"evenodd\"\/><\/svg>\n    <\/button>\n    <iframe id=\"modalIframe\" class=\"w-full h-full rounded-lg\" src=\"\" frameborder=\"0\" allowfullscreen allow=\"autoplay\"><\/iframe>\n  <\/div>\n<\/div>\n\n\n<script>\n  const apiKey = 'AIzaSyAej25a3RGw0VlDs2r8nGqENps3NIRpee8'; \/\/ \u2705 Your API Key\n  const channelHandle = '@TBSPanthers';\n  const channelIconUrl = \"https:\/\/tbspanthers.british-school.org\/wp-content\/uploads\/2025\/04\/Panther-With-Moto-Logo.png\";\nconst DEFAULT_BADGE = 'Sports';\n\n  function openVideoModal(videoId) {\n    const embedURL = `https:\/\/www.youtube.com\/embed\/${videoId}?autoplay=1&mute=1&loop=1&rel=0&playlist=${videoId}`;\n    document.getElementById(\"modalIframe\").src = embedURL;\n    document.getElementById(\"videoModal\").classList.remove(\"hidden\");\n    document.body.style.overflow = 'hidden';\n  }\n  document.getElementById(\"closeModal\").addEventListener(\"click\", () => {\n    document.getElementById(\"videoModal\").classList.add(\"hidden\");\n    document.getElementById(\"modalIframe\").src = \"\";\n    document.body.style.overflow = '';\n  });\n\n  \/* ---------- Your existing helpers (kept) ---------- *\/\n  async function getChannelId(handle) {\n    const res = await fetch(`https:\/\/www.googleapis.com\/youtube\/v3\/search?part=snippet&q=${handle}&type=channel&key=${apiKey}`);\n    const data = await res.json();\n    \/\/ search.list returns snippet.channelId and id.channelId; either works for type=channel\n    return (data.items[0].id && data.items[0].id.channelId) || data.items[0].snippet.channelId;\n  }\n\n  async function getPlaylists(channelId) {\n    const res = await fetch(`https:\/\/www.googleapis.com\/youtube\/v3\/playlists?part=snippet&channelId=${channelId}&maxResults=50&key=${apiKey}`);\n    const data = await res.json();\n    return data.items || [];\n  }\n\n  async function getVideosForPlaylist(playlist, labelOverride) {\n    const res = await fetch(`https:\/\/www.googleapis.com\/youtube\/v3\/playlistItems?part=snippet&playlistId=${playlist.id || playlist}&maxResults=50&key=${apiKey}`);\n    const data = await res.json();\n    return (data.items || []).map(item => ({\n      id: item.snippet.resourceId.videoId,\n      title: item.snippet.title,\n      thumbnail: (item.snippet.thumbnails.high || item.snippet.thumbnails.medium || {}).url,\n      playlistName: labelOverride || playlist.snippet.title,\n      publishedAt: item.snippet.publishedAt\n    })).filter(v => v.id);\n  }\n\n  \/* ---------- NEW: fetch the channel's Uploads playlist too ---------- *\/\n  async function getUploadsPlaylistId(channelId){\n    const res = await fetch(`https:\/\/www.googleapis.com\/youtube\/v3\/channels?part=contentDetails&id=${channelId}&key=${apiKey}`);\n    const data = await res.json();\n    return data.items?.[0]?.contentDetails?.relatedPlaylists?.uploads || null;\n  }\n\n  function renderVideos(videos) {\n  const gallery = document.getElementById(\"youtubeGallery\");\n  gallery.innerHTML = '';\n  videos.forEach(video => {\n    const label = video.playlistName || DEFAULT_BADGE;\n    gallery.innerHTML += `\n      <div class=\"bg-white rounded-xl shadow-md hover:shadow-xl transition relative group overflow-hidden\">\n        <div onclick=\"openVideoModal('${video.id}')\" class=\"block relative cursor-pointer\">\n          <!-- \ud83d\udd34 Playlist \/ Default badge in RED -->\n          <span class=\"absolute top-2 left-2 bg-red-600 text-white text-xs font-semibold px-2 py-1 rounded-full shadow-sm z-10\">${label}<\/span>\n          \n          <img decoding=\"async\" src=\"${video.thumbnail}\" alt=\"${video.title}\" class=\"w-full h-48 object-cover\">\n          <div class=\"absolute inset-0 flex items-center justify-center group-hover:scale-105 transition-transform\">\n            <div class=\"w-12 h-8 bg-[#eb333f] rounded-lg flex items-center justify-center shadow-lg\">\n              <svg class=\"w-4 h-4 text-white ml-[2px]\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M8 5v14l11-7z\" \/><\/svg>\n            <\/div>\n          <\/div>\n        <\/div>\n        <div class=\"p-4\">\n          <div class=\"flex items-start gap-3\">\n            <img decoding=\"async\" src=\"${channelIconUrl}\" class=\"w-12 h-12 rounded-full object-cover mt-1\" \/>\n            <div>\n              <h3 class=\"text-md font-semibold text-gray-800 line-clamp-2\">${video.title}<\/h3>\n              <!-- Keep #TBSPanthers badge dark blue if you want -->\n              <span class=\"inline-block bg-[#123458] text-white text-xs font-semibold px-3 py-1 rounded-full shadow-md hover:bg-[#0e2e44] transition\">\n                #TBSPanthers\n              <\/span>\n            <\/div>\n          <\/div>\n        <\/div>\n      <\/div>`;\n  });\n}\n\n\n  async function init() {\n    const channelId = await getChannelId(channelHandle);\n\n    \/\/ 1) Get the Uploads playlist and its videos (covers uncategorized videos)\n    const uploadsId = await getUploadsPlaylistId(channelId);\nconst uploadsVideos = uploadsId ? await getVideosForPlaylist(uploadsId, DEFAULT_BADGE) : [];\n\n\n    \/\/ 2) Get custom playlists and their videos\n    const playlists = await getPlaylists(channelId);\n    const customPlaylists = uploadsId ? playlists.filter(p => p.id !== uploadsId) : playlists;\n\n    \/\/ Deduplicate by video ID. Keep custom playlist label if available, otherwise keep 'Uploads'\n    const videoMap = new Map();\n    uploadsVideos.forEach(v => videoMap.set(v.id, v));\n    for (const pl of customPlaylists) {\n      const vids = await getVideosForPlaylist(pl);\n      vids.forEach(v => {\n        if (!videoMap.has(v.id) || videoMap.get(v.id).playlistName === DEFAULT_BADGE) {\n  videoMap.set(v.id, v);\n}\n      });\n    }\n\n    \/\/ Sort newest first when publishedAt is present\n    const allVideos = Array.from(videoMap.values()).sort((a,b)=> {\n      const ta = Date.parse(a.publishedAt || 0);\n      const tb = Date.parse(b.publishedAt || 0);\n      return tb - ta;\n    });\n\n    renderVideos(allVideos);\n  }\n\n  init();\n<\/script>\n","protected":false},"excerpt":{"rendered":"<p>TBS Panthers YouTube Channel Watch, learn, and celebrate student sports action! Subscribe<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-4221","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/tbspanthers.british-school.org\/index.php\/wp-json\/wp\/v2\/pages\/4221"}],"collection":[{"href":"https:\/\/tbspanthers.british-school.org\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/tbspanthers.british-school.org\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/tbspanthers.british-school.org\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tbspanthers.british-school.org\/index.php\/wp-json\/wp\/v2\/comments?post=4221"}],"version-history":[{"count":38,"href":"https:\/\/tbspanthers.british-school.org\/index.php\/wp-json\/wp\/v2\/pages\/4221\/revisions"}],"predecessor-version":[{"id":6075,"href":"https:\/\/tbspanthers.british-school.org\/index.php\/wp-json\/wp\/v2\/pages\/4221\/revisions\/6075"}],"wp:attachment":[{"href":"https:\/\/tbspanthers.british-school.org\/index.php\/wp-json\/wp\/v2\/media?parent=4221"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}