This commit is contained in:
Axce 2026-01-02 04:52:37 +01:00
parent 4e3e22b175
commit d8f3fc991d
2 changed files with 243 additions and 93 deletions

View File

@ -340,11 +340,11 @@
<h2>Charger un dictionnaire</h2> <h2>Charger un dictionnaire</h2>
<div class="form-group"> <div class="form-group">
<input type="text" id="dictionaryUrl" placeholder="Ou entrez une URL pour charger un dictionnaire (raw .txt)" style="width:100%; padding:8px 12px; border:1px solid #d1d5db; border-radius:4px; margin-bottom:8px;"> <input type="text" id="dictionaryUrl" placeholder="Ou entrez une URL pour charger un dictionnaire (raw .txt)" style="width:100%; padding:8px 12px; border:1px solid #d1d5db; border-radius:4px; margin-bottom:8px;">
<div style="margin-top:8px;margin-bottom:8px;"><button class="btn-primary" onclick="loadDictionaryFromUrl()">Charger depuis URL</button></div>
<textarea id="dictionaryText" rows="5" placeholder="Collez le contenu du dictionnaire ici (format: mot: définition)"></textarea> <textarea id="dictionaryText" rows="5" placeholder="Collez le contenu du dictionnaire ici (format: mot: définition)"></textarea>
</div> </div>
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:8px;"> <div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:8px;">
<button class="btn-success" onclick="loadDictionaryFromTextarea()">Charger depuis zone</button> <button class="btn-success" onclick="loadDictionaryFromTextarea()">Charger depuis zone</button>
<button class="btn-primary" onclick="loadDictionaryFromUrl()">Charger depuis URL</button>
</div> </div>
</div> </div>
@ -473,37 +473,36 @@
if (cell.type === 'definition') { if (cell.type === 'definition') {
// Determine associated words in both directions (starting at the neighboring letter cell) // Determine associated words in both directions (starting at the neighboring letter cell)
const wordInfoH = getWordAt(row, col + 1, 'horizontal'); // Special rules for top row and left column mappings
const wordInfoV = getWordAt(row + 1, col, 'vertical'); let wordInfoH;
let wordInfoV;
let targetWord = null; // Horizontal definition normally maps to word starting at (row, col+1) horizontally,
let targetDir = null; // but if this definition is on the top row, its horizontal definition maps to the vertical word starting at (row, col+1).
if (row === 0) {
if (half === 'h') { wordInfoH = getWordAt(row, col + 1, 'vertical');
targetWord = wordInfoH;
targetDir = 'horizontal';
} else if (half === 'v') {
targetWord = wordInfoV;
targetDir = 'vertical';
} else { } else {
// if no half specified, pick the direction that actually has a word wordInfoH = getWordAt(row, col + 1, 'horizontal');
if (wordInfoH && wordInfoH.word) {
targetWord = wordInfoH;
targetDir = 'horizontal';
} else if (wordInfoV && wordInfoV.word) {
targetWord = wordInfoV;
targetDir = 'vertical';
}
} }
if (targetWord && targetWord.word) { // Vertical definition normally maps to word starting at (row+1, col) vertically,
const normalized = normalizeWord(targetWord.word); // but if this definition is on the first column, its vertical definition maps to the horizontal word starting at (row+1, col).
const defs = dictionaries[normalized] || []; if (col === 0) {
const uniqueDefs = [...new Set(defs)]; wordInfoV = getWordAt(row + 1, col, 'horizontal');
} else {
currentModal = { row, col, half, direction: targetDir }; wordInfoV = getWordAt(row + 1, col, 'vertical');
showDefinitions(targetWord.word, uniqueDefs);
} }
// Build words and definitions for both sides (may be empty)
const wordH = (wordInfoH && wordInfoH.word) ? wordInfoH.word : null;
const wordV = (wordInfoV && wordInfoV.word) ? wordInfoV.word : null;
const defsH = wordH ? ([...new Set(dictionaries[normalizeWord(wordH)] || [])]) : [];
const defsV = wordV ? ([...new Set(dictionaries[normalizeWord(wordV)] || [])]) : [];
// If half specified, still show modal but prioritize that half for selection via UI
currentModal = { row, col, half };
showDefinitionModal(row, col, wordH, defsH, wordV, defsV);
} else { } else {
if (activeCell && activeCell.row === row && activeCell.col === col) { if (activeCell && activeCell.row === row && activeCell.col === col) {
orientation = orientation === 'horizontal' ? 'vertical' : 'horizontal'; orientation = orientation === 'horizontal' ? 'vertical' : 'horizontal';
@ -527,44 +526,110 @@
renderGrid(); renderGrid();
} }
// Affiche les définitions // Affiche le modal de sélection des définitions, en deux parties (horizontal / vertical)
function showDefinitions(word, definitions) { function showDefinitionModal(row, col, wordH, defsH, wordV, defsV) {
document.getElementById('modalTitle').textContent = `Définitions pour "${word}"`; currentModal = { row, col };
document.getElementById('modalTitle').textContent = `Définitions`;
const list = document.getElementById('definitionList'); const list = document.getElementById('definitionList');
list.innerHTML = ''; list.innerHTML = '';
if (definitions.length === 0) { const sectionH = document.createElement('div');
list.innerHTML = '<p style="color: #6b7280;">Aucune définition disponible pour ce mot.</p>'; const titleH = document.createElement('h4');
titleH.textContent = 'Mot horizontal';
sectionH.appendChild(titleH);
if (!wordH) {
const p = document.createElement('p');
p.style.color = '#6b7280';
p.textContent = 'Aucun mot associé';
sectionH.appendChild(p);
} else { } else {
definitions.forEach(def => { const wordP = document.createElement('p');
wordP.style.fontWeight = '600';
wordP.textContent = wordH;
sectionH.appendChild(wordP);
if (defsH.length === 0) {
const p = document.createElement('p');
p.style.color = '#6b7280';
p.textContent = 'Aucune définition disponible pour ce mot.';
sectionH.appendChild(p);
} else {
defsH.forEach(def => {
const btn = document.createElement('button'); const btn = document.createElement('button');
btn.className = 'definition-item'; btn.className = 'definition-item';
btn.textContent = def; btn.textContent = def;
btn.onclick = () => selectDefinition(def); btn.onclick = () => {
list.appendChild(btn); grid[row][col].definitionH = def;
closeModal();
renderGrid();
};
sectionH.appendChild(btn);
}); });
} }
}
const sectionV = document.createElement('div');
const titleV = document.createElement('h4');
titleV.textContent = 'Mot vertical';
sectionV.appendChild(titleV);
if (!wordV) {
const p = document.createElement('p');
p.style.color = '#6b7280';
p.textContent = 'Aucun mot associé';
sectionV.appendChild(p);
} else {
const wordP = document.createElement('p');
wordP.style.fontWeight = '600';
wordP.textContent = wordV;
sectionV.appendChild(wordP);
if (defsV.length === 0) {
const p = document.createElement('p');
p.style.color = '#6b7280';
p.textContent = 'Aucune définition disponible pour ce mot.';
sectionV.appendChild(p);
} else {
defsV.forEach(def => {
const btn = document.createElement('button');
btn.className = 'definition-item';
btn.textContent = def;
btn.onclick = () => {
grid[row][col].definitionV = def;
closeModal();
renderGrid();
};
sectionV.appendChild(btn);
});
}
}
// Delete button: transforms case into letter empty
const deleteBtn = document.createElement('button');
deleteBtn.className = 'btn-danger';
deleteBtn.style.marginTop = '12px';
deleteBtn.textContent = 'Supprimer (transformer en case lettre)';
deleteBtn.onclick = () => {
const cell = grid[row][col];
const hasDefs = !!(cell.definitionH || cell.definitionV);
if (hasDefs) {
if (!confirm('La case contient des définitions. Confirmer la transformation en case lettre vide ?')) return;
}
cell.type = 'letter';
cell.letter = '';
cell.definitionH = '';
cell.definitionV = '';
closeModal();
renderGrid();
};
// Append sections to list
list.appendChild(sectionH);
list.appendChild(sectionV);
list.appendChild(deleteBtn);
document.getElementById('definitionModal').classList.add('show'); document.getElementById('definitionModal').classList.add('show');
} }
// Sélectionne une définition
function selectDefinition(definition) {
if (!currentModal) return;
const { row, col, direction } = currentModal;
if (direction === 'horizontal') {
grid[row][col].definitionH = definition;
} else {
grid[row][col].definitionV = definition;
}
closeModal();
renderGrid();
}
// Ferme le modal
function closeModal() { function closeModal() {
document.getElementById('definitionModal').classList.remove('show'); document.getElementById('definitionModal').classList.remove('show');
currentModal = null; currentModal = null;
@ -770,12 +835,10 @@
const halfH = document.createElement('div'); const halfH = document.createElement('div');
halfH.className = 'definition-half'; halfH.className = 'definition-half';
halfH.textContent = cell.definitionH; halfH.textContent = cell.definitionH;
halfH.innerHTML += '<svg class="arrow" width="8" height="8" viewBox="0 0 8 8"><path d="M 2 4 L 6 4 M 6 4 L 4 2 M 6 4 L 4 6" stroke="black" stroke-width="1" fill="none"/></svg>';
const halfV = document.createElement('div'); const halfV = document.createElement('div');
halfV.className = 'definition-half'; halfV.className = 'definition-half';
halfV.textContent = cell.definitionV; halfV.textContent = cell.definitionV;
halfV.innerHTML += '<svg class="arrow" width="8" height="8" viewBox="0 0 8 8"><path d="M 4 2 L 4 6 M 4 6 L 2 4 M 4 6 L 6 4" stroke="black" stroke-width="1" fill="none"/></svg>';
split.appendChild(halfH); split.appendChild(halfH);
split.appendChild(halfV); split.appendChild(halfV);
@ -793,15 +856,6 @@
const text = document.createElement('div'); const text = document.createElement('div');
text.className = 'definition-text'; text.className = 'definition-text';
text.textContent = cell.definitionH || cell.definitionV; text.textContent = cell.definitionH || cell.definitionV;
if (cell.definitionH || cell.definitionV) {
if (cell.definitionV) {
text.innerHTML += '<svg class="arrow" width="8" height="8" viewBox="0 0 8 8"><path d="M 4 2 L 4 6 M 4 6 L 2 4 M 4 6 L 6 4" stroke="black" stroke-width="1" fill="none"/></svg>';
} else {
text.innerHTML += '<svg class="arrow" width="8" height="8" viewBox="0 0 8 8"><path d="M 2 4 L 6 4 M 6 4 L 4 2 M 6 4 L 4 6" stroke="black" stroke-width="1" fill="none"/></svg>';
}
}
cellEl.appendChild(text); cellEl.appendChild(text);
} }
} }
@ -812,6 +866,10 @@
cellEl.ondblclick = () => handleCellDoubleClick(rowIndex, colIndex); cellEl.ondblclick = () => handleCellDoubleClick(rowIndex, colIndex);
rowEl.appendChild(cellEl); rowEl.appendChild(cellEl);
// store reference for later arrow placement
if (!window._cellEls) window._cellEls = [];
if (!window._cellEls[rowIndex]) window._cellEls[rowIndex] = [];
window._cellEls[rowIndex][colIndex] = cellEl;
}); });
// Bouton d'ajout de colonne // Bouton d'ajout de colonne
@ -833,6 +891,90 @@
addRowLine.appendChild(addBtn); addRowLine.appendChild(addBtn);
} }
gridEl.appendChild(addRowLine); gridEl.appendChild(addRowLine);
// After grid built, place arrows in the first-letter cells based on adjacent definition cells
// Clear any previous arrows
if (window._cellEls) {
for (let r = 0; r < grid.length; r++) {
for (let c = 0; c < grid[0].length; c++) {
const el = window._cellEls[r] && window._cellEls[r][c];
if (el) {
const prev = el.querySelector('.def-arrow');
if (prev) prev.remove();
}
}
}
// For each definition cell, compute target first-letter cell and append arrow there
for (let dr = 0; dr < grid.length; dr++) {
for (let dc = 0; dc < grid[0].length; dc++) {
const defCell = grid[dr][dc];
if (defCell.type !== 'definition') continue;
// Horizontal-definition mapping
if (defCell.definitionH) {
const tr = dr;
const tc = dc + 1;
if (tr >= 0 && tr < grid.length && tc >= 0 && tc < grid[0].length) {
const targetEl = window._cellEls[tr] && window._cellEls[tr][tc];
if (targetEl) {
const arrow = document.createElement('div');
arrow.className = 'def-arrow';
// special top-row: curve down
if (dr === 0) {
arrow.innerHTML = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1" class="arrow-down-curved"><path d="M12 0 C12 0 12 6 12 6 L12 16"/><path d="M12 16 L9 13 M12 16 L15 13"/></svg>';
arrow.style.position = 'absolute';
arrow.style.top = '2px';
arrow.style.left = '50%';
arrow.style.transform = 'translateX(-50%)';
} else {
// left-pointing arrow near left border
arrow.innerHTML = '<svg width="12" height="12" viewBox="0 0 8 8" fill="none" stroke="black" stroke-width="1"><path d="M6 1 L2 4 L6 7"/></svg>';
arrow.style.position = 'absolute';
arrow.style.left = '4px';
arrow.style.top = '50%';
arrow.style.transform = 'translateY(-50%)';
}
arrow.style.pointerEvents = 'none';
arrow.classList.add('def-arrow');
targetEl.appendChild(arrow);
}
}
}
// Vertical-definition mapping
if (defCell.definitionV) {
const tr = dr + 1;
const tc = dc;
if (tr >= 0 && tr < grid.length && tc >= 0 && tc < grid[0].length) {
const targetEl = window._cellEls[tr] && window._cellEls[tr][tc];
if (targetEl) {
const arrow = document.createElement('div');
arrow.className = 'def-arrow';
if (dc === 0) {
// left-edge special: curve right
arrow.innerHTML = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1"><path d="M0 12 C0 12 6 12 6 12 L16 12"/><path d="M16 12 L13 9 M16 12 L13 15"/></svg>';
arrow.style.position = 'absolute';
arrow.style.left = '2px';
arrow.style.top = '50%';
arrow.style.transform = 'translateY(-50%)';
} else {
// up-pointing arrow near top border
arrow.innerHTML = '<svg width="12" height="12" viewBox="0 0 8 8" fill="none" stroke="black" stroke-width="1"><path d="M1 6 L4 2 L7 6"/></svg>';
arrow.style.position = 'absolute';
arrow.style.top = '4px';
arrow.style.left = '50%';
arrow.style.transform = 'translateX(-50%)';
}
arrow.style.pointerEvents = 'none';
arrow.classList.add('def-arrow');
targetEl.appendChild(arrow);
}
}
}
}
}
}
} }
// Gestion du clavier // Gestion du clavier

8
mots.txt Normal file
View File

@ -0,0 +1,8 @@
chat: doux mais violent
chat: mais violent
plante: assoiffée de calme
président: affiné un mandat
président: affiné pour un mandat
arc-en-ciel: couleur de la pluie
chômeur: cauchemar de libéral
ut: en rut sans en avoir lair