gallery tweaks

This commit is contained in:
Mario Lurig
2026-02-17 11:14:44 -07:00
parent 554b0be7f4
commit 7d47ac4a10
+365 -35
View File
@@ -11,8 +11,9 @@
theme: {
extend: {
colors: {
primary: '#3b82f6',
secondary: '#1e40af',
primary: '#3D3D3E',
accent: '#F8654B',
secondary: '#E7E7E7',
dark: '#0f172a',
light: '#f8fafc'
}
@@ -21,24 +22,145 @@
}
</script>
<style>
.gallery-item {
.gallery-item {
transition: all 0.3s ease;
height: 100%;
display: flex;
flex-direction: column;
}
.gallery-item:hover {
transform: translateY(-5px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
.filter-btn.active {
background-color: #3b82f6;
background-color: #3D3D3E;
color: white;
}
.splash-filter {
background-color: #3D3D3E;
color: white;
}
.loading-filter {
background-color: #F8654B;
color: white;
}
.screen-image {
object-fit: cover;
width: 100%;
height: 100%;
display: block;
}
.screen-container {
aspect-ratio: 16/9;
display: flex;
align-items: center;
justify-content: center;
flex: 1;
width: 100%;
}
.modal-content {
max-width: 90vw;
max-height: 90vh;
}
.credit-text {
font-size: 0.75rem;
color: #6b7280;
}
.density-btn {
background-color: #E7E7E7;
border: 1px solid #3D3D3E;
}
.density-btn.active {
background-color: #3D3D3E;
color: white;
}
.gallery-item:hover {
transform: translateY(-5px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
.filter-btn.active {
background-color: #3D3D3E;
color: white;
}
.splash-filter {
background-color: #3D3D3E;
color: white;
}
.loading-filter {
background-color: #F8654B;
color: white;
}
.screen-image {
object-fit: cover;
width: 100%;
height: 100%;
display: block;
}
.screen-container {
aspect-ratio: 16/9;
display: flex;
align-items: center;
justify-content: center;
}
.modal-content {
max-width: 90vw;
max-height: 90vh;
}
.credit-text {
font-size: 0.75rem;
color: #6b7280;
}
.density-btn {
background-color: #E7E7E7;
border: 1px solid #3D3D3E;
}
.density-btn.active {
background-color: #3D3D3E;
color: white;
}
.gallery-item:hover {
transform: translateY(-5px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
.filter-btn.active {
background-color: #3D3D3E;
color: white;
}
.splash-filter {
background-color: #3D3D3E;
color: white;
}
.loading-filter {
background-color: #F8654B;
color: white;
}
.screen-image {
object-fit: cover;
width: 100%;
height: 100%;
display: block;
}
.screen-container {
aspect-ratio: 16/9;
display: flex;
align-items: center;
justify-content: center;
}
.modal-content {
max-width: 90vw;
max-height: 90vh;
}
.credit-text {
font-size: 0.75rem;
color: #6b7280;
}
.density-btn {
background-color: #E7E7E7;
border: 1px solid #3D3D3E;
}
.density-btn.active {
background-color: #3D3D3E;
color: white;
}
</style>
</head>
@@ -51,15 +173,45 @@
</header>
<main class="container mx-auto px-4 py-8">
<!-- Filters -->
<div class="mb-8 bg-white rounded-lg shadow-sm p-6">
<div class="flex flex-wrap gap-4 items-center justify-between">
<h2 class="text-xl font-semibold text-gray-900">Filter Screens</h2>
<div class="flex flex-wrap gap-2">
<button id="all-filter" class="filter-btn px-4 py-2 rounded-lg bg-gray-100 hover:bg-blue-100 text-gray-800 hover:text-blue-700 transition-colors">All</button>
<button id="splash-filter" class="filter-btn px-4 py-2 rounded-lg bg-gray-100 hover:bg-blue-100 text-gray-800 hover:text-blue-700 transition-colors">Splash</button>
<button id="loading-filter" class="filter-btn px-4 py-2 rounded-lg bg-gray-100 hover:bg-blue-100 text-gray-800 hover:text-blue-700 transition-colors">Loading</button>
<!-- Stats and Controls -->
<div class="mb-6 bg-white rounded-lg shadow-sm p-4 flex flex-wrap justify-between items-center gap-4">
<div class="flex flex-wrap gap-4">
<div class="bg-gray-100 px-3 py-1 rounded-lg">
<span class="text-sm text-gray-600">Total Screens:</span>
<span id="total-count" class="font-semibold ml-1">0</span>
</div>
<div class="bg-blue-100 px-3 py-1 rounded-lg">
<span class="text-sm text-blue-800">Splash:</span>
<span id="splash-count" class="font-semibold ml-1">0</span>
</div>
<div class="bg-orange-100 px-3 py-1 rounded-lg">
<span class="text-sm text-orange-800">Loading:</span>
<span id="loading-count" class="font-semibold ml-1">0</span>
</div>
</div>
<div class="flex items-center gap-2">
<!-- Density Toggle -->
<div class="flex items-center gap-1">
<button id="density-1" class="density-btn px-3 py-1 rounded-lg text-sm" title="Dense">
<i class="fas fa-th"></i>
</button>
<button id="density-2" class="density-btn px-3 py-1 rounded-lg text-sm" title="Medium">
<i class="fas fa-th-large"></i>
</button>
<button id="density-3" class="density-btn px-3 py-1 rounded-lg text-sm" title="Large">
<i class="fas fa-expand-alt"></i>
</button>
</div>
<!-- Filter Buttons -->
<div class="flex gap-2">
<button id="all-filter" class="filter-btn px-4 py-2 rounded-lg border border-gray-300 text-gray-700 hover:bg-blue-100 hover:text-blue-700 transition-colors">All</button>
<button id="splash-filter" class="filter-btn px-4 py-2 rounded-lg border border-blue-500 text-blue-600 hover:bg-blue-100 transition-colors">Splash</button>
<button id="loading-filter" class="filter-btn px-4 py-2 rounded-lg border border-orange-500 text-orange-600 hover:bg-orange-100 transition-colors">Loading</button>
</div>
<!-- Search -->
<div class="flex items-center gap-2">
<input type="text" id="search-input" placeholder="Search screens..." class="px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent">
<button id="clear-search" class="px-3 py-2 text-gray-500 hover:text-gray-700">
@@ -70,7 +222,7 @@
</div>
<!-- Screen Grid -->
<div id="gallery-container" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
<div id="gallery-container" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
<!-- Screens will be dynamically loaded here -->
</div>
@@ -82,6 +234,26 @@
</div>
</main>
<!-- Modal for full-size view -->
<div id="image-modal" class="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center p-4 z-50 hidden">
<div class="modal-content bg-white rounded-lg overflow-hidden max-w-4xl max-h-full">
<div class="p-4 border-b flex justify-between items-center">
<h3 id="modal-title" class="text-xl font-semibold"></h3>
<button id="close-modal" class="text-gray-500 hover:text-gray-700">
<i class="fas fa-times text-2xl"></i>
</button>
</div>
<div class="p-4 flex justify-center">
<img id="modal-image" src="" alt="" class="max-h-[70vh] max-w-full object-contain">
</div>
<div class="p-4 border-t flex justify-end gap-2">
<button id="download-modal" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors flex items-center gap-2">
<i class="fas fa-download"></i> Download
</button>
</div>
</div>
</div>
<footer class="bg-white border-t mt-12">
<div class="container mx-auto px-4 py-6 text-center text-gray-500">
<p>Screen Gallery - A collection of screen designs</p>
@@ -128,19 +300,38 @@
const [resolution, type, ...nameParts] = parts;
const [width, height] = resolution.split('x').map(Number);
const name = nameParts.join('_').split('.')[0];
// Check if there's a credit part (last part before extension)
let name = nameParts.join('_').split('.')[0];
let credit = null;
// If the last part is not an extension, it might be credit
const namePartsArray = nameParts.join('_').split('.');
if (namePartsArray.length > 1) {
const ext = namePartsArray[namePartsArray.length - 1];
if (ext !== 'png') {
// This means there's a credit part
const fullName = nameParts.join('_');
const lastHyphenIndex = fullName.lastIndexOf('-');
if (lastHyphenIndex > 0) {
name = fullName.substring(0, lastHyphenIndex);
credit = fullName.substring(lastHyphenIndex + 1, fullName.length - 4); // Remove .png
}
}
}
return {
filename,
width,
height,
type: type.toLowerCase(),
name
name,
credit
};
}
// Function to render screens
function renderScreens(screensToRender) {
function renderScreens(screensToRender, density = '2') {
const container = document.getElementById('gallery-container');
const emptyState = document.getElementById('empty-state');
@@ -151,25 +342,101 @@
}
emptyState.classList.add('hidden');
container.innerHTML = screensToRender.map(screen => `
<div class="gallery-item bg-white rounded-lg shadow-sm overflow-hidden">
<div class="screen-container flex items-center justify-center p-4 bg-gray-100">
<img src="screens/${screen.filename}"
alt="${screen.name}"
class="screen-image max-h-64 object-contain"
onerror="this.src='https://placehold.co/300x200?text=Image+Not+Found'">
</div>
<div class="p-4">
<h3 class="font-semibold text-gray-900 truncate">${screen.name.replace(/_/g, ' ')}</h3>
<div class="flex justify-between items-center mt-2">
<span class="text-sm text-gray-500">${screen.width}×${screen.height}</span>
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800 capitalize">
${screen.type}
</span>
// Determine grid classes based on density
let gridClass = 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4';
let imageHeight = 'h-full';
let cardPadding = 'p-3';
let titleSize = 'text-base';
if (density === '1') { // Dense
gridClass = 'grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5';
imageHeight = 'h-full';
cardPadding = 'p-1';
titleSize = 'text-xs';
} else if (density === '2') { // Medium
gridClass = 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4';
imageHeight = 'h-full';
cardPadding = 'p-2';
titleSize = 'text-sm';
} else if (density === '3') { // Large
gridClass = 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3';
imageHeight = 'h-full';
cardPadding = 'p-4';
titleSize = 'text-lg';
}
container.className = `grid ${gridClass} gap-2`;
container.innerHTML = screensToRender.map(screen => {
const displayName = screen.name.replace(/_/g, ' ');
return `
<div class="gallery-item bg-white rounded-lg shadow-sm overflow-hidden cursor-pointer" data-filename="${screen.filename}" data-name="${displayName}" data-credit="${screen.credit || ''}">
<div class="screen-container bg-gray-100">
<img src="screens/${screen.filename}"
alt="${screen.name}"
class="screen-image"
onerror="this.src='https://placehold.co/300x200?text=Image+Not+Found'">
</div>
<div class="${cardPadding}">
<h3 class="${titleSize} font-semibold text-gray-900 truncate">${displayName}</h3>
<div class="flex justify-between items-center mt-1">
<span class="text-xs text-gray-500">${screen.width}×${screen.height}</span>
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium
${screen.type === 'splash' ? 'bg-blue-100 text-blue-800' : 'bg-orange-100 text-orange-800'} capitalize">
${screen.type}
</span>
</div>
<div class="flex justify-end mt-2">
<button class="text-xs text-green-600 hover:text-green-800 flex items-center gap-1 download-btn" data-filename="${screen.filename}">
<i class="fas fa-download"></i> Download
</button>
</div>
</div>
</div>
</div>
`).join('');
`;
}).join('');
}
// Function to update counts
function updateCounts(screens) {
const totalCount = screens.length;
const splashCount = screens.filter(s => s.type === 'splash').length;
const loadingCount = screens.filter(s => s.type === 'loading').length;
document.getElementById('total-count').textContent = totalCount;
document.getElementById('splash-count').textContent = splashCount;
document.getElementById('loading-count').textContent = loadingCount;
}
// Modal functions
function openModal(filename, name, credit) {
const modal = document.getElementById('image-modal');
const modalImage = document.getElementById('modal-image');
const modalTitle = document.getElementById('modal-title');
const downloadBtn = document.getElementById('download-modal');
modalImage.src = `screens/${filename}`;
modalTitle.textContent = name;
// Set download button to use the correct filename
downloadBtn.onclick = function() {
// Create a temporary link to trigger download
const link = document.createElement('a');
link.href = `screens/${filename}`;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
modal.classList.remove('hidden');
}
function closeModal() {
const modal = document.getElementById('image-modal');
modal.classList.add('hidden');
}
// Filter and search functionality
@@ -190,7 +457,8 @@
if (searchTerm) {
filteredScreens = filteredScreens.filter(screen =>
screen.name.toLowerCase().includes(searchTerm) ||
`${screen.width}×${screen.height}`.toLowerCase().includes(searchTerm)
`${screen.width}×${screen.height}`.toLowerCase().includes(searchTerm) ||
(screen.credit && screen.credit.toLowerCase().includes(searchTerm))
);
}
@@ -202,7 +470,7 @@
// Parse filenames to get metadata
const parsedScreens = screens.map(screen => parseFilename(screen.filename));
// Set up filter buttons
// Set up filter buttons - single select with outlined style
document.querySelectorAll('.filter-btn').forEach(button => {
button.addEventListener('click', function() {
// Remove active class from all buttons
@@ -216,6 +484,31 @@
});
});
// Set up density toggle buttons
document.getElementById('density-1').addEventListener('click', function() {
document.querySelectorAll('.density-btn').forEach(btn => btn.classList.remove('active'));
this.classList.add('active');
renderScreens(parsedScreens, '1');
});
document.getElementById('density-2').addEventListener('click', function() {
document.querySelectorAll('.density-btn').forEach(btn => btn.classList.remove('active'));
this.classList.add('active');
renderScreens(parsedScreens, '2');
});
document.getElementById('density-3').addEventListener('click', function() {
document.querySelectorAll('.density-btn').forEach(btn => btn.classList.remove('active'));
this.classList.add('active');
renderScreens(parsedScreens, '3');
});
// Set initial active state for density buttons
document.getElementById('density-2').classList.add('active');
// Set initial active filter to ALL
document.getElementById('all-filter').classList.add('active');
// Set up search functionality
const searchInput = document.getElementById('search-input');
searchInput.addEventListener('input', filterScreens);
@@ -226,7 +519,44 @@
filterScreens();
});
// Close modal
document.getElementById('close-modal').addEventListener('click', closeModal);
// Close modal when clicking outside
document.getElementById('image-modal').addEventListener('click', function(e) {
if (e.target === this) {
closeModal();
}
});
// Set up download buttons
document.addEventListener('click', function(e) {
if (e.target.classList.contains('download-btn') || e.target.closest('.download-btn')) {
const btn = e.target.classList.contains('download-btn') ? e.target : e.target.closest('.download-btn');
const filename = btn.getAttribute('data-filename');
// Create a temporary link to trigger download
const link = document.createElement('a');
link.href = `screens/${filename}`;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
});
// Set up thumbnail click to open modal
document.addEventListener('click', function(e) {
if (e.target.closest('.gallery-item') && !e.target.classList.contains('download-btn')) {
const item = e.target.closest('.gallery-item');
const filename = item.getAttribute('data-filename');
const name = item.getAttribute('data-name');
const credit = item.getAttribute('data-credit');
openModal(filename, name, credit);
}
});
// Initial render
updateCounts(parsedScreens);
renderScreens(parsedScreens);
});
</script>