diff --git a/web/themes/erabletheme/css/styles.css b/web/themes/erabletheme/css/styles.css index 82994c7..24bb9db 100644 --- a/web/themes/erabletheme/css/styles.css +++ b/web/themes/erabletheme/css/styles.css @@ -954,6 +954,15 @@ main.main-login .login > div > div:not(.hidden) form .button:hover, main.main-lo height: 22vh; } +.col-decor { + display: none; +} +@media (min-width: 760px) { + .col-decor { + display: block; + } +} + /* PAGES */ .home_introduction { box-sizing: border-box; diff --git a/web/themes/erabletheme/js/erabletheme.js b/web/themes/erabletheme/js/erabletheme.js index e649fa3..5b09950 100644 --- a/web/themes/erabletheme/js/erabletheme.js +++ b/web/themes/erabletheme/js/erabletheme.js @@ -391,13 +391,19 @@ const asideProjectMenu = document.querySelector('#block-erabletheme-views-block-projets-block-1 .projets_list'); if (asideProjectMenu) { - for (let item of asideProjectMenu.children) { - const itemTitle = item.innerText; - const contentPage = document.querySelector('.projet_full'); - const contentPageTitle = contentPage.querySelector('h2')?.innerText; - - if (itemTitle.toLowerCase() === contentPageTitle.toLowerCase()) { - item.querySelector('a').classList.add('is-active'); + // Le titre du projet n'est plus rendu dans .projet_full depuis la + // refonte (il a été remonté dans .page-header-outside, hors colonne + // blanche). On va chercher le titre courant à sa nouvelle place. + // Garde sur null + chaîne vide pour éviter l'exception qui plantait + // tout le behavior attach (et donc Rellax) sur /projets/. + const headerOutside = document.querySelector('.layout-content .page-header-outside h2'); + const contentPageTitle = (headerOutside?.innerText || '').trim().toLowerCase(); + if (contentPageTitle) { + for (let item of asideProjectMenu.children) { + const itemTitle = (item.innerText || '').trim().toLowerCase(); + if (itemTitle === contentPageTitle) { + item.querySelector('a')?.classList.add('is-active'); + } } } } @@ -433,6 +439,133 @@ } } + // + // Décors mid-left : 0, 1 ou 2 PNG empilés au pied gauche extérieur + // de chaque .fullpage interne (pages non-index). Tailles et gaps + // proportionnels à colH, bornés min/max. On n'affiche un motif + // que si la pile tient sous 50% colH (les deux) ou 30% (un seul), + // sinon la forme déborderait dans le contenu en haut. + // Position absolue calculée en px dans .layout-container ; + // bord droit du PNG calé sur bord gauche colonne via + // translateX(-100%). Parallax custom plus bas (sans Rellax). + // Visibilité responsive : _background.scss. + // + const layoutContainer = document.querySelector('.layout-container'); + const colDecorState = []; // { img1, img2, col } + + function clamp(min, val, max) { return Math.max(min, Math.min(val, max)); } + + function colDecorMetrics(colH) { + return { + gapBottom: clamp(120, 0.08 * colH, 320), + gapBetween: clamp(60, 0.03 * colH, 180), + h2: clamp(180, 0.14 * colH, 480), + h1: clamp(220, 0.17 * colH, 580), + }; + } + + function createColDecorImg(idx) { + const img = document.createElement('img'); + img.className = `col-decor col-decor-${idx}`; + img.setAttribute('aria-hidden', 'true'); + img.setAttribute('alt', ''); + img.src = `/themes/erabletheme/assets/new-bg-shapes/mid-left-${idx}.png`; + img.style.cssText = 'position:absolute;width:auto;pointer-events:none;z-index:0;transform:translateX(-100%);'; + return img; + } + + function setupColDecor() { + if (!layoutContainer) return; + layoutContainer.querySelectorAll('.col-decor').forEach(n => n.remove()); + colDecorState.length = 0; + document.querySelectorAll('.fullpage:not(.large-container)').forEach(col => { + const img1 = createColDecorImg(1); + const img2 = createColDecorImg(2); + layoutContainer.appendChild(img1); + layoutContainer.appendChild(img2); + colDecorState.push({ img1, img2, col }); + }); + positionColDecor(); + } + + function positionColDecor() { + if (!colDecorState.length) return; + const lcRect = layoutContainer.getBoundingClientRect(); + const lcTopAbs = lcRect.top + window.scrollY; + const lcLeftAbs = lcRect.left + window.scrollX; + for (const entry of colDecorState) { + const { img1, img2, col } = entry; + const r = col.getBoundingClientRect(); + const colH = r.height; + const m = colDecorMetrics(colH); + const stack2H = m.gapBottom + m.h2; + const stack1H = stack2H + m.gapBetween + m.h1; + const showBoth = stack1H <= colH * 0.5; + const showOne = !showBoth && stack2H <= colH * 0.3; + img1.style.display = showBoth ? 'block' : 'none'; + img2.style.display = (showBoth || showOne) ? 'block' : 'none'; + entry.visible1 = showBoth; + entry.visible2 = showBoth || showOne; + if (!showBoth && !showOne) continue; + const colBottomAbs = r.bottom + window.scrollY; + const leftRel = (r.left + window.scrollX) - lcLeftAbs; + const top2 = colBottomAbs - m.gapBottom - m.h2; + img2.style.top = (top2 - lcTopAbs) + 'px'; + img2.style.left = leftRel + 'px'; + img2.style.height = m.h2 + 'px'; + // Référence scroll : motif centré dans le viewport. + entry.scrollRef2 = top2 + m.h2 / 2 - window.innerHeight / 2; + if (showBoth) { + const top1 = top2 - m.gapBetween - m.h1; + img1.style.top = (top1 - lcTopAbs) + 'px'; + img1.style.left = leftRel + 'px'; + img1.style.height = m.h1 + 'px'; + entry.scrollRef1 = top1 + m.h1 / 2 - window.innerHeight / 2; + } + } + // Premier rendu du parallax après (re)position. + applyColDecorParallax(); + } + + // Parallax custom (sans Rellax) : remontée subtile, amplitude bornée, + // proportionnelle au scroll relatif à la position d'ancrage. À 0.15 + // de vitesse, pour 100px de scroll au-delà de la référence, le motif + // monte de 15px ; clamp à ±30px évite tout débordement. + const COL_DECOR_PARALLAX_SPEED = 0.15; + const COL_DECOR_PARALLAX_AMP = 30; + let colDecorRafPending = false; + + function applyColDecorParallax() { + const scrollY = window.scrollY || document.body.scrollTop || 0; + for (const { img1, img2, visible1, visible2, scrollRef1, scrollRef2 } of colDecorState) { + if (visible2 && scrollRef2 != null) { + const dY = clamp(-COL_DECOR_PARALLAX_AMP, + -(scrollY - scrollRef2) * COL_DECOR_PARALLAX_SPEED, + COL_DECOR_PARALLAX_AMP); + img2.style.transform = `translate3d(0, ${dY}px, 0) translateX(-100%)`; + } + if (visible1 && scrollRef1 != null) { + const dY = clamp(-COL_DECOR_PARALLAX_AMP, + -(scrollY - scrollRef1) * COL_DECOR_PARALLAX_SPEED, + COL_DECOR_PARALLAX_AMP); + img1.style.transform = `translate3d(0, ${dY}px, 0) translateX(-100%)`; + } + } + } + + function onColDecorScroll() { + if (colDecorRafPending) return; + colDecorRafPending = true; + requestAnimationFrame(() => { + colDecorRafPending = false; + applyColDecorParallax(); + }); + } + if (!document.body.dataset.colDecorScrollWired) { + document.body.addEventListener('scroll', onColDecorScroll, { passive: true }); + document.body.dataset.colDecorScrollWired = 'true'; + } + // // Décors latéraux parallax v2. // Cloner le .decor-tile autant de fois que nécessaire pour couvrir @@ -472,15 +605,30 @@ rellax = new Rellax('.bgImg', { wrapper: 'body' }); } + // Init initial : frises latérales (basées sur layout-container, + // mesure stable même sans images chargées) + Rellax. Les col-decor + // sont injectés et positionnés UNIQUEMENT au window.load — leur + // position dépend de getBoundingClientRect() de .fullpage qui n'est + // fiable qu'une fois toutes les images du contenu chargées (sinon + // Rellax cache un blockTop périmé et le motif dérive vers le bas + // sur les pages longues). setupBackgroundTiles(); initRellax(); - // Resize debouncé : recalcule le nombre de tiles, repart Rellax propre. + window.addEventListener('load', () => { + setupBackgroundTiles(); + setupColDecor(); + initRellax(); + }); + + // Resize debouncé : recalcule le nombre de tiles + col-decor, + // repart Rellax propre. let bgResizeTimer = null; window.addEventListener('resize', () => { clearTimeout(bgResizeTimer); bgResizeTimer = setTimeout(() => { setupBackgroundTiles(); + setupColDecor(); initRellax(); }, 200); }); diff --git a/web/themes/erabletheme/scss/partials/_background.scss b/web/themes/erabletheme/scss/partials/_background.scss index 719d554..0223428 100644 --- a/web/themes/erabletheme/scss/partials/_background.scss +++ b/web/themes/erabletheme/scss/partials/_background.scss @@ -63,3 +63,13 @@ $tile_height: 220vh; .decor-right-13 { top: 92%; height: 22vh; } } } + +// Décors mid-left au pied gauche extérieur des .fullpage internes. +// Injection et positionnement en JS (cf. setupColDecor dans +// erabletheme.js) ; on gère uniquement la visibilité responsive ici. +.col-decor { + display: none; + @media (min-width: $breakpoint_tablet) { + display: block; + } +}