responsive breakpoints framework

This commit is contained in:
2026-10-20 15:23:51 +02:00
parent 36c5b1e380
commit 0637c0f90d
9 changed files with 117 additions and 25 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+11 -2
View File
@@ -43,6 +43,15 @@ gsap.registerPlugin(ScrollTrigger);
const gsap_mm = gsap.matchMedia(); const gsap_mm = gsap.matchMedia();
console.log('gsap_mm', gsap_mm); console.log('gsap_mm', gsap_mm);
// Frontière mobile ↔ tablette : source unique = _breakpoints.scss,
// publiée en variable CSS --bp-mobile par _base.scss. Plus de littéral
// ici — le couple mq(mobile)/mq-above(mobile) du CSS est exclusif par
// construction, le JS doit l'être aussi (bug « à 768px pile », review #94).
const bpMobile = getComputedStyle(document.documentElement)
.getPropertyValue('--bp-mobile').trim() || '767px';
// Condition desktop STRICTEMENT au-dessus de la zone mobile, complémentaire du CSS.
const desktopQuery = `(min-width: calc(${bpMobile} + 1px))`;
// Instance Lenis partagée : initPageFull en a besoin pour resynchroniser la // Instance Lenis partagée : initPageFull en a besoin pour resynchroniser la
// plage de scroll après avoir gonflé le spacer (voir ensureScrollForTitle). // plage de scroll après avoir gonflé le spacer (voir ensureScrollForTitle).
let lenis = null; let lenis = null;
@@ -134,7 +143,7 @@ gsap.registerPlugin(ScrollTrigger);
if (!scroller || !firstColumn || !secondColumn) return; if (!scroller || !firstColumn || !secondColumn) return;
gsap_mm.add('(min-width: 768px)', () => { gsap_mm.add(desktopQuery, () => {
[firstColumn, secondColumn].forEach((column) => { [firstColumn, secondColumn].forEach((column) => {
gsap.to(column, { gsap.to(column, {
// Distance à parcourir pour que le bas de la colonne arrive en // Distance à parcourir pour que le bas de la colonne arrive en
@@ -191,7 +200,7 @@ gsap.registerPlugin(ScrollTrigger);
// layoutSync() dimensionne les colonnes AVANT la création des ScrollTriggers. // layoutSync() dimensionne les colonnes AVANT la création des ScrollTriggers.
requestAnimationFrame(() => { requestAnimationFrame(() => {
requestAnimationFrame(() => { requestAnimationFrame(() => {
gsap_mm.add('(min-width: 768px)', () => { gsap_mm.add(desktopQuery, () => {
const viewport = () => scroller.clientHeight; const viewport = () => scroller.clientHeight;
@@ -0,0 +1,69 @@
// ____ ____ _
// | __ )| _ \ __ _ __ _ ___ _ __ _ _| |__
// | _ \| |_) / _` / _` |/ _ \ '__| | | | '_ \| | | |
// | |_) | _ < (_| | (_| | __/ | | |_| | |_) | |_| |
// |____/|_| \_\__,_|\__, |\___|_| \__,_|_.__/ \__, |
// |___/ |___/
//
// Aucun CSS émis. LA seule maison des seuils de largeur du thème.
//
// ─── Modèle ──────────────────────────────────────────────────────────────────
// Thème desktop-first. Trois familles de variables :
//
// 1. Zones imbriquées vers le bas — « cette zone et tout ce qui est
// plus étroit » :
// @media #{$tablette}{ ... } ≤ 1023 (contient mobile)
// @media #{$mobile}{ ... } ≤ 767 (contient narrow)
// @media #{$narrow}{ ... } ≤ 431
//
// 2. Le grand format — desktop + tablette ensemble (≥ 768) :
// @media #{$desktop-et-tablette}{ ... }
// C'est le complément EXACT de $mobile (aucune largeur ne matche les
// deux) : pour les composants écrits en deux blocs explicites
// (grand format / mobile).
//
// 3. Formats seuls — exactement une zone, ni plus ni moins :
// @media #{$seulement-desktop}{ ... } ≥ 1024
// @media #{$seulement-tablette}{ ... } 768 – 1023
// @media #{$seulement-mobile}{ ... } 432 – 767
// (seulement-narrow ≡ $narrow : rien en dessous.)
//
// ─── Disciplines ────────────────────────────────────────────────────────────
// 1. Jamais de nombre brut égal à un seuil : interpoler une variable de zone.
// 2. Une valeur découverte pendant le pass responsive s'écrit brute avec un
// commentaire et se consolide ICI quand 2+ composants convergent dessus.
// 3. Le JS consomme ces seuils par les variables CSS --bp-* (posées dans
// _base.scss) : plus jamais de littéral dans main.js. Le bug « à 768px
// pile » de la review venait d'un chevauchement CSS/JS — les zones sont
// exclusives par construction, le JS doit l'être aussi.
// 4. Un futur leshed.breakpoints.yml (images responsives) doit reprendre
// exactement ces valeurs.
@use "sass:map";
$bp: (
// Ancre du layout (contenu + padding proportionnels) — référencée par _fluid.
layout: 1112px,
// Frontière tablette ↔ desktop : la zone desktop commence À 1024 (inclusive).
desktop: 1024px,
// Frontière mobile ↔ tablette : la zone mobile finit À 767 (inclusive).
// 767 et pas 768 : à 768 pile, l'ancien couple max-width:768/min-width:769
// laissait la requête mobile cohabiter avec le JS desktop — review #94.
mobile: 767px,
// Zone étroit (accueil) — découverte à re-valider au pass 1.
narrow: 431px,
);
// ─── Zones imbriquées vers le bas ────────────────────────────────────────────
// Dérivées de $bp : changer un seuil recalcule tout (CSS, --bp-*, JS).
$tablette: "(max-width: calc(#{map.get($bp, desktop)} - 1px))";
$mobile: "(max-width: #{map.get($bp, mobile)})";
$narrow: "(max-width: #{map.get($bp, narrow)})";
// Le grand format : desktop + tablette — complément exact de $mobile.
$desktop-et-tablette: "(min-width: calc(#{map.get($bp, mobile)} + 1px))";
// ─── Formats seuls ───────────────────────────────────────────────────────────
$seulement-desktop: "(min-width: #{map.get($bp, desktop)})";
$seulement-tablette: "(min-width: calc(#{map.get($bp, mobile)} + 1px)) and (max-width: calc(#{map.get($bp, desktop)} - 1px))";
$seulement-mobile: "(min-width: calc(#{map.get($bp, narrow)} + 1px)) and (max-width: #{map.get($bp, mobile)})";
@@ -15,7 +15,9 @@
// automatique si le plafond du layout ou son padding changent. // automatique si le plafond du layout ou son padding changent.
@use "sass:math"; @use "sass:math";
@use "sass:map";
@use "variables" as *; @use "variables" as *;
@use "breakpoints" as *;
$root: 16px; // taille de police de <html> $root: 16px; // taille de police de <html>
@@ -31,11 +33,10 @@ $root: 16px; // taille de police de <html>
} }
// Largeur d'ancrage des clamps : le viewport auquel le layout est plein — // Largeur d'ancrage des clamps : le viewport auquel le layout est plein —
// 1080px de contenu + 2 × 16px de padding = 1112px. // 1080px de contenu + 2 × 16px de padding = 1112px. Vit dans $bp.layout
// Depuis que $layout_padding et $colsgap sont proportionnels (clamp vw), // (_breakpoints.scss, la seule maison des largeurs) — alias local pour les
// le padding vaut exactement 16px à ce point (1.4388vw × 1112). Valeur // signatures de fonctions ci-dessous.
// constante : px() ne sait pas parser un clamp() pour la dériver. $layout-anchor: map.get($bp, layout);
$layout-anchor: 1112px;
// La valeur vw qui vaut exactement $size quand le viewport fait $at. // La valeur vw qui vaut exactement $size quand le viewport fait $at.
@function vw-at($size, $at: $layout-anchor) { @function vw-at($size, $at: $layout-anchor) {
@@ -6,6 +6,7 @@
@use "variables" as *; @use "variables" as *;
@use "fluid" as *; @use "fluid" as *;
@use "breakpoints" as *;
@use "../abstracts/nodes-on-liste" as *; @use "../abstracts/nodes-on-liste" as *;
@use "../abstracts/event-calendrier" as *; @use "../abstracts/event-calendrier" as *;
@@ -179,8 +180,8 @@
} }
} }
} }
// ONLY FOR DESKTOP // DESKTOP + TABLETTE (≥ 768) — complément exact de $mobile (cf. _breakpoints.scss).
@media (min-width:769px){ @media #{$desktop-et-tablette}{
div.cols{ div.cols{
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@@ -263,8 +264,9 @@
} }
} }
} }
// ONLY FOR MOBILE
@media (max-width:768px){ // MOBILE (≤ 767)
@media #{$mobile}{
div.cols{ div.cols{
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -4,7 +4,18 @@
// | |_) | (_| \__ \ __/ // | |_) | (_| \__ \ __/
// |____/ \__,_|___/\___| // |____/ \__,_|___/\___|
@use "sass:map";
@use "../abstracts/variables" as *; @use "../abstracts/variables" as *;
@use "../abstracts/breakpoints" as *;
// Seuils publiés en variables CSS : le JS (gsap.matchMedia) les lit au lieu
// de littéraux — contrat de valeur unique, voir _breakpoints.scss.
:root{
--bp-layout: #{map.get($bp, layout)};
--bp-desktop: #{map.get($bp, desktop)};
--bp-mobile: #{map.get($bp, mobile)};
--bp-narrow: #{map.get($bp, narrow)};
}
a, a:visited{ a, a:visited{
@@ -6,6 +6,7 @@
@use "../abstracts/variables" as *; @use "../abstracts/variables" as *;
@use "../abstracts/nodes-on-liste" as *; @use "../abstracts/nodes-on-liste" as *;
@use "../abstracts/breakpoints" as *;
div.layout.layout--twocol-section--50-50.layout--twocol-section{ div.layout.layout--twocol-section--50-50.layout--twocol-section{
@@ -34,7 +35,7 @@ div.layout.layout--twocol-section--50-50.layout--twocol-section{
} }
} }
@media (max-width:431px) { @media #{$narrow}{
&{ &{
display:block; display:block;
div.layout__region{ div.layout__region{
@@ -7,6 +7,7 @@
@use "../abstracts/variables" as *; @use "../abstracts/variables" as *;
@use "../abstracts/nodes-on-liste" as *; @use "../abstracts/nodes-on-liste" as *;
@use "../abstracts/breakpoints" as *;
div.views-archives-projets{ div.views-archives-projets{
@@ -27,9 +28,8 @@ div.views-archives-projets{
} }
// ONLY FOR DESKTOP // DESKTOP + TABLETTE (≥ 768) — gouttière + rangées de 4.
@media (min-width:769px){ @media #{$desktop-et-tablette}{
// gap: 2em;
gap: $colsgap; gap: $colsgap;
div.views-row{ div.views-row{
flex: 1 0 calc(25% - $colsgap); flex: 1 0 calc(25% - $colsgap);
@@ -37,9 +37,8 @@ div.views-archives-projets{
} }
} }
// ONLY FOR MOBILE // MOBILE (≤ 767) : 2 par ligne.
@media (max-width:768px){ @media #{$mobile}{
// gap: 2em;
gap: $colsgap; gap: $colsgap;
div.views-row{ div.views-row{
flex: 1 0 calc(50% - $colsgap); flex: 1 0 calc(50% - $colsgap);