prototype-1 / index.html
Maxwelljro's picture
Build a responsive mobile application with the following specifications and features.
dd59da7 verified
Raw History Blame Contribute Delete
16.8 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>YouTube Thumbnail Saver</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/feather-icons"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Inter', sans-serif;
background-color: #f0f0f0;
}
.neo-brutalism {
border: 3px solid #000;
box-shadow: 4px 4px 0px #000;
}
.neo-brutalism-sm {
border: 2px solid #000;
box-shadow: 2px 2px 0px #000;
}
.active-nav {
background-color: #000;
color: white;
}
.thumbnail-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 16px;
}
@media (max-width: 640px) {
.thumbnail-grid {
grid-template-columns: repeat(2, 1fr);
}
}
</style>
</head>
<body class="bg-gray-100 min-h-screen flex flex-col pb-20">
<!-- Create Page -->
<div id="create-page" class="container mx-auto px-4 py-8">
<h1 class="text-3xl font-bold mb-6 text-center">Create Thumbnail</h1>
<div class="mb-6">
<input
type="text"
id="url-input"
placeholder="Paste YouTube video or playlist URL here..."
class="w-full p-4 border-2 border-black rounded-lg neo-brutalism focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<p id="url-error" class="text-red-500 text-sm mt-2 hidden">Invalid YouTube URL</p>
</div>
<div class="flex justify-center mb-6">
<button
id="find-btn"
class="bg-blue-500 text-white px-6 py-3 rounded-lg neo-brutalism hover:bg-blue-600 transition-colors mr-4"
>
Find Thumbnail
</button>
<button
id="clear-btn"
class="bg-gray-200 p-3 rounded-lg neo-brutalism hover:bg-gray-300 transition-colors"
>
<i data-feather="trash-2"></i>
</button>
</div>
<div id="thumbnail-preview" class="hidden text-center">
<div class="relative inline-block">
<img id="thumbnail-img" src="" alt="Thumbnail" class="mx-auto mb-4 rounded-lg neo-brutalism">
<span id="content-type" class="absolute top-2 right-2 bg-black text-white px-2 py-1 text-xs rounded"></span>
</div>
<button
id="save-btn"
class="bg-green-500 text-white px-6 py-3 rounded-lg neo-brutalism hover:bg-green-600 transition-colors"
>
Save to Collection
</button>
</div>
</div>
<!-- Collections Page -->
<div id="collections-page" class="container mx-auto px-4 py-8 hidden">
<h1 class="text-3xl font-bold mb-6 text-center">Your Collections</h1>
<div class="mb-6 flex justify-between items-center">
<h2 class="text-2xl font-semibold">Folders</h2>
<button
id="add-folder-btn"
class="bg-purple-500 text-white px-4 py-2 rounded-lg neo-brutalism hover:bg-purple-600 transition-colors"
>
<i data-feather="plus"></i> Add Folder
</button>
</div>
<div id="folders-container" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-8">
<!-- Folders will be dynamically added here -->
</div>
<div id="folder-content" class="hidden">
<div class="flex items-center mb-4">
<button id="back-to-folders" class="mr-4">
<i data-feather="arrow-left"></i>
</button>
<h2 id="folder-title" class="text-2xl font-semibold"></h2>
</div>
<div id="thumbnails-container" class="thumbnail-grid">
<!-- Thumbnails will be dynamically added here -->
</div>
</div>
</div>
<!-- Bottom Navigation -->
<nav class="fixed bottom-0 left-0 right-0 bg-white border-t-2 border-black neo-brutalism">
<div class="flex justify-around items-center h-16">
<button id="nav-create" class="flex-1 h-full flex items-center justify-center active-nav">
<i data-feather="plus"></i>
</button>
<button id="nav-collections" class="flex-1 h-full flex items-center justify-center">
<i data-feather="folder"></i>
</button>
</div>
</nav>
<!-- Notification -->
<div id="notification" class="fixed top-4 right-4 bg-black text-white px-4 py-2 rounded-lg neo-brutalism hidden">
Notification message
</div>
<script>
// Initialize Feather Icons
feather.replace();
// DOM Elements
const createPage = document.getElementById('create-page');
const collectionsPage = document.getElementById('collections-page');
const navCreate = document.getElementById('nav-create');
const navCollections = document.getElementById('nav-collections');
const urlInput = document.getElementById('url-input');
const findBtn = document.getElementById('find-btn');
const clearBtn = document.getElementById('clear-btn');
const thumbnailPreview = document.getElementById('thumbnail-preview');
const thumbnailImg = document.getElementById('thumbnail-img');
const contentType = document.getElementById('content-type');
const saveBtn = document.getElementById('save-btn');
const urlError = document.getElementById('url-error');
const notification = document.getElementById('notification');
const foldersContainer = document.getElementById('folders-container');
const folderContent = document.getElementById('folder-content');
const folderTitle = document.getElementById('folder-title');
const thumbnailsContainer = document.getElementById('thumbnails-container');
const backToFolders = document.getElementById('back-to-folders');
const addFolderBtn = document.getElementById('add-folder-btn');
// State
let currentThumbnail = null;
let currentType = null;
let folders = JSON.parse(localStorage.getItem('folders')) || [];
// Navigation
function showPage(page) {
if (page === 'create') {
createPage.classList.remove('hidden');
collectionsPage.classList.add('hidden');
navCreate.classList.add('active-nav');
navCollections.classList.remove('active-nav');
} else {
createPage.classList.add('hidden');
collectionsPage.classList.remove('hidden');
navCreate.classList.remove('active-nav');
navCollections.classList.add('active-nav');
renderFolders();
}
}
navCreate.addEventListener('click', () => showPage('create'));
navCollections.addEventListener('click', () => showPage('collections'));
// URL Validation
function isValidYouTubeUrl(url) {
const regExp = /^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.?be)\/.+$/;
return regExp.test(url);
}
// Extract Video ID
function getYouTubeId(url) {
const regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/;
const match = url.match(regExp);
return (match && match[2].length === 11) ? match[2] : null;
}
// Extract Playlist ID
function getPlaylistId(url) {
const regExp = /[?&]list=([^#&?]+)/;
const match = url.match(regExp);
return match ? match[1] : null;
}
// Get First Video ID from Playlist
async function getFirstVideoIdFromPlaylist(playlistId) {
// In a real app, you would use YouTube Data API
// For this demo, we'll simulate with a placeholder
return new Promise(resolve => {
setTimeout(() => resolve('dQw4w9WgXcQ'), 1000); // Rickroll ID
});
}
// Find Thumbnail
findBtn.addEventListener('click', async () => {
const url = urlInput.value.trim();
if (!isValidYouTubeUrl(url)) {
urlError.classList.remove('hidden');
return;
}
urlError.classList.add('hidden');
let videoId = getYouTubeId(url);
currentType = 'Video';
if (!videoId) {
const playlistId = getPlaylistId(url);
if (playlistId) {
currentType = 'Playlist';
videoId = await getFirstVideoIdFromPlaylist(playlistId);
}
}
if (videoId) {
thumbnailImg.src = `https://img.youtube.com/vi/${videoId}/mqdefault.jpg`;
contentType.textContent = currentType;
thumbnailPreview.classList.remove('hidden');
currentThumbnail = {
id: videoId,
url: `https://www.youtube.com/watch?v=${videoId}`,
type: currentType
};
}
});
// Clear Input and Preview
clearBtn.addEventListener('click', () => {
urlInput.value = '';
thumbnailPreview.classList.add('hidden');
urlError.classList.add('hidden');
currentThumbnail = null;
});
// Save to Collection
saveBtn.addEventListener('click', () => {
if (!currentThumbnail) return;
if (folders.length === 0) {
showNotification('Create a folder first!');
return;
}
// For demo, we'll just save to the first folder
const folder = folders[0];
const existing = folder.thumbnails.find(t => t.id === currentThumbnail.id);
if (existing) {
showNotification('Already saved in this folder!');
return;
}
folder.thumbnails.push(currentThumbnail);
localStorage.setItem('folders', JSON.stringify(folders));
showNotification('Saved to collection!');
});
// Render Folders
function renderFolders() {
foldersContainer.innerHTML = '';
if (folders.length === 0) {
foldersContainer.innerHTML = `
<div class="col-span-full text-center py-12">
<i data-feather="folder" class="text-gray-400 mb-4" style="width: 64px; height: 64px;"></i>
<p class="text-gray-600">No folders yet. Create one to get started.</p>
</div>
`;
feather.replace();
return;
}
folders.forEach((folder, index) => {
const folderEl = document.createElement('div');
folderEl.className = 'bg-white p-4 rounded-lg neo-brutalism cursor-pointer hover:shadow-lg transition-shadow';
folderEl.innerHTML = `
<div class="flex justify-between items-start mb-2">
<h3 class="font-bold text-lg">${folder.name}</h3>
<span class="bg-gray-200 px-2 py-1 rounded text-sm">${folder.thumbnails.length} items</span>
</div>
<p class="text-gray-600 text-sm mb-3">Click to view contents</p>
<div class="flex justify-between">
<button class="edit-folder text-blue-500" data-index="${index}">
<i data-feather="edit-2"></i>
</button>
<button class="delete-folder text-red-500" data-index="${index}">
<i data-feather="trash-2"></i>
</button>
</div>
`;
foldersContainer.appendChild(folderEl);
folderEl.addEventListener('click', (e) => {
if (!e.target.closest('.edit-folder') && !e.target.closest('.delete-folder')) {
showFolderContent(index);
}
});
folderEl.querySelector('.edit-folder').addEventListener('click', (e) => {
e.stopPropagation();
const newName = prompt('Enter new folder name:', folder.name);
if (newName) {
folder.name = newName;
localStorage.setItem('folders', JSON.stringify(folders));
renderFolders();
}
});
folderEl.querySelector('.delete-folder').addEventListener('click', (e) => {
e.stopPropagation();
if (confirm(`Delete folder "${folder.name}" and all its contents?`)) {
folders.splice(index, 1);
localStorage.setItem('folders', JSON.stringify(folders));
renderFolders();
}
});
});
feather.replace();
}
// Show Folder Content
function showFolderContent(folderIndex) {
const folder = folders[folderIndex];
folderTitle.textContent = folder.name;
thumbnailsContainer.innerHTML = '';
if (folder.thumbnails.length === 0) {
thumbnailsContainer.innerHTML = `
<div class="col-span-full text-center py-8">
<p class="text-gray-600">This folder is empty.</p>
</div>
`;
} else {
folder.thumbnails.forEach(thumbnail => {
const thumbEl = document.createElement('div');
thumbEl.className = 'relative cursor-pointer group';
thumbEl.innerHTML = `
<img src="https://img.youtube.com/vi/${thumbnail.id}/mqdefault.jpg" alt="Thumbnail" class="w-full rounded-lg neo-brutalism">
<div class="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-30 transition-all rounded-lg flex items-center justify-center">
<i data-feather="play-circle" class="text-white opacity-0 group-hover:opacity-100 transition-opacity"></i>
</div>
`;
thumbEl.addEventListener('click', () => {
window.open(thumbnail.url, '_blank');
});
thumbnailsContainer.appendChild(thumbEl);
});
}
folderContent.classList.remove('hidden');
foldersContainer.classList.add('hidden');
feather.replace();
}
// Back to Folders
backToFolders.addEventListener('click', () => {
folderContent.classList.add('hidden');
foldersContainer.classList.remove('hidden');
});
// Add Folder
addFolderBtn.addEventListener('click', () => {
const name = prompt('Enter folder name:');
if (name) {
folders.push({
name: name,
thumbnails: []
});
localStorage.setItem('folders', JSON.stringify(folders));
renderFolders();
}
});
// Show Notification
function showNotification(message) {
notification.textContent = message;
notification.classList.remove('hidden');
setTimeout(() => {
notification.classList.add('hidden');
}, 3000);
}
// Initialize
showPage('create');
renderFolders();
</script>
</body>
</html>