diff --git a/public/index.html b/public/index.html
index 023c254..f816164 100644
--- a/public/index.html
+++ b/public/index.html
@@ -245,6 +245,33 @@
.vision-quote { font-family: 'Playfair Display', serif; font-size: 17px; font-style: italic; color: var(--text-muted); line-height: 1.7; border-left: 3px solid var(--accent); padding-left: 16px; margin: 12px 0; }
+ /* ── Editable Vision ── */
+ [contenteditable] { outline: none; border-radius: 6px; transition: background 0.15s, box-shadow 0.15s; cursor: text; }
+ [contenteditable]:hover { background: rgba(138,112,96,0.06); }
+ [contenteditable]:focus { background: rgba(138,112,96,0.08); box-shadow: 0 0 0 2px var(--accent); }
+ .dark [contenteditable]:hover { background: rgba(196,168,130,0.08); }
+ .dark [contenteditable]:focus { background: rgba(196,168,130,0.1); }
+ [contenteditable].placeholder { color: var(--text-light); font-style: italic; }
+ .vision-saving { position: fixed; bottom: 20px; right: 20px; background: var(--accent); color: var(--bg); padding: 8px 18px; border-radius: 8px; font-size: 13px; font-family: 'Lora', serif; opacity: 0; transition: opacity 0.3s; z-index: 200; pointer-events: none; }
+ .vision-saving.show { opacity: 1; }
+
+ .tag-remove { display: none; margin-left: 4px; cursor: pointer; font-size: 11px; opacity: 0.5; }
+ .tag:hover .tag-remove { display: inline; }
+ .tag-remove:hover { opacity: 1; }
+ .tag-add { padding: 8px 16px; border-radius: 20px; font-size: 14px; border: 1px dashed var(--border); background: transparent; color: var(--text-hint); cursor: pointer; font-family: 'Lora', serif; }
+ .tag-add:hover { background: var(--bg-alt); color: var(--text); }
+
+ .list-item-text { flex: 1; }
+ .list-item-remove { display: none; cursor: pointer; color: var(--text-hint); font-size: 14px; margin-left: 8px; }
+ .list-item:hover .list-item-remove { display: inline; }
+ .list-item-remove:hover { color: #8b3030; }
+ .list-add { padding: 6px 0; font-size: 14px; color: var(--text-hint); cursor: pointer; display: flex; align-items: center; gap: 6px; }
+ .list-add:hover { color: var(--accent); }
+
+ .inspi-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; }
+ .inspi-card:hover .inspi-card-remove { display: flex; }
+ .inspi-card { position: relative; }
+
/* Responsive */
@media (max-width: 768px) {
.sidebar { display: none; }
@@ -313,44 +340,25 @@
-
Les trois mots qui définissent votre univers...
-
Image d'ambiance — cliquer pour remplacer
+
+
L'histoire en une phrase
-
Cliquez pour écrire la phrase qui résume votre histoire...
-
Modifiable à tout moment par les deux auteurs
+
+
Synopsis
-
Développez ici le résumé de votre histoire en quelques paragraphes — les grandes lignes, les enjeux, l'arc principal...
-
Peut évoluer au fil de l'écriture
+
Ambiance, époque, rythme
-
- Ambiance sombre
- Époque fictive
- Rythme lent puis rapide
- Aventure
- Drame
- Suspense
-
-
-
-
-
Moodboard
-
-
Ambiance nuit
-
Architecture
-
Forêt
-
Costumes
-
+
-
+
@@ -358,47 +366,24 @@
On veut
-
Des personnages profonds
-
Des retournements crédibles
-
Un univers immersif
+
On ne veut pas
-
Un ton Young Adult
-
Des coïncidences faciles
-
De la violence gratuite
+
Inspirations et références
-
-
-
-
-
🎨
-
Tableau C
-
Peinture
-
-
-
+
+ Enregistré
@@ -836,6 +821,273 @@ document.addEventListener('keydown', e => {
});
loadData();
+
+// ══════════════════════════════════════
+// ══ VISION — Editable sections ══
+// ══════════════════════════════════════
+
+let visionData = {}; // section -> {Id, Contenu}
+
+const PLACEHOLDERS = {
+ 'trois-mots': 'Les trois mots qui définissent votre univers...',
+ 'hero-sous-titre': 'Sous-titre ou accroche...',
+ 'phrase': 'Cliquez pour écrire la phrase qui résume votre histoire...',
+ 'phrase-detail': 'Un détail, une nuance, une précision...',
+ 'synopsis': 'Développez ici le résumé de votre histoire en quelques paragraphes...',
+};
+
+const FIELD_MAP = {
+ 'trois-mots': 'v-trois-mots',
+ 'hero-sous-titre': 'v-hero-sub',
+ 'phrase': 'v-phrase',
+ 'phrase-detail': 'v-phrase-detail',
+ 'synopsis': 'v-synopsis',
+};
+
+function showSaved() {
+ const el = document.getElementById('vision-saving');
+ el.classList.add('show');
+ setTimeout(() => el.classList.remove('show'), 1200);
+}
+
+async function saveVisionSection(section, contenu) {
+ const row = visionData[section];
+ if (!row) return;
+ await fetch(`/api/vision/${row.Id}`, {
+ method: 'PATCH',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ Contenu: contenu }),
+ });
+ row.Contenu = contenu;
+ showSaved();
+}
+
+function setupEditable(elId, section) {
+ const el = document.getElementById(elId);
+ if (!el) return;
+ const placeholder = PLACEHOLDERS[section] || '';
+
+ function updatePlaceholder() {
+ const text = el.textContent.trim();
+ if (!text || text === placeholder) {
+ el.classList.add('placeholder');
+ if (!text) el.textContent = placeholder;
+ } else {
+ el.classList.remove('placeholder');
+ }
+ }
+
+ el.addEventListener('focus', () => {
+ if (el.classList.contains('placeholder')) {
+ el.textContent = '';
+ el.classList.remove('placeholder');
+ }
+ });
+
+ el.addEventListener('blur', () => {
+ const text = el.textContent.trim();
+ if (text && text !== placeholder) {
+ const row = visionData[section];
+ if (row && row.Contenu !== text) {
+ saveVisionSection(section, text);
+ }
+ } else {
+ el.textContent = placeholder;
+ el.classList.add('placeholder');
+ const row = visionData[section];
+ if (row && row.Contenu) {
+ saveVisionSection(section, '');
+ }
+ }
+ });
+
+ // Prevent Enter creating divs in single-line fields
+ if (section !== 'synopsis') {
+ el.addEventListener('keydown', e => {
+ if (e.key === 'Enter') { e.preventDefault(); el.blur(); }
+ });
+ }
+
+ updatePlaceholder();
+}
+
+// ── Tags ──
+const TAG_TYPES = ['ambiance', 'epoque', 'rythme', 'genre', 'theme'];
+
+function renderTags() {
+ const container = document.getElementById('v-tags');
+ const row = visionData['tags'];
+ let tags = [];
+ try { tags = JSON.parse(row?.Contenu || '[]'); } catch { tags = []; }
+
+ container.innerHTML = '';
+ tags.forEach((t, i) => {
+ const span = document.createElement('span');
+ span.className = `tag ${t.type || 'ambiance'}`;
+ span.innerHTML = `${esc(t.label)}×`;
+ container.appendChild(span);
+ });
+
+ const addBtn = document.createElement('button');
+ addBtn.className = 'tag-add';
+ addBtn.textContent = '+ Ajouter';
+ addBtn.onclick = () => {
+ const label = prompt('Nouveau tag :');
+ if (!label) return;
+ const type = prompt('Type (ambiance, epoque, rythme, genre, theme) :', 'genre') || 'genre';
+ tags.push({ label, type });
+ saveVisionSection('tags', JSON.stringify(tags));
+ renderTags();
+ };
+ container.appendChild(addBtn);
+}
+
+function removeTag(index) {
+ const row = visionData['tags'];
+ let tags = [];
+ try { tags = JSON.parse(row?.Contenu || '[]'); } catch { return; }
+ tags.splice(index, 1);
+ saveVisionSection('tags', JSON.stringify(tags));
+ renderTags();
+}
+
+// ── Lists (on-veut / on-ne-veut-pas) ──
+function renderList(section, containerId, cssClass) {
+ const container = document.getElementById(containerId);
+ const row = visionData[section];
+ let items = [];
+ try { items = JSON.parse(row?.Contenu || '[]'); } catch { items = []; }
+
+ container.innerHTML = '';
+ items.forEach((text, i) => {
+ const div = document.createElement('div');
+ div.className = `list-item ${cssClass}`;
+ div.innerHTML = `${esc(text)}×`;
+ const textEl = div.querySelector('.list-item-text');
+ textEl.addEventListener('blur', () => {
+ const newText = textEl.textContent.trim();
+ if (newText && newText !== items[i]) {
+ items[i] = newText;
+ saveVisionSection(section, JSON.stringify(items));
+ }
+ });
+ textEl.addEventListener('keydown', e => {
+ if (e.key === 'Enter') { e.preventDefault(); textEl.blur(); }
+ });
+ container.appendChild(div);
+ });
+
+ const addEl = document.createElement('div');
+ addEl.className = 'list-add';
+ addEl.innerHTML = '+ Ajouter';
+ addEl.onclick = () => {
+ const text = prompt('Nouvel élément :');
+ if (!text) return;
+ items.push(text);
+ saveVisionSection(section, JSON.stringify(items));
+ renderList(section, containerId, cssClass);
+ };
+ container.appendChild(addEl);
+}
+
+function removeListItem(section, containerId, cssClass, index) {
+ const row = visionData[section];
+ let items = [];
+ try { items = JSON.parse(row?.Contenu || '[]'); } catch { return; }
+ items.splice(index, 1);
+ saveVisionSection(section, JSON.stringify(items));
+ renderList(section, containerId, cssClass);
+}
+
+// ── Inspirations ──
+const INSPI_ICONS = { 'Livre': '\u{1F4D6}', 'Film': '\u{1F3AC}', 'Peinture': '\u{1F3A8}', 'Musique': '\u{1F3B5}', 'Série': '\u{1F4FA}', 'Jeu': '\u{1F3AE}', 'Autre': '\u{2728}' };
+
+function renderInspirations() {
+ const container = document.getElementById('v-inspirations');
+ const row = visionData['inspirations'];
+ let items = [];
+ try { items = JSON.parse(row?.Contenu || '[]'); } catch { items = []; }
+
+ container.innerHTML = '';
+ items.forEach((item, i) => {
+ const card = document.createElement('div');
+ card.className = 'inspi-card';
+ card.innerHTML = `
+
+ ${item.icon || INSPI_ICONS[item.type] || '\u{2728}'}
+ ${esc(item.title)}
+ ${esc(item.type)}
+ `;
+ const titleEl = card.querySelector('.inspi-title');
+ titleEl.addEventListener('blur', () => {
+ const newTitle = titleEl.textContent.trim();
+ if (newTitle && newTitle !== items[i].title) {
+ items[i].title = newTitle;
+ saveVisionSection('inspirations', JSON.stringify(items));
+ }
+ });
+ titleEl.addEventListener('keydown', e => {
+ if (e.key === 'Enter') { e.preventDefault(); titleEl.blur(); }
+ });
+ container.appendChild(card);
+ });
+
+ const addCard = document.createElement('div');
+ addCard.className = 'inspi-card';
+ addCard.style.cssText = 'border:1px dashed var(--border); background:transparent; display:flex; flex-direction:column; align-items:center; justify-content:center; cursor:pointer;';
+ addCard.innerHTML = '+
Ajouter
';
+ addCard.onclick = () => {
+ const title = prompt('Titre de la référence :');
+ if (!title) return;
+ const type = prompt('Type (Livre, Film, Peinture, Musique, Série, Jeu, Autre) :', 'Livre') || 'Livre';
+ items.push({ title, type, icon: INSPI_ICONS[type] || '\u{2728}' });
+ saveVisionSection('inspirations', JSON.stringify(items));
+ renderInspirations();
+ };
+ container.appendChild(addCard);
+}
+
+function removeInspiration(index) {
+ const row = visionData['inspirations'];
+ let items = [];
+ try { items = JSON.parse(row?.Contenu || '[]'); } catch { return; }
+ items.splice(index, 1);
+ saveVisionSection('inspirations', JSON.stringify(items));
+ renderInspirations();
+}
+
+// ── Load Vision data ──
+async function loadVision() {
+ try {
+ const res = await fetch('/api/vision');
+ if (res.status === 401) return;
+ const data = await res.json();
+ const rows = data.list || [];
+
+ // Index by section
+ visionData = {};
+ rows.forEach(r => { visionData[r.Section] = r; });
+
+ // Populate text fields
+ for (const [section, elId] of Object.entries(FIELD_MAP)) {
+ const el = document.getElementById(elId);
+ if (!el) continue;
+ const contenu = visionData[section]?.Contenu || '';
+ el.textContent = contenu || PLACEHOLDERS[section] || '';
+ setupEditable(elId, section);
+ }
+
+ // Render structured sections
+ renderTags();
+ renderList('on-veut', 'v-on-veut', 'yes');
+ renderList('on-ne-veut-pas', 'v-on-ne-veut-pas', 'no');
+ renderInspirations();
+ } catch (e) {
+ console.error('Vision load error:', e);
+ }
+}
+
+loadVision();