Simplify moodboard: free images + editable captions
- Remove category/label system, replace with free text captions - Click image area to upload, caption editable below - Images uploaded as files to server (same as hero, avoids NocoDB limit) - Wider cards (160px min), cleaner layout Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+56
-30
@@ -289,14 +289,15 @@ window.dispatchEvent(new Event('tiptap-loaded'));
|
||||
.list-item-expand { font-size: 12px; color: var(--text-hint); cursor: pointer; margin-left: 8px; white-space: nowrap; }
|
||||
.list-item-expand:hover { color: var(--accent); }
|
||||
|
||||
.mood-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 12px; }
|
||||
.mood-card { position: relative; border-radius: 10px; overflow: hidden; aspect-ratio: 1; background: var(--bg-alt); border: 1px solid var(--border); cursor: pointer; display: flex; align-items: flex-end; background-size: cover; background-position: center; }
|
||||
.mood-card.has-image { border-color: transparent; }
|
||||
.mood-label { width: 100%; padding: 6px 10px; font-size: 12px; font-weight: 600; color: #fff; background: rgba(44,24,16,0.55); backdrop-filter: blur(4px); text-align: center; }
|
||||
.mood-card:not(.has-image) .mood-label { color: var(--text-muted); background: transparent; }
|
||||
.mood-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 16px; }
|
||||
.mood-card { position: relative; border-radius: 10px; overflow: hidden; background: var(--bg-alt); border: 1px solid var(--border); }
|
||||
.mood-card-img { width: 100%; aspect-ratio: 1; background-size: cover; background-position: center; cursor: pointer; background-color: var(--bg-alt); display: flex; align-items: center; justify-content: center; }
|
||||
.mood-card-img.empty { border: 1px dashed var(--border); border-radius: 10px 10px 0 0; }
|
||||
.mood-card-img.empty::after { content: '+'; font-size: 28px; color: var(--text-hint); }
|
||||
.mood-card-caption { padding: 8px 10px; font-size: 13px; color: var(--text); min-height: 32px; }
|
||||
.mood-card-remove { position: absolute; top: 4px; right: 4px; display: none; background: rgba(139,48,48,0.8); color: #fff; border: none; border-radius: 50%; width: 20px; height: 20px; font-size: 12px; cursor: pointer; align-items: center; justify-content: center; z-index: 2; }
|
||||
.mood-card:hover .mood-card-remove { display: flex; }
|
||||
.mood-add { border: 1px dashed var(--border); background: transparent; display: flex; flex-direction: column; align-items: center; justify-content: center; cursor: pointer; aspect-ratio: 1; border-radius: 10px; }
|
||||
.mood-add { border: 1px dashed var(--border); background: transparent; display: flex; flex-direction: column; align-items: center; justify-content: center; cursor: pointer; min-height: 120px; border-radius: 10px; }
|
||||
.mood-add:hover { background: var(--bg-alt); }
|
||||
.mood-add-icon { font-size: 24px; color: var(--text-hint); }
|
||||
|
||||
@@ -1208,7 +1209,7 @@ function removeListItem(section, containerId, cssClass, index) {
|
||||
renderList(section, containerId, cssClass);
|
||||
}
|
||||
|
||||
// ── Moodboard ──
|
||||
// ── Moodboard (images + free text captions) ──
|
||||
function renderMoodboard() {
|
||||
const container = document.getElementById('v-moodboard');
|
||||
const row = visionData['moodboard'];
|
||||
@@ -1218,18 +1219,34 @@ function renderMoodboard() {
|
||||
container.innerHTML = '';
|
||||
items.forEach((item, i) => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'mood-card' + (item.image ? ' has-image' : '');
|
||||
if (item.image) card.style.backgroundImage = `url(${item.image})`;
|
||||
card.className = 'mood-card';
|
||||
|
||||
card.innerHTML = `
|
||||
<button class="mood-card-remove" onclick="event.stopPropagation();removeMoodItem(${i})">×</button>
|
||||
<div class="mood-label">${esc(item.label)}</div>
|
||||
`;
|
||||
card.onclick = (e) => {
|
||||
if (e.target.closest('.mood-card-remove')) return;
|
||||
moodUploadTarget = i;
|
||||
document.getElementById('mood-file-input').click();
|
||||
};
|
||||
const imgDiv = document.createElement('div');
|
||||
imgDiv.className = 'mood-card-img' + (item.image ? '' : ' empty');
|
||||
if (item.image) imgDiv.style.backgroundImage = `url(${item.image})`;
|
||||
imgDiv.onclick = () => { moodUploadTarget = i; document.getElementById('mood-file-input').click(); };
|
||||
|
||||
const caption = document.createElement('div');
|
||||
caption.className = 'mood-card-caption';
|
||||
caption.contentEditable = 'true';
|
||||
caption.textContent = item.caption || '';
|
||||
caption.setAttribute('placeholder', 'Légende...');
|
||||
caption.addEventListener('blur', () => {
|
||||
const v = caption.textContent.trim();
|
||||
if (v !== (items[i].caption || '')) {
|
||||
items[i].caption = v;
|
||||
saveVisionSection('moodboard', JSON.stringify(items));
|
||||
}
|
||||
});
|
||||
|
||||
const removeBtn = document.createElement('button');
|
||||
removeBtn.className = 'mood-card-remove';
|
||||
removeBtn.innerHTML = '×';
|
||||
removeBtn.onclick = (e) => { e.stopPropagation(); removeMoodItem(i); };
|
||||
|
||||
card.appendChild(removeBtn);
|
||||
card.appendChild(imgDiv);
|
||||
card.appendChild(caption);
|
||||
container.appendChild(card);
|
||||
});
|
||||
|
||||
@@ -1237,9 +1254,7 @@ function renderMoodboard() {
|
||||
addCard.className = 'mood-add';
|
||||
addCard.innerHTML = '<div class="mood-add-icon">+</div>';
|
||||
addCard.onclick = () => {
|
||||
const label = prompt('Catégorie (ex : Ambiance, Architecture, Costumes...) :');
|
||||
if (!label) return;
|
||||
items.push({ label, image: '' });
|
||||
items.push({ caption: '', image: '' });
|
||||
saveVisionSection('moodboard', JSON.stringify(items));
|
||||
renderMoodboard();
|
||||
};
|
||||
@@ -1255,7 +1270,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
img.onload = async () => {
|
||||
const maxW = 400;
|
||||
let w = img.width, h = img.height;
|
||||
if (w > maxW) { h = Math.round(h * maxW / w); w = maxW; }
|
||||
@@ -1263,14 +1278,25 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
canvas.width = w; canvas.height = h;
|
||||
canvas.getContext('2d').drawImage(img, 0, 0, w, h);
|
||||
const dataUrl = canvas.toDataURL('image/jpeg', 0.8);
|
||||
const row = visionData['moodboard'];
|
||||
let items = [];
|
||||
try { items = JSON.parse(row?.Contenu || '[]'); } catch { items = []; }
|
||||
if (items[moodUploadTarget]) {
|
||||
items[moodUploadTarget].image = dataUrl;
|
||||
saveVisionSection('moodboard', JSON.stringify(items));
|
||||
renderMoodboard();
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/mood-upload', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ dataUrl, index: moodUploadTarget }),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.path) {
|
||||
const row = visionData['moodboard'];
|
||||
let items = [];
|
||||
try { items = JSON.parse(row?.Contenu || '[]'); } catch { items = []; }
|
||||
if (items[moodUploadTarget]) {
|
||||
items[moodUploadTarget].image = data.path;
|
||||
saveVisionSection('moodboard', JSON.stringify(items));
|
||||
renderMoodboard();
|
||||
}
|
||||
}
|
||||
} catch (err) { console.error('Mood upload error:', err); }
|
||||
};
|
||||
img.src = e.target.result;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user