interface selection des cartes
This commit is contained in:
parent
0a4ebc4500
commit
443ea3b839
2 changed files with 166 additions and 2 deletions
|
|
@ -30,13 +30,13 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="card rules" id="cardRules" style="margin-top:16px">
|
<section class="card rules" id="cardRules" style="margin-top:16px">
|
||||||
<h3>Règles des cartes</h3>
|
<h3>Règles du jeu</h3>
|
||||||
<p>Les cartes ajoutent des effets spéciaux au-dessus des règles d'échecs classiques. Voici l'essentiel pour jouer :</p>
|
<p>Les cartes ajoutent des effets spéciaux au-dessus des règles d'échecs classiques. Voici l'essentiel pour jouer :</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>Jouer une carte :</strong> cliquez sur une carte dans votre main puis suivez l'invite (sélection d'une pièce, d'une case). Certaines cartes n'exigent pas de cible.</li>
|
<li><strong>Jouer une carte :</strong> cliquez sur une carte dans votre main puis suivez l'invite (sélection d'une pièce, d'une case). Certaines cartes n'exigent pas de cible.</li>
|
||||||
<li><strong>Carte mouvement :</strong> certaines cartes comptent déjà comme un mouvement à part entière. Il faut donc en tenir compte lors de votre tour.</li>
|
<li><strong>Carte mouvement :</strong> certaines cartes comptent déjà comme un mouvement à part entière. Il faut donc en tenir compte lors de votre tour.</li>
|
||||||
<li><strong>Durée des effets :</strong> certaines cartes sont temporaires (durée mesurée en tours), d'autres restent actives. Un tour correspond aux actions d'un seul joueur. Un cycle vaut donc pour deux tours.</li>
|
<li><strong>Durée des effets :</strong> certaines cartes sont temporaires (durée mesurée en tours), d'autres restent actives. Un tour correspond aux actions d'un seul joueur. Un cycle vaut donc pour deux tours.</li>
|
||||||
<li><strong>Fin de partie :</strong> si un roi est retiré, la partie s'arrête automatiquement et un message indique victoire/défaite ou égalité. Les joueurs peuvent choisir de rester en spectateur ou de quitter la partie.</li>
|
<li><strong>Fin de partie :</strong> si un roi est capturé, la partie s'arrête sur une victoire, une défaite ou peut-être même une égalité. Les joueurs peuvent choisir de rester en spectateur ou de quitter la partie.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p style="margin-top:8px;font-size:0.95em;color:#444">Remarque : ces règles couvrent uniquement les mécaniques des cartes — pour le jeu d'échecs standard, les mouvements habituels s'appliquent (pions, tours, cavaliers, fous, dames, rois).</p>
|
<p style="margin-top:8px;font-size:0.95em;color:#444">Remarque : ces règles couvrent uniquement les mécaniques des cartes — pour le jeu d'échecs standard, les mouvements habituels s'appliquent (pions, tours, cavaliers, fous, dames, rois).</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,12 @@
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>ChessNut - Salle d'attente</title>
|
<title>ChessNut - Salle d'attente</title>
|
||||||
<link rel="stylesheet" href="/styles/style.css">
|
<link rel="stylesheet" href="/styles/style.css">
|
||||||
|
<style>
|
||||||
|
/* small waiting-room styles for selectable cards */
|
||||||
|
.pokemon-card { position: relative; cursor: pointer; transition: transform .08s ease, box-shadow .08s ease; }
|
||||||
|
.pokemon-card.selected { border-color: #28a745; box-shadow: 0 6px 14px rgba(40,167,69,0.08); transform: translateY(-3px); }
|
||||||
|
.pokemon-card .select-mark { position: absolute; right:8px; top:8px; background:#28a745; color:#fff; border-radius:50%; width:20px;height:20px; display:flex;align-items:center;justify-content:center;font-size:12px; }
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="page">
|
<div class="page">
|
||||||
|
|
@ -35,6 +41,25 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-top:8px">
|
||||||
|
<label id="customDrawLabel" style="display:none;align-items:center;gap:8px;">
|
||||||
|
<input type="checkbox" id="customDrawToggle" />
|
||||||
|
Pioche personnalisée
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Minimal card list: shown only if Pioche personnalisée is checked -->
|
||||||
|
<div id="customCardsSection" style="display:none;margin-top:8px;border:1px solid #eee;padding:8px;border-radius:6px;background:#fff;">
|
||||||
|
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:6px">
|
||||||
|
<div style="font-weight:600">Cartes disponibles</div>
|
||||||
|
<div style="display:flex;gap:8px">
|
||||||
|
<button id="customSelectAll" class="btn-ghost">Tout sélectionner</button>
|
||||||
|
<button id="customDeselectAll" class="btn-ghost">Tout déselectionner</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="customCardsContainer" style="color:#333;padding-right:6px">(Cochez "Pioche personnalisée" pour charger la liste)</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div style="margin-top:8px">
|
<div style="margin-top:8px">
|
||||||
<button id="startBtn" style="display:none" class="btn-cta">Commencer la partie</button>
|
<button id="startBtn" style="display:none" class="btn-cta">Commencer la partie</button>
|
||||||
<button id="copyInviteBtn" style="margin-left:8px" class="btn-ghost">Copier le lien d'invitation</button>
|
<button id="copyInviteBtn" style="margin-left:8px" class="btn-ghost">Copier le lien d'invitation</button>
|
||||||
|
|
@ -63,6 +88,14 @@
|
||||||
const autoDrawToggleEl = document.getElementById('autoDrawToggle');
|
const autoDrawToggleEl = document.getElementById('autoDrawToggle');
|
||||||
const noRemiseLabelEl = document.getElementById('no-remise');
|
const noRemiseLabelEl = document.getElementById('no-remise');
|
||||||
const noRemiseToggleEl = document.getElementById('no-remiseToggle');
|
const noRemiseToggleEl = document.getElementById('no-remiseToggle');
|
||||||
|
const customDrawLabelEl = document.getElementById('customDrawLabel');
|
||||||
|
const customDrawToggleEl = document.getElementById('customDrawToggle');
|
||||||
|
|
||||||
|
// Selection state for custom draw (persisted per room)
|
||||||
|
let selectedCards = new Set();
|
||||||
|
function storageKey(){ return `customDrawSelection:${roomId}`; }
|
||||||
|
function saveSelection(){ try{ localStorage.setItem(storageKey(), JSON.stringify(Array.from(selectedCards))); }catch(_){} }
|
||||||
|
function loadSelection(){ try{ const v = localStorage.getItem(storageKey()); if(v){ const arr = JSON.parse(v); selectedCards = new Set(arr || []); } }catch(_){} }
|
||||||
|
|
||||||
function log(...args){ console.log(...args); }
|
function log(...args){ console.log(...args); }
|
||||||
|
|
||||||
|
|
@ -137,6 +170,18 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}catch(_){ }
|
}catch(_){ }
|
||||||
|
// show custom draw checkbox to host (UI-only for now)
|
||||||
|
try{
|
||||||
|
if(customDrawLabelEl && customDrawToggleEl){
|
||||||
|
if(myPlayerId && roomHostId && myPlayerId === roomHostId){
|
||||||
|
customDrawLabelEl.style.display = 'inline-flex';
|
||||||
|
// do not override the user's local choice; leave checked as-is
|
||||||
|
customDrawToggleEl.disabled = false;
|
||||||
|
} else {
|
||||||
|
customDrawLabelEl.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}catch(_){ }
|
||||||
});
|
});
|
||||||
|
|
||||||
// register game started handler early so we don't miss the broadcast
|
// register game started handler early so we don't miss the broadcast
|
||||||
|
|
@ -195,6 +240,18 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}catch(_){ }
|
}catch(_){ }
|
||||||
|
// show custom draw checkbox to host on initial fetch as well
|
||||||
|
try{
|
||||||
|
if(customDrawLabelEl && customDrawToggleEl){
|
||||||
|
if(myPlayerId && roomHostId && myPlayerId === roomHostId){
|
||||||
|
customDrawLabelEl.style.display = 'inline-flex';
|
||||||
|
// preserve local UI state for this control; do not force-assign checked
|
||||||
|
customDrawToggleEl.disabled = false;
|
||||||
|
} else {
|
||||||
|
customDrawLabelEl.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}catch(_){ }
|
||||||
}catch(_){ }
|
}catch(_){ }
|
||||||
if(roomInfo && roomInfo.status === 'playing'){
|
if(roomInfo && roomInfo.status === 'playing'){
|
||||||
// server already marked room as playing; redirect immediately
|
// server already marked room as playing; redirect immediately
|
||||||
|
|
@ -243,6 +300,113 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Custom draw toggle (minimal behavior)
|
||||||
|
// When checked, show a new section and fetch /cards to display a simple list.
|
||||||
|
if(customDrawToggleEl){
|
||||||
|
const customCardsSection = document.getElementById('customCardsSection');
|
||||||
|
const customCardsContainer = document.getElementById('customCardsContainer');
|
||||||
|
const customSelectAllBtn = document.getElementById('customSelectAll');
|
||||||
|
const customDeselectAllBtn = document.getElementById('customDeselectAll');
|
||||||
|
customDrawToggleEl.addEventListener('change', async (ev)=>{
|
||||||
|
const checked = !!customDrawToggleEl.checked;
|
||||||
|
log('custom-draw toggled (minimal)', { checked });
|
||||||
|
if(customCardsSection) customCardsSection.style.display = checked ? 'block' : 'none';
|
||||||
|
if(checked){
|
||||||
|
// fetch card list and render simple names
|
||||||
|
if(customCardsContainer){
|
||||||
|
customCardsContainer.textContent = 'Chargement...';
|
||||||
|
try{
|
||||||
|
const r = await fetch('/cards');
|
||||||
|
const data = await r.json();
|
||||||
|
const cards = Array.isArray(data) ? data : (data && Array.isArray(data.cards) ? data.cards : []);
|
||||||
|
if(cards.length === 0){ customCardsContainer.textContent = 'Aucune carte trouvée.'; return; }
|
||||||
|
// restore previous selection for this room
|
||||||
|
try{ loadSelection(); }catch(_){}
|
||||||
|
const grid = document.createElement('div');
|
||||||
|
grid.style.display = 'grid';
|
||||||
|
grid.style.gridTemplateColumns = 'repeat(auto-fill,minmax(220px,1fr))';
|
||||||
|
grid.style.gap = '10px';
|
||||||
|
|
||||||
|
cards.forEach(c => {
|
||||||
|
// create the same visual structure as in game.renderHands -> .pokemon-card
|
||||||
|
const cardEl = document.createElement('div');
|
||||||
|
cardEl.className = 'pokemon-card';
|
||||||
|
const cardId = (c.id || c.cardId || c.name || c.title || JSON.stringify(c)).toString();
|
||||||
|
cardEl.dataset.cardId = cardId;
|
||||||
|
|
||||||
|
const top = document.createElement('div'); top.className = 'pokemon-card-top';
|
||||||
|
const art = document.createElement('div'); art.className = 'pokemon-card-art'; art.textContent = '';
|
||||||
|
try{
|
||||||
|
const cid = (c.cardId || c.id || '').toString().toLowerCase();
|
||||||
|
const titleRaw = (c.title || '').toString().toLowerCase();
|
||||||
|
let imgSrc = null;
|
||||||
|
if(cid === 'adoubement' || titleRaw.indexOf('adoub') !== -1) imgSrc = '/assets/img/cards/adoubement.png';
|
||||||
|
if(cid === 'folie' || titleRaw.indexOf('folie') !== -1 || titleRaw.indexOf('fou') !== -1) imgSrc = '/assets/img/cards/folie.png';
|
||||||
|
if(cid === 'fortification' || titleRaw.indexOf('fortification') !== -1 || titleRaw.indexOf('fortif') !== -1) imgSrc = '/assets/img/cards/fortification.png';
|
||||||
|
if(imgSrc){ const img = document.createElement('img'); img.src = imgSrc; img.alt = c.title || cid || 'card'; img.className = 'card-art-img'; img.style.width='100%'; img.style.height='100%'; img.style.objectFit='cover'; art.appendChild(img); }
|
||||||
|
}catch(e){}
|
||||||
|
top.appendChild(art);
|
||||||
|
|
||||||
|
const mid = document.createElement('div'); mid.className = 'pokemon-card-mid';
|
||||||
|
const h = document.createElement('div'); h.className = 'pokemon-card-title'; h.textContent = c.name || c.title || c.cardId || c.id || 'Carte';
|
||||||
|
const p = document.createElement('div'); p.className = 'pokemon-card-desc'; p.textContent = c.short || c.description || '';
|
||||||
|
mid.appendChild(h); mid.appendChild(p);
|
||||||
|
|
||||||
|
const footer = document.createElement('div'); footer.className = 'pokemon-card-footer';
|
||||||
|
|
||||||
|
// add a small select mark (hidden unless selected)
|
||||||
|
const selectMark = document.createElement('div'); selectMark.className = 'select-mark'; selectMark.textContent = '✓'; selectMark.style.display = 'none';
|
||||||
|
cardEl.appendChild(selectMark);
|
||||||
|
|
||||||
|
cardEl.appendChild(top); cardEl.appendChild(mid); cardEl.appendChild(footer);
|
||||||
|
|
||||||
|
// apply selection state if previously selected
|
||||||
|
if(selectedCards.has(cardId)){
|
||||||
|
cardEl.classList.add('selected');
|
||||||
|
selectMark.style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
// click toggles selection (no other action in waiting room)
|
||||||
|
cardEl.addEventListener('click', (ev)=>{
|
||||||
|
ev.stopPropagation();
|
||||||
|
if(selectedCards.has(cardId)){
|
||||||
|
selectedCards.delete(cardId);
|
||||||
|
cardEl.classList.remove('selected');
|
||||||
|
selectMark.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
selectedCards.add(cardId);
|
||||||
|
cardEl.classList.add('selected');
|
||||||
|
selectMark.style.display = 'flex';
|
||||||
|
}
|
||||||
|
saveSelection();
|
||||||
|
});
|
||||||
|
|
||||||
|
grid.appendChild(cardEl);
|
||||||
|
});
|
||||||
|
customCardsContainer.innerHTML = '';
|
||||||
|
customCardsContainer.appendChild(grid);
|
||||||
|
}catch(e){ customCardsContainer.textContent = 'Erreur lors du chargement des cartes.'; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// wire up select all / deselect all
|
||||||
|
function setAllVisible(yes){
|
||||||
|
// find card elements in the container grid
|
||||||
|
const els = customCardsContainer ? customCardsContainer.querySelectorAll('.pokemon-card') : [];
|
||||||
|
els.forEach(el => {
|
||||||
|
const id = el.dataset.cardId;
|
||||||
|
const mark = el.querySelector('.select-mark');
|
||||||
|
if(yes){ selectedCards.add(id); el.classList.add('selected'); if(mark) mark.style.display = 'flex'; }
|
||||||
|
else { selectedCards.delete(id); el.classList.remove('selected'); if(mark) mark.style.display = 'none'; }
|
||||||
|
});
|
||||||
|
saveSelection();
|
||||||
|
}
|
||||||
|
if(customSelectAllBtn) customSelectAllBtn.addEventListener('click', (e)=>{ e.preventDefault(); setAllVisible(true); });
|
||||||
|
if(customDeselectAllBtn) customDeselectAllBtn.addEventListener('click', (e)=>{ e.preventDefault(); setAllVisible(false); });
|
||||||
|
// ensure initial state: hide unless checkbox is already checked
|
||||||
|
if(customDrawToggleEl.checked && customCardsSection){ customCardsSection.style.display = 'block'; customDrawToggleEl.dispatchEvent(new Event('change')); }
|
||||||
|
}
|
||||||
|
|
||||||
// Copy invite link (hidden link, no display)
|
// Copy invite link (hidden link, no display)
|
||||||
document.getElementById('copyInviteBtn').addEventListener('click', async ()=>{
|
document.getElementById('copyInviteBtn').addEventListener('click', async ()=>{
|
||||||
const link = `${window.location.origin}/waiting.html?roomId=${encodeURIComponent(roomId)}`;
|
const link = `${window.location.origin}/waiting.html?roomId=${encodeURIComponent(roomId)}`;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue