diff --git a/frontend/index.html b/frontend/index.html
index 24db269..fa2ad66 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -774,7 +774,7 @@
diff --git a/frontend/js/config.js b/frontend/js/config.js
index a8d8caa..ea1bbde 100644
--- a/frontend/js/config.js
+++ b/frontend/js/config.js
@@ -189,11 +189,21 @@ function initHelpModal() {
const modal = document.getElementById("help-modal");
if (!openBtn || !closeBtn || !modal) return;
+ let helpNavInitialized = false;
+
openBtn.addEventListener("click", () => {
modal.classList.add("active");
closeHeaderMenu();
safeCreateIcons();
- initHelpNavigation();
+ if (!helpNavInitialized) {
+ initHelpNavigation();
+ helpNavInitialized = true;
+ }
+ // Focus search on open
+ const searchInput = document.getElementById("help-nav-search");
+ if (searchInput) {
+ setTimeout(() => searchInput.focus(), 100);
+ }
});
closeBtn.addEventListener("click", closeHelpModal);
@@ -216,24 +226,57 @@ function initHelpNavigation() {
const nav = document.getElementById("help-nav");
const searchInput = document.getElementById("help-nav-search");
const toggleBtn = document.getElementById("help-nav-toggle");
+ const helpContent = document.querySelector(".help-content");
if (!helpBody || !navList || !nav) return;
const allLinks = [...navList.querySelectorAll(".help-nav-link")];
+ const allSections = [...document.querySelectorAll(".help-section")];
+ const sectionMap = {};
+ allSections.forEach(s => { sectionMap[s.id] = s; });
- // ── Scroll to section and focus it ──
+ // Build text index for content search (section title + text content)
+ const sectionTextCache = {};
+ allSections.forEach(s => {
+ sectionTextCache[s.id] = (s.textContent || '').toLowerCase();
+ });
+
+ // ── Scroll to section ──
function scrollToSection(targetId) {
- const section = document.getElementById(targetId);
+ const section = sectionMap[targetId];
if (!section) return;
section.scrollIntoView({ behavior: "smooth", block: "start" });
section.setAttribute("tabindex", "-1");
- section.focus({ preventScroll: true });
// On mobile, collapse nav after selection
if (window.innerWidth <= 768 && nav) {
nav.classList.add("collapsed");
}
}
+ // ── Focus the content pane (first heading in active section) ──
+ function focusContent() {
+ // Find the active section and focus its first heading
+ const activeLink = navList.querySelector(".help-nav-link.active:not(.hidden)");
+ if (activeLink) {
+ const targetId = activeLink.getAttribute("href").substring(1);
+ const section = sectionMap[targetId];
+ if (section) {
+ const h2 = section.querySelector("h2, h3");
+ if (h2) { h2.setAttribute("tabindex", "-1"); h2.focus({ preventScroll: true }); return; }
+ }
+ }
+ // Fallback: focus the content area itself
+ if (helpContent) { helpContent.setAttribute("tabindex", "-1"); helpContent.focus({ preventScroll: true }); }
+ }
+
+ // ── Focus TOC active link ──
+ function focusTOC() {
+ const activeLink = navList.querySelector(".help-nav-link.active:not(.hidden)");
+ if (activeLink) { activeLink.focus(); return; }
+ const firstLink = navList.querySelector(".help-nav-link:not(.hidden)");
+ if (firstLink) firstLink.focus();
+ }
+
// ── Nav link click handler ──
allLinks.forEach((link) => {
link.addEventListener("click", (e) => {
@@ -249,11 +292,20 @@ function initHelpNavigation() {
if (!items.length) return;
const currentIdx = items.indexOf(document.activeElement);
- if (e.key === "ArrowDown" || e.key === "ArrowRight") {
+ if (e.key === "ArrowDown") {
e.preventDefault();
const next = (currentIdx + 1) % items.length;
items[next].focus();
- } else if (e.key === "ArrowUp" || e.key === "ArrowLeft") {
+ } else if (e.key === "ArrowUp") {
+ e.preventDefault();
+ const prev = (currentIdx - 1 + items.length) % items.length;
+ items[prev].focus();
+ } else if (e.key === "ArrowRight") {
+ // Right arrow: go to content pane
+ e.preventDefault();
+ focusContent();
+ } else if (e.key === "ArrowLeft") {
+ // Left arrow in TOC: stay (prev item)
e.preventDefault();
const prev = (currentIdx - 1 + items.length) % items.length;
items[prev].focus();
@@ -267,45 +319,114 @@ function initHelpNavigation() {
e.preventDefault();
const targetId = items[currentIdx].getAttribute("href").substring(1);
scrollToSection(targetId);
+ // After scrolling, move focus to content
+ setTimeout(() => focusContent(), 400);
}
});
// Make nav items focusable
navList.querySelectorAll("li").forEach((li) => li.setAttribute("role", "none"));
- // ── Search filter ──
- if (searchInput) {
- searchInput.addEventListener("input", () => {
- const q = searchInput.value.toLowerCase().trim();
- allLinks.forEach((link) => {
- const text = link.textContent.toLowerCase();
- if (!q || text.includes(q)) {
- link.classList.remove("hidden");
- } else {
- link.classList.add("hidden");
- }
- });
- // Update active state for visible links
- updateActiveLink();
- });
- // Clear search on Escape
- searchInput.addEventListener("keydown", (e) => {
- if (e.key === "Escape") {
- searchInput.value = "";
- searchInput.dispatchEvent(new Event("input"));
+ // ── Keyboard navigation in content pane ──
+ if (helpContent) {
+ helpContent.setAttribute("tabindex", "-1");
+ helpContent.addEventListener("keydown", (e) => {
+ if (e.key === "ArrowLeft") {
+ // Left arrow in content: go back to TOC
+ e.preventDefault();
+ focusTOC();
+ } else if (e.key === "ArrowUp" && document.activeElement === helpContent) {
+ e.preventDefault();
+ focusTOC();
}
});
}
+ // ── Keyboard navigation in search input ──
+ if (searchInput) {
+ searchInput.addEventListener("keydown", (e) => {
+ if (e.key === "ArrowDown") {
+ e.preventDefault();
+ const firstLink = navList.querySelector(".help-nav-link:not(.hidden)");
+ if (firstLink) firstLink.focus();
+ }
+ });
+ }
+
+ // ── Search: filter TOC + hide non-matching content sections ──
+ let currentQuery = "";
+ if (searchInput) {
+ searchInput.addEventListener("input", () => {
+ currentQuery = searchInput.value.toLowerCase().trim();
+ applySearchFilter();
+ });
+ searchInput.addEventListener("keydown", (e) => {
+ if (e.key === "Escape" && currentQuery) {
+ searchInput.value = "";
+ currentQuery = "";
+ applySearchFilter();
+ }
+ });
+ }
+
+ function applySearchFilter() {
+ const q = currentQuery;
+ // Filter TOC links
+ allLinks.forEach((link) => {
+ const text = link.textContent.toLowerCase();
+ if (!q || text.includes(q)) {
+ link.classList.remove("hidden");
+ } else {
+ link.classList.add("hidden");
+ }
+ });
+ // Filter content sections
+ allSections.forEach((section) => {
+ if (!q) {
+ section.classList.remove("help-hidden");
+ return;
+ }
+ const sectionText = sectionTextCache[section.id] || '';
+ const sectionTitle = (section.querySelector("h2, h3")?.textContent || '').toLowerCase();
+ if (sectionTitle.includes(q) || sectionText.includes(q)) {
+ section.classList.remove("help-hidden");
+ } else {
+ section.classList.add("help-hidden");
+ }
+ });
+ updateActiveLink();
+ // If no visible results, show a message
+ ensureNoResultsMessage(q);
+ }
+
+ function ensureNoResultsMessage(q) {
+ const existing = document.getElementById("help-no-results");
+ const visibleSections = allSections.filter(s => !s.classList.contains("help-hidden"));
+ if (q && visibleSections.length === 0) {
+ if (!existing) {
+ const msg = document.createElement("div");
+ msg.id = "help-no-results";
+ msg.style.cssText = "padding:40px;text-align:center;color:var(--text-muted);font-size:0.9rem";
+ msg.innerHTML = `
🔍
Aucun résultat pour "${q}"
Essayez un autre terme
`;
+ if (helpContent) helpContent.appendChild(msg);
+ }
+ } else if (existing) {
+ existing.remove();
+ }
+ }
+
// ── Mobile toggle ──
if (toggleBtn) {
toggleBtn.addEventListener("click", () => {
+ const wasCollapsed = nav.classList.contains("collapsed");
nav.classList.toggle("collapsed");
+ // If expanding, focus search
+ if (wasCollapsed && searchInput) {
+ setTimeout(() => searchInput.focus(), 150);
+ }
});
- // Start collapsed on mobile
- if (window.innerWidth <= 768) {
- nav.classList.add("collapsed");
- }
+ // Start expanded on mobile so user sees the TOC immediately
+ // (we use collapsed only after user taps a section or explicitly toggles)
window.addEventListener("resize", () => {
if (window.innerWidth > 768) {
nav.classList.remove("collapsed");
@@ -314,31 +435,25 @@ function initHelpNavigation() {
}
// ── Scroll spy (IntersectionObserver) ──
- let observer;
function updateActiveLink() {
- // Find the first visible section and highlight its link
- const sections = [...document.querySelectorAll(".help-section")];
+ const visibleLinks = allLinks.filter(l => !l.classList.contains("hidden"));
+ const visibleSections = allSections.filter(s => !s.classList.contains("help-hidden"));
let activeId = null;
- for (const section of sections) {
+ for (const section of visibleSections) {
const rect = section.getBoundingClientRect();
- const helpContent = document.querySelector(".help-content");
const contentRect = helpContent ? helpContent.getBoundingClientRect() : null;
- // Section top is in the upper half of the viewport (or within content)
if (rect.top < (contentRect ? contentRect.top + 120 : 200)) {
activeId = section.id;
}
}
- if (activeId) {
- allLinks.forEach((link) => {
- const isMatch = link.getAttribute("href") === `#${activeId}`;
- link.classList.toggle("active", isMatch);
- });
- }
+ visibleLinks.forEach((link) => {
+ const isMatch = link.getAttribute("href") === `#${activeId}`;
+ link.classList.toggle("active", isMatch);
+ });
}
- observer = new IntersectionObserver(
+ const observer = new IntersectionObserver(
(entries) => {
- // Find the entry most visible near the top
let bestEntry = null;
for (const entry of entries) {
if (entry.isIntersecting) {
@@ -362,27 +477,24 @@ function initHelpNavigation() {
}
);
- document.querySelectorAll(".help-section").forEach((section) => {
- observer.observe(section);
- });
+ allSections.forEach((section) => observer.observe(section));
- // ── Keyboard: Ctrl+↑/↓ inside help body to jump between sections ──
+ // ── Keyboard: Ctrl+↑/↓ to jump between sections ──
helpBody.addEventListener("keydown", (e) => {
if ((e.ctrlKey || e.metaKey) && (e.key === "ArrowDown" || e.key === "ArrowUp")) {
e.preventDefault();
- const sections = [...document.querySelectorAll(".help-section")];
- const currentSection = sections.find((s) => {
+ const visibleSections = allSections.filter(s => !s.classList.contains("help-hidden"));
+ const currentSection = visibleSections.find((s) => {
const rect = s.getBoundingClientRect();
- const helpContent = document.querySelector(".help-content");
const contentRect = helpContent ? helpContent.getBoundingClientRect() : null;
return rect.top >= (contentRect ? contentRect.top : 0) && rect.top < (contentRect ? contentRect.top + 120 : 120);
});
if (!currentSection) return;
- const currentIdx = sections.indexOf(currentSection);
- if (e.key === "ArrowDown" && currentIdx < sections.length - 1) {
- sections[currentIdx + 1].scrollIntoView({ behavior: "smooth", block: "start" });
+ const currentIdx = visibleSections.indexOf(currentSection);
+ if (e.key === "ArrowDown" && currentIdx < visibleSections.length - 1) {
+ visibleSections[currentIdx + 1].scrollIntoView({ behavior: "smooth", block: "start" });
} else if (e.key === "ArrowUp" && currentIdx > 0) {
- sections[currentIdx - 1].scrollIntoView({ behavior: "smooth", block: "start" });
+ visibleSections[currentIdx - 1].scrollIntoView({ behavior: "smooth", block: "start" });
}
}
});
diff --git a/frontend/style.css b/frontend/style.css
index a84d5dd..cece3f7 100644
--- a/frontend/style.css
+++ b/frontend/style.css
@@ -2287,6 +2287,9 @@ select {
.help-nav-link.hidden {
display: none;
}
+.help-section.help-hidden {
+ display: none;
+}
.help-content {
flex: 1;
@@ -6410,29 +6413,49 @@ body.popup-mode .content-area {
border-right: none;
border-bottom: 1px solid var(--border);
flex-shrink: 0;
+ transition: max-height 0.25s ease;
+ overflow: hidden;
}
.help-nav-header {
- padding: 12px 16px 8px;
+ padding: 10px 16px 6px;
+ cursor: pointer;
}
.help-nav-toggle {
- display: inline-flex; /* visible on mobile */
+ display: inline-flex;
+ transition: transform 0.2s;
+ }
+ .help-nav:not(.collapsed) .help-nav-toggle {
+ transform: rotate(180deg);
}
.help-nav.collapsed .help-nav-search-wrap,
.help-nav.collapsed .help-nav-list {
display: none;
}
+ .help-nav-search-wrap {
+ padding: 0 12px 6px;
+ }
.help-nav-list {
- max-height: 40vh;
+ max-height: 45vh;
flex: none;
- padding-bottom: 12px;
+ padding-bottom: 14px;
}
.help-content {
- padding: 16px 12px 40px;
+ padding: 14px 12px 40px;
+ flex: 1;
+ overflow-y: auto;
+ -webkit-overflow-scrolling: touch;
+ }
+ .help-content:focus {
+ outline: none;
}
.help-hero {
flex-direction: column;
gap: 16px;
}
.help-hero-visual { display: none; }
- .help-section { padding: 24px 0 20px; }
+ .help-section { padding: 20px 0 16px; }
+ .help-nav-link {
+ padding: 10px 12px;
+ font-size: 0.88rem;
+ }
}