Skip to content

Instantly share code, notes, and snippets.

@erikusaj
Created August 22, 2025 12:57
Show Gist options
  • Select an option

  • Save erikusaj/b162ded2c3de826c3f3db2c1b3e08ec0 to your computer and use it in GitHub Desktop.

Select an option

Save erikusaj/b162ded2c3de826c3f3db2c1b3e08ec0 to your computer and use it in GitHub Desktop.
simple tv grid
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>4-Column TV Navigation with Wrapping and Dates</title>
<!-- Tailwind CSS CDN -->
<script src="https://cdn.tailwindcss.com"></script>
<style>
/* Custom styles for the navigation columns and items */
.column {
flex-shrink: 0;
width: 300px;
max-height: 90vh;
position: relative;
overflow: hidden; /* Hide overflow to create the wrapping effect */
}
.column ul {
list-style-type: none;
padding: 0;
margin: 0;
transition: transform 0.3s ease-in-out; /* Smooth transition for shifting */
}
.column li {
padding: 1rem;
margin-bottom: 0.5rem;
border-radius: 0.5rem;
transition: all 0.2s ease-in-out;
cursor: pointer;
text-align: left;
background-color: #374151; /* Gray 700 */
min-height: 50px; /* Ensure a consistent item height */
display: flex;
align-items: center;
}
/* Styles for the focused item in any column */
.column li.focused {
border: 4px solid #6366f1; /* Indigo 500 */
background-color: #4f46e5; /* Indigo 600 */
color: white;
transform: scale(1.05);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
/* Styles for the central column to make the focused item stand out */
.central-column li {
background-color: #1f2937; /* Gray 800 */
min-height: 80px;
margin-bottom: 0.75rem;
}
.central-column li.focused {
min-height: 200px;
width: 350px;
justify-content: space-between;
background-color: #f3f4f6; /* Gray 100 */
color: #1f2937; /* Gray 900 */
padding: 1.5rem;
}
.central-column .movie-details {
display: none;
}
.central-column li.focused .movie-details {
display: block;
}
.central-column .movie-title {
font-size: 1.5rem;
font-weight: bold;
}
.central-column .movie-info {
font-size: 0.875rem;
color: #4b5563; /* Gray 600 */
}
/* Hide notification by default */
.notification-popup.hidden {
display: none;
}
</style>
</head>
<body class="bg-gray-900 text-gray-100 flex items-center justify-center min-h-screen p-4 font-sans">
<div class="container mx-auto flex items-start justify-center space-x-8 h-[90vh]">
<!-- Left Column (Index 0) -->
<span id="column-0" class="column">
<ul>
<li data-column="0" data-index="0">pridruženi</li>
<li data-column="0" data-index="1">izbrani</li>
<li data-column="0" data-index="2">dokumentarni</li>
<li data-column="0" data-index="3">filmski</li>
<li data-column="0" data-index="4">otroški</li>
<li data-column="0" data-index="5">glasbeni</li>
<li data-column="0" data-index="6">lokalni</li>
<li data-column="0" data-index="7">informativni</li>
<li data-column="0" data-index="8">balkan</li>
<li data-column="0" data-index="9">odrasli</li>
</ul>
</span>
<!-- Center Column (Index 1) -->
<span id="column-1" class="column central-column w-[400px]">
<ul id="central-list">
<li data-column="1" data-index="0">
<img src="https://placehold.co/100x150/374151/E5E7EB?text=234" class="rounded-lg w-auto h-auto">
<span>DKino - New</span>
</li>
<li data-column="1" data-index="1">
<img src="https://placehold.co/100x150/374151/E5E7EB?text=234" class="rounded-lg w-auto h-auto">
<div class="movie-details">
<div class="movie-title">Superman 2</div>
<div class="movie-info">Drama • IMDB 7,3</div>
</div>
</li>
<li data-column="1" data-index="2">
<img src="https://placehold.co/100x150/374151/E5E7EB?text=234" class="rounded-lg w-auto h-auto">
<span>Ljubica</span>
</li>
<li data-column="1" data-index="3">
<img src="https://placehold.co/100x150/374151/E5E7EB?text=234" class="rounded-lg w-auto h-auto">
<span>Ameriške prevare</span>
</li>
<li data-column="1" data-index="4">
<img src="https://placehold.co/100x150/374151/E5E7EB?text=234" class="rounded-lg w-auto h-auto">
<span>Julie & Julia</span>
</li>
</ul>
</span>
<!-- New Date Column (Index 2) -->
<span id="column-2" class="column">
<ul>
<!-- Dates will be populated here by JavaScript -->
</ul>
</span>
<!-- Right Column (Index 3) -->
<span id="column-3" class="column">
<ul>
<li data-column="3" data-index="0">7:10 Cernobil</li>
<li data-column="3" data-index="1">8:00 Cernobil</li>
<li data-column="3" data-index="2">9:00 Drops of God</li>
<li data-column="3" data-index="3">10:00 72 Hours: True Crime</li>
<li data-column="3" data-index="4">11:00 72 Hours: True Crime</li>
<li data-column="3" data-index="5">12:10 Zgodbe iz skrajlike</li>
<li data-column="3" data-index="6">13:00 Traktor, ljubezen in rock'n'roll</li>
<li data-column="3" data-index="7">14:00 Drops of God</li>
</ul>
</span>
</div>
<!-- Notification Popup -->
<div id="notification-popup" class="notification-popup hidden fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50">
<div class="bg-white p-8 rounded-lg shadow-xl text-center max-w-sm w-full">
<h2 class="text-2xl font-bold text-gray-800 mb-4">Action Confirmed!</h2>
<p class="text-gray-700 mb-6">You pressed Enter on: <span id="selected-item-text" class="font-semibold text-indigo-600"></span></p>
<button id="close-notification" class="px-6 py-3 bg-indigo-500 text-white rounded-md hover:bg-indigo-600 transition duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-opacity-75">
Got It!
</button>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const columns = document.querySelectorAll('.column');
const notificationPopup = document.getElementById('notification-popup');
const closeNotificationBtn = document.getElementById('close-notification');
const selectedItemText = document.getElementById('selected-item-text');
const days = ['Nedelja', 'Ponedeljek', 'Torek', 'Sreda', 'Četrtek', 'Petek', 'Sobota'];
// Function to generate and populate the date list
function populateDates() {
const dateList = document.querySelector('#column-2 ul');
const today = new Date();
const items = [];
// Add 14 days in the past
for (let i = 14; i >= 1; i--) {
const d = new Date(today);
d.setDate(today.getDate() - i);
items.push(d);
}
// Add today
items.push(today);
// Add one day in the future
const dFuture = new Date(today);
dFuture.setDate(today.getDate() + 1);
items.push(dFuture);
dateList.innerHTML = items.map((date, index) => {
const dayOfWeek = days[date.getDay()];
const month = date.getMonth() + 1;
const day = date.getDate();
return `<li data-column="2" data-index="${index}">${day}. ${month}. ${dayOfWeek}</li>`;
}).join('');
return items.length;
}
const dateItemsCount = populateDates();
let currentColumnIndex = 1; // Start with the central column
const itemsInColumn = [];
// Populate the itemsInColumn array after populating the dates
for (let i = 0; i < columns.length; i++) {
itemsInColumn.push(document.querySelectorAll(`#column-${i} li`));
}
let currentItemIndex = [0, 0, 0, 0]; // 4 columns, initial selected index is 0 for each
// Set the initial focus for the date column to today
// The "today" item is the 15th item (index 14) in the list of 16 items
currentItemIndex[2] = 14;
// Function to update the focused item and manage the visual wrap-around
function updateFocus() {
// Remove 'focused' class from all items
document.querySelectorAll('li.focused').forEach(item => item.classList.remove('focused'));
const currentItems = itemsInColumn[currentColumnIndex];
if (currentItems.length === 0) return;
const currentIndex = currentItemIndex[currentColumnIndex];
const focusedItem = currentItems[currentIndex];
focusedItem.classList.add('focused');
// Get the list container for the current column
const listContainer = document.querySelector(`#column-${currentColumnIndex} ul`);
// Calculate the offset needed to center the focused item
const containerHeight = listContainer.parentElement.clientHeight;
const itemHeight = focusedItem.offsetHeight + parseFloat(getComputedStyle(focusedItem).marginBottom);
const offset = (containerHeight / 2) - (itemHeight / 2);
// Calculate the translateY value to center the focused item
// This accounts for the cumulative height of items before the focused one
const translateYValue = - (currentIndex * itemHeight) + offset;
listContainer.style.transform = `translateY(${translateYValue}px)`;
}
// Function to show the notification popup
function showNotification(itemElement) {
selectedItemText.textContent = itemElement.textContent.trim();
notificationPopup.classList.remove('hidden');
}
// Function to hide the notification popup
function hideNotification() {
notificationPopup.classList.add('hidden');
}
// Handle keyboard key presses
document.addEventListener('keydown', (e) => {
if (!notificationPopup.classList.contains('hidden')) {
// If notification is open, only handle Escape key
if (e.key === 'Escape' || e.key === 'Backspace') {
hideNotification();
}
return;
}
const maxItemsInColumn = itemsInColumn[currentColumnIndex].length;
if (maxItemsInColumn === 0) return;
switch (e.key) {
case 'ArrowLeft':
currentColumnIndex = Math.max(0, currentColumnIndex - 1);
break;
case 'ArrowRight':
currentColumnIndex = Math.min(columns.length - 1, currentColumnIndex + 1);
break;
case 'ArrowUp':
// Wrapping logic for up key
currentItemIndex[currentColumnIndex] = (currentItemIndex[currentColumnIndex] - 1 + maxItemsInColumn) % maxItemsInColumn;
break;
case 'ArrowDown':
// Wrapping logic for down key
currentItemIndex[currentColumnIndex] = (currentItemIndex[currentColumnIndex] + 1) % maxItemsInColumn;
break;
case 'Enter':
const focusedItem = itemsInColumn[currentColumnIndex][currentItemIndex[currentColumnIndex]];
if (focusedItem) {
showNotification(focusedItem);
}
break;
case 'Escape':
case 'Backspace':
// Handled above
break;
default:
return;
}
e.preventDefault();
updateFocus();
});
// Handle item clicks for initial focus setting
columns.forEach(column => {
column.querySelectorAll('li').forEach(item => {
item.addEventListener('click', () => {
currentColumnIndex = parseInt(item.dataset.column);
currentItemIndex[currentColumnIndex] = parseInt(item.dataset.index);
updateFocus();
});
});
});
// Handle closing notification via button click
closeNotificationBtn.addEventListener('click', hideNotification);
// Set initial focus when the page loads
updateFocus();
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment