feat: reorganize Vision layout + moodboard image drag repositioning
- Phrase + Tags side by side (2 columns) - Moodboard full width - Synopsis full width - On veut / On ne veut pas as separate cards (2 columns) - Moodboard images: drag to reposition in any direction (2D) - Double-click to replace image, single-click to upload on empty Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+61
-28
@@ -269,10 +269,10 @@ window.dispatchEvent(new Event('tiptap-loaded'));
|
||||
|
||||
.two-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
|
||||
.list-block h4 { font-family: 'Playfair Display', serif; font-size: 15px; font-weight: 600; margin-bottom: 12px; }
|
||||
.list-block h4.yes { color: #5a7a3a; }
|
||||
.list-block h4.no { color: #a0523d; }
|
||||
.dark .list-block h4.yes { color: #8aaa6a; }
|
||||
.dark .list-block h4.no { color: #c07050; }
|
||||
.list-block h4.yes, .v-card h3.yes { color: #5a7a3a; }
|
||||
.list-block h4.no, .v-card h3.no { color: #a0523d; }
|
||||
.dark .list-block h4.yes, .dark .v-card h3.yes { color: #8aaa6a; }
|
||||
.dark .list-block h4.no, .dark .v-card h3.no { color: #c07050; }
|
||||
.list-item { padding: 10px 0; font-size: 15px; color: var(--text); display: flex; align-items: flex-start; gap: 8px; }
|
||||
.list-item .bullet { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; margin-top: 8px; }
|
||||
.list-item.yes .bullet { background: #5a7a3a; }
|
||||
@@ -291,7 +291,9 @@ window.dispatchEvent(new Event('tiptap-loaded'));
|
||||
|
||||
.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 { 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; user-select: none; }
|
||||
.mood-card-img.has-image { cursor: grab; }
|
||||
.mood-card-img.has-image.dragging { cursor: grabbing; }
|
||||
.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; }
|
||||
@@ -473,40 +475,36 @@ window.dispatchEvent(new Event('tiptap-loaded'));
|
||||
</div>
|
||||
<input type="file" id="hero-file-input" accept="image/*" style="display:none;" onchange="uploadHeroImage(this)">
|
||||
|
||||
<div class="v-card full">
|
||||
<div class="v-card">
|
||||
<h3>L'histoire en une phrase</h3>
|
||||
<div class="vision-phrase" id="v-phrase" contenteditable="true"></div>
|
||||
<div class="vision-detail" id="v-phrase-detail" contenteditable="true"></div>
|
||||
</div>
|
||||
|
||||
<div class="v-card">
|
||||
<h3>Ambiance, époque, rythme</h3>
|
||||
<div class="tag-group" id="v-tags"></div>
|
||||
</div>
|
||||
|
||||
<div class="v-card full">
|
||||
<h3>Moodboard</h3>
|
||||
<div class="mood-grid" id="v-moodboard"></div>
|
||||
<input type="file" id="mood-file-input" accept="image/*" style="display:none;">
|
||||
</div>
|
||||
|
||||
<div class="v-card full">
|
||||
<h3>Synopsis</h3>
|
||||
<div class="vision-detail" id="v-synopsis" contenteditable="true" style="min-height:80px;"></div>
|
||||
</div>
|
||||
|
||||
<div class="v-card">
|
||||
<h3>Ambiance, époque, rythme</h3>
|
||||
<div class="tag-group" id="v-tags"></div>
|
||||
<h3 class="yes">Ce qu'on veut</h3>
|
||||
<div id="v-on-veut"></div>
|
||||
</div>
|
||||
|
||||
<div class="v-card">
|
||||
<h3>Moodboard</h3>
|
||||
<div class="mood-grid" id="v-moodboard"></div>
|
||||
<input type="file" id="mood-file-input" accept="image/*" style="display:none;">
|
||||
</div>
|
||||
|
||||
<div class="v-card full">
|
||||
<h3>Ce qu'on veut / ne veut pas</h3>
|
||||
<div class="two-cols">
|
||||
<div class="list-block">
|
||||
<h4 class="yes">On veut</h4>
|
||||
<div id="v-on-veut"></div>
|
||||
</div>
|
||||
<div class="list-block">
|
||||
<h4 class="no">On ne veut pas</h4>
|
||||
<div id="v-on-ne-veut-pas"></div>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="no">Ce qu'on ne veut pas</h3>
|
||||
<div id="v-on-ne-veut-pas"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -1222,9 +1220,42 @@ function renderMoodboard() {
|
||||
card.className = 'mood-card';
|
||||
|
||||
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 hasImg = !!item.image;
|
||||
imgDiv.className = 'mood-card-img' + (hasImg ? ' has-image' : ' empty');
|
||||
if (hasImg) {
|
||||
imgDiv.style.backgroundImage = `url(${item.image})`;
|
||||
imgDiv.style.backgroundPosition = `${item.posX ?? 50}% ${item.posY ?? 50}%`;
|
||||
}
|
||||
// Click to upload (only if no image or double-click)
|
||||
imgDiv.ondblclick = () => { moodUploadTarget = i; document.getElementById('mood-file-input').click(); };
|
||||
if (!hasImg) imgDiv.onclick = () => { moodUploadTarget = i; document.getElementById('mood-file-input').click(); };
|
||||
// Drag to reposition
|
||||
if (hasImg) {
|
||||
let startX, startY, startPosX, startPosY;
|
||||
const onMove = (e) => {
|
||||
const rect = imgDiv.getBoundingClientRect();
|
||||
const dx = ((e.clientX - startX) / rect.width) * -100;
|
||||
const dy = ((e.clientY - startY) / rect.height) * -100;
|
||||
item.posX = Math.max(0, Math.min(100, startPosX + dx));
|
||||
item.posY = Math.max(0, Math.min(100, startPosY + dy));
|
||||
imgDiv.style.backgroundPosition = `${item.posX}% ${item.posY}%`;
|
||||
};
|
||||
const onUp = () => {
|
||||
imgDiv.classList.remove('dragging');
|
||||
window.removeEventListener('mousemove', onMove);
|
||||
window.removeEventListener('mouseup', onUp);
|
||||
saveVisionSection('moodboard', JSON.stringify(items));
|
||||
};
|
||||
imgDiv.addEventListener('mousedown', (e) => {
|
||||
if (e.button !== 0) return;
|
||||
e.preventDefault();
|
||||
imgDiv.classList.add('dragging');
|
||||
startX = e.clientX; startY = e.clientY;
|
||||
startPosX = item.posX ?? 50; startPosY = item.posY ?? 50;
|
||||
window.addEventListener('mousemove', onMove);
|
||||
window.addEventListener('mouseup', onUp);
|
||||
});
|
||||
}
|
||||
|
||||
const caption = document.createElement('div');
|
||||
caption.className = 'mood-card-caption';
|
||||
@@ -1292,6 +1323,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
try { items = JSON.parse(row?.Contenu || '[]'); } catch { items = []; }
|
||||
if (items[moodUploadTarget]) {
|
||||
items[moodUploadTarget].image = data.path;
|
||||
items[moodUploadTarget].posX = 50;
|
||||
items[moodUploadTarget].posY = 50;
|
||||
saveVisionSection('moodboard', JSON.stringify(items));
|
||||
renderMoodboard();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user