Files
archicratie-edition/src/components/SidePanel.astro
Archicratia 70611d16f8
All checks were successful
SMOKE / smoke (push) Successful in 11s
CI / build-and-anchors (push) Successful in 44s
CI / build-and-anchors (pull_request) Successful in 44s
feat(glossaire): add advanced external paradigms from chapter 3
2026-03-15 10:05:32 +01:00

1074 lines
31 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
// src/components/SidePanel.astro
---
<aside class="page-panel" aria-label="Panneau contextuel">
<div class="page-panel__inner">
<div class="panel-head">
<div class="panel-head__left">
<span class="panel-head__label" aria-hidden="true">¶</span>
<code class="panel-head__id" id="panel-para-id">—</code>
</div>
</div>
<div class="panel-msg panel-msg--head" id="panel-head-msg" hidden></div>
{/* ✅ actions medias déplacées en haut (niveau 3 uniquement) */}
<div class="panel-top-actions level-3" aria-label="Actions médias">
<div class="panel-actions">
<button class="panel-btn" id="panel-media-all" type="button">Voir tous les éléments</button>
<button class="panel-btn panel-btn--primary" id="panel-media-submit" type="button">
Soumettre un média (Gitea)
</button>
</div>
<div class="panel-msg" id="panel-media-msg" hidden></div>
</div>
{/* ✅ actions références en haut (niveau 2 uniquement) */}
<div class="panel-top-actions level-2" aria-label="Actions références">
<div class="panel-actions">
<button class="panel-btn panel-btn--primary" id="panel-ref-submit" type="button">
Soumettre une référence (Gitea)
</button>
</div>
<div class="panel-msg" id="panel-ref-msg" hidden></div>
</div>
<section class="panel-block level-2" aria-label="Références et auteurs">
<h2 class="panel-title">Références & auteurs</h2>
<div class="panel-body" id="panel-l2"></div>
</section>
<section class="panel-block level-3" aria-label="Illustrations et diagrammes">
<h2 class="panel-title">Illustrations & diagrammes</h2>
<div class="panel-body" id="panel-l3"></div>
</section>
<section class="panel-block level-4" aria-label="Commentaires">
<h2 class="panel-title">Commentaires</h2>
<div class="panel-body" id="panel-l4"></div>
<div class="panel-compose">
<label class="panel-label" for="panel-comment-text">Nouveau commentaire</label>
<textarea id="panel-comment-text" class="panel-textarea" rows="5" placeholder="Écris ici…"></textarea>
<div class="panel-actions">
<button class="panel-btn panel-btn--primary" id="panel-comment-send" type="button">Envoyer</button>
</div>
<div class="panel-msg" id="panel-comment-msg" hidden></div>
</div>
</section>
</div>
{/* ✅ Lightbox media (plein écran) */}
<div class="panel-lightbox" id="panel-lightbox" hidden aria-hidden="true">
<div class="panel-lightbox__overlay" data-close="1"></div>
<div class="panel-lightbox__dialog" role="dialog" aria-modal="true" aria-label="Aperçu du média">
<button class="panel-lightbox__close" type="button" data-close="1" aria-label="Fermer">×</button>
<div class="panel-lightbox__content" id="panel-lightbox-content"></div>
<div class="panel-lightbox__caption" id="panel-lightbox-caption" hidden></div>
</div>
</div>
</aside>
<script is:inline>
(() => {
const root = document.querySelector(".page-panel");
if (!root) return;
// Anti double-init (HMR / script injecté 2x / re-mount)
if (root.dataset.archiSidePanelInit === "1") return;
root.dataset.archiSidePanelInit = "1";
const pageKey = String(location.pathname || "").replace(/^\/+/, "").replace(/\/+$/, "");
const elId = root.querySelector("#panel-para-id");
const elL2 = root.querySelector("#panel-l2");
const elL3 = root.querySelector("#panel-l3");
const elL4 = root.querySelector("#panel-l4");
const msgHead = root.querySelector("#panel-head-msg");
const btnMediaAll = root.querySelector("#panel-media-all");
const btnMediaSubmit = root.querySelector("#panel-media-submit");
const msgMedia = root.querySelector("#panel-media-msg");
const btnRefSubmit = root.querySelector("#panel-ref-submit");
const msgRef = root.querySelector("#panel-ref-msg");
const taComment = root.querySelector("#panel-comment-text");
const btnSend = root.querySelector("#panel-comment-send");
const msgComment = root.querySelector("#panel-comment-msg");
const lb = root.querySelector("#panel-lightbox");
const lbContent = root.querySelector("#panel-lightbox-content");
const lbCaption = root.querySelector("#panel-lightbox-caption");
const docTitle = document.body?.dataset?.docTitle || document.title || "Archicratie";
const docVersion = document.body?.dataset?.docVersion || "";
const FULL_TEXT_SOFT_LIMIT = 1200;
const URL_HARD_LIMIT = 6500;
let _idxP = null;
let currentParaId = "";
let mediaShowAll = (localStorage.getItem("archicratie:panel:mediaAll") === "1");
// ===== cosmetics: micro flash “update” =====
let _flashT = 0;
function flashUpdate(){
try {
root.classList.add("is-updating");
if (_flashT) clearTimeout(_flashT);
_flashT = setTimeout(() => root.classList.remove("is-updating"), 180);
} catch {}
}
// ===== globals =====
function getG() {
return window.__archiGitea || { ready: false, base: "", owner: "", repo: "" };
}
function getAuthInfoP() {
return window.__archiAuthInfoP || Promise.resolve({ ok: false, groups: [] });
}
const access = { ready: false, canUsers: false };
// On verrouille les boutons tant que laccès nest pas résolu
if (btnMediaSubmit) btnMediaSubmit.disabled = true;
if (btnSend) btnSend.disabled = true;
if (btnRefSubmit) btnRefSubmit.disabled = true;
function inGroup(groups, g) {
const gg = String(g || "").toLowerCase();
return Array.isArray(groups) && groups.some((x) => String(x).toLowerCase() === gg);
}
// ✅ readers + editors peuvent soumettre médias + commentaires + refs
getAuthInfoP().then((info) => {
const groups = Array.isArray(info?.groups) ? info.groups : [];
const canReaders = inGroup(groups, "readers");
const canEditors = inGroup(groups, "editors");
const whoamiSkipped = Boolean(window.__archiFlags && window.__archiFlags.whoamiSkipped);
access.canUsers = Boolean((info?.ok && (canReaders || canEditors)) || whoamiSkipped);
access.ready = true;
if (btnMediaSubmit) btnMediaSubmit.disabled = !access.canUsers;
if (btnSend) btnSend.disabled = !access.canUsers;
if (btnRefSubmit) btnRefSubmit.disabled = !access.canUsers;
if (!access.canUsers) {
if (msgHead) {
msgHead.hidden = false;
msgHead.textContent = "Connexion requise (readers ou editors) pour soumettre des médias/commentaires.";
msgHead.dataset.kind = "warn";
}
}
}).catch(() => {
access.ready = true;
if (Boolean(window.__archiFlags && window.__archiFlags.whoamiSkipped)) {
access.canUsers = true;
if (btnMediaSubmit) btnMediaSubmit.disabled = false;
if (btnSend) btnSend.disabled = false;
if (btnRefSubmit) btnRefSubmit.disabled = false;
}
});
function esc(s) { return String(s ?? ""); }
function clear(el) {
if (!el) return;
while (el.firstChild) el.removeChild(el.firstChild);
}
function pText(txt) {
const p = document.createElement("p");
p.textContent = txt;
return p;
}
function link(url, label) {
const a = document.createElement("a");
a.href = url;
a.target = "_blank";
a.rel = "noopener noreferrer";
a.textContent = label || url;
return a;
}
function showMsg(el, text, kind = "info") {
if (!el) return;
el.hidden = false;
el.textContent = text;
el.dataset.kind = kind;
}
function hideMsg(el) {
if (!el) return;
el.hidden = true;
el.textContent = "";
el.dataset.kind = "";
}
async function loadIndex() {
if (_idxP) return _idxP;
_idxP = (async () => {
try {
const res = await fetch("/annotations-index.json?_=" + Date.now(), { cache: "no-store" });
if (res && res.ok) return await res.json();
} catch {}
_idxP = null;
return null;
})();
return _idxP;
}
function getParaText(paraId) {
try {
const el = document.getElementById(paraId);
if (!el) return "";
const clone = el.cloneNode(true);
clone.querySelectorAll(".para-tools,[data-noquote]").forEach((n) => n.remove());
return String(clone.innerText || clone.textContent || "").replace(/\s+/g, " ").trim();
} catch { return ""; }
}
function quoteBlock(s) {
return String(s || "")
.split(/\r?\n/)
.map((l) => (`> ${l}`).trimEnd())
.join("\n");
}
function buildIssueURL({ title, body }) {
const g = getG();
if (!g.ready) return "";
const base = String(g.base).replace(/\/+$/, "");
const issue = new URL(`${base}/${g.owner}/${g.repo}/issues/new`);
issue.searchParams.set("title", title);
let b = String(body || "");
if (b.length > 9000) b = b.slice(0, 9000) + "\n\n(…troncature automatique…)";
issue.searchParams.set("body", b);
if (issue.toString().length > URL_HARD_LIMIT) {
const shortBody = b.slice(0, 1800) + "\n\n(…troncature pour limite URL…)";
issue.searchParams.set("body", shortBody);
}
return issue.toString();
}
function openNewTab(url) {
try {
const a = document.createElement("a");
a.href = url;
a.target = "_blank";
a.rel = "noopener noreferrer";
a.style.display = "none";
document.body.appendChild(a);
a.click();
a.remove();
return true;
} catch {
return false;
}
}
const _openStamp = new Map();
function openOnce(key, fn) {
const now = Date.now();
const prev = _openStamp.get(key) || 0;
if (now - prev < 700) return false;
_openStamp.set(key, now);
return Boolean(fn());
}
function guardEventOnce(ev, key) {
try {
const k = "__archiOnce_" + key;
if (ev && ev[k]) return true;
if (ev) ev[k] = true;
try { ev.stopImmediatePropagation(); } catch {}
try { ev.stopPropagation(); } catch {}
} catch {}
return false;
}
function bindClickOnce(el, handler) {
if (!el) return;
if (el.dataset.bound === "1") return;
el.dataset.bound = "1";
el.addEventListener("click", handler, { passive: false });
}
// ===== Lightbox =====
function lockScroll(on) {
try {
document.documentElement.classList.toggle("archi-lb-open", !!on);
} catch {}
}
function closeLightbox() {
if (!lb) return;
lb.hidden = true;
lb.setAttribute("aria-hidden", "true");
if (lbContent) clear(lbContent);
if (lbCaption) { lbCaption.hidden = true; lbCaption.textContent = ""; }
lockScroll(false);
}
function openLightbox({ type, src, caption }) {
if (!lb || !lbContent) return;
clear(lbContent);
const t = String(type || "link");
const s = String(src || "");
const cap = String(caption || "").trim();
if (!s) return;
if (t === "image") {
const img = document.createElement("img");
img.loading = "eager";
img.alt = cap || "Illustration";
img.src = s;
lbContent.appendChild(img);
} else if (t === "video") {
const v = document.createElement("video");
v.controls = true;
v.autoplay = false;
v.src = s;
lbContent.appendChild(v);
} else if (t === "audio") {
const a = document.createElement("audio");
a.controls = true;
a.src = s;
lbContent.appendChild(a);
} else {
const a = link(s, cap || s);
a.className = "panel-lightbox__link";
lbContent.appendChild(a);
}
if (lbCaption) {
if (cap) { lbCaption.hidden = false; lbCaption.textContent = cap; }
else { lbCaption.hidden = true; lbCaption.textContent = ""; }
}
lockScroll(true);
lb.hidden = false;
lb.setAttribute("aria-hidden", "false");
}
if (lb) {
bindClickOnce(lb, (ev) => {
const t = ev.target;
if (t && t.getAttribute && t.getAttribute("data-close") === "1") {
ev.preventDefault();
closeLightbox();
}
});
window.addEventListener("keydown", (ev) => {
if (ev.key === "Escape" && !lb.hidden) closeLightbox();
});
}
function renderLevel2(data) {
clear(elL2);
if (!elL2) return;
if (!data) {
elL2.appendChild(pText("Aucune annotation pour ce paragraphe."));
return;
}
if (Array.isArray(data.mobilizedAuthors) && data.mobilizedAuthors.length) {
const h = document.createElement("h3");
h.className = "panel-subtitle";
h.textContent = "Auteurs";
elL2.appendChild(h);
const ul = document.createElement("ul");
ul.className = "panel-list";
for (const a of data.mobilizedAuthors) {
const li = document.createElement("li");
li.textContent = esc(a);
ul.appendChild(li);
}
elL2.appendChild(ul);
}
if (Array.isArray(data.refs) && data.refs.length) {
const h = document.createElement("h3");
h.className = "panel-subtitle";
h.textContent = "Références";
elL2.appendChild(h);
const ul = document.createElement("ul");
ul.className = "panel-list";
for (const r of data.refs) {
const li = document.createElement("li");
const label = esc(r?.label || r?.url || "Référence");
const url = esc(r?.url || "");
const kind = esc(r?.kind || "");
if (url) li.appendChild(link(url, label));
else li.textContent = label;
if (kind) {
const k = document.createElement("span");
k.className = "panel-chip";
k.textContent = kind;
li.appendChild(document.createTextNode(" "));
li.appendChild(k);
}
ul.appendChild(li);
}
elL2.appendChild(ul);
}
if (Array.isArray(data.quotes) && data.quotes.length) {
const h = document.createElement("h3");
h.className = "panel-subtitle";
h.textContent = "Citations";
elL2.appendChild(h);
for (const q of data.quotes) {
const block = document.createElement("blockquote");
block.className = "panel-quote";
const t = document.createElement("div");
t.textContent = esc(q?.text || "");
block.appendChild(t);
const src = esc(q?.source || "");
if (src) {
const s = document.createElement("div");
s.className = "panel-quote__src";
s.textContent = src;
block.appendChild(s);
}
elL2.appendChild(block);
}
}
if (!elL2.firstChild) {
elL2.appendChild(pText("Aucune annotation pour ce paragraphe."));
}
}
function renderLevel3(data) {
clear(elL3);
hideMsg(msgMedia);
if (!elL3) return;
const arr = Array.isArray(data?.media) ? data.media : [];
if (!arr.length) {
elL3.appendChild(pText("Aucun média associé à ce paragraphe."));
if (btnMediaAll) btnMediaAll.disabled = true;
return;
}
if (btnMediaAll) btnMediaAll.disabled = arr.length <= 6;
const grid = document.createElement("div");
grid.className = "panel-media-grid";
const items = mediaShowAll ? arr : arr.slice(0, 6);
for (const m of items) {
const type = esc(m?.type || "link");
const src = esc(m?.src || "");
const caption = esc(m?.caption || "");
const credit = esc(m?.credit || "");
const btn = document.createElement("button");
btn.type = "button";
btn.className = "panel-media-tile";
btn.setAttribute("aria-label", caption ? `Ouvrir média: ${caption}` : "Ouvrir média");
btn.dataset.src = src;
btn.dataset.type = type;
btn.dataset.caption = caption;
if (type === "image" && src) {
const img = document.createElement("img");
img.loading = "lazy";
img.alt = caption || "Illustration";
img.src = src;
btn.appendChild(img);
} else {
const ph = document.createElement("div");
ph.className = "panel-media-ph";
ph.textContent = type.toUpperCase();
btn.appendChild(ph);
}
if (caption) {
const c = document.createElement("div");
c.className = "panel-media-cap";
c.textContent = caption;
btn.appendChild(c);
} else if (src) {
const c = document.createElement("div");
c.className = "panel-media-cap";
c.textContent = src.split("/").pop() || src;
btn.appendChild(c);
}
if (credit) {
const cr = document.createElement("div");
cr.className = "panel-media-credit";
cr.textContent = credit;
btn.appendChild(cr);
}
bindClickOnce(btn, (ev) => {
ev.preventDefault();
if (!src) return;
openLightbox({ type, src, caption });
});
grid.appendChild(btn);
}
elL3.appendChild(grid);
}
function renderLevel4(data) {
clear(elL4);
hideMsg(msgComment);
if (!elL4) return;
const arr = Array.isArray(data?.comments_editorial) ? data.comments_editorial : [];
if (!arr.length) {
elL4.appendChild(pText("Aucun commentaire éditorial pour ce paragraphe."));
return;
}
const ul = document.createElement("ul");
ul.className = "panel-list";
for (const c of arr) {
const li = document.createElement("li");
li.className = "panel-comment";
const txt = document.createElement("div");
txt.textContent = esc(c?.text || "");
li.appendChild(txt);
const st = esc(c?.status || "");
if (st) {
const s = document.createElement("span");
s.className = "panel-chip";
s.textContent = st;
li.appendChild(s);
}
ul.appendChild(li);
}
elL4.appendChild(ul);
}
async function updatePanel(paraId) {
currentParaId = paraId || currentParaId || "";
if (elId) elId.textContent = currentParaId || "—";
flashUpdate();
hideMsg(msgHead);
hideMsg(msgMedia);
hideMsg(msgComment);
hideMsg(msgRef);
const idx = await loadIndex();
if (!idx && msgHead && msgHead.hidden) {
msgHead.hidden = false;
msgHead.textContent = "Index annotations indisponible (annotations-index.json).";
msgHead.dataset.kind = "info";
}
const data = idx?.pages?.[pageKey]?.paras?.[currentParaId] || null;
renderLevel2(data);
renderLevel3(data);
renderLevel4(data);
}
if (btnMediaAll) {
bindClickOnce(btnMediaAll, (ev) => {
ev.preventDefault();
mediaShowAll = !mediaShowAll;
localStorage.setItem("archicratie:panel:mediaAll", mediaShowAll ? "1" : "0");
btnMediaAll.textContent = mediaShowAll ? "Réduire la liste" : "Voir tous les éléments";
updatePanel(currentParaId);
});
btnMediaAll.textContent = mediaShowAll ? "Réduire la liste" : "Voir tous les éléments";
}
if (btnMediaSubmit) {
bindClickOnce(btnMediaSubmit, (ev) => {
ev.preventDefault();
hideMsg(msgMedia);
if (guardEventOnce(ev, "gitea_open_media")) return;
if (!currentParaId) return showMsg(msgMedia, "Choisis dabord un paragraphe (scroll / survol).", "warn");
if (!getG().ready) return showMsg(msgMedia, "Gitea non configuré (PUBLIC_GITEA_*).", "error");
if (btnMediaSubmit.disabled) return showMsg(msgMedia, "Connexion requise (readers/editors).", "error");
const pageUrl = new URL(location.href);
pageUrl.search = "";
pageUrl.hash = currentParaId;
const paraTxt = getParaText(currentParaId);
const excerpt = paraTxt.length > FULL_TEXT_SOFT_LIMIT ? (paraTxt.slice(0, FULL_TEXT_SOFT_LIMIT) + "…") : paraTxt;
const title = `[Media] ${currentParaId} — ${docTitle}`;
const body = [
`Chemin: ${location.pathname}`,
`URL: ${pageUrl.toString()}`,
`Ancre: #${currentParaId}`,
`Version: ${docVersion || "(non renseignée)"}`,
`Type: type/media`,
``,
`Contexte (extrait):`,
quoteBlock(excerpt || ""),
``,
`---`,
`Action: dans Gitea, ajoute le fichier via la zone “Joindre un fichier” (drag & drop / upload).`,
`But: associer le média au paragraphe #${currentParaId}.`,
].join("\n");
const url = buildIssueURL({ title, body });
if (!url) return showMsg(msgMedia, "Impossible de générer lissue (config).", "error");
const ok = openOnce(`media:${currentParaId}`, () => openNewTab(url));
if (!ok) showMsg(msgMedia, "Popup bloqué : autorise les popups pour ouvrir Gitea.", "error");
});
}
if (btnRefSubmit) {
bindClickOnce(btnRefSubmit, (ev) => {
ev.preventDefault();
hideMsg(msgRef);
if (guardEventOnce(ev, "gitea_open_ref")) return;
if (!currentParaId) return showMsg(msgRef, "Choisis dabord un paragraphe (scroll / survol).", "warn");
if (!getG().ready) return showMsg(msgRef, "Gitea non configuré (PUBLIC_GITEA_*).", "error");
if (btnRefSubmit.disabled) return showMsg(msgRef, "Connexion requise (readers/editors).", "error");
const pageUrl = new URL(location.href);
pageUrl.search = "";
pageUrl.hash = currentParaId;
const paraTxt = getParaText(currentParaId);
const excerpt = paraTxt.length > FULL_TEXT_SOFT_LIMIT ? (paraTxt.slice(0, FULL_TEXT_SOFT_LIMIT) + "…") : paraTxt;
const title = `[Reference] ${currentParaId} — ${docTitle}`;
const body = [
`Chemin: ${location.pathname}`,
`URL: ${pageUrl.toString()}`,
`Ancre: #${currentParaId}`,
`Version: ${docVersion || "(non renseignée)"}`,
`Type: type/reference`,
``,
`Contexte (extrait):`,
quoteBlock(excerpt || ""),
``,
`Référence (à compléter):`,
`- URL:`,
`- Label:`,
`- Kind: (livre / article / vidéo / site / autre)`,
`- Citation / passage (optionnel):`,
``,
`---`,
`Note: issue générée depuis le site (pré-remplissage).`,
].join("\n");
const url = buildIssueURL({ title, body });
if (!url) return showMsg(msgRef, "Impossible de générer lissue.", "error");
const ok = openOnce(`ref:${currentParaId}`, () => openNewTab(url));
if (!ok) showMsg(msgRef, "Si rien ne souvre : autorise les popups pour ce site.", "error");
});
}
if (btnSend) {
bindClickOnce(btnSend, (ev) => {
ev.preventDefault();
hideMsg(msgComment);
if (guardEventOnce(ev, "gitea_open_comment")) return;
if (!currentParaId) return showMsg(msgComment, "Choisis dabord un paragraphe (scroll / survol).", "warn");
if (!getG().ready) return showMsg(msgComment, "Gitea non configuré (PUBLIC_GITEA_*).", "error");
if (btnSend.disabled) return showMsg(msgComment, "Connexion requise (readers/editors).", "error");
const txt = String(taComment?.value || "").trim();
if (txt.length < 3) return showMsg(msgComment, "Commentaire trop court.", "warn");
const pageUrl = new URL(location.href);
pageUrl.search = "";
pageUrl.hash = currentParaId;
const paraTxt = getParaText(currentParaId);
const excerpt = paraTxt.length > FULL_TEXT_SOFT_LIMIT ? (paraTxt.slice(0, FULL_TEXT_SOFT_LIMIT) + "…") : paraTxt;
const title = `[Comment] ${currentParaId} — ${docTitle}`;
const body = [
`Chemin: ${location.pathname}`,
`URL: ${pageUrl.toString()}`,
`Ancre: #${currentParaId}`,
`Version: ${docVersion || "(non renseignée)"}`,
`Type: type/comment`,
``,
`Contexte (extrait):`,
quoteBlock(excerpt || ""),
``,
`Commentaire:`,
txt,
``,
`---`,
`Note: issue générée depuis le site (pré-remplissage).`,
].join("\n");
const url = buildIssueURL({ title, body });
if (!url) return showMsg(msgComment, "Impossible de générer lissue.", "error");
const ok = openOnce(`comment:${currentParaId}`, () => openNewTab(url));
if (!ok) return showMsg(msgComment, "Popup bloqué : autorise les popups pour ouvrir Gitea.", "error");
taComment.value = "";
showMsg(msgComment, "Issue Gitea ouverte dans un nouvel onglet.", "ok");
setTimeout(() => hideMsg(msgComment), 900);
});
}
// ===== wiring: para courant (SOURCE OF TRUTH = EditionLayout) =====
function onCurrentPara(ev) {
try {
const id = ev?.detail?.id ? String(ev.detail.id) : "";
if (!id || !/^p-\d+-/i.test(id)) return;
if (id === currentParaId) return;
updatePanel(id);
} catch {}
}
window.addEventListener("archicratie:currentPara", onCurrentPara);
const initial = String(location.hash || "").replace(/^#/, "").trim();
if (/^p-\d+-/i.test(initial)) {
updatePanel(initial);
} else if (window.__archiCurrentParaId && /^p-\d+-/i.test(String(window.__archiCurrentParaId))) {
updatePanel(String(window.__archiCurrentParaId));
} else {
setTimeout(() => {
try {
const id = String(window.__archiCurrentParaId || "").trim();
if (/^p-\d+-/i.test(id)) updatePanel(id);
} catch {}
}, 0);
}
})();
</script>
<style>
/* Fallback : niveau 1 → jamais de panel */
:global(body[data-reading-level="1"]) .page-panel{ display: none !important; }
.page-panel{
grid-column: 3;
position: sticky;
top: calc(var(--sticky-header-h) + var(--page-gap));
align-self: start;
--thumb: 92px; /* ✅ taille des vignettes (80110 selon goût) */
}
:global(body[data-reading-level="3"]) .page-panel{
grid-column: 2;
}
.page-panel__inner{
max-height: calc(100vh - (var(--sticky-header-h) + var(--page-gap) + 12px));
overflow: auto;
scrollbar-gutter: stable;
border: 1px solid rgba(127,127,127,.22);
border-radius: 16px;
padding: 12px;
background: rgba(127,127,127,0.04);
}
.panel-head{
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin-bottom: 10px;
padding-bottom: 10px;
border-bottom: 1px dashed rgba(127,127,127,0.25);
}
.panel-head__left{
display: inline-flex;
align-items: center;
gap: 8px;
min-width: 0;
}
.panel-head__label{ font-weight: 900; opacity: .8; }
.panel-head__id{
font-weight: 850;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 14rem;
}
.panel-msg{
margin-top: 8px;
font-size: 12px;
opacity: .95;
}
.panel-msg--head{
margin-top: 0;
margin-bottom: 10px;
}
.panel-title{
font-size: 13px;
font-weight: 900;
margin: 10px 0 8px;
opacity: .9;
}
.panel-subtitle{
font-size: 12px;
font-weight: 850;
margin: 10px 0 6px;
opacity: .88;
}
.panel-body p{ margin: 8px 0; opacity: .9; }
.panel-list{ margin: 0; padding-left: 18px; }
.panel-list li{ margin: 6px 0; }
.panel-chip{
display: inline-block;
margin-left: 8px;
font-size: 11px;
font-weight: 800;
padding: 2px 7px;
border-radius: 999px;
border: 1px solid rgba(127,127,127,0.30);
background: rgba(127,127,127,0.10);
opacity: .92;
}
.panel-quote{
margin: 8px 0;
padding: 8px 10px;
border-left: 3px solid rgba(127,127,127,0.35);
background: rgba(127,127,127,0.06);
border-radius: 10px;
}
.panel-quote__src{ margin-top: 6px; font-size: 12px; opacity: .85; }
.panel-actions{
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-top: 10px;
}
.panel-btn{
border: 1px solid rgba(127,127,127,0.40);
background: rgba(127,127,127,0.10);
border-radius: 999px;
padding: 6px 10px;
font-size: 13px;
cursor: pointer;
}
.panel-btn:disabled{ opacity: .55; cursor: default; }
.panel-btn--primary{
border-color: rgba(127,127,127,0.65);
font-weight: 800;
}
/* actions médias en haut */
.panel-top-actions{ margin-top: 8px; }
/* ===== media thumbnails (plus petits + plus denses) ===== */
.panel-media-grid{
display: grid;
grid-template-columns: repeat(auto-fill, minmax(var(--thumb), 1fr));
gap: 10px;
}
.panel-media-tile{
width: 100%;
border: 1px solid rgba(127,127,127,.20);
border-radius: 14px;
padding: 8px;
background: rgba(127,127,127,0.04);
cursor: pointer;
text-align: left;
transition: transform 120ms ease, background 120ms ease, border-color 120ms ease;
}
.panel-media-tile:hover{
transform: translateY(-1px);
background: rgba(127,127,127,0.07);
border-color: rgba(127,127,127,.32);
}
.panel-media-tile img{
width: 100%;
height: var(--thumb);
object-fit: cover;
display: block;
border-radius: 10px;
margin-bottom: 8px;
}
.panel-media-ph{
width: 100%;
height: var(--thumb);
border-radius: 10px;
display: grid;
place-items: center;
background: rgba(127,127,127,0.10);
margin-bottom: 8px;
font-weight: 900;
font-size: 12px;
opacity: .9;
}
.panel-media-cap{
font-size: 12px;
font-weight: 800;
opacity: .92;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.panel-media-credit{
margin-top: 4px;
font-size: 11px;
opacity: .8;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* ===== compose ===== */
.panel-compose{ margin-top: 12px; padding-top: 10px; border-top: 1px dashed rgba(127,127,127,0.25); }
.panel-label{ display: block; font-size: 12px; font-weight: 900; opacity: .9; margin-bottom: 6px; }
.panel-textarea{
width: 100%;
box-sizing: border-box;
border: 1px solid rgba(127,127,127,0.35);
border-radius: 12px;
padding: 8px 10px;
background: transparent;
font-size: 13px;
resize: vertical;
}
/* ===== Lightbox (plein écran “cinéma”) ===== */
:global(html.archi-lb-open){
overflow: hidden; /* ✅ empêche le scroll derrière */
}
.panel-lightbox{
position: fixed;
inset: 0;
z-index: 120;
}
.panel-lightbox__overlay{
position: absolute;
inset: 0;
background: rgba(0,0,0,0.84);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.panel-lightbox__dialog{
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: min(1100px, 92vw);
max-height: 92vh;
overflow: auto;
border: 1px solid rgba(255,255,255,0.14);
border-radius: 18px;
background: rgba(20,20,20,0.55);
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
padding: 16px;
box-shadow: 0 24px 70px rgba(0,0,0,0.55);
}
.panel-lightbox__close{
position: absolute;
top: 12px;
right: 12px;
display: inline-flex;
align-items: center;
justify-content: center;
width: 44px;
height: 40px;
border-radius: 14px;
border: 1px solid rgba(255,255,255,0.22);
background: rgba(255,255,255,0.10);
cursor: pointer;
font-size: 22px;
font-weight: 900;
}
.panel-lightbox__content{
margin-top: 36px;
}
.panel-lightbox__content img,
.panel-lightbox__content video{
display: block;
width: 100%;
max-height: calc(92vh - 160px);
object-fit: contain;
background: rgba(0,0,0,0.22);
border-radius: 14px;
}
.panel-lightbox__content audio{
width: 100%;
}
.panel-lightbox__caption{
margin-top: 12px;
font-size: 12px;
font-weight: 800;
opacity: .92;
color: rgba(255,255,255,0.92);
}
@media (max-width: 1100px){
.page-panel{ display: none; }
}
</style>