Hairitics's picture
a site to organize my Midjourney Prompts and Pictures - Initial Deployment
24a7b9b verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Midjourney Prompt Organizer</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">
<style>
.prompt-card:hover .prompt-actions {
opacity: 1;
}
.tag:hover {
transform: scale(1.05);
}
.grid-item {
transition: all 0.3s ease;
}
.grid-item:hover {
transform: translateY(-5px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
}
#lightbox {
transition: opacity 0.3s ease;
}
.loader {
border-top-color: #6366f1;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body class="bg-gray-100 min-h-screen">
<!-- Header -->
<header class="bg-indigo-600 text-white shadow-lg">
<div class="container mx-auto px-4 py-6">
<div class="flex flex-col md:flex-row justify-between items-center">
<div class="flex items-center mb-4 md:mb-0">
<i class="fas fa-robot text-3xl mr-3"></i>
<h1 class="text-2xl md:text-3xl font-bold">Midjourney Prompt Vault</h1>
</div>
<button id="addNewBtn" class="bg-indigo-500 hover:bg-indigo-700 text-white font-bold py-2 px-4 rounded-full flex items-center transition duration-300">
<i class="fas fa-plus mr-2"></i> New Prompt
</button>
</div>
</div>
</header>
<!-- Main Content -->
<main class="container mx-auto px-4 py-8">
<!-- Search and Filters -->
<div class="bg-white rounded-xl shadow-md p-6 mb-8">
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
<div class="relative flex-grow">
<i class="fas fa-search absolute left-3 top-3 text-gray-400"></i>
<input id="searchInput" type="text" placeholder="Search prompts..." class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
</div>
<div class="flex flex-wrap gap-2">
<select id="sortSelect" class="px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
<option value="newest">Newest First</option>
<option value="oldest">Oldest First</option>
<option value="az">A-Z</option>
<option value="za">Z-A</option>
</select>
<select id="filterSelect" class="px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
<option value="all">All Prompts</option>
<option value="favorite">Favorites</option>
<option value="character">Characters</option>
<option value="landscape">Landscapes</option>
<option value="concept">Concepts</option>
<option value="portrait">Portraits</option>
</select>
</div>
</div>
<div id="activeTags" class="flex flex-wrap gap-2 mt-4"></div>
</div>
<!-- Prompt Statistics -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
<div class="bg-white rounded-xl shadow-md p-6">
<div class="flex items-center">
<div class="p-3 rounded-full bg-indigo-100 text-indigo-600 mr-4">
<i class="fas fa-list text-lg"></i>
</div>
<div>
<p class="text-gray-500">Total Prompts</p>
<h3 id="totalPrompts" class="text-2xl font-bold">0</h3>
</div>
</div>
</div>
<div class="bg-white rounded-xl shadow-md p-6">
<div class="flex items-center">
<div class="p-3 rounded-full bg-blue-100 text-blue-600 mr-4">
<i class="fas fa-star text-lg"></i>
</div>
<div>
<p class="text-gray-500">Favorites</p>
<h3 id="favoritePrompts" class="text-2xl font-bold">0</h3>
</div>
</div>
</div>
<div class="bg-white rounded-xl shadow-md p-6">
<div class="flex items-center">
<div class="p-3 rounded-full bg-green-100 text-green-600 mr-4">
<i class="fas fa-tags text-lg"></i>
</div>
<div>
<p class="text-gray-500">Unique Tags</p>
<h3 id="uniqueTags" class="text-2xl font-bold">0</h3>
</div>
</div>
</div>
<div class="bg-white rounded-xl shadow-md p-6">
<div class="flex items-center">
<div class="p-3 rounded-full bg-purple-100 text-purple-600 mr-4">
<i class="fas fa-image text-lg"></i>
</div>
<div>
<p class="text-gray-500">Images</p>
<h3 id="totalImages" class="text-2xl font-bold">0</h3>
</div>
</div>
</div>
</div>
<!-- Prompts Grid -->
<div id="promptsContainer" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
<!-- Prompts will be loaded here -->
<div id="emptyState" class="col-span-full text-center py-20">
<i class="fas fa-robot text-6xl text-gray-300 mb-4"></i>
<h3 class="text-2xl font-bold text-gray-700 mb-2">No prompts found</h3>
<p class="text-gray-500 mb-6">Start by adding your first Midjourney prompt</p>
<button id="emptyAddBtn" class="bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-2 px-6 rounded-full inline-flex items-center transition duration-300">
<i class="fas fa-plus mr-2"></i> Add Prompt
</button>
</div>
</div>
</main>
<!-- Add/Edit Prompt Modal -->
<div id="promptModal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden flex items-center justify-center p-4">
<div class="bg-white rounded-xl shadow-xl w-full max-w-2xl max-h-[90vh] overflow-y-auto">
<div class="p-6">
<div class="flex justify-between items-center mb-4">
<h2 class="text-2xl font-bold" id="modalTitle">Add New Prompt</h2>
<button id="closeModal" class="text-gray-500 hover:text-gray-700">
<i class="fas fa-times text-xl"></i>
</button>
</div>
<form id="promptForm" class="space-y-4">
<input type="hidden" id="promptId">
<div>
<label for="promptText" class="block text-sm font-medium text-gray-700 mb-1">Prompt</label>
<textarea id="promptText" rows="5" class="w-full p-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500" placeholder="Enter your Midjourney prompt..."></textarea>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label for="promptName" class="block text-sm font-medium text-gray-700 mb-1">Name</label>
<input type="text" id="promptName" class="w-full p-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500" placeholder="My Awesome Art">
</div>
<div>
<label for="promptModel" class="block text-sm font-medium text-gray-700 mb-1">Model Version</label>
<select id="promptModel" class="w-full p-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500">
<option value="MJ 5.2">Midjourney 5.2</option>
<option value="MJ 5.1">Midjourney 5.1</option>
<option value="Niji 5">Niji 5</option>
<option value="Niji 4">Niji 4</option>
<option value="Other">Other</option>
</select>
</div>
</div>
<div>
<label for="promptParameters" class="block text-sm font-medium text-gray-700 mb-1">Parameters</label>
<input type="text" id="promptParameters" class="w-full p-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500" placeholder="--ar 16:9 --v 5.2 --q 2">
</div>
<div>
<label for="promptImages" class="block text-sm font-medium text-gray-700 mb-1">Image URLs (one per line)</label>
<textarea id="promptImages" rows="3" class="w-full p-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500" placeholder="https://example.com/image1.jpg"></textarea>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Tags</label>
<div class="flex flex-wrap gap-2 mb-2" id="currentTags"></div>
<div class="flex">
<input type="text" id="newTag" class="flex-grow p-2 border border-gray-300 rounded-l-lg focus:outline-none focus:ring-2 focus:ring-indigo-500" placeholder="Add new tag">
<button type="button" id="addTagBtn" class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 rounded-r-lg">Add</button>
</div>
</div>
<div class="flex items-center">
<input type="checkbox" id="isFavorite" class="w-4 h-4 text-indigo-600 border-gray-300 rounded focus:ring-indigo-500">
<label for="isFavorite" class="ml-2 text-sm text-gray-700">Mark as favorite</label>
</div>
<div class="flex justify-end space-x-3 pt-4">
<button type="button" id="cancelPrompt" class="px-4 py-2 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50">Cancel</button>
<button type="submit" id="savePrompt" class="px-6 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700">Save Prompt</button>
</div>
</form>
</div>
</div>
</div>
<!-- Lightbox Modal -->
<div id="lightbox" class="fixed inset-0 bg-black bg-opacity-90 z-50 hidden flex items-center justify-center p-4">
<div class="relative max-w-6xl w-full">
<button id="closeLightbox" class="absolute top-4 right-4 text-white text-3xl z-50">
<i class="fas fa-times"></i>
</button>
<div class="flex justify-between items-center absolute top-1/2 left-0 right-0 transform -translate-y-1/2 z-50 px-4">
<button id="prevImage" class="bg-black bg-opacity-50 text-white rounded-full w-10 h-10 flex items-center justify-center hover:bg-opacity-70">
<i class="fas fa-chevron-left"></i>
</button>
<button id="nextImage" class="bg-black bg-opacity-50 text-white rounded-full w-10 h-10 flex items-center justify-center hover:bg-opacity-70">
<i class="fas fa-chevron-right"></i>
</button>
</div>
<img id="lightboxImage" src="" alt="" class="max-h-[90vh] w-auto mx-auto">
<div id="lightboxCaption" class="text-white text-center mt-4"></div>
</div>
</div>
<!-- Loading Overlay -->
<div id="loadingOverlay" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden flex items-center justify-center">
<div class="bg-white p-8 rounded-lg shadow-xl flex flex-col items-center">
<div class="loader ease-linear rounded-full border-4 border-t-4 border-gray-200 h-12 w-12 mb-4"></div>
<p class="text-gray-700">Loading prompts...</p>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Elements
const promptsContainer = document.getElementById('promptsContainer');
const emptyState = document.getElementById('emptyState');
const promptModal = document.getElementById('promptModal');
const lightbox = document.getElementById('lightbox');
const loadingOverlay = document.getElementById('loadingOverlay');
// Buttons
const addNewBtn = document.getElementById('addNewBtn');
const emptyAddBtn = document.getElementById('emptyAddBtn');
const closeModal = document.getElementById('closeModal');
const closeLightbox = document.getElementById('closeLightbox');
const prevImage = document.getElementById('prevImage');
const nextImage = document.getElementById('nextImage');
const cancelPrompt = document.getElementById('cancelPrompt');
const savePrompt = document.getElementById('savePrompt');
const addTagBtn = document.getElementById('addTagBtn');
// Form elements
const promptForm = document.getElementById('promptForm');
const promptId = document.getElementById('promptId');
const promptName = document.getElementById('promptName');
const promptText = document.getElementById('promptText');
const promptModel = document.getElementById('promptModel');
const promptParameters = document.getElementById('promptParameters');
const promptImages = document.getElementById('promptImages');
const currentTags = document.getElementById('currentTags');
const newTag = document.getElementById('newTag');
const isFavorite = document.getElementById('isFavorite');
// Search and filter elements
const searchInput = document.getElementById('searchInput');
const sortSelect = document.getElementById('sortSelect');
const filterSelect = document.getElementById('filterSelect');
const activeTags = document.getElementById('activeTags');
// Stats elements
const totalPrompts = document.getElementById('totalPrompts');
const favoritePrompts = document.getElementById('favoritePrompts');
const uniqueTags = document.getElementById('uniqueTags');
const totalImages = document.getElementById('totalImages');
// State
let prompts = JSON.parse(localStorage.getItem('midjourneyPrompts')) || [];
let currentTagsArray = [];
let currentLightboxImages = [];
let currentLightboxIndex = 0;
// Initialize
updateStats();
renderPrompts();
renderTagFilters();
// Event Listeners
addNewBtn.addEventListener('click', openNewPromptModal);
emptyAddBtn.addEventListener('click', openNewPromptModal);
closeModal.addEventListener('click', closePromptModal);
closeLightbox.addEventListener('click', closeLightboxModal);
cancelPrompt.addEventListener('click', closePromptModal);
promptForm.addEventListener('submit', savePromptHandler);
addTagBtn.addEventListener('click', addTag);
// Search and filter listeners
searchInput.addEventListener('input', renderPrompts);
sortSelect.addEventListener('change', renderPrompts);
filterSelect.addEventListener('change', renderPrompts);
// Functions
function openNewPromptModal() {
promptId.value = '';
promptForm.reset();
currentTagsArray = [];
renderCurrentTags();
document.getElementById('modalTitle').textContent = 'Add New Prompt';
promptModal.classList.remove('hidden');
}
function openEditPromptModal(id) {
const prompt = prompts.find(p => p.id === id);
if (prompt) {
promptId.value = prompt.id;
promptName.value = prompt.name || '';
promptText.value = prompt.text;
promptModel.value = prompt.model || 'MJ 5.2';
promptParameters.value = prompt.parameters || '';
promptImages.value = prompt.images?.join('\n') || '';
isFavorite.checked = prompt.favorite || false;
currentTagsArray = [...(prompt.tags || [])];
renderCurrentTags();
document.getElementById('modalTitle').textContent = 'Edit Prompt';
promptModal.classList.remove('hidden');
}
}
function closePromptModal() {
promptModal.classList.add('hidden');
}
function closeLightboxModal() {
lightbox.classList.add('hidden');
}
function addTag() {
const tag = newTag.value.trim();
if (tag && !currentTagsArray.includes(tag)) {
currentTagsArray.push(tag);
renderCurrentTags();
newTag.value = '';
}
}
function removeTag(tag) {
currentTagsArray = currentTagsArray.filter(t => t !== tag);
renderCurrentTags();
renderPrompts();
}
function renderCurrentTags() {
currentTags.innerHTML = '';
currentTagsArray.forEach(tag => {
const tagElement = document.createElement('span');
tagElement.className = 'inline-flex items-center bg-indigo-100 text-indigo-800 text-sm px-3 py-1 rounded-full';
tagElement.innerHTML = `
${tag}
<button data-tag="${tag}" class="ml-2 text-indigo-600 hover:text-indigo-900">
<i class="fas fa-times"></i>
</button>
`;
currentTags.appendChild(tagElement);
});
// Add event listeners to remove buttons
currentTags.querySelectorAll('button').forEach(button => {
button.addEventListener('click', (e) => {
e.stopPropagation();
removeTag(button.dataset.tag);
});
});
}
function savePromptHandler(e) {
e.preventDefault();
const id = promptId.value || Date.now().toString();
const name = promptName.value.trim();
const text = promptText.value.trim();
const model = promptModel.value;
const parameters = promptParameters.value.trim();
const images = promptImages.value.split('\n').filter(url => url.trim());
const favorite = isFavorite.checked;
const tags = [...currentTagsArray];
const createdAt = promptId.value ?
(prompts.find(p => p.id === id)?.createdAt || new Date().toISOString()) :
new Date().toISOString();
const updatedAt = new Date().toISOString();
if (!text) {
alert('Prompt text is required');
return;
}
const prompt = {
id,
name,
text,
model,
parameters,
images,
favorite,
tags,
createdAt,
updatedAt
};
// Check if we're updating an existing prompt
const index = prompts.findIndex(p => p.id === id);
if (index !== -1) {
prompts[index] = prompt;
} else {
prompts.unshift(prompt);
}
// Save to local storage
localStorage.setItem('midjourneyPrompts', JSON.stringify(prompts));
// Update UI
updateStats();
renderPrompts();
renderTagFilters();
closePromptModal();
}
function deletePrompt(id) {
if (confirm('Are you sure you want to delete this prompt?')) {
prompts = prompts.filter(p => p.id !== id);
localStorage.setItem('midjourneyPrompts', JSON.stringify(prompts));
updateStats();
renderPrompts();
renderTagFilters();
}
}
function toggleFavorite(id) {
const prompt = prompts.find(p => p.id === id);
if (prompt) {
prompt.favorite = !prompt.favorite;
localStorage.setItem('midjourneyPrompts', JSON.stringify(prompts));
updateStats();
renderPrompts();
}
}
function updateStats() {
totalPrompts.textContent = prompts.length;
favoritePrompts.textContent = prompts.filter(p => p.favorite).length;
// Calculate unique tags
const allTags = prompts.flatMap(p => p.tags || []);
const uniqueTagsSet = new Set(allTags);
uniqueTags.textContent = uniqueTagsSet.size;
// Calculate total images
const totalImagesCount = prompts.reduce((sum, p) => sum + (p.images?.length || 0), 0);
totalImages.textContent = totalImagesCount;
}
function renderPrompts() {
loadingOverlay.classList.remove('hidden');
// Simulate loading (for demo purposes)
setTimeout(() => {
let filteredPrompts = [...prompts];
// Apply search filter
const searchTerm = searchInput.value.toLowerCase();
if (searchTerm) {
filteredPrompts = filteredPrompts.filter(p =>
p.text.toLowerCase().includes(searchTerm) ||
(p.name && p.name.toLowerCase().includes(searchTerm)) ||
(p.tags && p.tags.some(tag => tag.toLowerCase().includes(searchTerm)))
);
}
// Apply category filter
const filterValue = filterSelect.value;
if (filterValue !== 'all') {
if (filterValue === 'favorite') {
filteredPrompts = filteredPrompts.filter(p => p.favorite);
} else {
filteredPrompts = filteredPrompts.filter(p =>
p.tags && p.tags.includes(filterValue)
);
}
}
// Apply active tag filters
const activeTagButtons = activeTags.querySelectorAll('.tag-filter');
const activeTagsArray = Array.from(activeTagButtons).map(btn => btn.dataset.tag);
if (activeTagsArray.length > 0) {
filteredPrompts = filteredPrompts.filter(p =>
p.tags && activeTagsArray.every(tag => p.tags.includes(tag))
);
}
// Apply sorting
const sortValue = sortSelect.value;
switch (sortValue) {
case 'newest':
filteredPrompts.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
break;
case 'oldest':
filteredPrompts.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt));
break;
case 'az':
filteredPrompts.sort((a, b) => (a.name || a.text).localeCompare(b.name || b.text));
break;
case 'za':
filteredPrompts.sort((a, b) => (b.name || b.text).localeCompare(a.name || a.text));
break;
}
// Clear the container
promptsContainer.innerHTML = '';
// Show empty state if no prompts
if (filteredPrompts.length === 0) {
emptyState.classList.remove('hidden');
promptsContainer.appendChild(emptyState);
} else {
emptyState.classList.add('hidden');
// Render prompts
filteredPrompts.forEach(prompt => {
const promptCard = document.createElement('div');
promptCard.className = 'grid-item bg-white rounded-xl shadow-md overflow-hidden hover:shadow-lg transition duration-300';
// First image or placeholder
const firstImage = prompt.images?.[0] || '';
const hasImages = prompt.images && prompt.images.length > 0;
promptCard.innerHTML = `
<div class="relative">
${hasImages ? `
<img src="${firstImage}" alt="${prompt.name || 'Midjourney creation'}" class="w-full h-48 object-cover cursor-pointer view-image" data-prompt-id="${prompt.id}" data-image-index="0">
` : `
<div class="w-full h-48 bg-gray-100 flex items-center justify-center text-gray-400">
<i class="fas fa-image text-4xl"></i>
</div>
`}
${prompt.favorite ? `
<div class="absolute top-2 right-2 bg-yellow-500 text-white p-2 rounded-full shadow-md">
<i class="fas fa-star"></i>
</div>
` : ''}
<div class="prompt-actions absolute top-2 left-2 opacity-0 transition-opacity duration-300 flex gap-2">
<button class="edit-prompt bg-indigo-600 text-white p-2 rounded-full shadow-md hover:bg-indigo-700" data-id="${prompt.id}">
<i class="fas fa-pencil-alt text-sm"></i>
</button>
<button class="delete-prompt bg-red-600 text-white p-2 rounded-full shadow-md hover:bg-red-700" data-id="${prompt.id}">
<i class="fas fa-trash-alt text-sm"></i>
</button>
</div>
</div>
<div class="p-4">
<div class="flex justify-between items-start mb-2">
<h3 class="font-bold text-lg truncate">${prompt.name || 'Untitled Prompt'}</h3>
<button class="favorite-prompt text-gray-400 hover:text-yellow-500 ${prompt.favorite ? 'text-yellow-500' : ''}" data-id="${prompt.id}">
<i class="fas fa-star"></i>
</button>
</div>
<p class="text-gray-600 text-sm mb-3 line-clamp-3">${prompt.text}</p>
<div class="flex flex-wrap gap-1 mb-2">
${(prompt.tags || []).slice(0, 3).map(tag => `
<span class="tag bg-indigo-100 text-indigo-800 text-xs px-2 py-1 rounded-full">${tag}</span>
`).join('')}
${!prompt.tags || prompt.tags.length === 0 ? '' : prompt.tags.length > 3 ? `
<span class="tag bg-gray-100 text-gray-800 text-xs px-2 py-1 rounded-full">+${prompt.tags.length - 3}</span>
` : ''}
</div>
<div class="flex justify-between items-center text-xs text-gray-500">
<span>${new Date(prompt.createdAt).toLocaleDateString()}</span>
${hasImages && prompt.images.length > 1 ? `
<span class="flex items-center">
<i class="fas fa-images mr-1"></i>
${prompt.images.length}
</span>
` : ''}
</div>
</div>
`;
promptsContainer.appendChild(promptCard);
});
}
// Add event listeners to the newly created elements
addPromptEventListeners();
loadingOverlay.classList.add('hidden');
}, 500);
}
function addPromptEventListeners() {
// Edit buttons
document.querySelectorAll('.edit-prompt').forEach(button => {
button.addEventListener('click', (e) => {
e.stopPropagation();
openEditPromptModal(button.dataset.id);
});
});
// Delete buttons
document.querySelectorAll('.delete-prompt').forEach(button => {
button.addEventListener('click', (e) => {
e.stopPropagation();
deletePrompt(button.dataset.id);
});
});
// Favorite buttons
document.querySelectorAll('.favorite-prompt').forEach(button => {
button.addEventListener('click', (e) => {
e.stopPropagation();
toggleFavorite(button.dataset.id);
});
});
// Image click (lightbox)
document.querySelectorAll('.view-image').forEach(img => {
img.addEventListener('click', (e) => {
const promptId = img.dataset.promptId;
const prompt = prompts.find(p => p.id === promptId);
if (prompt && prompt.images && prompt.images.length > 0) {
currentLightboxImages = prompt.images;
currentLightboxIndex = parseInt(img.dataset.imageIndex);
openLightboxModal(currentLightboxIndex);
}
});
});
// Tag filters
document.querySelectorAll('.tag').forEach(tag => {
tag.addEventListener('click', (e) => {
e.stopPropagation();
const tagText = tag.textContent.replace(/^\+/, '');
toggleTagFilter(tagText);
});
});
}
function openLightboxModal(index) {
if (currentLightboxImages.length === 0) return;
const imageUrl = currentLightboxImages[index];
const promptId = currentLightboxImages.promptId;
const prompt = promptId ? prompts.find(p => p.id === promptId) : null;
document.getElementById('lightboxImage').src = imageUrl;
document.getElementById('lightboxCaption').textContent = prompt ? (prompt.name || prompt.text.substring(0, 100) + (prompt.text.length > 100 ? '...' : '')) : '';
// Update navigation visibility
prevImage.style.visibility = index > 0 ? 'visible' : 'hidden';
nextImage.style.visibility = index < currentLightboxImages.length - 1 ? 'visible' : 'hidden';
lightbox.classList.remove('hidden');
}
function renderTagFilters() {
// Get all unique tags
const allTags = prompts.flatMap(p => p.tags || []);
const tagCounts = {};
allTags.forEach(tag => {
tagCounts[tag] = (tagCounts[tag] || 0) + 1;
});
const uniqueTags = Object.keys(tagCounts);
// Clear active tags (except the applied ones)
const appliedTags = Array.from(activeTags.querySelectorAll('.tag-filter')).map(el => el.dataset.tag);
activeTags.innerHTML = '';
// Add the applied tags back
appliedTags.forEach(tag => {
if (uniqueTags.includes(tag)) {
addActiveTagFilter(tag);
}
});
}
function toggleTagFilter(tag) {
const isActive = activeTags.querySelector(`[data-tag="${tag}"]`);
if (isActive) {
isActive.remove();
} else {
addActiveTagFilter(tag);
}
renderPrompts();
}
function addActiveTagFilter(tag) {
const activeTag = document.createElement('span');
activeTag.className = 'tag-filter inline-flex items-center bg-indigo-600 text-white text-sm px-3 py-1 rounded-full cursor-pointer';
activeTag.dataset.tag = tag;
activeTag.innerHTML = `
${tag}
<button class="ml-2 text-white hover:text-indigo-200">
<i class="fas fa-times"></i>
</button>
`;
activeTag.addEventListener('click', (e) => {
if (e.target.tagName === 'BUTTON' || e.target.tagName === 'I') {
e.stopPropagation();
activeTag.remove();
renderPrompts();
} else {
toggleTagFilter(tag);
}
});
activeTags.appendChild(activeTag);
}
// Set up lightbox navigation
prevImage.addEventListener('click', () => {
if (currentLightboxIndex > 0) {
currentLightboxIndex--;
openLightboxModal(currentLightboxIndex);
}
});
nextImage.addEventListener('click', () => {
if (currentLightboxIndex < currentLightboxImages.length - 1) {
currentLightboxIndex++;
openLightboxModal(currentLightboxIndex);
}
});
// Keyboard navigation for lightbox
document.addEventListener('keydown', (e) => {
if (!lightbox.classList.contains('hidden')) {
if (e.key === 'Escape') {
closeLightboxModal();
} else if (e.key === 'ArrowLeft' && currentLightboxIndex > 0) {
currentLightboxIndex--;
openLightboxModal(currentLightboxIndex);
} else if (e.key === 'ArrowRight' && currentLightboxIndex < currentLightboxImages.length - 1) {
currentLightboxIndex++;
openLightboxModal(currentLightboxIndex);
}
}
});
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Hairitics/midjourney-prompt-vault" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>