Skip to content

Frontend - Vite, themes SCSS, JS

Le pipeline frontend a remplace l'ancien Gulp (un gulpfile.js legacy subsiste mais n'est plus utilise). Tout se lance depuis src/Web avec Node 22.12.0.

Vite

Config : src/Web/vite.config.js (+ helper vite.config.helper.js).

  • Entrees decouvertes dynamiquement (vite.config.helper.js) :
    • JS : entree unique scripts/index.js -> wwwroot/js/index.js.
    • SCSS : globSync sur styles/themes/*/main.scss ; chaque theme produit wwwroot/css/<theme>/main.css (via assetFileNames : un fichier styles-<theme>.css est renomme en <theme>/main.css).
  • emptyOutDir: false (ne vide pas wwwroot, qui contient aussi favicons/og/images).
  • Plugin maison cleanupScssJs : Vite genere un stub JS vide pour chaque entree SCSS ; ce plugin supprime ces js/styles-*.js.
  • vite-plugin-live-reload : surveille wwwroot/css/**/*.css et wwwroot/js/**/*.js pour declencher le rechargement navigateur.
  • Dev server : port 8080, proxy / -> https://localhost:44360 (l'app .NET).
  • Prod : terser (drop console), cssMinify, sourcemaps desactivees. Dev : sourcemaps + rollup-plugin-visualizer (scripts/logs/stats.html).
  • Alias : @ -> scripts, @styles -> styles.

Commandes (package.json) :

  • npm run dev : vite build --mode development puis chokidar re-build sur changement styles/**/*.scss et scripts/**/*.js.
  • npm run build : build (mode par defaut).
  • npm run build:prod : ESLint puis vite build --mode production (une erreur de lint stoppe le build).

PostCSS

Chaine appliquee aux CSS (vite.config.js) :

  1. postcss-sort-media-queries
  2. autoprefixer
  3. postcss-combine-duplicated-selectors (removeDuplicatedValues)
  4. en prod uniquement : PurgeCSS puis cssnano

SCSS via api: "modern-compiler", deprecations silencieuses (if-function, global-builtin, color-functions, import).

Breakpoints responsive

Utiliser les mixins de styles/abstracts/_breakpoints.scss (@use "…/abstracts/breakpoints" as bp;) plutôt que des @media en dur : @include bp.down(xl), bp.up(lg), bp.between(md, xl). Le map $breakpoints reprend les valeurs de Bootstrap (xs 0, sm 576, md 768, lg 992, xl 1200, xxl 1400) — down() soustrait 0.02px comme Bootstrap. On ne peut pas utiliser les mixins Bootstrap directement (5.3.3 en @import legacy, non @use-able sans charger tout le framework), d'où ce fichier maison = source unique de vérité. Seuls restent en dur quelques breakpoints custom hors grille repris du site source (footer APOL 1280/980/930/600/480/408 ; méga-menu 1150).

PurgeCSS

purgeCss() (vite.config.helper.js) scanne Views/**/*.cshtml et scripts/**/*.js. Safelist : standard = /^js-/, /^is-/, /^has-/, /^u-/, /^util--/, /^btn-(lavaux|...)$/ (boutons communes footer), /^btn--/ et /^sep--/ (modificateurs de blocs construits dynamiquement dans les vues : btn--@(style), sep--@(size)) ; deep = /^cookie/. Toute classe injectee dynamiquement (par JS, par une lib, ou construite par interpolation dans une vue) doit etre ajoutee a cette safelist sinon elle sera purgee en prod. Ajouts recents : animations block-grid (/^scroll-anim$/, /^show-*, /^showed$/, /^d-primary$/) et bloc Documents (/^doc-ico/, couleur d'icone construite depuis l'extension du fichier).

Icones de type de fichier (bloc Documents) : glyphes extraits des fontes icomoon/themify des sites sources vers le sprite SVG Views/Partials/layout/_fileIconsSprite.cshtml (genere par migrations/file-icons/gen-file-icons.mjs, inclus dans _svgSprite.cshtml). Symboles icon-file-<type>, references via <use> dans des pastilles doc-ico--<type> colorees par styles/base/blocks/_documents.scss.

Themes SCSS

Arborescence sous src/Web/styles/ :

  • themes/<theme>/main.scss : point d'entree d'un theme. Importe Bootstrap (@use ".../node_modules/bootstrap/scss/bootstrap") et les partials base/ (header, footer, cookies, a11y, sections d'exemple). Peut surcharger via with (...).
  • themes/<theme>/_bootstrap_variables.scss : overrides des tokens Bootstrap par theme.
  • base/ : _bootstrap_variables.scss, layout/ (_header, _footer, _cookies, _a11y, _error), blocks/ (styles des blocs), sections/, elements/.

Pages d'erreur (404 / 500) : mise en page partagee Views/Partials/layout/_errorPage.cshtml, incluse par NotFoundPage.cshtml / InternalServerErrorPage.cshtml (qui passent ErrorCode/ErrorTitle/ErrorDescription via ViewData). Les deux affichent une illustration : wwwroot/images/errors/404.png (« 404 » + labyrinthe) et .../500.png (« OFFLINE » + prise debranchee), portees depuis metiersdart.ch. Le code n'est pas repete en texte (present dans l'image). Titre (ErrorTitle), description et bouton « Revenir a l'accueil » (.btn.btn--primary) communs. Styles : base/layout/_error.scss (.error-page*), @use dans les 2 themes.

Couleurs = variables uniquement. Aucune couleur en dur (hex / rgb / hsl) dans les regles SCSS. Toute couleur passe par une variable ; le seul endroit ou l'on ecrit une valeur litterale est themes/<theme>/_bootstrap_variables.scss. Pour une teinte derivee, appliquer l'alpha sur une variable : rgba(vars.$black, .1).

Bootstrap 5 d'abord (utility-first). On a Bootstrap 5 : privilegier les classes utilitaires dans le HTML (d-flex, d-block, text-white-50, border-bottom border-white border-opacity-25, row-cols-md-2, position-relative, overflow-hidden, py-3, order-md-0...) plutot que d'ecrire du SCSS. Le SCSS custom est reserve a ce que les utilitaires ne peuvent pas exprimer : decors (bokeh du footer PEL), glyphes de police d'icones, :hover/transition, white-space: pre-line, tailles fixes. Regle : si une classe Bootstrap fait le travail, ne pas creer de regle SCSS.

Themes livres dans le template : default (reference), cave, piscines (exemples de variations, dont une section card-section propre a piscines). Ce sont des exemples a adapter/supprimer dans un projet aval.

Styles des blocs (base/blocks/)

Styles de base (neutres, communs aux thèmes) des blocs BlockGrid/BlockList : styles/base/blocks/_<block>.scss agreges par styles/base/blocks/_index.scss, @use dans chaque themes/<theme>/main.scss (section // -------- BLOCKS --------). Un thème peut surcharger dans themes/<theme>/blocks/.

  • Convention de classes : BEM léger block--<type> (racine du bloc), sous-elements block__x ou <type>__x (ex. hero__title), modificateurs --<variante> (ex. btn--primary, sep--large) et hooks JS js- (sans style).
  • Les modificateurs construits dynamiquement dans les vues (btn--@(style), sep--@(size)) sont safelistes PurgeCSS (voir section PurgeCSS).
  • Blocs interactifs : galerie FAITE (lightbox Splide) et slider FAIT (carrousel Splide, voir plus bas) ; onglets / accordeon via Bootstrap. JS en modules lazy scripts/modules/blocks/. Voir docs/migration/blocks-apol-pel.md.

Header PEL (design porte depuis pel-website)

Le header du theme pel reproduit le « mainnav » de pel-website (police-el.ch).

  • Markup : Views/Partials/layout/pel/_header.cshtml (+ _sosIcon.cshtml). Desktop (.main-header-desktop, d-none d-lg-block) : logo blanc /logos/pel/main-logo.svg + nav en pilule translucide (.mainnav-desktop) sur bande navy ($header-bg), liens .nav-link.level-1 (actif = pilule blanche / texte navy), dropdowns blancs (.sub-nav), bouton SOS rouge. Mobile (.main-header-mobile, sticky-top, d-lg-none) : barre blanche (logo sombre + SOS + recherche + burger) ouvrant un offcanvas plein ecran (.mainnav-mobile). Nav = Home > En-tete (headerNavMenuLinks).
  • Carte SOS (#sosCard, collapse) : numeros en dur 117/144/118/145 ; « toutes les urgences » et recherche = placeholders #. L'auto-ouverture grand ecran n'est pas portee.
  • Styles : styles/themes/pel/layout/_header.scss (couleurs via _bootstrap_variables.scss). Burger « squeeze » CSS pur. JS : scripts/modules/pelHeader.js (hover dropdowns + burger is-active), lazy dans index.js, no-op hors pel.
  • Assets : wwwroot/logos/pel/main-logo.svg + main-logo-dark.svg ; glyphes themify ti-search/ti-close/ti-angle-right/ti-arrow-top-right dans themes/pel/_fonts.scss.
  • Adaptation : desktop pose sur une bande navy ($header-bg), l'intranet n'ayant pas le hero du site public.
  • Méga-menu (dropdown à sections) : une entrée de menu peut avoir un sous-menu en colonnes au lieu d'une liste plate. Modèle de contenu : navMenuElement porte, en plus de navSubmenuLinks (liste plate, conservée), une BlockList navMenuSections (datatype Custom - Bloc List - Nav Menu Sections, max 3) de l'element type navMenuSection = sectionTitle (Textstring) + sectionLinks (MultiURLPicker). Rendu (_header.cshtml) : si des sections existent → <div class="sub-nav sub-nav--mega"> en display:grid (.sub-nav--mega, hérite de .sub-nav la carte blanche/ombre) avec un .sub-nav__col + .sub-nav__title par section ; sinon le dropdown simple actuel ; mobile = sections empilées (sous-titre + liens) dans le collapse. Priorité sections > liste plate. Le JS pelHeader.js cible .sub-nav (générique) → inchangé. Voir .claude/docs/modules/content-model.md. Cycle uSync→ModelsBuilder requis avant que la vue typée (item.NavMenuSections, NavMenuSection) compile.

Le footer du theme apol reproduit fidelement celui du site public apol-website (Umbraco 13) : bandeau sombre avec image de fond (montagnes), 4 colonnes separees par des bordures cyan (logo / reseaux sociaux + adresse / horaires / liens), puis credits Spektrum.

  • Markup : Views/Partials/layout/apol/_footer.cshtml. Les donnees restent branchees sur le modele intranet : adresse / horaires / contact = champs Footer* du noeud Home ; liens = FooterLinkColumns (chaque colonne rendue avec son titre .footer-nav-title + sa liste de liens) ; reseaux = FooterSocialLinks (la classe sociale — facebook, instagram, linkedin, twitter, youtube — est deduite de l'URL).
  • Styles : styles/themes/apol/layout/_footer.scss (cible le selecteur footer ; couleurs via @use "../bootstrap_variables" as vars — pas de couleur en dur ni de variable redeclaree, y compris $footer-cyan / $footer-cities-title ajoutees au _bootstrap_variables.scss du theme).
  • Assets portes depuis apol-website : police d'icones themify-icons (wwwroot/fonts/apol/, @font-face dans themes/apol/_fonts.scss) pour les boutons ronds sociaux, images de fond wwwroot/images/apol/footer/footer_bg-{xl,lg,sm,xs}.png, et jeux d'images des boutons communes wwwroot/images/apol/footer/{lavaux,chexbres,lutry,puidoux,rivaz,stsaphorin}[-over][@2x].png.
  • Section « communes / police de proximite » : bandeau de boutons communes alimente par la propriete footerCommunes du noeud Home (voir content-model.md). La classe CSS commune (btn-lavaux, btn-chexbres, ...) vient du contenu, donc elle est safelistee dans PurgeCSS (vite.config.helper.js) sinon les regles .btn-* seraient supprimees en prod.
  • Convention d'assets par theme : polices sous wwwroot/fonts/<theme>/, images specifiques sous wwwroot/images/<theme>/....

Le footer du theme pel reproduit celui du site public pel-website (StarterKit V4 / Nanoxi, Umbraco 13) : fond navy avec decor bokeh (cercles flous statiques), colonne adresse a gauche (col-md-4 : logo, horaires, contacts telephone/email/adresse avec icones et bordures basses) et colonne contenus a droite (col-md-8 : navigation sur deux colonnes + reseaux), puis barre copyright Spektrum alignee a droite.

  • Markup : Views/Partials/layout/pel/_footer.cshtml, classes de pel-website (main-footer, main-footer-container, main-footer-address, main-footer-nav, main-footer-copyright, btn-copyright) + utilitaires Bootstrap. Donnees branchees sur le modele intranet (adresse / horaires / contact = noeud Home ; liens = FooterLinkColumns via _footerColumns.cshtml, wrappes dans row row-cols-1 row-cols-md-2 ; reseaux = FooterSocialLinks via _footerSocial.cshtml).
  • Bokeh : .footer-bokeh (gradients) + .footer-bokeh__field span x10 (cercles radial-gradient + box-shadow glow, blur). Statique (les animations ont ete desactivees cote pel-website). Couleurs bokeh dans _bootstrap_variables.scss ($bokeh-*).
  • Icones de contact : polices themify-icons (.ti-email \e75a, .ti-location-pin \e693) et font-icon/icomoon (.ico-phone \e60a), portees depuis pel-website (wwwroot/fonts/pel/*.woff2, @font-face + glyphes dans themes/pel/_fonts.scss).
  • Styles : styles/themes/pel/layout/_footer.scss (cible .main-footer ; couleurs via _bootstrap_variables.scss). Nav : titres letter-spacing + blanc (majuscules via l'utilitaire text-uppercase du partial), liens hover translateX(6px). Copyright : SVG blanc opacite 0.5 -> 1 au survol.
  • Non porte : les widgets Nanoxi (navigation et newsletter) ; la navigation est remplacee par FooterLinkColumns, la newsletter est omise.

Header APOL (adapte d'apol-website)

Le header du theme apol est un port fidele (structure + valeurs exactes) du header d'apol-website. Markup : Views/Partials/layout/apol/_header.cshtml (classes de reference : header-container, top-header, left-side, logo + logo-up/logo-sticky, menu-navbar, min-logo-area, container-fluid, social-nav, nav-link-search). Styles : styles/themes/apol/layout/_header.scss (couleurs via _bootstrap_variables.scss). Reseaux = FooterSocialLinks ; menu = _headerNav.cshtml.

On reprend la version standard (page non-home) d'apol-website — pas la home (700px / logo agrandi / menu a 618) qui est un cas particulier a ne pas utiliser ici.

Desktop (>= 992px) — valeurs exactes mesurees sur une page non-home (Contact) d'apol-website :

  • Cover : header-container de 420px. Image saisonniere aleatoire (54 images sous wwwroot/images/apol/cover/{spring,summer,winter}/bg-1..18.jpg, saison selon DateTime.Now.Month) en background sur le header.
  • Logo : .top-header (fixed, height 0) > .left-side (220x220, overflow:hidden, padding-left:20) > .logo (absolute top/left 20, ~150px au repos, sans scale). Le logo est dimensionne par max-width:100% de la .left-side : quand elle retrecit, le logo suit.
  • Barre : .menu-navbar (relative, top: 338px, padding-left: 130px, min-height: 82px) avec .min-logo-area (encoche 130px, translateX(-100%), ::before qui se remplit) + .container-fluid (absolute, width: calc(100% - 130px), fond $secondary) -> barre decalee, pas pleine largeur. .nav-link : padding: 29px 16px 21px, line-height: 32px, font 15px majuscules -> 82px.

Animation au scroll (scripts/modules/header.js, classes sur .js-header, safelist /^is-/) — seuils exacts d'apol-website :

  • is-logo-sm (>=70) : le .logo-sticky (rond, logo-desktop-2.svg) apparait en fondu par-dessus le .logo-up (logo-desktop.svg).
  • is-logo-xs (>=160) : .left-side -> 122x112 (le logo passe de ~150px a 102px), .logo top: 10.
  • is-logo-bg (>=240) : l'encoche .min-logo-area::before se remplit ($header-social-bg).
  • is-stuck (affix, >=338) : tout le header passe position: fixed, header-container -> 82px, menu-navbar top: 0. Le main recoit un margin-top de 420px pour eviter le saut (pose par le JS).

Méga-menu (dropdown à sections) — porté d'apol-website. Quand une entrée de menu a des navMenuSections (cf. content-model.md), _headerNav.cshtml rend <li class="nav-item dropdown full-width position-static"> + <div class="dropdown-menu dropdown-columns ..."> avec un <ul> par section (titre li.level-3 + liens a.dropdown-item). Panneau gris pleine largeur ($menu-hover-bg = #eeeeee), colonnes float:left; width:20% (5 par ligne), ouverture au hover (desktop, visibility/opacity) ; mobile = dropdown Bootstrap empilé par défaut (.show). Détails fidèles au source (vérifiés sur les computed styles en ligne) : liens en text-transform: lowercase + ::first-letter majuscule, padding-bottom: 15px, titres .level-3 18px/#151515 ; responsive <1150px (liens 14px, titres 17px). Bootstrap-first : mise en page (w-100 start-0 border-0 rounded-0 clearfix list-unstyled position-static) en classes dans le markup ; le SCSS (_header.scss) ne garde que le non-mappable (couleurs, paddings custom, width:20%, et position/display/float responsive — utilitaires !important non responsive).

Mobile (< lg) — porté d'apol-website. Le chrome desktop est masqué (d-none d-lg-block) et apol/_headerMobile.cshtml (d-lg-none) rend une barre fixe 54px (logo + recherche + burger navy \e68e → croix \e646) au-dessus d'un drawer plein largeur (.dl-menu-container, révélé par .dl-show). Navigation multi-niveaux par glissement horizontal : le JS scripts/modules/apolMobileMenu.js (vanilla, lazy, no-op hors APOL) échange le contenu d'un unique ul.dl-menu et empile l'historique, en animant translateX (.dl-animate-* / keyframes apolMove*). Les sections méga (li.level-3) sont regroupées en un niveau supplémentaire (Titre → Sections → Liens) ; un bouton RETOUR bleu (li.dl-back) est injecté à chaque niveau. Fermeture : re-clic burger, clic extérieur, Échap. Le drawer/burger/.dl-* sont stylés en SCSS (markup piloté par JS + icônes themify en pseudo-éléments) ; le chrome statique utilise des utilitaires Bootstrap. Alimenté par le même navMenuElement (navSubmenuLinks / navMenuSections) que le desktop.

Popup urgence (desktop + mobile) — portée d'apol-website. apol/_emergencyPopup.cshtml = dropdown Bootstrap natif paramétré par @@model bool (isMobile) ; data-bs-display="static" désactive Popper (positionnement en CSS). Déclencheurs (SVG source copiés dans wwwroot/images/apol/) :

  • mobile : emergency-btn-mobile.svg (combiné téléphone rouge sur fond transparent) dans la barre mobile entre recherche et burger → panneau position:fixed pleine largeur sous la barre 54px.
  • desktop : emergency-btn.svg (téléphone blanc) sur un bouton rouge $danger 82px collé à droite (margin-left:auto) de la barre de nav → dropdown position:absolute aligné à droite (min-width:380px). Flèche d'appel \e62a (pseudo-élément, le thème apol ne définit pas les classes .ti-*) → croix \e646 à l'ouverture ; ondes « pulse » animées au survol (.btn-emergency-pulse, keyframe anim-dash-pulse).

Numéros en dur (verbatim source : Police Lavaux 021 791 11 21 ; 117/144/118/145/1414/143 + Médecin de garde + MalleyPrairie), aucun JS custom (Bootstrap Dropdown). Bandeaux rouge $danger / bleu $info, numéros #192f89 avec soulignement animé. Styles dans _header.scss (.dropdown-emergency partagé + variantes .site-header--apol / .site-header--apol-mobile).

Non porte : popup recherche (le bouton nav-link-search est visuel).

  • Styles : styles/themes/apol/layout/_header.scss (couleurs via _bootstrap_variables.scss, cover background-size: cover). Reutilise la police themify-icons deja chargee pour le footer.
  • Le drawer mobile est porté en glissement 2D (le perspective 3D d'origine était vestigial).

Hero de page (bloc manuel) et bokeh fixe PEL

Fidele a pel-website, le hero n'est pas automatique : c'est le bloc heroBlock (sous-titre + titre + texte + image/couleur de fond + lien, tous saisis par l'editeur), place dans une BlockList dediee « Heros » portee par genericSection, au-dessus du BlockGrid blocks. Sur la page generique le rendu est donc [ Heros ] puis [ Blocs ]. Voir modules/content-model.md.

  • Rendu : sections/genericSection.cshtml sort le hero (Model.Value<BlockListModel>("hero")) en pleine largeur via blocklist/default.cshtml -> blocklist/Components/heroBlock.cshtml, puis les blocs dans .section-body.bg-white (.container-fluid py-5).
  • Vue du bloc : .block--hero (Bootstrap-first : text-white py-5, container), .hero__subtitle (eyebrow text-uppercase small fw-bold opacity-75), .hero__title (h1 fw-light), .hero__text, lien btn btn-primary. Style dans base/blocks/_hero.scss (transparent par defaut : seul le letter-spacing du subtitle et le max-width du texte y sont ; une image/couleur editeur le rend opaque).
  • Bokeh fixe PEL (mecanisme du .bokeh-stage de pel-website) : pel/_bokeh.cshtml (.site-bokeh + __field 10 span) rendu par _MasterLayout avant le header ; pel/layout/_siteTop.scss le pose en position: fixed; inset: 0; z-index: -1 (gradient exact radial(#02285a) + radial(#011e44) + linear(#02285a->#071839) + cercles identiques au footer). Le header desktop devient transparent (.main-header-desktop { background: transparent }, @use en dernier dans pel/main.scss pour gagner en ordre ; le meme bloc force aussi overflow: visible car le port du header le clippait en overflow: hidden pour contenir son .header-bokeh — bokeh desactive ici, le clip ne rognait donc plus que les dropdowns du menu) et le hero est transparent : header + hero laissent voir un seul fond bokeh continu. Le .body-content est transparent ; le fond blanc vit sur .section-body (couvre le bokeh sous le hero).
  • _MasterLayout : plus de .container interne autour de @RenderBody() (chaque page/section gere sa largeur : le hero est pleine largeur, SearchPage/_errorPage ont deja le leur). apol/_bokeh.cshtml est vide (APOL a son cover-header, pas de bokeh).

Blog (listing + cartes par theme)

Le blog (blogPage -> articlePage, voir modules/content-model.md) est rendu serveur. BlogPage.cshtml filtre par tag (?tags=, OR), trie (blogSortOrder), decoupe selon blogPageSize + blogDisplay, et rend une grille .js-blog-grid de cartes. Les cartes reprennent fidelement le markup + style des sites de reference (rien d'invente) :

  • PEL : layout/pel/_articleCard.cshtml = structure feed de pel-website (feed-cover 16:9, feed-body date/tags/titre/intro). Style porte dans themes/pel/layout/_blog.scss (zoom .feed-cover img au hover du .feed-link).
  • APOL : layout/apol/_articleCard.cshtml = structure card d'apol-website (card-img-overlay en bas). Style porte dans themes/apol/layout/_blog.scss (degrade bleu #13134f montant, date jaune rgb(228,210,26), titre blanc, image 13em cover, hover scale).
  • Choix du partial par theme via Html.ResolveTheme() (helper Extensions/ThemeExtensions.cs : Site:Theme + cookie debug-theme en Development, meme logique que _MasterLayout).
  • Infinity scroll : scripts/modules/blog.js (lazy-load) ; le bouton .js-blog-load-more fetch ?page=N+1 et append les .js-article-card. Les filtres tags et la pagination marchent sans JS (liens ?tags= / ?page=).
  • Pieges Razor rencontres : une variable nommee page casse la generation (@page = directive) -> nommer currentPage ; @x@(y) colle deux expressions et corrompt la classe -> pre-construire les URLs en C# ; Umbraco.GetDictionaryValue(key, "fallback") n'existe pas (2e arg = CultureInfo) -> utiliser GetDictionaryValueOrDefault(key, altText).

Icônes SVG — sprite + <use>

Convention : aucune icône SVG en double ni en inline répété. Chaque icône reutilisable est definie une seule fois comme <symbol id="icon-..."> dans Views/Partials/layout/_svgSprite.cshtml, rendu une fois en tete de <body> par _MasterLayout. On la reference ensuite par <svg class="..." viewBox="..." aria-hidden="true"><use href="#icon-..." /></svg>.

  • Symbols existants : icon-spektrum (logo, reference 3x : footers apol/pel + _footerBottom), icon-search (loupe navbar APOL), icon-sos (bouton urgences PEL).
  • Couleur : le CSS externe ne traverse pas le <use> (shadow tree). Le <symbol> ne porte donc pas de fill ; la couleur vient du <svg> referençant, par heritage : fill="currentColor" (search), attribut fill="#BBB" (logo APOL) ou une regle CSS sur le <svg> et non sur path (.svg-logo { fill: white } en PEL). L'exception icon-sos garde ses fill/stroke fixes dans le symbol.
  • Pas de font-icon pour une icone deja disponible en SVG : l'icone de recherche APOL etait doublee (SVG inline + glyphe themify ::after \e610) ; le ::after a ete supprime, seul le SVG (via sprite) reste, dimensionne par .nav-link-search__icon { width/height: 1.4em }.

JavaScript

  • scripts/index.js : expose Bootstrap sur window, puis sur DOMContentLoaded charge modules/header (sticky header, promptement) et modules/cookiesConsent via requestIdleCallback (fallback setTimeout) pour ne pas bloquer le LCP.
  • scripts/modules/ : un module par fonctionnalite, objet avec init() (ex. header.js = header sticky APOL, cookiesConsent.js, searchFilter.js = filtre par type de la page de recherche, no-op ailleurs).
  • Événements : scripts/modules/events.js (lazy) — agenda FullCalendar + carte Google Maps depuis le JSON inline #events-data ; bascule ?view=, filtres ?y=/?cat=/?commune= ; clé GoogleMaps:ApiKey (appsettings). Cartes layout/{theme}/_eventCard.cshtml (styles des cartes blog).
    • Plugins FullCalendar : daygrid (Mois), timegrid (Semaine/Jour) et multimonth (Année). Pas de plugin list : la vue Liste de la page est rendue côté serveur (timeline), un listMonth ferait doublon.
    • headerToolbar : l'espace sépare les éléments, la virgule les regroupe en button group. Écrire prev,title,next tente de plier le titre dans un groupe de boutons et casse la ligne — utiliser prev title next.
    • FullCalendar 6 injecte son CSS au runtime : ses classes n'apparaissent jamais dans le CSS compilé. Nos surcharges .fc-* vivent dans styles/base/blocks/_eventsTimeline.scss (safelist PurgeCSS /^fc-/).
  • Galerie (lightbox Splide) : scripts/modules/blocks/gallery.js (lazy). La grille reste rendue serveur (galleryBlock.cshtml, .block--gallery) ; quand l'editeur active la lightbox, la vue ajoute .js-gallery + data-lightbox sur chaque lien. Le module attache les clics ; Splide et son CSS ne sont importes qu'au premier clic (import("@splidejs/splide") + @splidejs/splide/css), et s'ouvrent en overlay plein ecran (.gallery-lightbox, fermeture croix / Escape / clic backdrop). Splide sort en chunks separes (js/chunks/splide.esm.js, css/splide/main.css) charges a la demande — pas dans les bundles de theme. Le CSS lib traverse PurgeCSS -> safelist /^splide/ (vite.config.helper.js). Styles overlay dans styles/base/blocks/_gallery.scss (markup Splide au runtime -> SCSS justifie).
  • Slider (carrousel Splide) : scripts/modules/blocks/slider.js (lazy). Le markup .splide.js-slider est rendu serveur (sliderBlock.cshtml) avec les options carrousel dans data-splide (JSON construit côté serveur : type loop/slide, perPage + breakpoints, autoplay, fixedHeight, gap). Le module importe Splide + son CSS (@splidejs/splide + @splidejs/splide/css) seulement s'il y a un .js-slider sur la page, puis new Splide(el, options).mount(). Réutilise les chunks Splide de la galerie ; safelist /^splide/. Styles styles/base/blocks/_slider.scss.
  • Animations block-grid : scripts/modules/scrollAnim.js (lazy) — révèle les items .scroll-anim via IntersectionObserver (toggle showed, rejoue si show-loop, fallback = tout afficher). Les classes viennent des réglages du bloc (ButtonPicker animation/delay = scroll-anim show-up/show-delay-*, isLoopAnimationshow-loop, isMobileFirstPositiond-primary) émis sur le wrapper blockgrid/items.cshtml. Styles : styles/base/blocks/_scrollAnim.scss. Classes issues du contenu → safelistées (scroll-anim, show-*, showed, d-primary). Voir docs/migration/blocks-apol-pel.md.
  • Annonces (pop-up / bande) : scripts/modules/announcement.js (lazy, sur .js-announcement, no-op sans annonce). Le markup est rendu serveur par thème (layout/{apol,pel}/_announcement.cshtml) ; le module décide de l'affichage selon les fermetures mémorisées, puis enregistre la fermeture. oncePerVisitorlocalStorage (masquée durablement sur l'appareil), oncePerVisitsessionStorage (réapparaît à la session suivante), clé commune announcement_closed_ids. Pop-up = modale Bootstrap (bootstrap.Modal) ; bande = retrait de d-none + classe has-announcement sur <body>, repli géré nativement par Bootstrap collapse. SCSS structurel partagé styles/base/blocks/_announcement.scss (couleur de bande = var(--bs-danger), donc rouge propre à chaque thème). La modale étant montée en JS, ses classes runtime (modal-backdrop, modal-open, show, fade) sont safelistées dans vite.config.helper.js.
  • Vacances (calendrier) : scripts/modules/blocks/vacations.js (lazy, sur .js-vacations-calendar, no-op sans calendrier). Réutilise FullCalendar (mêmes chunks que les événements) : chaque période = un événement journée entière ; clic → popup de détail construit en JS (vacations-popup, fermeture croix / Escape / clic extérieur), sans navigation. Données JSON inline (#vacations-data-<key>). La vue timeline du même bloc est rendue serveur (aucun JS). SCSS styles/base/blocks/_vacations.scss ; safelist PurgeCSS /^vacations-popup/.
  • scripts/helpers/utilityHelper.js : utilitaires partages.
  • Charge dans _MasterLayout via <script src="/js/index.js" asp-append-version="true" defer>.

ESLint

eslint.config.js (flat-config) cible scripts/**/*.js. Regles en error : prefer-const, no-var, eqeqeq, prefer-template, no-duplicate-imports, no-implicit-globals, default-case.

Important

  • Ne jamais editer src/Web/wwwroot/css|js (genere). Editer styles/ et scripts/.
  • /css et /js sont fingerprintes (asp-append-version) et caches en immutable (voir overview.md).

Contributors

No contributors

Changelog

No recent changes