<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.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>
No pins found
Try a different search or add some new pins!
Add New Pin