feat(ui): harmoniser navigation pages d’entrée et recherche
All checks were successful
SMOKE / smoke (push) Successful in 6s
CI / build-and-anchors (push) Successful in 44s
CI / build-and-anchors (pull_request) Successful in 36s

This commit is contained in:
2026-04-25 01:31:14 +02:00
parent 8605b7198f
commit 64e56e8abc
10 changed files with 835 additions and 254 deletions

View File

@@ -184,7 +184,8 @@ const WHOAMI_FORCE_LOCALHOST = (import.meta.env.PUBLIC_WHOAMI_FORCE_LOCALHOST ??
.catch(() => Boolean(__DEV__));
}
})();
</script>
</script>
</head>
<body
@@ -665,6 +666,40 @@ const WHOAMI_FORCE_LOCALHOST = (import.meta.env.PUBLIC_WHOAMI_FORCE_LOCALHOST ??
}
}
@media (orientation: landscape) and (min-width: 981px) and (max-width: 1220px) and (pointer: coarse){
.page{
padding-left: 10px;
padding-right: 10px;
}
.page-shell{
--aside-w: 300px;
--gap: 14px;
max-width: calc(100vw - 20px) !important;
grid-template-columns: minmax(260px, var(--aside-w)) minmax(0, 1fr) !important;
}
.reading{
max-width: none !important;
width: 100% !important;
min-width: 0 !important;
margin: 0 !important;
}
:global(.reading p[id^="p-"]){
padding-right: 108px;
}
:global(.para-tools){
right: 0;
}
:global(.para-tools-actions){
flex-wrap: wrap;
justify-content: flex-end;
}
}
@media (prefers-color-scheme: dark){
:global(body[data-edition-key="glossaire"][data-sticky-mode="glossary-portal"] #reading-follow .reading-follow__inner){
background: rgba(0,0,0,.6);
@@ -957,7 +992,7 @@ const WHOAMI_FORCE_LOCALHOST = (import.meta.env.PUBLIC_WHOAMI_FORCE_LOCALHOST ??
}
}
.mobile-para-menu{
.mobile-para-menu{
position: fixed;
inset: auto 10px 10px 10px;
z-index: 120;
@@ -1009,7 +1044,7 @@ const WHOAMI_FORCE_LOCALHOST = (import.meta.env.PUBLIC_WHOAMI_FORCE_LOCALHOST ??
transform: translateY(1px);
}
.mobile-para-menu{
.mobile-para-menu{
touch-action: manipulation;
}
@@ -1082,6 +1117,7 @@ const WHOAMI_FORCE_LOCALHOST = (import.meta.env.PUBLIC_WHOAMI_FORCE_LOCALHOST ??
const docEditionKey = document.body?.dataset?.editionKey || "";
const docStickyMode = String(document.body?.dataset?.stickyMode || "default");
const isGlossaryEdition = docEditionKey === "glossaire";
const isIntroEdition = docEditionKey === "commencer";
const hasLocalGlossaryFollow =
isGlossaryEdition && Boolean(document.getElementById("glossary-hero-follow"));
@@ -1501,45 +1537,45 @@ const WHOAMI_FORCE_LOCALHOST = (import.meta.env.PUBLIC_WHOAMI_FORCE_LOCALHOST ??
}
function updateResumeButton() {
const btn = document.getElementById("resume-btn");
if (!btn) return;
const btn = document.getElementById("resume-btn");
if (!btn) return;
const current = getResumeBookmark();
const current = getResumeBookmark();
btn.hidden = false;
btn.setAttribute("href", "#");
btn.hidden = false;
btn.setAttribute("href", "#");
if (!current) {
delete btn.dataset.resumeAnchor;
delete btn.dataset.resumePath;
delete btn.dataset.resumeKind;
btn.setAttribute("aria-disabled", "true");
btn.classList.add("is-disabled");
btn.title = "Aucun marque-page explicite enregistré";
return;
if (!current) {
delete btn.dataset.resumeAnchor;
delete btn.dataset.resumePath;
delete btn.dataset.resumeKind;
btn.setAttribute("aria-disabled", "true");
btn.classList.add("is-disabled");
btn.title = "Aucun marque-page explicite enregistré";
return;
}
btn.dataset.resumeAnchor = current.anchor;
btn.dataset.resumePath = current.path || "";
btn.dataset.resumeKind = current.kind || "explicit-bookmark";
btn.setAttribute("aria-disabled", "false");
btn.classList.remove("is-disabled");
btn.title = `Reprendre : ${current.anchor}`;
if (!__resumeClickBound) {
__resumeClickBound = true;
btn.addEventListener("click", (ev) => {
ev.preventDefault();
ev.stopPropagation();
if (btn.getAttribute("aria-disabled") === "true") return;
performResume();
}, { passive: false });
}
}
btn.dataset.resumeAnchor = current.anchor;
btn.dataset.resumePath = current.path || "";
btn.dataset.resumeKind = current.kind || "explicit-bookmark";
btn.setAttribute("aria-disabled", "false");
btn.classList.remove("is-disabled");
btn.title = `Reprendre : ${current.anchor}`;
if (!__resumeClickBound) {
__resumeClickBound = true;
btn.addEventListener("click", (ev) => {
ev.preventDefault();
ev.stopPropagation();
if (btn.getAttribute("aria-disabled") === "true") return;
performResume();
}, { passive: false });
}
}
window.__archiUpdateResumeButton = updateResumeButton;
if (window.location.search.includes("body=")) {
@@ -1858,7 +1894,7 @@ const WHOAMI_FORCE_LOCALHOST = (import.meta.env.PUBLIC_WHOAMI_FORCE_LOCALHOST ??
}
safe("para-tools", () => {
if (isGlossaryEdition) return;
if (isGlossaryEdition || isIntroEdition) return;
for (const p of parasAll) {
if (p.querySelector(".para-tools")) continue;
@@ -1961,7 +1997,7 @@ const WHOAMI_FORCE_LOCALHOST = (import.meta.env.PUBLIC_WHOAMI_FORCE_LOCALHOST ??
});
safe("mobile-para-tools", () => {
if (isGlossaryEdition) return;
if (isGlossaryEdition || isIntroEdition) return;
if (!reading) return;
const isMobileLike = () =>
@@ -1985,8 +2021,12 @@ const WHOAMI_FORCE_LOCALHOST = (import.meta.env.PUBLIC_WHOAMI_FORCE_LOCALHOST ??
let menuOpen = false;
let touchLongPressActive = false;
const LONG_PRESS_DELAY = 460;
const LONG_PRESS_MOVE_TOLERANCE = 18;
const LONG_PRESS_DELAY = 520;
const isTabletLike = () =>
window.matchMedia("(pointer: coarse)").matches &&
Math.min(window.innerWidth, window.innerHeight) >= 700;
const getMoveTolerance = () => isTabletLike() ? 42 : 22;
function isInteractiveTarget(node) {
const el = node?.closest?.(
@@ -2029,7 +2069,12 @@ const WHOAMI_FORCE_LOCALHOST = (import.meta.env.PUBLIC_WHOAMI_FORCE_LOCALHOST ??
function movedTooFar(x, y) {
const dx = Math.abs(Number(x || 0) - pressStartX);
const dy = Math.abs(Number(y || 0) - pressStartY);
return dx > LONG_PRESS_MOVE_TOLERANCE || dy > LONG_PRESS_MOVE_TOLERANCE;
const tol = getMoveTolerance();
// iPad/tablette : on tolère mieux la dérive verticale naturelle du doigt.
if (isTabletLike()) return dx > tol || dy > (tol * 1.65);
return dx > tol || dy > tol;
}
function closeMenu() {
@@ -2118,13 +2163,10 @@ const WHOAMI_FORCE_LOCALHOST = (import.meta.env.PUBLIC_WHOAMI_FORCE_LOCALHOST ??
}
}
function handlePressEnd() {
cancelLongPress();
}
function handlePressEnd() {
clearPressTimer();
pressPointerId = null;
touchLongPressActive = false;
}
reading.addEventListener("pointerdown", handlePressStart, { passive: true });
@@ -2162,15 +2204,17 @@ const WHOAMI_FORCE_LOCALHOST = (import.meta.env.PUBLIC_WHOAMI_FORCE_LOCALHOST ??
cancelLongPress();
}, { passive: true });
window.addEventListener("scroll", () => {
clearPressTimer();
if (menuOpen) closeMenu();
}, { passive: true });
function handleScrollDuringMobileMenu() {
// Tant que le timer long-press est en cours, ne pas l'annuler brutalement
// sur tablette : iPadOS peut produire un micro-scroll fantôme.
if (pressTimer && isTabletLike()) return;
document.addEventListener("scroll", () => {
clearPressTimer();
if (menuOpen) closeMenu();
}, { passive: true, capture: true });
}
window.addEventListener("scroll", handleScrollDuringMobileMenu, { passive: true });
document.addEventListener("scroll", handleScrollDuringMobileMenu, { passive: true, capture: true });
document.addEventListener("click", (ev) => {
const p = ev.target?.closest?.('.reading p[id^="p-"]');
@@ -2559,6 +2603,14 @@ const WHOAMI_FORCE_LOCALHOST = (import.meta.env.PUBLIC_WHOAMI_FORCE_LOCALHOST ??
nav.classList.toggle("is-collapsed", !open);
toggle.setAttribute("aria-expanded", open ? "true" : "false");
const bodyClip =
nav.querySelector(".toc-global__body-clip") ||
nav.querySelector(".toc-local__body-clip");
if (bodyClip) {
bodyClip.hidden = !open;
}
const key = nav.dataset.tocKey
? `archicratie:${nav.dataset.tocKey || ""}`
: nav.classList.contains("toc-local")
@@ -2580,9 +2632,16 @@ const WHOAMI_FORCE_LOCALHOST = (import.meta.env.PUBLIC_WHOAMI_FORCE_LOCALHOST ??
}
function syncAutoCollapse() {
const shouldOpen = nearTopZone();
// Sur mobile/tablette, les TOC restent fermées par défaut.
// Elles ne souvrent que par action explicite utilisateur.
if (mqCompact.matches) {
setTocOpen(tocGlobal, false, false);
setTocOpen(tocLocal, false, false);
return;
}
// En desktop comme en mobile : TOC ouverts en haut, repliés dès quon quitte le haut.
// Desktop : comportement historique conservé.
const shouldOpen = nearTopZone();
setTocOpen(tocGlobal, shouldOpen, false);
setTocOpen(tocLocal, shouldOpen, false);
}
@@ -2607,45 +2666,12 @@ const WHOAMI_FORCE_LOCALHOST = (import.meta.env.PUBLIC_WHOAMI_FORCE_LOCALHOST ??
schedule();
});
safe("mobile-toc-collapse", () => {
const isMobile = () => window.innerWidth <= 760;
const bindToggle = (rootSel, headSel) => {
document.querySelectorAll(rootSel).forEach((box) => {
const head = box.querySelector(headSel);
if (!head) return;
if (!box.hasAttribute("data-mobile-collapsible-bound")) {
box.setAttribute("data-mobile-collapsible-bound", "1");
if (isMobile()) box.classList.remove("is-open");
else box.classList.add("is-open");
head.addEventListener("click", () => {
if (!isMobile()) return;
box.classList.toggle("is-open");
});
}
});
};
const syncOpenState = () => {
document
.querySelectorAll(".toc-global, .toc-local")
.forEach((box) => {
if (isMobile()) box.classList.remove("is-open");
else box.classList.add("is-open");
});
};
bindToggle(".toc-global", ".toc-global__head");
bindToggle(".toc-local", ".toc-local__head");
bindToggle(".toc-global", "[data-toc-global-head]");
bindToggle(".toc-local", "[data-toc-local-head]");
syncOpenState();
window.addEventListener("resize", syncOpenState, { passive: true });
});
/*
* TOC accordions are owned by EditionToc.astro / LocalToc.astro.
* Keep only the top-zone auto-collapse below; the previous legacy
* mobile-toc-collapse binder toggled an unused .is-open state and
* added duplicate click listeners on the same headers.
*/
safe("reading-follow", () => {
if (!reading || !followEl) return;