v2.7.1 : encyclopedie - section Templates + menu de nav + scroll-to-top

treasures.js : charge /api/reference/templates (TEMPLATES_DB) ; section
'Templates de drop' (templateCard reutilise GEAR_MOD_LABELS) ; menu .tz-nav
collant avec ancres vers chaque section (ids tz-sec-*) ; bouton flottant
#tz-top (remonter), visible apres 400px de scroll. window.scrollTo au lieu
du scrollTop d'element (la fenetre scrolle, .screen n'a pas d'overflow).
CSS : .tz-nav sticky, .tz-top, scroll-margin-top sur .tz-section.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
perco
2026-06-14 08:46:32 +02:00
co-authored by Claude Opus 4.8
parent 00a4669928
commit 48145f6977
5 changed files with 104 additions and 8 deletions
+5
View File
@@ -234,6 +234,11 @@ non affilié au jeu original de Mountyhall SARL.
## Versions ## Versions
- **2.7.1** (2026-06-14) — Encyclopédie « Trésors du Hall » : nouvelle section
**✨ Templates** (les 19 templates de drop, lus dans la base), un **menu de
navigation** collant en haut pour sauter directement à chaque section (Potions,
Parchemins, Équipement, Templates) et un bouton flottant **« remonter en
haut »** qui apparaît dès qu'on a défilé — la page étant devenue longue.
- **2.7.0** (2026-06-14) — **Templates de drop** façon MountyHall. 19 suffixes - **2.7.0** (2026-06-14) — **Templates de drop** façon MountyHall. 19 suffixes
(« de l'Aigle », « des Mages », « des Béhémoths »…) stockés dans une nouvelle (« de l'Aigle », « des Mages », « des Béhémoths »…) stockés dans une nouvelle
table `templates` de la base (seedés au démarrage, éditables comme le reste via table `templates` de la base (seedés au démarrage, éditables comme le reste via
+42
View File
@@ -569,7 +569,49 @@ header h1 { font-size: 1.3em; color: #FFFFCC; text-shadow: 1px 1px 2px #000; }
border-bottom: 2px solid #000000; border-bottom: 2px solid #000000;
margin: 24px 0 12px; margin: 24px 0 12px;
font-size: 1.3em; font-size: 1.3em;
scroll-margin-top: 64px; /* pour que l'ancre ne passe pas sous le menu collant */
} }
/* Menu de navigation entre sections (collant en haut). */
.tz-nav {
position: sticky;
top: 0;
z-index: 5;
display: flex;
flex-wrap: wrap;
gap: 6px;
padding: 8px 0;
margin: 8px 0 4px;
background: #FFFFEE url("../img/mh/tableau1.jpg");
border-bottom: 1px solid #000000;
}
.tz-nav a {
padding: 5px 10px;
font-size: .9em;
text-decoration: none;
color: #333300;
background: #EBEDAB url("../img/mh/tableau2.jpg");
border: 1px solid #666633;
}
.tz-nav a:hover { border-color: #990000; color: #990000; }
/* Bouton « remonter en haut » flottant. */
.tz-top {
position: fixed;
right: 18px;
bottom: 18px;
z-index: 6;
width: 44px;
height: 44px;
font-size: 1.4em;
line-height: 1;
color: #FFFFCC;
background: #666633;
border: 1px solid #000000;
border-radius: 50%;
cursor: pointer;
box-shadow: 1px 1px 4px rgba(0, 0, 0, .4);
}
.tz-top:hover { background: #990000; }
.tz-top.hidden { display: none; }
.tz-grid { .tz-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
+1
View File
@@ -214,6 +214,7 @@
<button id="treasures-back" class="menu-btn">← Retour</button> <button id="treasures-back" class="menu-btn">← Retour</button>
</header> </header>
<div id="treasures-body"></div> <div id="treasures-body"></div>
<button id="tz-top" class="tz-top hidden" title="Remonter en haut"></button>
</div> </div>
<!-- Écran de fin --> <!-- Écran de fin -->
+1 -1
View File
@@ -4,7 +4,7 @@
"use strict"; "use strict";
const APP_VERSION = "2.7.0"; const APP_VERSION = "2.7.1";
/* Alpha : maîtrise initiale haute pour les tests. Remettre 15 % / 15 % à la v1.0 officielle. */ /* Alpha : maîtrise initiale haute pour les tests. Remettre 15 % / 15 % à la v1.0 officielle. */
const START_COMP_PCT = 90; const START_COMP_PCT = 90;
+55 -7
View File
@@ -44,6 +44,23 @@ const TREASURE_SCROLLS = [
["runeFoins", "1 à 5", "3 tours", ["DEG X", "VUE 1", "PV 2D3 ou 4D3 (immédiat)"]], ["runeFoins", "1 à 5", "3 tours", ["DEG X", "VUE 1", "PV 2D3 ou 4D3 (immédiat)"]],
]; ];
/* Carte d'un template de drop : ses bonus, lus directement sur la ligne BDD
* (mêmes clés que les mods d'équipement, donc on réutilise GEAR_MOD_LABELS). */
function templateCard(t) {
const fx = [];
for (const [key, label, unit] of GEAR_MOD_LABELS) {
const v = t[key];
if (!v) continue;
fx.push(`<span class="tz-fx ${v > 0 ? "tz-good" : "tz-bad"}">${label} ${v > 0 ? "+" : ""}${Math.abs(v)}${unit}</span>`);
}
if (!fx.length) fx.push('<span class="tz-fx">aucun bonus (titre honorifique)</span>');
return `
<div class="tz-card" style="--tz-color:#7a5c9e">
<div class="tz-head"><span class="tz-name">✨ ${t.name}</span></div>
<div class="tz-fx-list">${fx.join("")}</div>
</div>`;
}
function treasureCard(def, x, duration, fx) { function treasureCard(def, x, duration, fx) {
const fxHtml = fx.map(f => { const fxHtml = fx.map(f => {
const neg = f.includes(""); const neg = f.includes("");
@@ -70,13 +87,21 @@ function treasuresRender() {
l'<b>armure magique</b>, seule à réduire les dégâts magiques. l'<b>armure magique</b>, seule à réduire les dégâts magiques.
1 tour = 1 DLA. Boire une potion ou lire un parchemin coûte <b>1 PA</b>.</p>`; 1 tour = 1 DLA. Boire une potion ou lire un parchemin coûte <b>1 PA</b>.</p>`;
html += `<h2 class="tz-section">🧪 Potions (${TREASURE_POTIONS.length})</h2><div class="tz-grid">`; const templates = TEMPLATES_DB || [];
html += `<nav class="tz-nav">
<a href="#tz-sec-potions">🧪 Potions</a>
<a href="#tz-sec-scrolls">📜 Parchemins</a>
<a href="#tz-sec-gear">⚔️ Équipement</a>
<a href="#tz-sec-templates">✨ Templates</a>
</nav>`;
html += `<h2 class="tz-section" id="tz-sec-potions">🧪 Potions (${TREASURE_POTIONS.length})</h2><div class="tz-grid">`;
for (const [id, x, duration, fx] of TREASURE_POTIONS) { for (const [id, x, duration, fx] of TREASURE_POTIONS) {
html += treasureCard(POTION_DEFS[id], powerLabel(x, POTIONS_DB && POTIONS_DB[id]), duration, fx); html += treasureCard(POTION_DEFS[id], powerLabel(x, POTIONS_DB && POTIONS_DB[id]), duration, fx);
} }
html += "</div>"; html += "</div>";
html += `<h2 class="tz-section">📜 Parchemins standards (${TREASURE_SCROLLS.length})</h2><div class="tz-grid">`; html += `<h2 class="tz-section" id="tz-sec-scrolls">📜 Parchemins standards (${TREASURE_SCROLLS.length})</h2><div class="tz-grid">`;
for (const [id, x, duration, fx] of TREASURE_SCROLLS) { for (const [id, x, duration, fx] of TREASURE_SCROLLS) {
html += treasureCard(SCROLL_DEFS[id], powerLabel(x, SCROLLS_DB && SCROLLS_DB[id]), duration, fx); html += treasureCard(SCROLL_DEFS[id], powerLabel(x, SCROLLS_DB && SCROLLS_DB[id]), duration, fx);
} }
@@ -86,7 +111,7 @@ function treasuresRender() {
// a pu être chargée, sinon les valeurs statiques de gear.js (ex. hors-ligne). // a pu être chargée, sinon les valeurs statiques de gear.js (ex. hors-ligne).
const gearSource = GEAR_DB || GEAR; const gearSource = GEAR_DB || GEAR;
const gearCount = Object.values(gearSource).reduce((n, list) => n + list.length, 0); const gearCount = Object.values(gearSource).reduce((n, list) => n + list.length, 0);
html += `<h2 class="tz-section">⚔️ Équipement (${gearCount})</h2> html += `<h2 class="tz-section" id="tz-sec-gear">⚔️ Équipement (${gearCount})</h2>
<p class="tz-intro">Six emplacements : ${Object.values(GEAR_SLOTS).map(s => s.label).join(", ")}. <p class="tz-intro">Six emplacements : ${Object.values(GEAR_SLOTS).map(s => s.label).join(", ")}.
Les bonus d'équipement sont <b>fixes</b> (jamais en dés) et s'ajoutent toujours aux jets. Les bonus d'équipement sont <b>fixes</b> (jamais en dés) et s'ajoutent toujours aux jets.
La plupart sont <b>physiques</b> ; certains objets portent aussi des bonus <b>magiques</b> La plupart sont <b>physiques</b> ; certains objets portent aussi des bonus <b>magiques</b>
@@ -117,6 +142,19 @@ function treasuresRender() {
html += "</div>"; html += "</div>";
} }
html += `<h2 class="tz-section" id="tz-sec-templates">✨ Templates de drop (${templates.length})</h2>
<p class="tz-intro">Suffixes à la MountyHall ajoutés aux objets qui tombent dans le
monde partagé : un objet reçoit jusqu'à <b>3 templates</b> (<b>6</b> pour une arme à
2 mains), chacun tiré au hasard selon une probabilité réglable sur la page admin. Leurs
bonus s'ajoutent à ceux de l'objet (le mod <b>TOUR</b> est en minutes de DLA), et leur
nom s'accole à celui de l'objet (« Épée Courte <i>de l'Aigle des Mages</i> »).
Valeurs lues dans la <b>base de référence</b> (modifiables côté admin).</p>`;
if (templates.length) {
html += `<div class="tz-grid">${templates.map(templateCard).join("")}</div>`;
} else {
html += `<p class="tz-note">Liste indisponible (base non joignable).</p>`;
}
html += `<p class="tz-note">Effet de Zone : touche le lecteur et tous les monstres à 3 cases html += `<p class="tz-note">Effet de Zone : touche le lecteur et tous les monstres à 3 cases
ou moins. « PV 2D3 ou 4D3 » (Rune des Foins) : la Mountypedia note « -2/4 D3 », interprété ou moins. « PV 2D3 ou 4D3 » (Rune des Foins) : la Mountypedia note « -2/4 D3 », interprété
ici comme un tirage au sort entre 2D3 et 4D3.</p>`; ici comme un tirage au sort entre 2D3 et 4D3.</p>`;
@@ -129,7 +167,7 @@ let treasuresReturnTo = "create";
// Données de la base de référence (BDD), rechargées à chaque ouverture pour // Données de la base de référence (BDD), rechargées à chaque ouverture pour
// refléter les éditions (admin OU sqlite-web) ; null tant qu'on n'a pas (ou pas // refléter les éditions (admin OU sqlite-web) ; null tant qu'on n'a pas (ou pas
// pu) charger → repli sur les valeurs statiques du code. // pu) charger → repli sur les valeurs statiques du code.
let GEAR_DB = null, POTIONS_DB = null, SCROLLS_DB = null; let GEAR_DB = null, POTIONS_DB = null, SCROLLS_DB = null, TEMPLATES_DB = null;
async function fetchRef(cat) { async function fetchRef(cat) {
try { try {
@@ -143,12 +181,13 @@ async function fetchRef(cat) {
} }
async function loadReference() { async function loadReference() {
const [gear, potions, scrolls] = await Promise.all([ const [gear, potions, scrolls, templates] = await Promise.all([
fetchRef("gear"), fetchRef("potions"), fetchRef("scrolls"), fetchRef("gear"), fetchRef("potions"), fetchRef("scrolls"), fetchRef("templates"),
]); ]);
GEAR_DB = gear ? gearBySlotFromRows(gear) : null; GEAR_DB = gear ? gearBySlotFromRows(gear) : null;
POTIONS_DB = potions ? indexPowerById(potions) : null; POTIONS_DB = potions ? indexPowerById(potions) : null;
SCROLLS_DB = scrolls ? indexPowerById(scrolls) : null; SCROLLS_DB = scrolls ? indexPowerById(scrolls) : null;
TEMPLATES_DB = templates;
} }
function gearBySlotFromRows(rows) { function gearBySlotFromRows(rows) {
@@ -184,7 +223,7 @@ async function treasuresShow(from) {
treasuresReturnTo = from; treasuresReturnTo = from;
document.getElementById("screen-" + from).classList.add("hidden"); document.getElementById("screen-" + from).classList.add("hidden");
document.getElementById("screen-treasures").classList.remove("hidden"); document.getElementById("screen-treasures").classList.remove("hidden");
document.getElementById("screen-treasures").scrollTop = 0; window.scrollTo(0, 0);
await loadReference(); await loadReference();
treasuresRender(); treasuresRender();
} }
@@ -197,6 +236,15 @@ if (typeof document !== "undefined") {
document.getElementById("screen-treasures").classList.add("hidden"); document.getElementById("screen-treasures").classList.add("hidden");
document.getElementById("screen-" + treasuresReturnTo).classList.remove("hidden"); document.getElementById("screen-" + treasuresReturnTo).classList.remove("hidden");
}; };
// Bouton « remonter en haut » : visible dès qu'on a un peu défilé.
const topBtn = document.getElementById("tz-top");
if (topBtn) {
topBtn.onclick = () => window.scrollTo({ top: 0, behavior: "smooth" });
window.addEventListener("scroll", () => {
const onTz = !document.getElementById("screen-treasures").classList.contains("hidden");
topBtn.classList.toggle("hidden", !onTz || window.scrollY < 400);
});
}
if (new URLSearchParams(location.search).get("screen") === "treasures") { if (new URLSearchParams(location.search).get("screen") === "treasures") {
treasuresShow("create"); treasuresShow("create");
} }