Structure fichiers projet

J'utilise GULP pour :
- générer et minifier les CSS à partir de fichiers SCSS
- concaténer et minifier les JS.

Les fichiers sources sont situés dans le répertoire /src/
Les fichiers générés, les pages HTML et les images sont situés dans le répertoire /dist/.

Pour installer la routine gulp, dans une console, se placer au niveau du répertoire de dev et lancer la commande npm install.
Ne pas tenir compte des alertes de sécurité qui proviennent de composants qui ne sont pas utilisés...

Pour générer les fichiers, dans une console, se placer au niveau du répertoire de dev et lancer la commande gulp.

Tous les fichiers *.scss présent dans le répertoire /src/scss/ et ses sous-dossiers sont concaténés dans le fichier unique /dist/css/styles.css.

Tous les fichiers *.js présent dans le répertoire /src/js/ sont concaténés dans le dossier /dist/javascripts/.
Tous les fichiers *.js présents dans un sous-dossier de /src/js/ sont concaténés dans un fichier unique du nom du répertoire dans le dossier /dist/javascripts/.
Par exemple, tous les fichiers *.js présents dans le dossier /src/js/plugins/ sont concaténés et minifiés dans le fichier /dist/javascripts/plugins.js.

Filtres recherche simple, Ressources

Un bloc de filtres de recherche est un <div class="filters"> contenant un <form> avec un <fieldset> (lui-même contenu dans un <div class="mobile-scrollable" pour avoir une scrollbar sous mobile) avec des checkboxes format généré par Drupal + 1 <input type="submit"> et 1 <input type="reset"> placés dans un <div class="actions">.
Il peut être affiché n'importe où et prend toute la largeur de son conteneur.

<div class="filters">
  <form action="#">
    <div class="mobile-scrollable">
      <fieldset>
        <legend>Filtrer les ressources</legend>
        <div class="js-form-item form-item js-form-type-checkbox form-type-checkbox js-form-item-all form-item-all">
          <input data-drupal-selector="ressources-all" type="checkbox" id="ressources-all" name="all" value="1" class="form-checkbox" checked="checked">
          <label for="ressources-all" class="option">Toutes</label>
        </div>
        etc.
      </fieldset>
    </div>
    <div class="actions">
      <input type="submit" value="Filtrer">
      <input type="reset" value="Effacer">
    </div>
  </form>
</div>
Filtrer les ressources

Filtres Projets

Les retours serveur sont séparés en deux éléments : la liste et la map.

La cible du retour AJAX pour la liste est l'élément <div class="js-ajax-list">.
Le retour attendu est une liste de pushes à un seul élément par ligne <ul class="push-list push-1-items"> (voir plus bas pour le détail de la structure HTML).

La cible du retour AJAX pour la map est l'élément <div id="js-leaflet-bigmap">.
Voir plus bas pour le détail.

La zone de filtres regroupe plusieurs blocs .filters dans un seul et même form.
Les blocs .filters sont très proches du filtre simple mais n'ont pas de <form> ni de <div class="mobile-scrollable">, celui-ci (classe spécifique .mobile-scrollable-filters) encadre tous les .filters.

Structure HTML des filtres

<form action="projets.php" id="js-projects-form">
  <div class="multi-filters"> // pour mise en forme CSS responsive et détection javascript du contexte multi-filters
    <div class="title">Filtrer les projets</div> // titre affiché uniquement pour desktop
    <div class="mobile-scrollable-filters"> // pour scroll mobiles
      <div class="flex-filters"> // pour alignement flex filtres et reset
        <div class="filter"> // bloc de filtres
          <button type="button" class="open-filters"> // ouverture des filtres pour desktop
            Intercommunalités
            <span class="picto"><svg viewBox="0 0 8 14" fill="none"><use xlink:href="#svg-chevron"></use></svg></span>
          </button>
          <div class="filters"> // les filtres checkbox
            <fieldset>
              <legend>Intercommunalités</legend>
              ...checkboxes...
            </fieldset>
            <input type="submit" value="Filtrer"> // chaque bloc de filtres contient un bouton submit qui déclenche l'envoi du form complet
            <button class="fieldset-reset js-fieldset-reset">Effacer</button> // Chaque bloc contient un bouton qui reset uniquement ses filtres
          </div>
        </div>
        ...autres .filter...
        </div>
      </div>
      <div class="actions">
        <input type="submit" value="Filtrer" class="mobile-only"> // Pour mobiles, un seul bouton submit pour l'ensemble des filtres
        <div class="reset"> // Reset complet du form
          <input type="reset" value="Effacer tous les filtres" class="js-reset-all">
        </div>
      </div>
    </div>
  </form>

CSS

Tous les styles sont déclarés dans le fichier /src/scss/modules/filters.scss
L'aspect étant très différent entre desktop et mobile, les déclarations des classes sont bien séparées avec @media.

Fonctionnalités diférentes mobile / desktop

Desktop

Les filtres sont séparés en 3 blocs de forme "drop-down menu".
Chaque bloc de filtres contient ses boutons reset et submit.
Le reset vide les values des checkbox du bloc et déclenche le submit du form (tous les filtres sont donc envoyés en même temps).
Le submit déclenche le submit du form (tous les filtres sont donc envoyés en même temps).

Mobile

Les filtres sont tous regroupés et ne s'affichent qu'au click sur le bouton flottant "Filtrer".
Les boutons reset et submit de chaque bloc de filtres sont masqués.
Un bouton submit général est affiché.
Le reset vide les values des checkbox et déclenche le submit du form.

Javascript

Les fonctions sont déclarées dans le fichier /src/js/general.js et ne sont exécutées que lorsqu'un élément .multi-filters est présent dans le DOM.

Ouverture des filtres Desktop

À chaque élément .open-filters est appliqué un event click qui ajoute ou enlève la classe .open à l'élément .filters sibling.

var openFilters = multiFilters.querySelectorAll('.open-filters');
openFilters.forEach(function(elt){
  elt.addEventListener('click', function(e){
    e.preventDefault();
    var cur = e.currentTarget;
    if(cur.classList.contains('open')){
      cur.classList.remove('open');
      cur.parentNode.querySelector('.filters').classList.remove('open');

    }
    else{
      //close all open filters
      closeFilters();
      //open cur filters
      cur.classList.add('open');
      cur.parentNode.querySelector('.filters').classList.add('open');

    }
  });
});

Boutons reset

Les boutons reset de chaque bloc de filtres vident les value de leurs checkbox puis déclenchent le submit du formulaire.
De même, le reset principal, après son action de base, déclenche le submit du form.

var resetFieldset = multiFilters.querySelectorAll('.js-fieldset-reset');
resetFieldset.forEach(function(elt){
  elt.addEventListener('click', function(e){
    e.preventDefault();
    //uncheck all checkboxes
    var curFieldset = e.currentTarget.parentNode.querySelector('fieldset');
    var check = curFieldset.querySelectorAll('input[type=checkbox]');
    check.forEach(function(elt){
      elt.checked = false;
    });
    //submit form with a little timeout to let see reset is well done !
    var submitBtn = e.currentTarget.parentNode.querySelector('input[type=submit]');
    setTimeout(function(){
      submitBtn.click();
    }, 800);
  });
});
multiFilters.querySelector('.js-reset-all').addEventListener('click', function(e){
  //submit form with a little timeout to let see reset is well done !
  var submitBtn = multiFilters.querySelector('input[type=submit]');
  setTimeout(function(){
    submitBtn.click();
  }, 800);
});
}

Ouverture des filtres Mobiles (utilisé aussi pour les filtres Ressources)

Les textes du bouton ouvert/fermé sont placé dans des attributs data-text-open et data-text-close de la balise <a>.
Le lien est en position fixed et placé tout de suite après l'ouverture de la <section>.

Le conteneur des filtres .multi-filters est identifié par un id unique.
Cet id est indiqué dans un attribut data-target du bouton d'ouverture :data-target="#js-multi-filters" (avec le dièse).

L'ouverture des filtres (ajout de classe .open) déclenche également le blocage du scroll du body et le masquage de l'éventuel bouton d'ouverture de la map (voir plus bas).

La fermeture des filtres revient à la normale...

//open filters for mobile
var openFilters = document.querySelector('.open-filters-mobile');
if(openFilters){
  openFilters.addEventListener('click', function(e){
    var that = e.currentTarget;
    var filters = document.querySelector(that.getAttribute('data-target'));
    if(filters.classList.contains('open')){ // fermeture
      that.querySelector('span').textContent = that.getAttribute('data-text-open'); // maj du texte
      filters.classList.remove('open');
      filters.classList.add('closing');// la fermeture des filtres passe par une transitionEnd
      filters.addEventListener(transitionEvent, closeFiltersCallback);
      document.querySelector('body').classList.remove('no-overflow');
      var openMap = document.querySelector('.open-map-mobile');
      if(openMap){
        openMap.classList.remove('hide');
      }
    }
    else{ // ouverture
      that.querySelector('span').textContent = that.getAttribute('data-text-close');
      filters.classList.add('open');
      document.querySelector('body').classList.add('no-overflow'); // on empêche le scroll du body
      var openMap = document.querySelector('.open-map-mobile');
      if(openMap){
        openMap.classList.add('hide'); // on masque l'open-map si il existe
      }
      //mobile context, pour assurer un bon positionnement, la hauteur de la zone scrollable est calculée en javascript
      if(isMobileContext){
        var mobileScroll = filters.querySelector('.mobile-scrollable') || filters.querySelector('.mobile-scrollable-filters');
        var availH = window.innerHeight;
        var headerH = window.getComputedStyle(document.querySelector('.main-header')).height;
        var actionsH = window.getComputedStyle(filters.querySelector('.actions') ).height;
        var scrollH = availH - parseInt(headerH) - parseInt(actionsH) - 100;
        mobileScroll.style.height = scrollH + 'px';
      }
    }
  });
  function closeFiltersCallback(event) { // transitionEnd fermeture volet filtres
    if(event.propertyName == 'transform'){
      event.target.classList.remove('closing');
      event.target.removeEventListener(transitionEvent, closeFiltersCallback);
    }
  }
}

Map Projets

La mise en place de maps Leaflet nécessite le rajout de fichiers CSS et JS spécifiques :
JS en bas de page
Si votre serveur de dev et le serveur de prod final sont en https, je recommande de pointer vers https://unpkg.com pour bénéficier des mises à jour automatiques.
Sinon, pointer vers le fichier local /javascripts/leaflet.js.

<!-- si vous êtes en https, pointer vers https://unpkg.com -->
<script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js" integrity="sha512-gZwIG9x3wUXg2hdXF6+rVkLF/0Vi9U8D2Ntg4Ga5I5BZpVkVxlJWbSQtXPSiUTtC0TjtGOmxa1AJPuV0CPthew==" crossorigin=""></script> <script src="javascripts/leaflet.js"></script>

CSS, dans le <head> avant l'appel aux styles du site :

<link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css" integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ==" crossorigin=""/>

De façon à pouvoir styler correctement les markers et popups de la map leaflet, j'ai mis en place un process javascript qui pourra éventuellement vous servir de base pour la dynamisation.
J'ai placé le code dans un fichier *.js séparé pour vous permettre de ne pas l'inclure dans les pages dynamisées.
/src/js/projets-map.js pour la version non-minifiée.
/dist/javascripts/projets-map.js pour la version minifiée.

Process js / json

json

J'ai utilisé une structure JSON pour faciliter la génération des markers / popups leaflet.
/dist/json/projets-map.php

{
  "response": "1", value 1 pour confirmer que le retout serveur est OK
  "coord": [45.299184, -1.161326], position centrale de la map
  "zoom": "8", zoom par défaut de la map
  "markers": [ liste des markers
    {
      "coord": [45.299184, -1.161326], position du marker
      "title": "Nom Projet 1", Titre de la popup
      "icon": "blue", couleur de l'icone (blue, orange, green)
      "img": ["img/push1.jpg", "img/push2.jpg", "img/push3.jpg"], URL des images du slider
      "id": "ID-projet-01" ID unique du projet (qui permet de faire le lien avec le projet dans la liste - voir plus bas)
    },
    etc...,
  "message": "une erreur est survenue" message erreur
}

javascript

Une fonction callMapData() simule l'appel au webService en récupérant les datas du formulaire identifié #js-projects-form,charge le json en Ajax et exécute la fonction showMap() qui affiche la map et ses markers..

function callMapData(){
  var url = 'json/projets-map.php';
  var params = '';
  var params = new FormData(document.querySelector('#js-projects-form'));
  var method = 'post';
  XXL.ajaxCall(url, params, method, function(data){
    jsonMap = JSON.parse(data);
    if(jsonMap.response === '1'){
      showMap();
    }
    else{
      // show error
      ...
    }
  });
}

showMap() affiche la map leaflet dans l'élément identifié #js-leaflet-bigmap.
J'applique le TileLayer de base, c'est ici que vous pourrez choisir le TileLayer final.
Il construit ensuite tous les markers et leurs popups avec des document.createElement.

Les sliders

leaflet génère le code HTML des popup à la volée lorsqu'on clique sur le marker.
L'initialisation des sliders doit donc être effectuée à ce moment seulement (voir plus bas pour l'initialisation des sliders).
Il faut ajouter un évènement click à chaque marker :

marker.sliderInit = false; variable sliderInit qui permet d'éviter de créer plusieurs fois le slide (sinon ça bugue...).
marker.addEventListener('click', function(e){
  if(!e.target.sliderInit){
    e.target.sliderInit = true;
    var sliders = map.querySelectorAll('.slides');
    if(sliders){
      sliders.forEach(function(elt){
        XXL.initTinySlider(elt, 1, 1, true, 400, false, false);
      });
    }
  }
});
Rollover entre liste et map

Chaque item de la liste doit avoir une classe .js-push-map et est identifié par l'ID du projet avec le préfixe "push-".

Leaflet ne permet pas d'identifier directement un marker. Il faut donc passer par une petite ruse :
On stocke les markers présents sur la map au moment de leur création dans un objet curMarkers dans lequel chaque marker est identifié par l'ID présent dans le json.

var marker = curMarkers[markers[i].id] = L.marker(markers[i].coord, {icon: leafletIcon}).addTo(mymap);

On utilise ensuite cet objet pour retrouver le marker correspondant.
J'ai créé deux petites fonctions pour le rollover liste -> map :

//rollover push / markers map
rollOnMarker = function(id){
  var marker = curMarkers[id];
  if(marker){
    var icon = marker._icon; _icon est la variable dans laquelle leaflet stocke l'élément picto du marker.
    icon.classList.add('highlight');
  }
}
rollOffMarker = function(id){
  var marker = curMarkers[id];
  if(marker){
    var icon = marker._icon;
    icon.classList.remove('highlight');
  }
}
var pushes = document.querySelectorAll('.js-push-map'); chaque item de la liste contient la classe .js-push-map
if(pushes){
  pushes.forEach(function(elt){
    elt.addEventListener('mouseover', function(e){
      var id = e.currentTarget.id.replace('push-', '');
      rollOnMarker(id);
    });
    elt.addEventListener('mouseout', function(e){
      var id = e.currentTarget.id.replace('push-', '');
      rollOffMarker(id);
    });
  });
}

Autres maps

Le fichier projets-map.js contient également la déclaration des autres map du site de démo qui seront remplacées par vos propres déclarations...

Ouverture map pour mobiles

HTML

Pour les mobiles, la map est masquée par défaut et s'affiche en plein écran au click sur le bouton .open-map-mobile qui est position fixed en haut de l'écran.
Les textes ouvert / fermé sont stockés dans des attributs data-text-open et data-text-close.
L'ID de la map (avec le dièse) à ouvrir est stocké dans un attribut data-target.

<a href="#" class="open-map-mobile" data-text-open="Carte" data-text-close="Fermer la carte" data-target="#js-leaflet-map"><span>Carte</span></a>
Javascript

Le code est placé dans le fichier general.js.

L'ouverture de la map (ajout de classe .open) déclenche également le changement du texte du bouton, le blocage du scroll du body, la fermeture du .main-header et le masquage de l'éventuel bouton d'ouverture des filtres (voir plus haut).

that.querySelector('span').textContent = that.getAttribute('data-text-close');
map.classList.add('open');
document.querySelector('body').classList.add('no-overflow');
var openFilters = document.querySelector('.open-filters-mobile');
if(openFilters){
  openFilters.classList.add('hide');
}
document.querySelector('.main-header').classList.add('hidden');
that.classList.add('clicked');

La fermeture des filtres revient à la normale...

Les listes de pushes

Peuvent être affichées n'importe où dans le site.
Ce sont des liste non-ordonnées.

<ul class="push-list"><li class="push">
avec des classes spécifiques selon le nombre d'élément pas ligne.

4 éléments

<ul class="push-list push-4-items">

3 éléments

<ul class="push-list push-3-items">

1 élément

<ul class="push-list push-1-items">

Structure HTML

<li class="push js-push-map" id="push-ID-projet-01">
  <article>
    <div class="push-img">
      <div class="slides">
        <div>
          <div class="slides-img">
            <img src="img/push1.jpg" alt="">
          </div>
        </div>
        etc.
      </div>
    </div>
    <div class="desc">
      <a href="projet-detail.php">
        <p>Département 17 | CARA</p>
        <h2>Etude prospective sur l’organisation de l’espace littoral</h2>
        <div class="labels">
          <div class="label">Planification</div>
          <div class="label blue">Projet à venir</div>
          .etc
        </div>
        <p>
          De 2012 à 2014, le partenariat du GIP Littoral Aquitain, accompagné par les 9 territoires de Scot, a animé un travail de réflexion sur les grands enjeux d’aménagement du littoral aquitain.
        </p>
      </a>
    </div>
  </article>
</li>

Slider

Le slider prend la largeur totale de son conteneur.
L'image placée dans un conteneur .slides-img prend toute la largeur.
La hauteur du slider dépend donc de la hauteur de l'image de chaque slide. Il se resize.
L'éventuel contenu de chaque slide est positionné par-dessus le visuel en absolute.

Structure HTML:

<div class="slides"> <!-- classe .slides obligatoire pour automatiser initialisation du slider-->
  <div> <!-- conteneur de la slide, pas de classe nécessaire -->
    <div class="slides-img"> <!-- classe .slide-img nécessaire pour image full-size -->
      <img src="img/push1.jpg" alt="">
    </div>
    <div class="slides-content"> <!-- conteneur pour éléments placés au-dessus de l'image full-size -->
      Contenu en absolute
    </div>
  </div>
  <div>
    etc.
  </div>
</div>

Javascript

Le constructeur est inclus dans le fichier /dist/javascripts/plugins.js, /src/js/plugins/tiny-slider.js
Il s'agit du tiny-slider développé par William Lin.

Déclaration des sliders dans /javascripts/general.js.
Tous les éléments avec la classe .slides sont automatiquement traités :

//sliders
var sliders = document.querySelectorAll('.slides');
if(sliders){
  sliders.forEach(function(elt){
    XXL.initTinySlider(elt, 1, 1, true, 400, false, false);
  });
}

L'appel au constructeur est inclus dans le fichier /javascripts/XXL.js.
Les paramètres sont :

XXL.initTinySlider(container, items, slideBy, autoHeight, speed, autoplay, autoplayTimeout);

container : l'élément qui va contenir le slider (div.slider);
items : le nombre de slides visibles;
slideBy : le nombre de slides déplacées au clic sur flèches ou bullets;
autoHeight : le slider change de hauteur selon hauteur de la slide active;
speed : la vitesse de déplacement;
autoplay : booléen détermine autoplay;
autoplayTimeout : tempo entre chaque autoplay.

CSS

/src/scss/components/_tiny-slider.scss pour les styles nécessaires au bon fonctionnement du plugin.
/src/scss/modules/_slider.scss pour les styles de mise en forme spécifiques au projet.

Contenu en absolute
Contenu en absolute
Contenu en absolute
Contenu en absolute
Contenu en absolute
Contenu en absolute

Les galeries full-screen

Les galeries utilisent aussi le constructeur de sliders tinySlider.

HTML

Même structure HTML que les sliders simples sauf leur classe générique qui n'est pas .sliders mais .gallery-slider.
Un élément supplémentaire est prévu pour les légendes :

<div class="gallery-slider"> <!-- classe .slides obligatoire pour automatiser initialisation du slider-->
  <div>
    <div class="slides-img">
      <img src="img/push1.jpg" alt="">
    </div>
    <div class="slides-content">
      Contenu en absolute
    </div>
    <div class="legend">
      <div class="title">
        Île Madame vue du ciel
      </div>
      <div class="copyright">
        © Rochefort Ocean
      </div>
    </div>

  </div>
  <div>
    etc.
  </div>
</div>

Javascript

Dans le fichier general.js.

Déclaration des galeries, tous les éléments avec la classe .slides sont automatiquement traités :

var galleries = document.querySelectorAll('.gallery-slider');
if(galleries){
  galleries.forEach(function(elt){
    elt.parentNode.tinySlider = XXL.initTinySlider(elt, 1, 1, true, 400, false, 2500);
  });
}

Ouverture des galeries par bouton avec classe .open-gallery contenant l'ID de la galerie dans un attribut data-gallery-id.
On peut choisir quel slide est affiché par défaut via un attribut data-index (le premier a l'indice 0).

<a href="#" class="open-gallery" data-gallery-id="js-projet-gallery" data-index="0">XXX</a>

L'ouverture d'une galerie (ajout d'une classe .open à la galerie) déclenche également les actions suivantes :
- blocage scroll body;
- on cache le .main-header;
- on déclare les actions du clavier.

gal.classList.add('open');
document.querySelector('body').classList.add('no-overflow');
document.querySelector('.main-header').classList.add('hidden');
document.addEventListener('keydown', keyNav);

Processus inverse à la fermeture...

curGal.classList.remove('open');
curGal.classList.add('closing');
La fermeture de la galerie passe par un transitionEvent
curGal.addEventListener(transitionEvent, closeGalleryCallback);
document.querySelector('body').classList.remove('no-overflow');
document.querySelector('.main-header').classList.remove('hidden');
document.removeEventListener('keydown', keyNav);

La galerie est manipulable via le clavier.
- Flèche gauche: slide précédent.
- Flèche droite: slide suivant.
- Escape: fermeture de la galerie.

function keyNav(e) {
  if(e.code == 'ArrowRight'){
    curGal.tinySlider.goTo('next');
  }
  if(e.code == 'ArrowLeft'){
    curGal.tinySlider.goTo('prev');
  }
  if(e.code == 'Escape'){
    closeCurGallery();
  }
}

Voir toutes les photos

Layout de deux colonnes

Des layouts de deux colonnes avec la méthode FLEX peuvent être affichés n'importe où.
Il suffit de respecter les structures suivantes :

<div class="cols">
  <div class="col1"></div>
  <div class="col2"></div>
</div>

Sur mobile, les colonnes sont forcées à 100% de largeur et se placent l'une en dessous l'autre.
Sur petites tablettes <769px, les 2 colonnes sont forcées à 50%.

J'ai prévu les largeurs repérées sur les créas (+ leur inversion).

1ere colonne 70% / 2e colonne 30%.

<div class="cols cols-70-30">
  ...
</div>

1ere colonne 60% / 2e colonne 40%.

<div class="cols 60-40">
  ...
</div>

1ere colonne 50% / 2e colonne 50%.

<div class="cols cols-50-50">
  ...
</div>

1ere colonne 40% / 2e colonne 60%.

<div class="cols cols-40-60">
  ...
</div>

1ere colonne 30% / 2e colonne 70%.

<div class="cols cols-30-70">
  ...
</div>

1ere colonne 66% / 2e colonne 34% (les Projets).

<div class="cols cols-66-34">
  ...
</div>

Exemple 70-30

Player vidéo

Le player vidéo peut lire des vidéos Youtube, Viméo ou MP4 uploadé sur le serveur.
L'URL de la vidéo est à placer dans un attribut data-video du lien .play-video.
URL complète pour youtube ou viméo : https://www.youtube.com/watch?v=G7eIa1w_mVU ou https://vimeo.com/382054256
Chemin relatif vers le fichier MP4 :videos/Braziloid.mp4

Pour appliquer l'ouverture du player à un lien, il faut écrire le code :

<a href="#" class="play-video" data-video="PATH/VIDEO">
Le lecteur se place dans le parentNode de ce lien et prend toute sa taille.

La fonction playVideo est dans le fichier src/js/XXL/XXL.js.

Vidéo youtube

<a href="#" class="play-video" data-video="https://www.youtube.com/watch?v=wEDJo5PZL6A">

Vidéo viméo

<a href="#" class="play-video" data-video="https://vimeo.com/382054256">

Vidéo mp4

<a href="#" class="play-video" data-video="videos/Braziloid.mp4">

Les pictos SVG

Afin de ne pas avoir à ré-écrire tout le code XML des SVG à chaque fois, j'ai utilisé une méthode d'includes.
Tous les includes sont placés dans l'include /dist/includes/_header.php ainsi ils sont bien présents dans chaque page du site.

Déclaration des SVG

<svg xmlns="http://www.w3.org/2000/svg" style="display:none;">
  <g id="svg-chevron">
    <path d="M8 6v2H6V6h2zM6 4v2H4V4h2zM4 2v2H2V2h2zM2 0v2H0V0h2zM6 10V8H4v2h2zM4 12v-2H2v2h2zM2 14v-2H0v2h2z" fill="currentColor"/>
  </g>
  etc.
</svg>

La valeur currentColor permet de récupérer la couleur de texte déclarée au parent du svg, pratique pour les effets de rollover par ex.

Appel au svg dans le corps des pages

<svg viewBox="0 0 8 14"><use xlink:href="#svg-chevron"></use></svg>

Les classes génériques

Pour des mises en forme simples sur l'ensemble du site.

Couleurs du texte.
.t-blue, .t-orange, .t-green, .t-grey, .t-black ou .t-white

Couleurs du background sans padding.
.b-blue, .b-orange, .b-green, .b-grey, .b-black ou .b-white

Couleurs du background + padding 3.2rem Desktop / 1.6rem mobile.
.bg-grey, .bg-orange, .bg-blue, .bg-green ou .bg-black

Texte en capitales.
.t-up

Image pleine largeur.
.full-width
Applicable au conteneur de l'image ou à l'image elle-même.

Sticky, reste fixé en haut de l'écran tant que son conteneur scrolle.
.sticky
Mobile: 16px (1.6rem) sous la nav fixed.
Desktop: 32px sous le haut de l'écran.

Angles arrondis (12px).
.radius

Listes ul avec bullet et texte décalé.
.bullet

Masquer pour les mobiles.
.no-mobile

Les étiquettes.
.label : applique border, padding et margin-right.
Couleurs de fond en rajoutant:
.blue, .orange, .grey ou .green

Les accroches.
Pour styler les textes comme intro des pages.
.accroche

//  generic  classes
.t-blue{
    color:  $blue;
}

.t-up{
    text-transform:  uppercase;
}

.b-blue{
    background-color:  $blue;
}

.grey-bg{
    background:  $greyLight;
    padding:  1.6rem;
    
    @media  (min-width:  $mobile-phone){
        padding:  3.2rem;
    }
}

.radius{
    border-radius: 1.2rem;
}

.bullet{
  margin-left: 2.4rem;

  li{
    margin-bottom: .8rem;
  }
}

.no-mobile{
  display: none !important;
}

.full-width{
    width:  100%;
    height:  auto;
    margin-bottom:  3.2rem;
    
    img{
        width:  100%;
        height:  auto;
    }
}

.sticky{
    position:  sticky;
    top:  $headerHeightMobile  +  1.6rem;
    
    @media  (min-width:  $desktop-width){
        top:  3.2rem;
    }
}

.label{
    border:  .1rem  solid  $black;
    background:  $white;
    color:  $black;
    padding:  .5rem  1rem;
    margin-right:  1.2rem;
    
    &.blue{
        background:  $blueLight;
        border-color:  $blueLight;
        color:  $white;
    }
    &.orange{
        background:  $orange;
        border-color:  $orange;
        color:  $white;
    }
    etc.
}

.accroche{
    font-size:  2rem;
    color:  $blue;
    
    p{
        max-width:  $maxWidthSection;
    }
    
    @media  (min-width:  $mobile-phone){
        font-size:  2.5rem;
    }
}