gallery first draft

This commit is contained in:
Mario Lurig
2026-02-17 00:08:41 -07:00
parent 92254cb3e3
commit 3026e82b97
3 changed files with 276 additions and 0 deletions
+39
View File
@@ -0,0 +1,39 @@
# Tailor Designs Repository
## Purpose
This repository stores screen designs created by individuals. Screens are pushed via pull requests and validated through a GitHub Actions workflow.
## File Structure
- `screens/` - Directory containing all screen files
- `.github/workflows/validate-images.yml` - Workflow that validates file naming conventions
## Naming Convention
Files in the `screens/` directory must follow this format:
```
{WIDTH}x{HEIGHT}-{TYPE}-{NAME}.{EXT}
```
Where:
- `{WIDTH}x{HEIGHT}` - Screen resolution (e.g., 800x480)
- `{TYPE}` - Type of screen, either "loading" or "splash"
- `{NAME}` - Descriptive name using lowercase letters, numbers, underscores
- `{EXT}` - File extension (must be PNG)
## Example Files
- `800x480-splash-hourglass_with_butterflies.png`
- `800x480-loading-dungeon_crawler_carl_princess_donut.png`
## Gallery Features
The gallery webpage provides:
- Filter by screen type (splash/loading)
- Search functionality
- Responsive grid layout
- Modern UI with Tailwind CSS styling
## Validation Workflow
The validation workflow ensures all files:
1. Use only lowercase letters, numbers, underscores, and hyphens
2. Have 3 or 4 segments separated by hyphens
3. Have a valid PNG file format
4. Match the specified dimensions in filename with actual image dimensions
5. Have the correct type (loading or splash)
+234
View File
@@ -0,0 +1,234 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Screen Gallery</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#3b82f6',
secondary: '#1e40af',
dark: '#0f172a',
light: '#f8fafc'
}
}
}
}
</script>
<style>
.gallery-item {
transition: all 0.3s ease;
}
.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;
color: white;
}
.screen-image {
object-fit: cover;
width: 100%;
height: 100%;
}
.screen-container {
aspect-ratio: 16/9;
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<header class="bg-white shadow-sm">
<div class="container mx-auto px-4 py-6">
<h1 class="text-3xl font-bold text-gray-900">Screen Gallery</h1>
<p class="text-gray-600 mt-2">Browse all the screens created by contributors</p>
</div>
</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>
</div>
<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">
<i class="fas fa-times"></i>
</button>
</div>
</div>
</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">
<!-- Screens will be dynamically loaded here -->
</div>
<!-- Empty state -->
<div id="empty-state" class="text-center py-12 hidden">
<i class="fas fa-search text-gray-400 text-5xl mb-4"></i>
<h3 class="text-xl font-medium text-gray-900 mb-2">No screens found</h3>
<p class="text-gray-500">Try adjusting your search or filter criteria</p>
</div>
</main>
<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>
</div>
</footer>
<script>
// Sample data for screens (in a real implementation, this would come from a JSON file or API)
const screens = [
{
filename: "800x480-splash-hourglass_with_butterflies.png",
width: 800,
height: 480,
type: "splash",
name: "hourglass_with_butterflies"
},
{
filename: "800x480-splash-blank.png",
width: 800,
height: 480,
type: "splash",
name: "blank"
},
{
filename: "800x480-splash-a_fishing_party_winslow_homer-clevelandart_org.png",
width: 800,
height: 480,
type: "splash",
name: "a_fishing_party_winslow_homer-clevelandart_org"
},
{
filename: "800x480-loading-dungeon_crawler_carl_princess_donut.png",
width: 800,
height: 480,
type: "loading",
name: "dungeon_crawler_carl_princess_donut"
}
];
// Function to parse filename and extract metadata
function parseFilename(filename) {
const parts = filename.split('-');
if (parts.length < 3) return null;
const [resolution, type, ...nameParts] = parts;
const [width, height] = resolution.split('x').map(Number);
const name = nameParts.join('_').split('.')[0];
return {
filename,
width,
height,
type: type.toLowerCase(),
name
};
}
// Function to render screens
function renderScreens(screensToRender) {
const container = document.getElementById('gallery-container');
const emptyState = document.getElementById('empty-state');
if (screensToRender.length === 0) {
emptyState.classList.remove('hidden');
container.innerHTML = '';
return;
}
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>
</div>
</div>
</div>
`).join('');
}
// Filter and search functionality
function filterScreens() {
const searchTerm = document.getElementById('search-input').value.toLowerCase();
const activeFilter = document.querySelector('.filter-btn.active')?.id;
let filteredScreens = screens;
// Apply type filter
if (activeFilter === 'splash-filter') {
filteredScreens = filteredScreens.filter(screen => screen.type === 'splash');
} else if (activeFilter === 'loading-filter') {
filteredScreens = filteredScreens.filter(screen => screen.type === 'loading');
}
// Apply search filter
if (searchTerm) {
filteredScreens = filteredScreens.filter(screen =>
screen.name.toLowerCase().includes(searchTerm) ||
`${screen.width}×${screen.height}`.toLowerCase().includes(searchTerm)
);
}
renderScreens(filteredScreens);
}
// Initialize the gallery
document.addEventListener('DOMContentLoaded', () => {
// Parse filenames to get metadata
const parsedScreens = screens.map(screen => parseFilename(screen.filename));
// Set up filter buttons
document.querySelectorAll('.filter-btn').forEach(button => {
button.addEventListener('click', function() {
// Remove active class from all buttons
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.classList.remove('active');
});
// Add active class to clicked button
this.classList.add('active');
filterScreens();
});
});
// Set up search functionality
const searchInput = document.getElementById('search-input');
searchInput.addEventListener('input', filterScreens);
// Clear search button
document.getElementById('clear-search').addEventListener('click', function() {
searchInput.value = '';
filterScreens();
});
// Initial render
renderScreens(parsedScreens);
});
</script>
</body>
</html>
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7c31f2e363a92765914e34d2135edc9168e78690de86d0b756bbfb35f8796e18
size 9475