---
import ProposeModal from "../components/ProposeModal.astro";
import SiteNav from "../components/SiteNav.astro";
import LevelToggle from "../components/LevelToggle.astro";
import BuildStamp from "../components/BuildStamp.astro";
import SidePanel from "../components/SidePanel.astro";
import "../styles/global.css";

const {
  title,
  editionLabel,
  editionKey,
  statusLabel,
  statusKey,
  level,
  version,
} = Astro.props;

const lvl = level ?? 1;

const canonical = Astro.site
  ? new URL(Astro.url.pathname, Astro.site).href
  : Astro.url.href;

const IS_DEV = import.meta.env.DEV;

const GITEA_BASE = import.meta.env.PUBLIC_GITEA_BASE ?? "";
const GITEA_OWNER = import.meta.env.PUBLIC_GITEA_OWNER ?? "";
const GITEA_REPO = import.meta.env.PUBLIC_GITEA_REPO ?? "";

// ✅ OPTIONNEL : bridge serveur (proxy same-origin)
const ISSUE_BRIDGE_PATH = import.meta.env.PUBLIC_ISSUE_BRIDGE_PATH ?? "";
---

<!doctype html>
<html lang="fr">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>{title ? `${title} — Archicratie` : "Archicratie"}</title>

    <link rel="canonical" href={canonical} />
    <link rel="sitemap" href="/sitemap-index.xml" />

    <meta data-pagefind-filter="edition[content]" content={String(editionKey ?? editionLabel)} />
    <meta data-pagefind-filter="level[content]" content={String(lvl)} />
    <meta data-pagefind-filter="status[content]" content={String(statusKey ?? statusLabel)} />

    <meta data-pagefind-meta={`edition:${String(editionKey ?? editionLabel)}`} />
    <meta data-pagefind-meta={`level:${String(lvl)}`} />
    <meta data-pagefind-meta={`status:${String(statusKey ?? statusLabel)}`} />
    <meta data-pagefind-meta={`version:${String(version ?? "")}`} />

    {/* ✅ BOOT EARLY : SidePanel dépend de ces globals. */}
    <script is:inline define:vars={{ IS_DEV, GITEA_BASE, GITEA_OWNER, GITEA_REPO, ISSUE_BRIDGE_PATH }}>
(() => {
  const __DEV__ = Boolean(IS_DEV);
  window.__archiFlags = Object.assign({}, window.__archiFlags, { dev: __DEV__ });

  const base = String(GITEA_BASE || "").replace(/\/+$/, "");
  const owner = String(GITEA_OWNER || "");
  const repo = String(GITEA_REPO || "");
  const giteaReady = Boolean(base && owner && repo);
  window.__archiGitea = { ready: giteaReady, base, owner, repo };

  const rawBridge = String(ISSUE_BRIDGE_PATH || "").trim();
  const normBridge = rawBridge
    ? (rawBridge.startsWith("/") ? rawBridge : ("/" + rawBridge.replace(/^\/+/, ""))).replace(/\/+$/, "")
    : "";
  window.__archiIssueBridge = { ready: Boolean(normBridge), path: normBridge };

  const WHOAMI_PATH = "/_auth/whoami";
  const REQUIRED_GROUP = "editors";
  const READ_GROUP = "readers";

  function parseWhoamiLine(text, key) {
    const re = new RegExp(`^${key}:\\s*(.*)$`, "mi");
    const m = String(text || "").match(re);
    return (m?.[1] ?? "").trim();
  }
  function inGroup(groups, g) {
    const gg = String(g || "").toLowerCase();
    return Array.isArray(groups) && groups.some((x) => String(x).toLowerCase() === gg);
  }

  if (!window.__archiAuthInfoP) {
    window.__archiAuthInfoP = (async () => {
      const res = await fetch(`${WHOAMI_PATH}?_=${Date.now()}`, {
        credentials: "include",
        cache: "no-store",
        redirect: "manual",
        headers: { Accept: "text/plain" },
      }).catch(() => null);

      if (!res) return { ok: false, user: "", name: "", email: "", groups: [], raw: "" };
      if (res.status >= 300 && res.status < 400) return { ok: false, user: "", name: "", email: "", groups: [], raw: "" };

      const text = await res.text().catch(() => "");
      const looksLikeWhoami = /Remote-(User|Groups|Email|Name)\s*:/i.test(text);
      if (!res.ok || !looksLikeWhoami) return { ok: false, user: "", name: "", email: "", groups: [], raw: text };

      const groups = parseWhoamiLine(text, "Remote-Groups")
        .split(/[;,]/)
        .map((s) => s.trim())
        .filter(Boolean)
        .map((s) => s.toLowerCase());

      return {
        ok: true,
        user: parseWhoamiLine(text, "Remote-User"),
        name: parseWhoamiLine(text, "Remote-Name"),
        email: parseWhoamiLine(text, "Remote-Email"),
        groups,
        raw: text,
      };
    })().catch(() => ({ ok: false, user: "", name: "", email: "", groups: [], raw: "" }));
  }

  if (!window.__archiCanReadP) {
    window.__archiCanReadP = window.__archiAuthInfoP.then((info) =>
      Boolean(info.ok && (inGroup(info.groups, READ_GROUP) || inGroup(info.groups, REQUIRED_GROUP)))
    );
  }

  if (!window.__archiIsEditorP) {
    window.__archiIsEditorP = window.__archiAuthInfoP
      .then((info) => Boolean(inGroup(info.groups, REQUIRED_GROUP) || (__DEV__ && !info.ok)))
      .catch(() => false);
  }
})();
    </script>
  </head>

  <body
    data-doc-title={title}
    data-doc-version={version}
    data-reading-level={String(lvl)}
  >
    <header>
      <SiteNav />
      <div class="edition-bar">
        <span class="badge" data-badge="edition"><strong>Édition</strong> : {editionLabel}</span>
        <span class="badge" data-badge="status"><strong>Statut</strong> : {statusLabel}</span>
        <span class="badge" data-badge="version"><strong>Version</strong> : {version}</span>

        <a class="resume-btn" id="resume-btn" href="#" hidden>Reprendre la lecture</a>

        <form class="jump-form" id="jump-form" role="search" aria-label="Aller à un paragraphe par identifiant">
          <input
            class="jump-input"
            id="jump-input"
            type="search"
            inputmode="search"
            autocomplete="off"
            spellcheck="false"
            placeholder="Aller à ¶ (p-… / URL#p-… / extrait)"
            aria-label="Identifiant ou extrait de paragraphe"
          />
          <button class="jump-btn" id="jump-go" type="submit">Aller</button>
        </form>

        <LevelToggle initialLevel={lvl} />
      </div>
    </header>

    <main class="page">
      <div class="page-shell">
        <aside class="page-aside" aria-label="Navigation de lecture">
          <div class="page-aside__scroll" aria-label="Sommaires">
            <slot name="aside" />
          </div>
        </aside>

        <article class="reading" data-pagefind-body>
          <slot />
          <BuildStamp />
        </article>

        <SidePanel />
      </div>
    </main>

    <div class="reading-follow" id="reading-follow" aria-hidden="true">
      <div class="reading-follow__inner">
        <button class="rf-line rf-h1" id="rf-h1" type="button" hidden></button>
        <button class="rf-line rf-h2" id="rf-h2" type="button" hidden></button>
        <button class="rf-line rf-h3" id="rf-h3" type="button" hidden></button>

        <div class="rf-actions" aria-label="Navigation rapide">
          <button class="rf-btn" id="rf-top-chapter" type="button" hidden aria-label="Haut du chapitre" title="Haut du chapitre">↑</button>
          <button class="rf-btn" id="rf-top-section" type="button" hidden aria-label="Haut de la section" title="Haut de la section">↥</button>
        </div>
      </div>
    </div>

    <ProposeModal />

    <style>
      .page{
        padding: var(--page-gap) 16px 48px;
      }

      .page-shell{
        --aside-w: 320px;
        --reading-w: 78ch;
        --panel-w: 380px;
        --gap: 18px;

        max-width: calc(var(--aside-w) + var(--reading-w) + var(--panel-w) + (var(--gap) * 2));
        margin: 0 auto;

        display: grid;
        grid-template-columns: var(--aside-w) minmax(0, var(--reading-w)) var(--panel-w);
        column-gap: var(--gap);
        align-items: start;
      }

      .page-aside{
        grid-column: 1;
        position: sticky;
        top: calc(var(--sticky-header-h) + var(--page-gap));
        overflow: visible;
        max-height: none;
      }

      .page-aside__scroll{
        max-height: calc(100vh - (var(--sticky-header-h) + var(--page-gap) + 12px));
        overflow: auto;
        padding-right: 8px;
        padding-top: 6px;
        scrollbar-gutter: stable;
      }

      /* ✅ Mode “lecture continue” :
        - on garde <details> pour les ancres / JS
        - mais on supprime l’UX accordéon (summary) et on montre le vrai H2
      */
      :global(.reading details.details-section){
        border: 0 !important;
        padding: 0 !important;
        margin: 22px 0 !important;
        background: transparent !important;
      }

      /* summary invisible (plus de “Ouvrir la section…”) */
      :global(.reading details.details-section > summary.details-summary){
        position: absolute !important;
        left: -9999px !important;
        width: 1px !important;
        height: 1px !important;
        overflow: hidden !important;
        white-space: nowrap !important;
        border: 0 !important;
        padding: 0 !important;
        margin: 0 !important;
      }

      /* pas de “gap” artificiel ajouté après le summary */
      :global(.reading details .details-body){
        margin-top: 0 !important;
      }

      /* le vrai H2 redevient visible */
      :global(.reading details .details-body > h2:first-child){
        position: static !important;
        left: auto !important;
        width: auto !important;
        height: auto !important;
        overflow: visible !important;
        white-space: normal !important;
      }


      .reading{
        grid-column: 2;
        max-width: none;
        margin: 0;
      }

      /* Niveau 1 : panel OFF + lecture large */
      :global(body[data-reading-level="1"]) .page-shell{
        --panel-w: 0px;
        max-width: min(1560px, calc(100vw - 32px));
        grid-template-columns: var(--aside-w) minmax(0, 1fr);
      }
      :global(body[data-reading-level="1"]) .page-panel{ display: none !important; }

      /* Niveau 3 : lecture plus étroite + panel large ; TOC masqué */
      :global(body[data-reading-level="3"]) .page-shell{
        --reading-w: 62ch;
        --panel-w: 560px;
        max-width: calc(var(--reading-w) + var(--panel-w) + (var(--gap) * 1));
        grid-template-columns: minmax(0, var(--reading-w)) var(--panel-w);
      }
      :global(body[data-reading-level="3"]) .page-aside{ display: none; }
      :global(body[data-reading-level="3"]) .reading{ grid-column: 1; }

      /* Niveau 4 : TOC à gauche + panel */
      :global(body[data-reading-level="4"]) .page-shell{
        --aside-w: 320px;
        --reading-w: 64ch;
        --panel-w: 520px;
        max-width: calc(var(--aside-w) + var(--reading-w) + var(--panel-w) + (var(--gap) * 2));
        grid-template-columns: var(--aside-w) minmax(0, var(--reading-w)) var(--panel-w);
      }
      :global(body[data-reading-level="4"]) .page-aside{ display: block; }
      :global(body[data-reading-level="4"]) .reading{ grid-column: 2; }

      @media (max-width: 1260px){
        .page-shell{
          max-width: 1180px;
          grid-template-columns: var(--aside-w) minmax(0, 1fr);
        }
        .reading{ max-width: 78ch; margin: 0 auto; }
      }

      @media (max-width: 860px){
        .page-shell{
          max-width: 78ch;
          grid-template-columns: 1fr;
        }
        .page-aside{
          position: static;
          margin-bottom: 12px;
        }
        .page-aside__scroll{
          max-height: none;
          overflow: visible;
          padding-right: 0;
          padding-top: 0;
        }
        .reading{ max-width: 78ch; margin: 0 auto; }
      }

      /* ===== para-tools layout (non destructif, surcouche) ===== */
      :global(.reading p[id^="p-"]){
        position: relative;
        padding-right: 132px;
      }
      :global(.para-tools){
        position: absolute;
        right: 0;
        top: -2px;
        display: flex;
        flex-direction: column;
        align-items: flex-end;
        gap: 6px;
        z-index: 2;
      }
      :global(.para-id){
        border: 1px solid rgba(127,127,127,0.45);
        background: rgba(127,127,127,0.08);
        border-radius: 999px;
        padding: 2px 8px;
        font-size: 12px;
        font-weight: 750;
        cursor: pointer;
        user-select: none;
      }
      :global(.para-tools-actions){
        display: flex;
        gap: 6px;
      }
      :global(.para-bookmark){
        align-self: flex-end;
      }

      /* ✅ repère visuel du para "pilotant" le SidePanel */
      :global(.reading p.is-panel-current){
        background: rgba(140,140,255,0.07);
        border-radius: 12px;
      }
    </style>

    <script is:inline>
(() => {
  const safe = (label, fn) => {
    try { fn(); }
    catch (e) { console.error(`[EditionLayout] ${label} failed:`, e); }
  };

  const PAGE_GAP = 12;
  const HYST = 4;

  const headerEl = document.querySelector("header");
  const followEl = document.getElementById("reading-follow");
  const reading = document.querySelector("article.reading");

  const rfH1 = document.getElementById("rf-h1");
  const rfH2 = document.getElementById("rf-h2");
  const rfH3 = document.getElementById("rf-h3");
  const btnTopChapter = document.getElementById("rf-top-chapter");
  const btnTopSection = document.getElementById("rf-top-section");

  const docTitle = document.body?.dataset?.docTitle || document.title || "Archicratie";
  const docVersion = document.body?.dataset?.docVersion || "";

  function px(n){ return `${Math.max(0, Math.round(n))}px`; }
  function setRootVar(name, value) { document.documentElement.style.setProperty(name, value); }
  function headerH() { return headerEl ? headerEl.getBoundingClientRect().height : 0; }

  function syncHeaderH() {
    setRootVar("--sticky-header-h", px(headerH()));
    setRootVar("--page-gap", px(PAGE_GAP));
  }

  function syncReadingRect() {
    if (!reading) return;
    const r = reading.getBoundingClientRect();
    setRootVar("--reading-left", px(r.left));
    setRootVar("--reading-width", px(r.width));
  }

  function syncHeadingMetrics() {
    if (!reading) return;

    const h1 = reading.querySelector("h1");
    const h2 = reading.querySelector("h2");
    const h3 = reading.querySelector("h3");

    const sync = (el, prefix) => {
      if (!el) return;
      const cs = getComputedStyle(el);
      if (cs.fontSize) setRootVar(`--rf-${prefix}-size`, cs.fontSize);
      if (cs.lineHeight) setRootVar(`--rf-${prefix}-lh`, cs.lineHeight);
      if (cs.fontWeight) setRootVar(`--rf-${prefix}-fw`, cs.fontWeight);
    };

    sync(h1, "h1");
    sync(h2, "h2");
    sync(h3, "h3");
  }

  syncHeaderH(); syncReadingRect(); syncHeadingMetrics();

  if (document.fonts && document.fonts.ready) {
    document.fonts.ready.then(() => {
      syncHeaderH(); syncReadingRect(); syncHeadingMetrics();
    }).catch(() => {});
  }

  function getStickyOffsetPx() {
    const raw = getComputedStyle(document.documentElement)
      .getPropertyValue("--sticky-offset-px")
      .trim();
    const n = Number.parseFloat(raw);
    return Number.isFinite(n) ? n : (headerH() + PAGE_GAP);
  }

  function absTop(el) {
    const r = el.getBoundingClientRect();
    return r.top + (window.scrollY || 0);
  }

  function scrollToElWithOffset(el, extra = 10, behavior = "auto") {
    const y = absTop(el) - getStickyOffsetPx() - extra;
    window.scrollTo({ top: Math.max(0, y), behavior });
  }

  // ===== details open (robuste) + export =====
  function openDetailsIfNeeded(el) {
    try {
      if (!el) return;

      let d = el.closest?.("details") || null;

      if (!d && el.classList?.contains("details-anchor")) {
        const n = el.nextElementSibling;
        if (n && n.tagName === "DETAILS") d = n;
      }

      if (!d) {
        const s = el.closest?.("summary");
        if (s && s.parentElement && s.parentElement.tagName === "DETAILS") d = s.parentElement;
      }

      if (d && d.tagName === "DETAILS" && !d.open) d.open = true;
    } catch {}
  }
  window.__archiOpenDetailsIfNeeded = openDetailsIfNeeded;

  function highlightEl(el) {
    try {
      if (!el) return;
      el.classList.add("para-highlight");
      setTimeout(() => el.classList.remove("para-highlight"), 2400);
    } catch {}
  }

  function highlightFromTarget(el) {
    try {
      if (!el) return;
      if (el.matches?.('.reading p[id^="p-"]')) return highlightEl(el);
      const p = el.closest?.('.reading p[id^="p-"]');
      if (p) return highlightEl(p);

      if (el.classList?.contains("details-anchor")) {
        const d = (el.nextElementSibling && el.nextElementSibling.tagName === "DETAILS") ? el.nextElementSibling : null;
        const p2 = d ? d.querySelector('.details-body p[id^="p-"]') : null;
        if (p2) return highlightEl(p2);
      }

      highlightEl(el);
    } catch {}
  }

  function cssEscape(s) {
    try { return (window.CSS && CSS.escape) ? CSS.escape(String(s)) : String(s).replace(/["\\]/g, "\\$&"); }
    catch { return String(s).replace(/["\\]/g, "\\$&"); }
  }

  // ===== Bookmarks (pinned + auto) =====
  const PINNED_KEY = "archicratie:bookmark:pinned";
  const LAST_KEY = `archicratie:bookmark:last:${window.location.pathname}`;

  function readJSON(key) {
    try { return JSON.parse(localStorage.getItem(key) || "null"); }
    catch { return null; }
  }
  function writeJSON(key, obj) {
    try { localStorage.setItem(key, JSON.stringify(obj)); } catch {}
  }

  function updateResumeButton() {
    const btn = document.getElementById("resume-btn");
    if (!btn) return;

    const bm = readJSON(PINNED_KEY) || readJSON(LAST_KEY);
    if (!bm || !bm.url) { btn.hidden = true; return; }

    btn.hidden = false;
    btn.href = bm.url;
    btn.title = bm.title ? `Reprendre : ${bm.title}` : "Reprendre la lecture";

    btn.onclick = (ev) => {
      try {
        const here = new URL(window.location.href);
        here.search = "";

        const target = new URL(bm.url, window.location.origin);
        target.search = "";

        if (here.pathname === target.pathname) {
          ev.preventDefault();
          const id = (target.hash || "").slice(1);
          if (!id) return;

          const el = document.getElementById(id);
          if (!el) return;

          openDetailsIfNeeded(el);
          scrollToElWithOffset(el, 12, "auto");
          highlightFromTarget(el);
          history.replaceState(null, "", here.pathname + "#" + id);
        }
      } catch {}
    };
  }
  window.__archiUpdateResumeButton = updateResumeButton;

  if (window.location.search.includes("body=")) {
    history.replaceState(null, "", window.location.pathname + window.location.hash);
  }

  // ===== legacy hash fallback (inchangé) =====
  safe("legacy-hash", () => {
    const h = window.location.hash || "";
    const m = h.match(/^#p-(\d+)-[0-9a-f]{8}$/i);
    if (!m) return;

    const oldId = h.slice(1);
    if (document.getElementById(oldId)) return;

    const idx = m[1];
    const prefix = `p-${idx}-`;
    const replacement = document.querySelector(`.reading [id^="${prefix}"]`);
    if (!replacement) return;

    console.warn("[anchors] legacy hash fallback used:", `#${oldId}`, "→", `#${replacement.id}`);
    scrollToElWithOffset(replacement, 12, "auto");
    highlightFromTarget(replacement);
  });

  updateResumeButton();

  // ===== Jump (id / URL#id / extrait) =====
  const jumpForm = document.getElementById("jump-form");
  const jumpInput = document.getElementById("jump-input");

  function flashJumpError() {
    if (!jumpInput) return;
    jumpInput.classList.add("is-error");
    setTimeout(() => jumpInput.classList.remove("is-error"), 650);
  }

  function normalizeParaId(input) {
    const raw = String(input || "").trim();
    if (!raw) return "";
    const hashIdx = raw.lastIndexOf("#");
    const h = (hashIdx >= 0) ? raw.slice(hashIdx + 1) : raw;
    const id = h.replace(/^#/, "").trim();
    if (!/^p-\d+-/i.test(id)) return "";
    return id;
  }

  function findParaLocalByIdOrPrefix(id) {
    const exact = document.getElementById(id);
    if (exact) return exact;
    return document.querySelector(`.reading [id^="${cssEscape(id)}"]`);
  }

  function normalizeForSearch(s) {
    return String(s || "")
      .toLowerCase()
      .normalize("NFD")
      .replace(/\p{Diacritic}/gu, "")
      .replace(/\s+/g, " ")
      .trim();
  }

  let _paraIndexP = null;
  async function loadParaIndex() {
    if (_paraIndexP) return _paraIndexP;
    _paraIndexP = (async () => {
      const res = await fetch("/para-index.json?_=" + Date.now(), { cache: "no-store" }).catch(() => null);
      if (res && res.ok) return await res.json();
      return null;
    })();
    return _paraIndexP;
  }

  function gotoPageAnchor(page, id) {
    const url = new URL(page, window.location.origin);
    url.hash = id;
    window.location.assign(url.toString());
  }

  async function doJump(input) {
    const raw = String(input || "").trim();
    if (!raw) return false;

    const id = normalizeParaId(raw);
    if (id) {
      const local = findParaLocalByIdOrPrefix(id);
      if (local) {
        openDetailsIfNeeded(local);
        scrollToElWithOffset(local, 12, "auto");
        highlightFromTarget(local);
        history.replaceState(null, "", `${window.location.pathname}#${local.id}`);
        return true;
      }

      const idx = await loadParaIndex();
      if (idx?.byId && idx.items && idx.byId[id] != null) {
        const item = idx.items[idx.byId[id]];
        if (item?.page && item?.id) {
          const targetPath = new URL(item.page, window.location.origin).pathname;
          if (targetPath === window.location.pathname) {
            window.location.hash = item.id;
            return true;
          }
          gotoPageAnchor(item.page, item.id);
          return true;
        }
      }

      flashJumpError();
      return false;
    }

    const q = normalizeForSearch(raw);
    if (q.length < 10) { flashJumpError(); return false; }

    const paras = Array.from(document.querySelectorAll('.reading p[id^="p-"]'));
    for (const p of paras) {
      const t = normalizeForSearch(p.innerText || p.textContent || "");
      if (t.includes(q)) {
        openDetailsIfNeeded(p);
        scrollToElWithOffset(p, 12, "auto");
        highlightFromTarget(p);
        history.replaceState(null, "", `${window.location.pathname}#${p.id}`);
        return true;
      }
    }

    const idx = await loadParaIndex();
    if (!idx?.items) { flashJumpError(); return false; }

    let best = null;
    let bestScore = -1;

    for (const it of idx.items) {
      if (!it?.text || !it?.page || !it?.id) continue;
      const t = normalizeForSearch(it.text);
      const pos = t.indexOf(q);
      if (pos < 0) continue;

      const score = (100000 - pos) + Math.min(4000, q.length * 8);
      if (score > bestScore) { bestScore = score; best = it; }
    }

    if (!best) { flashJumpError(); return false; }

    const targetPath = new URL(best.page, window.location.origin).pathname;
    if (targetPath === window.location.pathname) {
      window.location.hash = best.id;
      return true;
    }
    gotoPageAnchor(best.page, best.id);
    return true;
  }

  safe("jump-form", () => {
    if (!jumpForm || !jumpInput) return;
    jumpForm.addEventListener("submit", async (ev) => {
      ev.preventDefault();
      const ok = await doJump(jumpInput.value);
      if (ok) jumpInput.value = "";
    });
  });

  // ===== Gitea globals (déjà posés par le boot head) =====
  const giteaReady = Boolean(window.__archiGitea && window.__archiGitea.ready);

  const quoteBlock = (s) =>
    String(s || "")
      .split(/\r?\n/)
      .map((l) => (`> ${l}`).trimEnd())
      .join("\n");

  function buildIssueURLCorrection(anchorId, fullText, excerpt) {
    const g = window.__archiGitea || { base:"", owner:"", repo:"" };
    const base = String(g.base).replace(/\/+$/, "");
    const issue = new URL(`${base}/${g.owner}/${g.repo}/issues/new`);

    const local = new URL(window.location.href);
    local.search = "";
    local.hash = anchorId;

    const path = local.pathname;
    const issueTitle = `[Correction] ${anchorId} — ${docTitle}`;

    const makeBody = (useFull) => {
      const header = [
        `Chemin: ${path}`,
        `URL: ${local.toString()}`,
        `Ancre: #${anchorId}`,
        `Version: ${docVersion || "(non renseignée)"}`,
        `Type: type/correction`,
        `State: state/recevable`,
        ``,
      ];

      const texteActuel = useFull
        ? [`Texte actuel (copie exacte du paragraphe):`, quoteBlock(fullText || "")]
        : [
            `Texte actuel (extrait):`,
            quoteBlock(excerpt || ""),
            ``,
            `Note: paragraphe long → extrait (pour éviter une URL trop longue).`,
          ];

      const footer = [
        ``,
        `Proposition (remplacer par):`,
        ``,
        `Justification:`,
        ``,
        `---`,
        `Note: issue générée depuis le site (pré-remplissage).`,
      ];

      return header.concat(texteActuel, footer).join("\n");
    };

    issue.searchParams.set("title", issueTitle);

    const fullOk = Boolean(fullText && fullText.length && fullText.length <= 1600);
    issue.searchParams.set("body", makeBody(fullOk));

    if (issue.toString().length > 6500) {
      issue.searchParams.set("body", makeBody(false));
    }

    return issue.toString();
  }

  // ===== Paragraph tools + pinned bookmark =====
  const parasAll = Array.from(document.querySelectorAll('.reading p[id^="p-"]'));

  function normalizeSpaces(s) {
    return String(s || "").replace(/\s+/g, " ").trim();
  }

  function getSelectionTextWithinPara(pEl) {
    try {
      const sel = window.getSelection ? window.getSelection() : null;
      if (!sel || sel.isCollapsed) return "";
      const t = String(sel.toString() || "").trim();
      if (!t) return "";
      const a = sel.anchorNode;
      const f = sel.focusNode;
      if (!a || !f) return "";
      if (!pEl.contains(a) || !pEl.contains(f)) return "";
      return normalizeSpaces(t);
    } catch { return ""; }
  }

  function getCleanParagraphText(pEl) {
    try {
      const clone = pEl.cloneNode(true);
      clone.querySelectorAll(".para-tools").forEach((n) => n.remove());
      clone.querySelectorAll("[data-noquote]").forEach((n) => n.remove());
      return normalizeSpaces(clone.innerText || "");
    } catch {
      return normalizeSpaces(pEl.innerText || pEl.textContent || "");
    }
  }

  function hideEl(el) {
    try {
      el.hidden = true;
      el.style.display = "none";
      el.setAttribute("aria-hidden", "true");
      el.setAttribute("tabindex", "-1");
    } catch {}
  }
  function showEl(el) {
    try {
      el.hidden = false;
      el.style.display = "";
      el.removeAttribute("aria-hidden");
      el.removeAttribute("tabindex");
    } catch {}
  }

  safe("para-tools", () => {
    for (const p of parasAll) {
      if (p.querySelector(".para-tools")) continue;

      const tools = document.createElement("span");
      tools.className = "para-tools";
      tools.setAttribute("data-noquote", "1");

      const idBtn = document.createElement("button");
      idBtn.type = "button";
      idBtn.className = "para-id";
      idBtn.textContent = p.id;
      idBtn.title = "Copier le lien du paragraphe";
      idBtn.addEventListener("click", async () => {
        try {
          const u = new URL(window.location.href);
          u.search = "";
          u.hash = p.id;
          await navigator.clipboard.writeText(u.toString());
          const prev = idBtn.textContent;
          idBtn.textContent = "Lien copié";
          setTimeout(() => (idBtn.textContent = prev), 900);
        } catch {}
      });

      const actions = document.createElement("span");
      actions.className = "para-tools-actions";

      const citeBtn = document.createElement("button");
      citeBtn.type = "button";
      citeBtn.className = "para-cite";
      citeBtn.textContent = "Citer";

      citeBtn.addEventListener("click", async (ev) => {
        const pageUrl = new URL(window.location.href);
        pageUrl.search = "";
        pageUrl.hash = p.id;

        const signature = `${docTitle}${docVersion ? ` (v${docVersion})` : ""} — ${pageUrl.toString()}`;
        let payload = signature;

        if (!(ev && ev.shiftKey)) {
          const selTxt = getSelectionTextWithinPara(p);
          const paraTxt = selTxt || getCleanParagraphText(p);
          payload = paraTxt ? `${paraTxt}\n\n${signature}` : signature;
        }

        try {
          await navigator.clipboard.writeText(payload);
          const prev = citeBtn.textContent;
          citeBtn.textContent = (ev && ev.shiftKey) ? "Lien copié" : "Copié";
          setTimeout(() => (citeBtn.textContent = prev), 900);
        } catch {
          window.prompt("Copiez la citation :", payload);
        }
      });

      actions.appendChild(citeBtn);

      let propose = null;
      if (giteaReady) {
        propose = document.createElement("a");
        propose.className = "para-propose";
        propose.textContent = "Proposer";
        propose.setAttribute("aria-label", "Proposer une correction sur Gitea");

        const raw = getCleanParagraphText(p);
        const excerpt = raw.length > 420 ? (raw.slice(0, 420) + "…") : raw;
        const issueUrl = buildIssueURLCorrection(p.id, raw, excerpt);

        propose.href = issueUrl;
        propose.target = "_blank";
        propose.rel = "noopener noreferrer";
        propose.dataset.propose = "1";
        propose.dataset.url = issueUrl;
        propose.dataset.full = raw;

        hideEl(propose);
        actions.appendChild(propose);
      }

      const bmBtn = document.createElement("button");
      bmBtn.type = "button";
      bmBtn.className = "para-bookmark";
      bmBtn.textContent = "Marque-page";

      bmBtn.addEventListener("click", () => {
        const pageUrl = new URL(window.location.href);
        pageUrl.search = "";
        pageUrl.hash = p.id;

        writeJSON(PINNED_KEY, {
          url: pageUrl.toString(),
          path: pageUrl.pathname,
          anchor: p.id,
          title: docTitle,
          version: docVersion,
          ts: Date.now(),
        });

        updateResumeButton();

        const prev = bmBtn.textContent;
        bmBtn.textContent = "Marqué ✓";
        setTimeout(() => (bmBtn.textContent = prev), 900);
      });

      tools.appendChild(idBtn);
      tools.appendChild(actions);
      tools.appendChild(bmBtn);
      p.appendChild(tools);
    }
  });

  safe("propose-gate", () => {
    if (!giteaReady) return;
    const p = window.__archiIsEditorP || Promise.resolve(false);
    p.then((ok) => {
      document.querySelectorAll(".para-propose").forEach((el) => {
        if (ok) showEl(el);
        else el.remove();
      });
    }).catch((err) => {
      console.warn("[proposer] gate failed; keeping Proposer hidden", err);
      document.querySelectorAll(".para-propose").forEach((el) => hideEl(el));
    });
  });

  // ===== Auto-checkpoint (sur paragraphe ACTIF, pas sur hover) =====
  let lastAuto = 0;
  function writeLastSeen(id) {
    const now = Date.now();
    if (now - lastAuto < 800) return;
    lastAuto = now;

    const u = new URL(window.location.href);
    u.search = "";
    u.hash = id;

    writeJSON(LAST_KEY, {
      url: u.toString(),
      path: u.pathname,
      anchor: id,
      title: docTitle,
      version: docVersion,
      ts: now,
    });
  }

  // ===== Détection “para actif” : juste sous reading-follow (✅ fixe le mismatch SidePanel) =====
  let __activeParaId = "";
  let __hoverParaId = "";
  let __panelParaEl = null;
  let __rafActive = 0;

  function markPanelCurrent(id) {
    try {
      if (__panelParaEl) __panelParaEl.classList.remove("is-panel-current");
      __panelParaEl = null;

      const el = document.getElementById(id);
      const p = el?.matches?.('.reading p[id^="p-"]') ? el : el?.closest?.('.reading p[id^="p-"]');
      if (p) {
        p.classList.add("is-panel-current");
        __panelParaEl = p;
      }
    } catch {}
  }

  function dispatchCurrentPara(id, kind) {
    try { window.dispatchEvent(new CustomEvent("archicratie:currentPara", { detail: { id, kind } })); } catch {}
  }

  function setCurrentPara(id, kind = "active") {
    if (!id) return;
    if (window.__archiCurrentParaId === id && kind !== "hover") return;

    window.__archiCurrentParaId = id;
    window.__archiLastParaId = id; // utile pour level switch
    markPanelCurrent(id);
    dispatchCurrentPara(id, kind);
  }

  function probeParaUnderFollow() {
    if (!reading) return null;

    const rr = reading.getBoundingClientRect();
    if (!rr || rr.width < 10) return null;

    const x = Math.round(rr.left + Math.min(140, rr.width * 0.45));
    const baseY = Math.round(getStickyOffsetPx() + 14);

    const ys = [baseY, baseY + 18, baseY + 36, baseY + 54, baseY + 72, baseY + 90, baseY + 108];

    for (const y of ys) {
      const stack = (document.elementsFromPoint ? document.elementsFromPoint(x, y) : [document.elementFromPoint(x, y)])
        .filter(Boolean);

      for (const el of stack) {
        const p = el.closest?.('.reading p[id^="p-"]');
        if (p && p.id) return p;
      }
    }

    const y0 = baseY;
    for (const p of parasAll) {
      const r = p.getBoundingClientRect();
      if (r.top <= y0 && r.bottom > y0) return p;
      if (r.top > y0) return p;
    }
    return null;
  }

  function recomputeActivePara() {
    const p = probeParaUnderFollow();
    const id = p?.id || "";
    if (!id) return;

    if (id !== __activeParaId) {
      __activeParaId = id;
      writeLastSeen(id);
      updateResumeButton();
    }

    if (!__hoverParaId) setCurrentPara(__activeParaId, "active");
  }

  function scheduleActive() {
    if (__rafActive) return;
    __rafActive = requestAnimationFrame(() => {
      __rafActive = 0;
      recomputeActivePara();
    });
  }

  safe("active-para", () => {
    scheduleActive();
    window.addEventListener("scroll", scheduleActive, { passive: true });
  });

  safe("hover-para", () => {
    if (!reading) return;

    let t = 0;
    reading.addEventListener("pointerover", (ev) => {
      const p = ev.target?.closest?.('.reading p[id^="p-"]');
      if (!p || !p.id) return;
      __hoverParaId = p.id;
      if (t) { clearTimeout(t); t = 0; }
      setCurrentPara(__hoverParaId, "hover");
    });

    reading.addEventListener("pointerout", () => {
      if (t) clearTimeout(t);
      t = setTimeout(() => {
        __hoverParaId = "";
        if (__activeParaId) setCurrentPara(__activeParaId, "active");
      }, 120);
    });
  });

  // ===== Hash handling (robuste) =====
  function resolveParaIdFromEl(el) {
    if (!el) return "";
    if (el.matches?.('.reading p[id^="p-"]')) return el.id;
    const p = el.closest?.('.reading p[id^="p-"]');
    if (p?.id) return p.id;

    if (el.classList?.contains("details-anchor")) {
      const d = (el.nextElementSibling && el.nextElementSibling.tagName === "DETAILS") ? el.nextElementSibling : null;
      const p2 = d ? d.querySelector('.details-body p[id^="p-"]') : null;
      if (p2?.id) return p2.id;
    }
    return "";
  }

  function handleHashTarget(id, behavior = "auto") {
    try {
      const clean = String(id || "").replace(/^#/, "").trim();
      if (!clean) return false;

      const el = document.getElementById(clean);
      if (!el) return false;

      openDetailsIfNeeded(el);
      scrollToElWithOffset(el, 12, behavior);
      highlightFromTarget(el);

      history.replaceState(null, "", `${window.location.pathname}#${clean}`);

      const pid = resolveParaIdFromEl(el);
      if (pid) {
        __activeParaId = pid;
        setCurrentPara(pid, "hash");
        writeLastSeen(pid);
        updateResumeButton();
      }

      return true;
    } catch { return false; }
  }

  safe("hash-links", () => {
    document.addEventListener("click", (ev) => {
      const a = ev.target?.closest?.('a[href^="#"]');
      if (!a) return;

      const href = a.getAttribute("href") || "";
      if (!href || href === "#") return;

      const id = href.slice(1);
      if (!id) return;
      if (!document.getElementById(id)) return;

      ev.preventDefault();
      handleHashTarget(id, "smooth");
    });

    window.addEventListener("hashchange", () => {
      handleHashTarget((window.location.hash || "").slice(1), "auto");
    });
  });

  // ===== TOC local sync (inchangé) =====
  function isScrollable(el) {
    try {
      if (!el || !(el instanceof HTMLElement)) return false;
      const cs = getComputedStyle(el);
      const oy = cs.overflowY;
      if (!(oy === "auto" || oy === "scroll" || oy === "overlay")) return false;
      return el.scrollHeight > el.clientHeight + 2;
    } catch { return false; }
  }

  function findScrollParent(fromEl, stopEl) {
    let n = fromEl;
    while (n && n !== document.body) {
      if (stopEl && n === stopEl) break;
      if (isScrollable(n)) return n;
      n = n.parentElement;
    }
    if (stopEl && isScrollable(stopEl)) return stopEl;
    return null;
  }

  function syncTocLocal(activeId) {
    if (!activeId) return;

    const list =
      document.querySelector(".toc-local__list") ||
      document.querySelector("[data-toc-local]") ||
      document.querySelector(".toc-local ul") ||
      null;

    if (!list) return;

    const sel = `a[href="#${cssEscape(activeId)}"]`;
    const link = list.querySelector(sel);
    if (!link) return;

    list.querySelectorAll("a[aria-current]").forEach((a) => a.removeAttribute("aria-current"));
    link.setAttribute("aria-current", "location");

    const stop = document.querySelector(".page-aside__scroll") || null;
    const scrollBox = findScrollParent(link, stop) || stop || list;
    if (!scrollBox) return;

    const pad = 12;
    const rLink = link.getBoundingClientRect();
    const rBox = scrollBox.getBoundingClientRect();
    const linkTopInBox = (rLink.top - rBox.top) + scrollBox.scrollTop;
    const target = Math.max(0, Math.round(linkTopInBox - pad));

    try { scrollBox.scrollTo({ top: target, behavior: "auto" }); }
    catch { scrollBox.scrollTop = target; }
  }
  window.__archiSyncTocLocal = syncTocLocal;

  // ===== Reading-follow (H1/H2/H3 only) =====
  safe("reading-follow", () => {
    if (!reading || !followEl) return;

    const h1 = reading.querySelector("h1");

    // ✅ FIX: ne pas dépendre du tag <span>; accepter .details-anchor quel que soit le tag,
    // et utiliser <details> comme marker (top fiable) pour que rf-h2 s'affiche correctement.
    const h2Anchors = Array.from(reading.querySelectorAll(".details-anchor[id]"))
      .map((s) => {
        const d = (s.nextElementSibling && s.nextElementSibling.tagName === "DETAILS")
          ? s.nextElementSibling
          : (s.closest?.("details") || null);

        const h2 = d ? d.querySelector(".details-body h2") : null;
        const sum = d ? d.querySelector("summary") : null;

        const title =
          ((s.getAttribute("data-title") || "").trim()
          || (h2?.textContent || "").trim()
          || (sum?.textContent || "").trim()
          || "Section");

        const marker = d || s;

        return { id: s.id, anchor: s, marker, title, h2, details: d };
      })
      .filter(Boolean);

    const h2Plain = Array.from(reading.querySelectorAll("h2[id]"))
      .map((h2) => ({ id: h2.id, anchor: h2, marker: h2, title: (h2.textContent || "").trim() || "Section", h2 }));

    const H2 = (h2Anchors.length ? h2Anchors : h2Plain)
      .slice()
      .sort((a,b) => absTop(a.marker) - absTop(b.marker));

    const H3 = Array.from(reading.querySelectorAll("h3[id]"))
      .map((h3) => ({ id: h3.id, el: h3, title: (h3.textContent || "").trim() || "Sous-section" }));

    let curH2 = null;
    let curH3 = null;
    let lastY = window.scrollY || 0;
    let lastOpenedH2 = "";

    function computeLineY(followH) {
      return headerH() + PAGE_GAP + (followH || 0) + HYST;
    }

    function pickH2(lineY) {
      if (!H2.length) return null;
      let cand = null;
      for (const t of H2) {
        const top = t.marker.getBoundingClientRect().top;
        if (top <= lineY) cand = t;
        else break;
      }
      if (!cand) cand = H2[0];

      const down = (window.scrollY || 0) >= lastY;
      if (!down && cand && curH2 && cand.id !== curH2.id) {
        const topNew = cand.marker.getBoundingClientRect().top;
        if (topNew > lineY - (HYST * 2)) cand = curH2;
      }
      return cand;
    }

    function isAfter(start, el) {
      if (!start || !el) return false;
      return Boolean(start.compareDocumentPosition(el) & Node.DOCUMENT_POSITION_FOLLOWING);
    }
    function isBefore(end, el) {
      if (!end || !el) return true;
      return Boolean(end.compareDocumentPosition(el) & Node.DOCUMENT_POSITION_PRECEDING);
    }

    function pickH3(lineY, activeH2) {
      if (!activeH2) return null;

      const visible = H3.filter((t) => {
        const d = t.el.closest?.("details");
        return !(d && !d.open);
      });

      const d2 = activeH2.details || activeH2.h2?.closest?.("details") || null;
      let scoped = d2 ? visible.filter((t) => t.el.closest?.("details") === d2) : visible;

      const i2 = H2.findIndex((t) => t.id === activeH2.id);
      const nextH2 = (i2 >= 0 && i2 + 1 < H2.length) ? H2[i2 + 1] : null;

      const startNode = activeH2.anchor || activeH2.h2 || activeH2.marker;
      const endNode = nextH2 ? (nextH2.anchor || nextH2.h2 || nextH2.marker) : null;

      scoped = scoped.filter((t) => isAfter(startNode, t.el) && isBefore(endNode, t.el));

      let cand = null;
      for (const t of scoped) {
        const top = t.el.getBoundingClientRect().top;
        if (top <= lineY) cand = t;
        else break;
      }
      return cand;
    }

    function maybeOpenActiveSection(activeH2, lineY) {
      if (!activeH2 || !activeH2.id) return;
      const pre = activeH2.marker.getBoundingClientRect().top <= (lineY + 140);
      if (!pre) return;
      if (activeH2.id !== lastOpenedH2) {
        lastOpenedH2 = activeH2.id;
        openDetailsIfNeeded(activeH2.anchor || activeH2.h2 || activeH2.marker);
      }
    }

    function updateFollow() {
      if (!followEl) return;

      const baseY = headerH() + PAGE_GAP;
      const showH1 = !!(h1 && (h1.getBoundingClientRect().bottom <= baseY + HYST));

      if (rfH1 && h1 && showH1) { rfH1.hidden = false; rfH1.textContent = (h1.textContent || "").trim(); }
      else if (rfH1) rfH1.hidden = true;

      const lineY0 = computeLineY(0);
      curH2 = pickH2(lineY0);
      maybeOpenActiveSection(curH2, lineY0);
      curH3 = pickH3(lineY0, curH2);

      try { syncTocLocal((curH3 && curH3.id) ? curH3.id : (curH2 && curH2.id) ? curH2.id : ""); } catch {}

      if (rfH2 && curH2) { rfH2.hidden = false; rfH2.textContent = curH2.title; }
      else if (rfH2) rfH2.hidden = true;

      if (rfH3 && curH3) { rfH3.hidden = false; rfH3.textContent = curH3.title; }
      else if (rfH3) rfH3.hidden = true;

      const any0 = (rfH1 && !rfH1.hidden) || (rfH2 && !rfH2.hidden) || (rfH3 && !rfH3.hidden);
      followEl.classList.toggle("is-on", Boolean(any0));
      followEl.setAttribute("aria-hidden", any0 ? "false" : "true");

      const inner = followEl.querySelector(".reading-follow__inner");
      const followH = (any0 && inner) ? inner.getBoundingClientRect().height : 0;

      const lineY = computeLineY(followH);
      curH2 = pickH2(lineY);
      maybeOpenActiveSection(curH2, lineY);
      curH3 = pickH3(lineY, curH2);

      try { syncTocLocal((curH3 && curH3.id) ? curH3.id : (curH2 && curH2.id) ? curH2.id : ""); } catch {}

      if (rfH2 && curH2) {
        const on = curH2.marker.getBoundingClientRect().top <= lineY;
        rfH2.hidden = !on;
        if (on) rfH2.textContent = curH2.title;
      } else if (rfH2) rfH2.hidden = true;

      if (rfH3 && curH3) {
        const on = curH3.el.getBoundingClientRect().top <= lineY;
        rfH3.hidden = !on;
        if (on) rfH3.textContent = curH3.title;
      } else if (rfH3) rfH3.hidden = true;

      const any = (rfH1 && !rfH1.hidden) || (rfH2 && !rfH2.hidden) || (rfH3 && !rfH3.hidden);
      followEl.classList.toggle("is-on", Boolean(any));
      followEl.setAttribute("aria-hidden", any ? "false" : "true");

      const inner2 = followEl.querySelector(".reading-follow__inner");
      const followH2 = (any && inner2) ? inner2.getBoundingClientRect().height : 0;

      setRootVar("--followbar-h", px(followH2));
      setRootVar("--sticky-offset-px", String(Math.round(headerH() + PAGE_GAP + followH2)));

      if (btnTopChapter) btnTopChapter.hidden = !any;
      if (btnTopSection) {
        const ok = Boolean(curH2 && rfH2 && !rfH2.hidden);
        btnTopSection.hidden = !ok;
      }

      lastY = window.scrollY || 0;
      syncReadingRect();
      scheduleActive();
    }

    window.__archiUpdateFollow = updateFollow;

    let ticking = false;
    const onScroll = () => {
      if (ticking) return;
      ticking = true;
      requestAnimationFrame(() => {
        ticking = false;
        updateFollow();
      });
    };

    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll);

    if (rfH1) rfH1.addEventListener("click", () => { if (h1) scrollToElWithOffset(h1, 12, "smooth"); });
    if (rfH2) rfH2.addEventListener("click", () => {
      if (!curH2) return;
      openDetailsIfNeeded(curH2.anchor || curH2.h2 || curH2.marker);
      scrollToElWithOffset(curH2.marker, 12, "smooth");
      history.replaceState(null, "", `${window.location.pathname}#${curH2.id}`);
    });
    if (rfH3) rfH3.addEventListener("click", () => {
      if (!curH3) return;
      openDetailsIfNeeded(curH3.el);
      scrollToElWithOffset(curH3.el, 12, "smooth");
      history.replaceState(null, "", `${window.location.pathname}#${curH3.id}`);
    });

    if (btnTopChapter) btnTopChapter.addEventListener("click", () => { if (h1) scrollToElWithOffset(h1, 12, "smooth"); });
    if (btnTopSection) btnTopSection.addEventListener("click", () => {
      if (!curH2) return;
      openDetailsIfNeeded(curH2.anchor || curH2.h2 || curH2.marker);
      scrollToElWithOffset(curH2.marker, 12, "smooth");
      history.replaceState(null, "", `${window.location.pathname}#${curH2.id}`);
    });

    updateFollow();

    const initialHash = (location.hash || "").slice(1);
    if (initialHash) {
      const el = document.getElementById(initialHash);
      if (el) {
        openDetailsIfNeeded(el);
        setTimeout(() => {
          scrollToElWithOffset(el, 12, "auto");
          highlightFromTarget(el);
          scheduleActive();
        }, 0);
      }
    }
  });

  // ✅ Reflow sync : resize + changement de niveau (restaure le même paragraphe)
  function afterReflow(fn) {
    requestAnimationFrame(() => requestAnimationFrame(fn));
  }

  safe("reflow-sync", () => {
    window.addEventListener("resize", () => {
      afterReflow(() => {
        syncHeaderH(); syncReadingRect(); syncHeadingMetrics();
        try { window.__archiUpdateFollow?.(); } catch {}
        scheduleActive();
      });
    });

    window.addEventListener("archicratie:readingLevel", () => {
      afterReflow(() => {
        syncHeaderH(); syncReadingRect(); syncHeadingMetrics();
        try { window.__archiUpdateFollow?.(); } catch {}

        const ctx = window.__archiLevelSwitchCtx || null;
        const want =
          (ctx && ctx.paraId) ||
          window.__archiCurrentParaId ||
          __activeParaId ||
          window.__archiLastParaId ||
          String(location.hash || "").replace(/^#/, "");

        if (want) {
          const el = document.getElementById(want);
          if (el) {
            openDetailsIfNeeded(el);
            scrollToElWithOffset(el, 12, "auto");
            highlightFromTarget(el);
            history.replaceState(null, "", `${location.pathname}#${want}`);
            __activeParaId = want;
            setCurrentPara(want, "level");
          } else if (ctx && Number.isFinite(ctx.scrollY)) {
            window.scrollTo({ top: ctx.scrollY, behavior: "auto" });
          }
        }

        scheduleActive();
      });
    });
  });

  // ===== Auto-unfold <details> (robuste) =====
  safe("details-autounfold", () => {
    if (!reading) return;

    const sections = Array.from(reading.querySelectorAll("details.details-section"));
    if (!sections.length) return;

    // ✅ Lecture continue : tout ouvert, plus d’accordéon
    for (const d of sections) d.open = true;

    try { window.__archiUpdateFollow?.(); } catch {}
    scheduleActive();
  });
})();
    </script>
  </body>
</html>
