487 lines
14 KiB
Plaintext
487 lines
14 KiB
Plaintext
---
|
||
interface Props {
|
||
heroMoreId: string;
|
||
heroToggleId: string;
|
||
sectionHeadSelector?: string;
|
||
mobileBreakpoint?: number;
|
||
autoCollapseDelta?: number;
|
||
}
|
||
|
||
const {
|
||
heroMoreId,
|
||
heroToggleId,
|
||
sectionHeadSelector = ".glossary-portal-section__head",
|
||
mobileBreakpoint = 860,
|
||
autoCollapseDelta = 160,
|
||
} = Astro.props;
|
||
---
|
||
|
||
<script
|
||
is:inline
|
||
define:vars={{ heroMoreId, heroToggleId, sectionHeadSelector, mobileBreakpoint, autoCollapseDelta }}
|
||
>
|
||
(() => {
|
||
const boot = () => {
|
||
const body = document.body;
|
||
const root = document.documentElement;
|
||
const hero = document.querySelector("[data-glossary-portal-hero]");
|
||
const follow = document.getElementById("reading-follow");
|
||
const heroMore = document.getElementById(heroMoreId);
|
||
const heroToggle = document.getElementById(heroToggleId);
|
||
|
||
if (!body || !root || !hero || !follow) return;
|
||
|
||
const BODY_CLASS = "is-glossary-portal-page";
|
||
const FOLLOW_ON_CLASS = "glossary-portal-follow-on";
|
||
const EXPANDED_CLASS = "glossary-portal-hero-expanded";
|
||
const CONDENSED_CLASS = "glossary-portal-hero-condensed";
|
||
|
||
const mqMobile = window.matchMedia(`(max-width: ${mobileBreakpoint}px)`);
|
||
const mqSmallLandscape = window.matchMedia(
|
||
"(orientation: landscape) and (max-width: 920px) and (max-height: 520px)"
|
||
);
|
||
|
||
let expandedAtY = null;
|
||
let lastScrollY = window.scrollY || 0;
|
||
let raf = 0;
|
||
let lastFollowOn = null;
|
||
let lastCondensed = null;
|
||
let lastHeroHeight = -1;
|
||
|
||
body.classList.add(BODY_CLASS);
|
||
|
||
const isCompactViewport = () =>
|
||
mqMobile.matches || mqSmallLandscape.matches;
|
||
|
||
const stripLocalSticky = () => {
|
||
document.querySelectorAll(sectionHeadSelector).forEach((el) => {
|
||
el.classList.remove("is-sticky");
|
||
el.removeAttribute("data-sticky-active");
|
||
});
|
||
};
|
||
|
||
const readStickyTop = () => {
|
||
const raw = getComputedStyle(document.documentElement)
|
||
.getPropertyValue("--glossary-sticky-top")
|
||
.trim();
|
||
const n = Number.parseFloat(raw);
|
||
return Number.isFinite(n) ? n : 64;
|
||
};
|
||
|
||
const computeFollowOn = () =>
|
||
!isCompactViewport() &&
|
||
follow.classList.contains("is-on") &&
|
||
follow.style.display !== "none" &&
|
||
follow.getAttribute("aria-hidden") !== "true";
|
||
|
||
const computeCondensed = () => {
|
||
if (isCompactViewport()) return false;
|
||
|
||
const heroRect = hero.getBoundingClientRect();
|
||
const stickyTop = readStickyTop();
|
||
|
||
return heroRect.top <= stickyTop + 2;
|
||
};
|
||
|
||
const measureHeroHeight = () =>
|
||
Math.max(0, Math.round(hero.getBoundingClientRect().height || 0));
|
||
|
||
const PIN_EPS = 3;
|
||
|
||
const isHeroPinned = () => {
|
||
if (isCompactViewport()) return false;
|
||
|
||
const rect = hero.getBoundingClientRect();
|
||
const stickyTop = readStickyTop();
|
||
const cs = getComputedStyle(hero);
|
||
|
||
if (cs.position !== "sticky") return false;
|
||
|
||
const pinnedOnRail = Math.abs(rect.top - stickyTop) <= PIN_EPS;
|
||
const stillVisible = rect.bottom > stickyTop + 24;
|
||
|
||
return pinnedOnRail && stillVisible;
|
||
};
|
||
|
||
const applyLocalStickyHeight = () => {
|
||
const h = isHeroPinned() ? measureHeroHeight() : 0;
|
||
if (h === lastHeroHeight) return;
|
||
lastHeroHeight = h;
|
||
|
||
if (typeof window.__archiSetLocalStickyHeight === "function") {
|
||
window.__archiSetLocalStickyHeight(h);
|
||
} else {
|
||
root.style.setProperty("--glossary-local-sticky-h", `${h}px`);
|
||
}
|
||
};
|
||
|
||
const syncFollowState = () => {
|
||
const on = computeFollowOn();
|
||
|
||
if (on !== lastFollowOn) {
|
||
lastFollowOn = on;
|
||
body.classList.toggle(FOLLOW_ON_CLASS, on);
|
||
}
|
||
|
||
return on;
|
||
};
|
||
|
||
const syncCondensedState = () => {
|
||
const condensed = computeCondensed();
|
||
|
||
if (condensed !== lastCondensed) {
|
||
lastCondensed = condensed;
|
||
body.classList.toggle(CONDENSED_CLASS, condensed);
|
||
}
|
||
|
||
return condensed;
|
||
};
|
||
|
||
const collapseHero = () => {
|
||
if (!body.classList.contains(EXPANDED_CLASS)) return;
|
||
|
||
body.classList.remove(EXPANDED_CLASS);
|
||
expandedAtY = null;
|
||
|
||
if (heroMore) {
|
||
heroMore.setAttribute("aria-hidden", "true");
|
||
}
|
||
|
||
if (heroToggle) {
|
||
heroToggle.hidden = false;
|
||
heroToggle.setAttribute("aria-expanded", "false");
|
||
}
|
||
|
||
try {
|
||
window.__archiUpdateFollow?.();
|
||
} catch {}
|
||
|
||
schedule();
|
||
};
|
||
|
||
const expandHero = () => {
|
||
body.classList.add(EXPANDED_CLASS);
|
||
expandedAtY = window.scrollY || 0;
|
||
|
||
if (heroMore) {
|
||
heroMore.setAttribute("aria-hidden", "false");
|
||
}
|
||
|
||
if (heroToggle) {
|
||
heroToggle.hidden = true;
|
||
heroToggle.setAttribute("aria-expanded", "true");
|
||
}
|
||
|
||
try {
|
||
window.__archiUpdateFollow?.();
|
||
} catch {}
|
||
|
||
schedule();
|
||
};
|
||
|
||
const syncHeroState = (condensed) => {
|
||
const expanded = body.classList.contains(EXPANDED_CLASS);
|
||
const collapsed = condensed && !expanded;
|
||
|
||
if (isCompactViewport() || !condensed) {
|
||
body.classList.remove(EXPANDED_CLASS);
|
||
expandedAtY = null;
|
||
|
||
if (heroMore) {
|
||
heroMore.setAttribute("aria-hidden", "false");
|
||
}
|
||
|
||
if (heroToggle) {
|
||
heroToggle.hidden = true;
|
||
heroToggle.setAttribute("aria-expanded", "false");
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
if (heroMore) {
|
||
heroMore.setAttribute("aria-hidden", collapsed ? "true" : "false");
|
||
}
|
||
|
||
if (heroToggle) {
|
||
heroToggle.hidden = !collapsed;
|
||
heroToggle.setAttribute("aria-expanded", expanded ? "true" : "false");
|
||
}
|
||
};
|
||
|
||
const maybeAutoCollapseOnScroll = () => {
|
||
if (isCompactViewport()) {
|
||
lastScrollY = window.scrollY || 0;
|
||
return;
|
||
}
|
||
|
||
if (!body.classList.contains(EXPANDED_CLASS)) {
|
||
lastScrollY = window.scrollY || 0;
|
||
return;
|
||
}
|
||
|
||
if (expandedAtY == null) {
|
||
lastScrollY = window.scrollY || 0;
|
||
return;
|
||
}
|
||
|
||
const currentY = window.scrollY || 0;
|
||
const scrollingDown = currentY > lastScrollY;
|
||
const delta = currentY - expandedAtY;
|
||
|
||
if (scrollingDown && delta >= autoCollapseDelta) {
|
||
collapseHero();
|
||
}
|
||
|
||
lastScrollY = currentY;
|
||
};
|
||
|
||
const syncAll = () => {
|
||
stripLocalSticky();
|
||
|
||
if (isCompactViewport()) {
|
||
body.classList.remove(FOLLOW_ON_CLASS);
|
||
body.classList.remove(CONDENSED_CLASS);
|
||
body.classList.remove(EXPANDED_CLASS);
|
||
|
||
lastFollowOn = false;
|
||
lastCondensed = false;
|
||
expandedAtY = null;
|
||
|
||
if (heroMore) {
|
||
heroMore.setAttribute("aria-hidden", "false");
|
||
}
|
||
|
||
if (heroToggle) {
|
||
heroToggle.hidden = true;
|
||
heroToggle.setAttribute("aria-expanded", "false");
|
||
}
|
||
|
||
requestAnimationFrame(() => {
|
||
applyLocalStickyHeight();
|
||
try {
|
||
window.__archiUpdateFollow?.();
|
||
} catch {}
|
||
});
|
||
|
||
return;
|
||
}
|
||
|
||
const condensed = syncCondensedState();
|
||
syncHeroState(condensed);
|
||
|
||
requestAnimationFrame(() => {
|
||
applyLocalStickyHeight();
|
||
syncFollowState();
|
||
try {
|
||
window.__archiUpdateFollow?.();
|
||
} catch {}
|
||
});
|
||
|
||
requestAnimationFrame(() => {
|
||
applyLocalStickyHeight();
|
||
try {
|
||
window.__archiUpdateFollow?.();
|
||
} catch {}
|
||
});
|
||
};
|
||
|
||
const schedule = () => {
|
||
if (raf) return;
|
||
raf = requestAnimationFrame(() => {
|
||
raf = 0;
|
||
syncAll();
|
||
});
|
||
};
|
||
|
||
heroToggle?.addEventListener("click", expandHero);
|
||
|
||
const onScroll = () => {
|
||
maybeAutoCollapseOnScroll();
|
||
schedule();
|
||
};
|
||
|
||
const followObserver = new MutationObserver(schedule);
|
||
followObserver.observe(follow, {
|
||
attributes: true,
|
||
attributeFilter: ["class", "style", "aria-hidden"],
|
||
subtree: false,
|
||
});
|
||
|
||
const heroResizeObserver =
|
||
typeof ResizeObserver !== "undefined"
|
||
? new ResizeObserver(schedule)
|
||
: null;
|
||
|
||
heroResizeObserver?.observe(hero);
|
||
|
||
window.addEventListener("scroll", onScroll, { passive: true });
|
||
window.addEventListener("resize", schedule);
|
||
window.addEventListener("pageshow", schedule);
|
||
|
||
if (document.fonts?.ready) {
|
||
document.fonts.ready.then(schedule).catch(() => {});
|
||
}
|
||
|
||
if (mqMobile.addEventListener) {
|
||
mqMobile.addEventListener("change", schedule);
|
||
} else if (mqMobile.addListener) {
|
||
mqMobile.addListener(schedule);
|
||
}
|
||
|
||
if (mqSmallLandscape.addEventListener) {
|
||
mqSmallLandscape.addEventListener("change", schedule);
|
||
} else if (mqSmallLandscape.addListener) {
|
||
mqSmallLandscape.addListener(schedule);
|
||
}
|
||
|
||
schedule();
|
||
};
|
||
|
||
if (document.readyState === "loading") {
|
||
document.addEventListener("DOMContentLoaded", boot, { once: true });
|
||
} else {
|
||
boot();
|
||
}
|
||
})();
|
||
</script>
|
||
|
||
<style>
|
||
:global(body.is-glossary-portal-page #reading-follow){
|
||
z-index: 10;
|
||
}
|
||
|
||
/* Le hero se condense dès qu’il devient sticky */
|
||
:global(body.is-glossary-portal-page.glossary-portal-hero-condensed .glossary-portal-hero){
|
||
padding:
|
||
var(--portal-hero-pad-top-condensed, 14px)
|
||
var(--portal-hero-pad-x-condensed, 16px)
|
||
var(--portal-hero-pad-bottom-condensed, 16px);
|
||
row-gap: var(--portal-hero-gap-condensed, 10px);
|
||
box-shadow:
|
||
inset 0 1px 0 rgba(255,255,255,0.02),
|
||
0 8px 20px rgba(0,0,0,0.12);
|
||
}
|
||
|
||
:global(body.is-glossary-portal-page.glossary-portal-hero-condensed .glossary-portal-hero h1){
|
||
font-size: var(--portal-hero-h1-size-condensed, clamp(2.05rem, 3.15vw, 2.7rem));
|
||
line-height: var(--portal-hero-h1-lh-condensed, 1);
|
||
letter-spacing: var(--portal-hero-h1-spacing-condensed, -.04em);
|
||
}
|
||
|
||
:global(body.is-glossary-portal-page.glossary-portal-hero-condensed .glossary-portal-hero__intro){
|
||
max-width: var(--portal-hero-follow-intro-max-w, 62ch);
|
||
font-size: var(--portal-hero-intro-size-condensed, .98rem);
|
||
line-height: var(--portal-hero-intro-lh-condensed, 1.46);
|
||
}
|
||
|
||
:global(body.is-glossary-portal-page.glossary-portal-hero-condensed .glossary-portal-hero__kicker){
|
||
opacity: .68;
|
||
}
|
||
|
||
/* Le more se replie dès l’état condensé */
|
||
:global(body.is-glossary-portal-page.glossary-portal-hero-condensed:not(.glossary-portal-hero-expanded) .glossary-portal-hero__more){
|
||
max-height: 0;
|
||
opacity: 0;
|
||
overflow: hidden;
|
||
pointer-events: none;
|
||
}
|
||
|
||
:global(body.is-glossary-portal-page.glossary-portal-hero-condensed:not(.glossary-portal-hero-expanded) .glossary-portal-hero__toggle){
|
||
display: inline-flex;
|
||
}
|
||
|
||
/* L’accolage hero + follow n’arrive que quand le follow est actif */
|
||
:global(body.is-glossary-portal-page.glossary-portal-hero-condensed.glossary-portal-follow-on .glossary-portal-hero){
|
||
margin-bottom: 0;
|
||
border-bottom-left-radius: 0;
|
||
border-bottom-right-radius: 0;
|
||
}
|
||
|
||
:global(body.is-glossary-portal-page.glossary-portal-follow-on #reading-follow .reading-follow__inner){
|
||
margin-top: -1px;
|
||
border-top-left-radius: 0;
|
||
border-top-right-radius: 0;
|
||
}
|
||
|
||
:global(body.is-glossary-portal-page.glossary-portal-follow-on #reading-follow .rf-h2){
|
||
letter-spacing: -.02em;
|
||
}
|
||
|
||
:global(body.is-glossary-portal-page .glossary-portal-section__head.is-sticky),
|
||
:global(body.is-glossary-portal-page .glossary-portal-section__head[data-sticky-active="true"]){
|
||
position: static !important;
|
||
top: auto !important;
|
||
z-index: auto !important;
|
||
padding: 0 !important;
|
||
border: 0 !important;
|
||
background: transparent !important;
|
||
box-shadow: none !important;
|
||
backdrop-filter: none !important;
|
||
-webkit-backdrop-filter: none !important;
|
||
}
|
||
|
||
@media (max-width: 860px){
|
||
:global(body.is-glossary-portal-page #reading-follow),
|
||
:global(body.is-glossary-portal-page #reading-follow .reading-follow__inner){
|
||
display: none !important;
|
||
opacity: 0 !important;
|
||
visibility: hidden !important;
|
||
pointer-events: none !important;
|
||
}
|
||
|
||
:global(body.is-glossary-portal-page){
|
||
--followbar-h: 0px !important;
|
||
--sticky-offset-px: calc(var(--sticky-header-h, 0px) + var(--page-gap, 12px)) !important;
|
||
}
|
||
|
||
:global(body.is-glossary-portal-page .glossary-portal-hero){
|
||
margin-bottom: var(--portal-hero-margin-bottom, 18px);
|
||
border-radius: 20px !important;
|
||
box-shadow: none !important;
|
||
}
|
||
|
||
:global(body.is-glossary-portal-page .glossary-portal-hero__more){
|
||
max-height: none !important;
|
||
opacity: 1 !important;
|
||
overflow: visible !important;
|
||
pointer-events: auto !important;
|
||
}
|
||
|
||
:global(body.is-glossary-portal-page .glossary-portal-hero__toggle){
|
||
display: none !important;
|
||
}
|
||
}
|
||
|
||
@media (orientation: landscape) and (max-width: 920px) and (max-height: 520px){
|
||
:global(body.is-glossary-portal-page #reading-follow),
|
||
:global(body.is-glossary-portal-page #reading-follow .reading-follow__inner){
|
||
display: none !important;
|
||
opacity: 0 !important;
|
||
visibility: hidden !important;
|
||
pointer-events: none !important;
|
||
}
|
||
|
||
:global(body.is-glossary-portal-page){
|
||
--followbar-h: 0px !important;
|
||
--sticky-offset-px: calc(var(--sticky-header-h, 0px) + var(--page-gap, 12px)) !important;
|
||
}
|
||
|
||
:global(body.is-glossary-portal-page .glossary-portal-hero){
|
||
margin-bottom: var(--portal-hero-margin-bottom, 12px);
|
||
border-radius: 16px !important;
|
||
box-shadow: none !important;
|
||
}
|
||
|
||
:global(body.is-glossary-portal-page .glossary-portal-hero__more){
|
||
max-height: none !important;
|
||
opacity: 1 !important;
|
||
overflow: visible !important;
|
||
pointer-events: auto !important;
|
||
}
|
||
|
||
:global(body.is-glossary-portal-page .glossary-portal-hero__toggle){
|
||
display: none !important;
|
||
}
|
||
}
|
||
</style> |