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>

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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