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:
@@ -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
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user