Compare commits

...

12 Commits

Author SHA1 Message Date
99cf0947da fix(glossaire): align reading follow top actions with glossary navigation
All checks were successful
SMOKE / smoke (push) Successful in 5s
CI / build-and-anchors (push) Successful in 47s
CI / build-and-anchors (pull_request) Successful in 43s
2026-03-26 22:30:05 +01:00
7033354011 fix(glossaire): include standalone relation headings in reading follow
All checks were successful
SMOKE / smoke (push) Successful in 3s
CI / build-and-anchors (push) Successful in 42s
CI / build-and-anchors (pull_request) Successful in 44s
2026-03-26 21:29:26 +01:00
7345730e3c Merge pull request 'fix(glossaire): expose relations heading to reading follow' (#303) from fix/glossaire-relations-follow-heading into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 24s
CI / build-and-anchors (push) Successful in 50s
SMOKE / smoke (push) Successful in 8s
Deploy staging+live (annotations) / deploy (push) Successful in 9m19s
Reviewed-on: #303
2026-03-26 20:47:02 +01:00
cea94c56db fix(glossaire): expose relations heading to reading follow
All checks were successful
SMOKE / smoke (push) Successful in 4s
CI / build-and-anchors (push) Successful in 41s
CI / build-and-anchors (pull_request) Successful in 43s
2026-03-26 20:42:01 +01:00
c1e24736e3 Merge pull request 'feat(glossaire): deduplicate entry aside relation groups' (#302) from feat/glossaire-entry-aside-dedup into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 21s
CI / build-and-anchors (push) Successful in 49s
SMOKE / smoke (push) Successful in 4s
Deploy staging+live (annotations) / deploy (push) Successful in 9m41s
Reviewed-on: #302
2026-03-26 20:27:31 +01:00
24bbfbc17f feat(glossaire): deduplicate entry aside relation groups
All checks were successful
SMOKE / smoke (push) Successful in 6s
CI / build-and-anchors (push) Successful in 47s
CI / build-and-anchors (pull_request) Successful in 46s
2026-03-26 20:24:49 +01:00
a11e2f1d18 Merge pull request 'fix(glossaire): compact sticky entry hero on glossary pages' (#301) from fix/glossaire-entry-sticky-hero-collapse into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 32s
CI / build-and-anchors (push) Successful in 45s
SMOKE / smoke (push) Successful in 8s
Deploy staging+live (annotations) / deploy (push) Successful in 9m18s
Reviewed-on: #301
2026-03-26 18:32:19 +01:00
630b146d02 fix(glossaire): compact sticky entry hero on glossary pages
All checks were successful
SMOKE / smoke (push) Successful in 5s
CI / build-and-anchors (push) Successful in 50s
CI / build-and-anchors (pull_request) Successful in 46s
2026-03-26 18:30:34 +01:00
551360db83 Merge pull request 'fix(ci): use local pagefind binary instead of npx wrapper' (#300) from fix/ci-pagefind-local-bin into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 26s
CI / build-and-anchors (push) Successful in 44s
SMOKE / smoke (push) Successful in 8s
Deploy staging+live (annotations) / deploy (push) Successful in 9m45s
Reviewed-on: #300
2026-03-26 14:45:29 +01:00
a96c282780 fix(ci): use local pagefind binary instead of npx wrapper
All checks were successful
SMOKE / smoke (push) Successful in 5s
CI / build-and-anchors (push) Successful in 45s
CI / build-and-anchors (pull_request) Successful in 46s
2026-03-26 14:41:02 +01:00
d2e0f147c2 Merge pull request 'audit(glossaire): tighten portal exposure and cross-page coherence' (#299) from audit/glossaire-transverse-coherence-fix into main
All checks were successful
Proposer Apply (Queue) / apply-proposer (push) Successful in 33s
SMOKE / smoke (push) Successful in 10s
Deploy staging+live (annotations) / deploy (push) Successful in 10m22s
CI / build-and-anchors (push) Successful in 45s
Reviewed-on: #299
2026-03-26 14:20:37 +01:00
ad95364021 audit(glossaire): tighten portal exposure and cross-page coherence
All checks were successful
SMOKE / smoke (push) Successful in 5s
CI / build-and-anchors (push) Successful in 1m11s
CI / build-and-anchors (pull_request) Successful in 1m17s
2026-03-26 14:16:05 +01:00
11 changed files with 246 additions and 31 deletions

View File

@@ -10,7 +10,8 @@
"clean": "rm -rf dist",
"build": "astro build",
"build:clean": "npm run clean && npm run build",
"postbuild": "node scripts/inject-anchor-aliases.mjs && node scripts/dedupe-ids-dist.mjs && node scripts/build-para-index.mjs && node scripts/build-annotations-index.mjs && node scripts/purge-dist-dev-whoami.mjs && npx pagefind --site dist",
"build:search": "pagefind --site dist",
"postbuild": "node scripts/inject-anchor-aliases.mjs && node scripts/dedupe-ids-dist.mjs && node scripts/build-para-index.mjs && node scripts/build-annotations-index.mjs && node scripts/purge-dist-dev-whoami.mjs && npm run build:search",
"import": "node scripts/import-docx.mjs",
"apply:ticket": "node scripts/apply-ticket.mjs",
"audit:dist": "node scripts/audit-dist.mjs",

View File

@@ -91,31 +91,44 @@ const hasScholarlyMeta =
}
.glossary-entry-head__title{
padding: 18px 18px 16px;
padding:
var(--entry-hero-pad-top, 18px)
var(--entry-hero-pad-x, 18px)
calc(var(--entry-hero-pad-top, 18px) - 2px);
transition: padding 180ms ease;
}
.glossary-entry-head h1{
margin: 0;
font-size: clamp(2.2rem, 4vw, 3.15rem);
font-size: var(--entry-hero-h1-size, clamp(2.2rem, 4vw, 3.15rem));
line-height: 1.02;
letter-spacing: -.04em;
font-weight: 850;
transition: font-size 180ms ease;
}
.glossary-entry-summary{
display: grid;
gap: 14px;
padding: 16px 18px 18px;
gap: var(--entry-hero-gap, 14px);
padding:
calc(var(--entry-hero-pad-bottom, 18px) - 2px)
var(--entry-hero-pad-x, 18px)
var(--entry-hero-pad-bottom, 18px);
border-top: 1px solid rgba(127,127,127,0.14);
background: rgba(255,255,255,0.02);
transition: gap 180ms ease, padding 180ms ease;
}
.glossary-entry-dek{
margin: 0;
max-width: 76ch;
font-size: 1.04rem;
line-height: 1.55;
max-width: var(--entry-hero-dek-maxw, 76ch);
font-size: var(--entry-hero-dek-size, 1.04rem);
line-height: var(--entry-hero-dek-lh, 1.55);
opacity: .94;
transition:
max-width 180ms ease,
font-size 180ms ease,
line-height 180ms ease;
}
.glossary-entry-signals{
@@ -123,6 +136,7 @@ const hasScholarlyMeta =
flex-wrap: wrap;
gap: 8px;
margin: 0;
transition: gap 180ms ease;
}
.glossary-pill{
@@ -135,6 +149,11 @@ const hasScholarlyMeta =
background: rgba(127,127,127,0.05);
font-size: 13px;
line-height: 1.35;
transition:
padding 180ms ease,
font-size 180ms ease,
background 120ms ease,
border-color 120ms ease;
}
.glossary-pill--family{
@@ -148,6 +167,14 @@ const hasScholarlyMeta =
border: 1px solid rgba(127,127,127,0.18);
border-radius: 12px;
background: rgba(127,127,127,0.04);
max-height: var(--entry-hero-meta-max-h, 12rem);
opacity: var(--entry-hero-meta-opacity, 1);
overflow: hidden;
transition:
max-height 180ms ease,
opacity 140ms ease,
padding 180ms ease,
border-color 180ms ease;
}
.glossary-entry-meta p{

View File

@@ -114,6 +114,10 @@
z-index: 10;
}
:global(body.is-glossary-entry-page.glossary-entry-follow-on .glossary-entry-signals){
gap: 6px;
}
:global(body.is-glossary-entry-page.glossary-entry-follow-on .glossary-entry-head){
margin-bottom: 0;
border-bottom-left-radius: 0;
@@ -126,12 +130,21 @@
padding-bottom: 10px;
}
:global(body.is-glossary-entry-page.glossary-entry-follow-on .glossary-entry-signals){
gap: 6px;
:global(body.is-glossary-entry-page.glossary-entry-follow-on .glossary-entry-dek){
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
:global(body.is-glossary-entry-page.glossary-entry-follow-on .glossary-pill){
padding: 4px 8px;
font-size: 12px;
}
:global(body.is-glossary-entry-page.glossary-entry-follow-on .glossary-entry-meta){
padding: 8px 10px;
padding: 0;
border-color: transparent;
}
:global(body.is-glossary-entry-page.glossary-entry-follow-on #reading-follow){

View File

@@ -7,12 +7,15 @@ interface Props {
}
const { relationBlocks = [] } = Astro.props;
const relationsHeadingId = "relations-conceptuelles";
---
{relationBlocks.length > 0 && (
<section class="glossary-relations" aria-label="Relations conceptuelles">
<h2>Relations conceptuelles</h2>
<section
class="glossary-relations"
aria-labelledby={relationsHeadingId}
>
<h2 id={relationsHeadingId}>Relations conceptuelles</h2>
<div class="glossary-relations-grid">
{relationBlocks.map((block) => (
<section class={`glossary-relations-card ${block.className}`}>

View File

@@ -1478,6 +1478,33 @@ const WHOAMI_FORCE_LOCALHOST = (import.meta.env.PUBLIC_WHOAMI_FORCE_LOCALHOST ??
const h1 = reading.querySelector("h1");
const topChapterLabel =
isGlossaryEntryMode
? "Haut de la fiche"
: isGlossaryEdition
? "Haut de la page"
: "Haut du chapitre";
if (btnTopChapter) {
btnTopChapter.setAttribute("aria-label", topChapterLabel);
btnTopChapter.setAttribute("title", topChapterLabel);
}
function scrollToTopChapter(behavior = "smooth") {
if (isGlossaryEdition) {
window.scrollTo({ top: 0, behavior });
return;
}
if (h1) {
scrollToElWithOffset(h1, 12, behavior);
}
}
function getH2ScrollTarget(item) {
return item?.h2 || item?.anchor || item?.marker || null;
}
const h2Anchors = Array.from(reading.querySelectorAll(".details-anchor[id]"))
.map((s) => {
const d = (s.nextElementSibling && s.nextElementSibling.tagName === "DETAILS")
@@ -1500,6 +1527,7 @@ const WHOAMI_FORCE_LOCALHOST = (import.meta.env.PUBLIC_WHOAMI_FORCE_LOCALHOST ??
.filter(Boolean);
const h2Plain = Array.from(reading.querySelectorAll("h2[id]"))
.filter((h2) => !h2.closest("details.details-section"))
.map((h2) => ({
id: h2.id,
anchor: h2,
@@ -1508,7 +1536,7 @@ const WHOAMI_FORCE_LOCALHOST = (import.meta.env.PUBLIC_WHOAMI_FORCE_LOCALHOST ??
h2,
}));
const H2 = (h2Anchors.length ? h2Anchors : h2Plain)
const H2 = [...h2Anchors, ...h2Plain]
.slice()
.sort((a, b) => absTop(a.marker) - absTop(b.marker));
@@ -1700,7 +1728,12 @@ const WHOAMI_FORCE_LOCALHOST = (import.meta.env.PUBLIC_WHOAMI_FORCE_LOCALHOST ??
syncGlossaryFollowState(followEl.classList.contains("is-on") && followH > 0);
if (btnTopChapter) {
btnTopChapter.hidden = !(rfH1 && !rfH1.hidden);
const showTopChapter =
isGlossaryEdition
? Boolean(followEl.classList.contains("is-on"))
: Boolean(rfH1 && !rfH1.hidden);
btnTopChapter.hidden = !showTopChapter;
}
if (btnTopSection) {
@@ -1736,8 +1769,11 @@ const WHOAMI_FORCE_LOCALHOST = (import.meta.env.PUBLIC_WHOAMI_FORCE_LOCALHOST ??
if (rfH2) {
rfH2.addEventListener("click", () => {
if (!curH2) return;
openDetailsIfNeeded(curH2.anchor || curH2.h2 || curH2.marker);
scrollToElWithOffset(curH2.marker, 12, "smooth");
const target = getH2ScrollTarget(curH2);
if (!target) return;
openDetailsIfNeeded(target);
scrollToElWithOffset(target, 12, "smooth");
history.replaceState(null, "", `${window.location.pathname}#${curH2.id}`);
});
}
@@ -1753,15 +1789,18 @@ const WHOAMI_FORCE_LOCALHOST = (import.meta.env.PUBLIC_WHOAMI_FORCE_LOCALHOST ??
if (btnTopChapter) {
btnTopChapter.addEventListener("click", () => {
if (h1) scrollToElWithOffset(h1, 12, "smooth");
scrollToTopChapter("smooth");
});
}
if (btnTopSection) {
btnTopSection.addEventListener("click", () => {
if (!curH2) return;
openDetailsIfNeeded(curH2.anchor || curH2.h2 || curH2.marker);
scrollToElWithOffset(curH2.marker, 12, "smooth");
const target = getH2ScrollTarget(curH2);
if (!target) return;
openDetailsIfNeeded(target);
scrollToElWithOffset(target, 12, "smooth");
history.replaceState(null, "", `${window.location.pathname}#${curH2.id}`);
});
}

View File

@@ -26,6 +26,38 @@ const {
</EditionLayout>
<style is:global>
body[data-edition-key="glossaire"][data-sticky-mode="glossary-entry"]{
--entry-hero-pad-top: 18px;
--entry-hero-pad-x: 18px;
--entry-hero-pad-bottom: 18px;
--entry-hero-gap: 14px;
--entry-hero-h1-size: clamp(2.2rem, 4vw, 3.15rem);
--entry-hero-dek-size: 1.04rem;
--entry-hero-dek-lh: 1.55;
--entry-hero-dek-maxw: 76ch;
--entry-hero-meta-max-h: 12rem;
--entry-hero-meta-opacity: 1;
}
body[data-edition-key="glossaire"][data-sticky-mode="glossary-entry"].glossary-entry-follow-on{
--entry-hero-pad-top: 8px;
--entry-hero-pad-x: 14px;
--entry-hero-pad-bottom: 6px;
--entry-hero-gap: 6px;
--entry-hero-h1-size: clamp(1.45rem, 2.4vw, 1.9rem);
--entry-hero-dek-size: .90rem;
--entry-hero-dek-lh: 1.32;
--entry-hero-dek-maxw: 56ch;
--entry-hero-meta-max-h: 0px;
--entry-hero-meta-opacity: 0;
}
body[data-edition-key="glossaire"][data-sticky-mode="glossary-portal"]{
--portal-hero-pad-top: 18px;
--portal-hero-pad-x: 18px;

View File

@@ -201,6 +201,36 @@ export function uniqueGlossaryEntries(
return out;
}
export function slugsOfGlossaryEntries(
entries: GlossaryEntry[] = [],
): Set<string> {
const slugs = new Set<string>();
for (const entry of entries) {
const slug = slugOfGlossaryEntry(entry);
if (!slug) continue;
slugs.add(slug);
}
return slugs;
}
export function excludeGlossaryEntries(
entries: GlossaryEntry[] = [],
excluded: Iterable<string> = [],
): GlossaryEntry[] {
const excludedSlugs = new Set(
Array.from(excluded)
.map((value) => normalizeGlossarySlug(value))
.filter(Boolean),
);
return entries.filter((entry) => {
const slug = slugOfGlossaryEntry(entry);
return Boolean(slug) && !excludedSlugs.has(slug);
});
}
export function resolveGlossaryEntriesInSourceOrder(
slugs: string[] = [],
allEntries: GlossaryEntry[] = [],
@@ -504,11 +534,27 @@ export function getGlossaryEntryAsideData(
allEntries: GlossaryEntry[] = [],
): GlossaryEntryAsideData {
const currentFamily = familyOf(currentEntry);
const currentSlug = slugOfGlossaryEntry(currentEntry);
const fondamentaux = getFondamentaux(allEntries);
const sameFamilyEntries = getEntriesOfSameFamily(currentEntry, allEntries);
const sameFamilyTitle = getSameFamilyTitle(currentEntry);
const relationSections = getRelationSections(currentEntry, allEntries);
const contextualTheory = getContextualTheory(currentEntry, allEntries);
const relationEntries = uniqueGlossaryEntries(
relationSections.flatMap((section) => section.items),
);
const relationSlugs = slugsOfGlossaryEntries(relationEntries);
const contextualTheory = excludeGlossaryEntries(
getContextualTheory(currentEntry, allEntries),
new Set([currentSlug, ...relationSlugs]),
).slice(0, 6);
const contextualTheorySlugs = slugsOfGlossaryEntries(contextualTheory);
const sameFamilyEntries = excludeGlossaryEntries(
getEntriesOfSameFamily(currentEntry, allEntries),
new Set([currentSlug, ...relationSlugs, ...contextualTheorySlugs]),
).slice(0, 8);
const showNoyau =
currentFamily !== "concept-fondamental" &&

View File

@@ -81,10 +81,13 @@ const sections = [
const totalCount = sections.reduce((sum, section) => sum + section.items.length, 0);
const pageItems = sections.map((section) => ({
href: `#${section.id}`,
label: section.title,
}));
const pageItems = [
...sections.map((section) => ({
href: `#${section.id}`,
label: section.title,
})),
{ href: "#prolonger-la-lecture", label: "Prolonger la lecture" },
];
const usefulLinks = [
{ href: "/glossaire/archicration/", label: "Archicration" },
@@ -93,6 +96,33 @@ const usefulLinks = [
{ href: "/glossaire/cratialite/", label: "Cratialité" },
{ href: "/glossaire/co-viabilite/", label: "Co-viabilité" },
];
const prolongerLinks = [
{
href: "/glossaire/concepts-fondamentaux/",
title: "Concepts fondamentaux",
text:
"Revenir au noyau minimal : arcalité, cratialité, tension, archicration, co-viabilité et archicratie.",
},
{
href: "/glossaire/paradigmes/",
title: "Paradigmes et doctrines",
text:
"Situer les archicrations dans le paysage théorique au sein duquel larchicratie se compare et se distingue.",
},
{
href: "/glossaire/tensions-irreductibles/",
title: "Tensions irréductibles",
text:
"Revenir aux foyers structuraux de conflictualité que les archicrations stabilisent sans les abolir.",
},
{
href: "/glossaire/index-complet/",
title: "Index complet",
text:
"Retrouver lensemble des entrées du glossaire dans une navigation alphabétique intégrale.",
},
];
---
<GlossaryLayout
@@ -154,6 +184,21 @@ const usefulLinks = [
</GlossaryPortalSection>
))}
<GlossaryPortalSection
id="prolonger-la-lecture"
title="Prolonger la lecture"
intro="Cette cartographie des archicrations peut ensuite être replacée dans le noyau conceptuel, dans le paysage théorique général et dans lindex complet du glossaire."
>
<div class="archi-cards">
{prolongerLinks.map((item) => (
<a class="archi-card" href={item.href}>
<strong>{item.title}</strong>
<span>{item.text}</span>
</a>
))}
</div>
</GlossaryPortalSection>
<GlossaryPortalSection
id="portee-densemble"
title="Portée densemble"

View File

@@ -292,7 +292,7 @@ const prolongerLinks = [
</GlossaryPortalSection>
<GlossaryPortalSection
id="prolonger-lecture"
id="prolonger-la-lecture"
title="Prolonger la lecture"
intro="Une fois cette grammaire minimale stabilisée, la lecture peut sélargir vers les familles de méta-régimes, les paradigmes de comparaison, les dynamiques archicratiques et lindex complet."
>

View File

@@ -461,7 +461,7 @@ const prolongerLinks = [
}
@media (max-width: 720px){
.dyna-block--panel{
.dyna-block.glossary-portal-panel--surface{
padding: 16px;
border-radius: 16px;
}
@@ -469,7 +469,7 @@ const prolongerLinks = [
@media (prefers-color-scheme: dark){
.dyna-focus-card,
.dyna-block--panel,
.dyna-block.glossary-portal-panel--surface,
.dyna-card{
background: rgba(255,255,255,0.04);
}

View File

@@ -39,6 +39,9 @@ const tension = bySlug.get("tension");
const sceneDepreuve = bySlug.get("scene-depreuve");
const archicration = bySlug.get("archicration");
const conceptsPageHref = "/glossaire/concepts-fondamentaux/";
const scenesPageHref = "/glossaire/scenes-archicratiques/";
const dynamiquesPageHref = "/glossaire/dynamiques-archicratiques/";
const paradigmeArchicratiquePageHref = "/glossaire/paradigme-archicratique/";
const metaRegimesPageHref = "/glossaire/archicrations/";
const tensionsPageHref = "/glossaire/tensions-irreductibles/";
@@ -215,6 +218,8 @@ const approfondirPortalItems = [
title="Concepts fondamentaux"
followSection="Concepts fondamentaux"
intro="Ces notions forment la grammaire minimale de larchicratie. Elles donnent accès à la structure générale du système."
ctaHref={conceptsPageHref}
ctaLabel="Ouvrir le portail"
>
<GlossaryCardGrid entries={fondamentaux} />
</GlossaryHomeSection>
@@ -237,6 +242,8 @@ const approfondirPortalItems = [
title="Scènes archicratiques"
followSection="Scènes archicratiques"
intro="Les scènes archicratiques rendent possible la comparution des architectures de régulation. Elles sont le lieu où lordre peut être exposé, discuté et révisé."
ctaHref={scenesPageHref}
ctaLabel="Ouvrir le portail"
>
<GlossaryCardGrid entries={scenes} wide={true} />
</GlossaryHomeSection>
@@ -248,6 +255,8 @@ const approfondirPortalItems = [
title="Dynamiques archicratiques"
followSection="Dynamiques archicratiques"
intro="Cette famille rassemble les processus de déplacement, les dérives et les formes de pathologisation de la régulation archicratique."
ctaHref={dynamiquesPageHref}
ctaLabel="Ouvrir le portail"
>
<GlossaryCardGrid entries={dynamiques} />
</GlossaryHomeSection>