Skip to content

PinVault #70

Description

@jeremyruben123-sudo
<title>PinVault — Visual Mood Board Curator</title> <style> :root { --bg: #f5f5f5; --surface: #ffffff; --text: #1a1a1a; --text-secondary: #666666; --text-muted: #999999; --border: #e0e0e0; --accent: #e60023; --accent-hover: #c5001e; --radius: 12px; --shadow: 0 2px 8px rgba(0,0,0,0.08); --shadow-hover: 0 8px 24px rgba(0,0,0,0.12); } * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Inter', sans-serif; background: var(--bg); color: var(--text); min-height: 100vh; } header { background: var(--surface); border-bottom: 1px solid var(--border); padding: 16px 32px; display: flex; align-items: center; justify-content: space-between; position: sticky; top: 0; z-index: 100; } .logo { display: flex; align-items: center; gap: 10px; font-size: 22px; font-weight: 700; color: var(--accent); } .logo svg { width: 28px; height: 28px; fill: var(--accent); } .search-bar { flex: 1; max-width: 500px; margin: 0 24px; position: relative; } .search-bar input { width: 100%; padding: 12px 16px 12px 44px; border: 2px solid var(--border); border-radius: 24px; font-size: 15px; outline: none; transition: border-color 0.2s; background: var(--bg); } .search-bar input:focus { border-color: var(--accent); } .search-bar svg { position: absolute; left: 16px; top: 50%; transform: translateY(-50%); width: 18px; height: 18px; color: var(--text-muted); } .btn { padding: 10px 20px; border-radius: 24px; font-size: 14px; font-weight: 600; cursor: pointer; border: none; transition: all 0.2s; } .btn-primary { background: var(--accent); color: white; } .btn-primary:hover { background: var(--accent-hover); } .btn-secondary { background: var(--bg); color: var(--text); border: 1px solid var(--border); margin-right: 10px; } .btn-secondary:hover { background: var(--border); }
.filters {
  display: flex;
  gap: 8px;
  padding: 16px 32px;
  overflow-x: auto;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}
.filter-chip {
  padding: 8px 16px;
  border-radius: 20px;
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-secondary);
  white-space: nowrap;
  transition: all 0.2s;
}
.filter-chip:hover, .filter-chip.active {
  background: var(--text);
  color: var(--surface);
  border-color: var(--text);
}

.masonry {
  column-count: 5;
  column-gap: 16px;
  padding: 24px 32px;
}
@media (max-width: 1400px) { .masonry { column-count: 4; } }
@media (max-width: 1100px) { .masonry { column-count: 3; } }
@media (max-width: 800px) { .masonry { column-count: 2; padding: 16px; } }
@media (max-width: 500px) { .masonry { column-count: 1; } }

.pin {
  break-inside: avoid;
  margin-bottom: 16px;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--surface);
  box-shadow: var(--shadow);
  transition: transform 0.2s, box-shadow 0.2s;
  cursor: pointer;
  position: relative;
}
.pin:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-hover);
}
.pin img {
  width: 100%;
  display: block;
  object-fit: cover;
}
.pin-overlay {
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
  background: rgba(0,0,0,0.4);
  opacity: 0;
  transition: opacity 0.2s;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  padding: 12px;
}
.pin:hover .pin-overlay { opacity: 1; }
.pin-save {
  background: var(--accent);
  color: white;
  border: none;
  padding: 8px 16px;
  border-radius: 20px;
  font-weight: 600;
  font-size: 13px;
  cursor: pointer;
}
.pin-save.saved { background: #333; }
.pin-info {
  padding: 12px;
}
.pin-title {
  font-size: 14px;
  font-weight: 600;
  margin-bottom: 4px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.pin-tags {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}
.pin-tag {
  font-size: 11px;
  color: var(--text-muted);
  background: var(--bg);
  padding: 2px 8px;
  border-radius: 10px;
}

.modal {
  display: none;
  position: fixed;
  top: 0; left: 0; right: 0; bottom: 0;
  background: rgba(0,0,0,0.6);
  z-index: 200;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.modal.show { display: flex; }
.modal-content {
  background: var(--surface);
  border-radius: var(--radius);
  width: 100%;
  max-width: 480px;
  padding: 28px;
  box-shadow: 0 20px 60px rgba(0,0,0,0.3);
  animation: slideUp 0.3s ease;
}
@keyframes slideUp {
  from { opacity: 0; transform: translateY(20px); }
  to { opacity: 1; transform: translateY(0); }
}
.modal h2 { margin-bottom: 20px; font-size: 20px; }
.form-group { margin-bottom: 16px; }
.form-group label {
  display: block;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-secondary);
  margin-bottom: 6px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.form-group input, .form-group select {
  width: 100%;
  padding: 12px;
  border: 2px solid var(--border);
  border-radius: 10px;
  font-size: 15px;
  outline: none;
  transition: border-color 0.2s;
}
.form-group input:focus, .form-group select:focus { border-color: var(--accent); }
.modal-actions {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
  margin-top: 8px;
}
.empty-state {
  text-align: center;
  padding: 80px 20px;
  color: var(--text-muted);
}
.empty-state svg {
  width: 64px;
  height: 64px;
  margin-bottom: 16px;
  opacity: 0.4;
}
.empty-state h3 {
  font-size: 18px;
  color: var(--text-secondary);
  margin-bottom: 8px;
}
</style>
PinVault
+ Add Pin Saved
All Interior Fashion Food Travel Art Photography Nature

No pins found

Try a different search or add some new pins!

Add New Pin

Image URL
Title
Category Interior Fashion Food Travel Art Photography Nature
Tags (comma separated)
Cancel Add Pin
<script> const samplePins = [ { id: 1, url: 'https://images.unsplash.com/photo-1618221195710-dd6b41faaea6?w=400', title: 'Modern minimalist living room', category: 'interior', tags: ['minimal', 'living room', 'white'], saved: false }, { id: 2, url: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=400', title: 'Autumn fashion layers', category: 'fashion', tags: ['autumn', 'style', 'outfit'], saved: true }, { id: 3, url: 'https://images.unsplash.com/photo-1504674900247-0877df9cc836?w=400', title: 'Gourmet pasta dish', category: 'food', tags: ['pasta', 'italian', 'dinner'], saved: false }, { id: 4, url: 'https://images.unsplash.com/photo-1476514525535-07fb3b4ae5f1?w=400', title: 'Swiss Alps sunrise', category: 'travel', tags: ['mountains', 'switzerland', 'sunrise'], saved: true }, { id: 5, url: 'https://images.unsplash.com/photo-1541961017774-22349e4a1262?w=400', title: 'Abstract oil painting', category: 'art', tags: ['abstract', 'colorful', 'painting'], saved: false }, { id: 6, url: 'https://images.unsplash.com/photo-1501854140801-50d01698950b?w=400', title: 'Forest misty morning', category: 'nature', tags: ['forest', 'fog', 'peaceful'], saved: false }, { id: 7, url: 'https://images.unsplash.com/photo-1556909114-f6e7ad7d3136?w=400', title: 'Cozy kitchen nook', category: 'interior', tags: ['kitchen', 'cozy', 'wood'], saved: false }, { id: 8, url: 'https://images.unsplash.com/photo-1515886657613-9f3515b0c78f?w=400', title: 'Street style Tokyo', category: 'fashion', tags: ['streetwear', 'tokyo', 'urban'], saved: true }, { id: 9, url: 'https://images.unsplash.com/photo-1565299624946-b28f40a0ae38?w=400', title: 'Artisan pizza', category: 'food', tags: ['pizza', 'cheese', 'italian'], saved: false }, { id: 10, url: 'https://images.unsplash.com/photo-1493976040374-85c8e12f0c0e?w=400', title: 'Kyoto temple path', category: 'travel', tags: ['japan', 'temple', 'zen'], saved: false }, { id: 11, url: 'https://images.unsplash.com/photo-1579783902614-a3fb3927b6a5?w=400', title: 'Watercolor flowers', category: 'art', tags: ['watercolor', 'flowers', 'delicate'], saved: false }, { id: 12, url: 'https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=400', title: 'Mountain lake reflection', category: 'nature', tags: ['lake', 'mountains', 'reflection'], saved: true }, ]; let pins = JSON.parse(localStorage.getItem('pinvault_pins')) || samplePins; let currentFilter = 'all'; let showSavedOnly = false; let searchQuery = ''; function savePins() { localStorage.setItem('pinvault_pins', JSON.stringify(pins)); } function render() { const masonry = document.getElementById('masonry'); const empty = document.getElementById('emptyState'); let filtered = pins.filter(p => { const matchesCategory = currentFilter === 'all' || p.category === currentFilter; const matchesSaved = !showSavedOnly || p.saved; const matchesSearch = !searchQuery || p.title.toLowerCase().includes(searchQuery.toLowerCase()) || p.tags.some(t => t.toLowerCase().includes(searchQuery.toLowerCase())); return matchesCategory && matchesSaved && matchesSearch; }); if (filtered.length === 0) { masonry.style.display = 'none'; empty.style.display = 'block'; return; } masonry.style.display = 'block'; empty.style.display = 'none'; masonry.innerHTML = filtered.map(pin => `
${pin.title}
${pin.saved ? 'Saved' : 'Save'}
${pin.title}
${pin.tags.map(t => `${t}`).join('')}
`).join(''); } function togglePinSave(e, id) { e.stopPropagation(); const pin = pins.find(p => p.id === id); if (pin) { pin.saved = !pin.saved; savePins(); render(); } } function toggleSaved() { showSavedOnly = !showSavedOnly; document.getElementById('savedBtn').textContent = showSavedOnly ? 'All Pins' : 'Saved'; render(); } function openModal() { document.getElementById('modal').classList.add('show'); } function closeModal() { document.getElementById('modal').classList.remove('show'); document.getElementById('imgUrl').value = ''; document.getElementById('imgTitle').value = ''; document.getElementById('imgTags').value = ''; } function addPin() { const url = document.getElementById('imgUrl').value.trim(); const title = document.getElementById('imgTitle').value.trim(); const category = document.getElementById('imgCategory').value; const tags = document.getElementById('imgTags').value.split(',').map(t => t.trim()).filter(t => t); if (!url || !title) { alert('Please fill in the image URL and title.'); return; } pins.unshift({ id: Date.now(), url, title, category, tags: tags.length ? tags : [category], saved: false }); savePins(); closeModal(); render(); } document.getElementById('filters').addEventListener('click', e => { if (e.target.classList.contains('filter-chip')) { document.querySelectorAll('.filter-chip').forEach(c => c.classList.remove('active')); e.target.classList.add('active'); currentFilter = e.target.dataset.category; render(); } }); document.getElementById('searchInput').addEventListener('input', e => { searchQuery = e.target.value; render(); }); document.getElementById('modal').addEventListener('click', e => { if (e.target.id === 'modal') closeModal(); }); document.addEventListener('keydown', e => { if (e.key === 'Escape') closeModal(); }); render(); </script>

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions