avancées centre de ressources

This commit is contained in:
Valentin Le Moign 2025-03-28 15:10:03 +01:00
parent a1916e3219
commit d805ef35b1
14 changed files with 380 additions and 225 deletions

View File

@ -6,7 +6,7 @@ import REST from '../api/rest-axios';
import { useLayoutStore } from './layout';
import { findContentByPath } from '../utils/content/findContentByPath';
import { getCleanDate, fetchFromRelationships, getRelatedEtape } from '../utils/content/contentFetchUtils';
import { getCleanDate, fetchFromRelationships, getRelatedEtape, getRelatedRessources } from '../utils/content/contentFetchUtils';
import { getCarteSensible, getTitreTexte, getChiffresCles, getDiaporama, getEntretien, getVideos, getDocument, getGallerie } from '../utils/content/cleanParties';
import { getPartenaires, getGouvernance, getRessources } from '../utils/content/multiItemPages';
@ -52,13 +52,22 @@ export const useContentStore = defineStore('content', {
start: getCleanDate(rawContent.attributes.field_dates.value),
end: getCleanDate(rawContent.attributes.field_dates.end_value),
}
this.content.relatedRessources = await getRelatedRessources(rawContent.id);
}
if (this.contentType === 'ressourceItem') {
console.log(rawContent);
this.content.ressourceType = rawContent.attributes.field_type_de_ressource;
this.content.auteurice = rawContent.attributes.field_autheurice;
this.content.date = getCleanDate(rawContent.attributes.field_date_ressource);
this.content.introduction = rawContent.attributes.field_introduction?.processed;
this.content.introduction = rawContent.attributes.field_introduction?.processed;
if (rawContent.relationships.field_etape.data) {
const relatedEtapeFetch = fetchFromRelationships('field_etape', rawContent.relationships);
const relatedEtape = await Promise.resolve(relatedEtapeFetch);
const relatedEtapeUrl = relatedEtape.attributes.metatag.find(tag => tag.tag === "link")?.attributes.href;
this.content.relatedEtape = await getRelatedEtape('', relatedEtapeUrl);
}
useLayoutStore().hideEtapeList(true);
}
@ -169,7 +178,7 @@ export const useContentStore = defineStore('content', {
switch (this.contentType) {
case 'ressource':
multiItemPageArray = await getRessources(rawContent);
multiItemPageArray = await getRessources(rawContent);
this.content.ressourceTypes = new Set(multiItemPageArray.map(item => item.ressourceType));
useLayoutStore().hideEtapeList(true);
break;

View File

@ -47,7 +47,8 @@ export const useLayoutStore = defineStore('layout', {
this.toggleEtapeListScroll(isIntersecting, listeEtape, column, headerRect.height, animationToggleRect.top);
},
hideEtapeList(souldListHide) {
const etapeList = document.querySelector('#etapes-liste');
const etapeList = document.querySelector(window.innerWidth >= this.minDesktopWidth ? '#etapes-liste' : '.layout__region--third');
const animationButton = document.querySelector('#animation-toggle');
const listContainer = etapeList.parentNode;
if (souldListHide) {
listContainer.style.minWidth = '30vw';
@ -55,12 +56,18 @@ export const useLayoutStore = defineStore('layout', {
setTimeout(() => {
etapeList.style.display = 'none';
}, 300);
if (window.innerWidth >= this.minDesktopWidth) {
animationButton.style.display = 'none';
}
} else {
listContainer.style.minWidth = 'unset';
etapeList.style.display = 'block';
setTimeout(() => {
etapeList.style.opacity = '1';
}, 10);
if (window.innerWidth >= this.minDesktopWidth) {
animationButton.style.display = 'block';
}
}
},
toggleEtapeListScroll(isIntersecting, listeEtape, column, headerHeight, animationToggleTop) {

View File

@ -60,9 +60,85 @@ export async function getRelatedEtape(direction, path) {
if (etape.attributes.metatag.some(tag =>
tag.tag === "link" && tag.attributes.href === path
)) {
const relatedEtapeIndex = direction === 'next' ? index + 1 : index - 1;
const relatedEtapeIndex = direction ? (direction === 'next' ? index + 1 : index - 1) : index;
return getRelatedEtapeContent(allEtapesData.data.data[relatedEtapeIndex]);
}
}
});
}
export async function getRessourceItemCard(item) {
try {
const ressourceFetch = await REST.get(item.links.self.href);
const partiesFetch = await REST.get(item.relationships.field_parties_ressource.links.related.href);
const parties = partiesFetch.data.data;
const vignettePartie = parties.find(partie => partie.type !== "paragraph--titre_texte");
let vignette = null;
if (vignettePartie) {
let alt;
switch (vignettePartie.type) {
case 'paragraph--diaporama':
alt = vignettePartie.relationships.field_diaporama.data[0].meta.alt;
const diaporamaFetch = await REST.get(vignettePartie.relationships.field_diaporama.links.related.href);
vignette = {
url: diaporamaFetch.data.data[0].attributes.image_style_uri.content_small,
alt
};
break;
case 'paragraph--video':
const videoId = vignettePartie.attributes.field_videos[0].split('?v=')[1];
vignette = {
url: `https://img.youtube.com/vi/${videoId}/0.jpg`,
alt: item.attributes.title
};
break;
case 'paragraph--galleries':
const gallerieFetch = await REST.get(vignettePartie.relationships.field_gallerie.links.related.href);
const galleryAlt = gallerieFetch.data.data.relationships.field_images.data[0].meta.alt;
const gallerieImageFetch = await REST.get(gallerieFetch.data.data.relationships.field_images.links.related.href);
vignette = {
url: gallerieImageFetch.data.data[0].attributes.image_style_uri.content_small,
alt: galleryAlt
};
break;
case 'paragraph--document':
alt = vignettePartie.relationships.field_vignette.data.meta.alt;
const documentFetch = await REST.get(vignettePartie.relationships.field_vignette.links.related.href);
vignette = {
url: documentFetch.data.data.attributes.image_style_uri.content_small,
alt
};
break;
default:
vignette = null;
}
}
return {
ressourceType: item.attributes.field_type_de_ressource,
title: item.attributes.title,
auteurice: item.attributes.field_autheurice,
date: getCleanDate(item.attributes.field_date_ressource),
url: ressourceFetch.data.data.attributes.metatag.find(tag => tag.tag === "link")?.attributes.href,
vignette
};
} catch (error) {
console.error('Error fetching resource:', error);
return null;
}
}
export async function getRelatedRessources(etapeId) {
const ressources = await REST.get(`/jsonapi/node/ressource/`);
const ressourcesRelatedToEtape = ressources.data.data.filter(ressource => ressource.relationships.field_etape.data?.id === etapeId);
const ressourcesRelatedPromises = ressourcesRelatedToEtape.map(ressource => getRessourceItemCard(ressource));
return await Promise.all(ressourcesRelatedPromises);
}

View File

@ -1,5 +1,5 @@
import REST from '../../api/rest-axios';
import { getCleanDate } from './contentFetchUtils';
import { getCleanDate, getRessourceItemCard } from './contentFetchUtils';
export async function getPartenaires(rawContent) {
const logoPromises = rawContent.map(item =>
@ -55,76 +55,7 @@ export async function getGouvernance(rawContent) {
}
export async function getRessources(rawContent) {
const ressourcesPromises = rawContent.map(item =>
REST.get(item.links.self.href)
.then(async ressourceFetch => {
const partiesPromises = REST.get(item.relationships.field_parties_ressource.links.related.href)
.then(async partiesFetch => {
const parties = partiesFetch.data.data;
const vignettePartie = parties.find(parties => parties.type !== "paragraph--titre_texte");
if (vignettePartie) {
let vignettePromise;
let alt;
switch (vignettePartie.type) {
case 'paragraph--diaporama':
alt = vignettePartie.relationships.field_diaporama.data[0].meta.alt;
vignettePromise = REST.get(vignettePartie.relationships.field_diaporama.links.related.href)
.then(diaporamaFetch => {
return {
url: diaporamaFetch.data.data[0].attributes.image_style_uri.content_small,
alt
}
});
break;
case 'paragraph--video':
const videoId = vignettePartie.attributes.field_videos[0].split('?v=')[1];
vignettePromise = {
url: `https://img.youtube.com/vi/${videoId}/0.jpg`,
alt: item.attributes.title
}
break;
case 'paragraph--galleries':
vignettePromise = REST.get(vignettePartie.relationships.field_gallerie.links.related.href)
.then(gallerieFetch => {
alt = gallerieFetch.data.data.relationships.field_images.data[0].meta.alt;
const galleriePromise = REST.get(gallerieFetch.data.data.relationships.field_images.links.related.href)
.then(gallerieImageFetch => {
return {
url: gallerieImageFetch.data.data[0].attributes.image_style_uri.content_small,
alt
}
});
return galleriePromise;
});
break;
case 'paragraph--document':
alt = vignettePartie.relationships.field_vignette.data.meta.alt;
vignettePromise = REST.get(vignettePartie.relationships.field_vignette.links.related.href)
.then(documentFetch => {
return {
url: documentFetch.data.data.attributes.image_style_uri.content_small,
alt
}
});
break;
default:
vignettePromise = Promise.resolve(null);
}
return vignettePromise;
}
});
return partiesPromises.then(vignette => ({
ressourceType: item.attributes.field_type_de_ressource,
title: item.attributes.title,
auteurice: item.attributes.field_autheurice,
date: getCleanDate(item.attributes.field_date_ressource),
url: ressourceFetch.data.data.attributes.metatag.find(tag => tag.tag === "link")?.attributes.href,
vignette
}));
})
);
const ressourcesPromises = rawContent.map(item => getRessourceItemCard(item));
return await Promise.all(ressourcesPromises);
}

View File

@ -51,18 +51,23 @@
:couleur="content.couleur || brandColor" />
</div>
<EquipeContent
v-if="contentType === 'gouvernance'"
:content="content"
:couleur="brandColor" />
v-if="contentType === 'gouvernance'"
:content="content"
:couleur="brandColor" />
<PartenairesContent
v-if="contentType === 'partenaire'"
:content="content" />
v-if="contentType === 'partenaire'"
:content="content" />
<CentreDeRessource
v-if="contentType === 'ressource'"
:content="content"
:couleur="brandColor" />
v-if="contentType === 'ressource'"
:content="content"
:couleur="brandColor" />
<RelatedRessources
v-if="contentType === 'etape' && content.relatedRessources"
:relatedRessources="content.relatedRessources"
:couleur="content.couleur || brandColor" />
</main>
<PiecesJointes
v-if="content.pieces_jointes || content.liens"
:content="content"
:couleur="content.couleur || brandColor" />
<ModaleFooter
@ -88,6 +93,7 @@ import PartenairesContent from './components/PartenairesContent.vue';
import CentreDeRessource from './components/CentreDeRessource.vue';
import RessourceItemHeader from './components/RessourceItemHeader.vue';
import PiecesJointes from './components/PiecesJointes.vue';
import RelatedRessources from './components/RelatedRessources.vue';
import ModaleCarteSensible from './components/parties/ModaleCarteSensible.vue';
import ModaleTitreTexte from './components/parties/ModaleTitreTexte.vue';
@ -98,6 +104,7 @@ import ModaleExergue from './components/parties/ModaleExergue.vue';
import ModaleVideos from './components/parties/ModaleVideos.vue';
import ModaleGallerie from './components/parties/ModaleGallerie.vue';
import ModaleDocument from './components/parties/ModaleDocument.vue';
import { getRelatedRessources } from '../utils/content/contentFetchUtils';
const store = useContentStore();
const mapState = useMapStore();

View File

@ -1,8 +1,5 @@
<template>
<div id="centre-de-ressource">
<div v-if="content.intro" class="intro">
<!-- <p v-html="content.intro"></p> -->
</div>
<div id="centre-de-ressource" :style="{ '--couleur': couleur }">
<div class="filters">
<input type="text" v-model="searchQuery" placeholder="Rechercher..." class="search-bar">
<select v-model="selectedType">
@ -12,24 +9,17 @@
</option>
</select>
</div>
<template v-for="type in filteredTypes" :key="type">
<div v-if="content.intro" v-html="content.intro" class="intro"></div>
<template v-for="(type, typeIndex) in filteredTypes" :key="type">
<div class="type-section" v-if="ressourcesToDisplay[type] && ressourcesToDisplay[type].length > 0">
<h3>{{ type.replace(/_/g, ' ').replace(/^\w/, char => char.toUpperCase()) }}</h3>
<div class="ressource-list">
<TransitionGroup name="itemFade" tag="div" appear>
<div class="ressource-item"
:data-href="ressource.url"
<div class="ressource-item"
v-for="(ressource, ressourceIndex) in ressourcesToDisplay[type]"
:key="`${type}-${ressourceIndex}`"
:style="{ '--index': ressourceIndex - visibleItemsPerSection }">
<figure>
<img :src="ressource.vignette.url" :alt="ressource.vignette.alt" />
</figure>
<div>
<h4>{{ ressource.title }}</h4>
<p>Le {{ ressource.date.d }} {{ ressource.date.m }} {{ ressource.date.y }}</p>
<p>Par {{ ressource.auteurice }}</p>
</div>
<RessourceCard :ressource="ressource" :index="`${typeIndex}-${ressourceIndex}`" />
</div>
</TransitionGroup>
</div>
@ -52,17 +42,9 @@
</template>
<script setup>
import { ref, computed, watch, onMounted } from 'vue';
import router from '../../router/router';
import RessourceCard from './RessourceCard.vue';
import { useContentStore } from '../../stores/content';
import { useMapStore } from '../../stores/map';
import { handleClickableElements } from '../../utils/handle-navigation.js';
const store = useContentStore();
const mapStore = useMapStore();
const siteName = document.querySelector('#site_name').innerText;
import { ref, computed, watch, nextTick } from 'vue';
const props = defineProps({
content: Object,
@ -111,31 +93,28 @@ watch(searchQuery, () => {
});
});
let relatedItemCards, baseUrl;
watch(ressourcesToDisplay.value, () => {
setClickableElements();
watch(ressourcesToDisplay.value, async () => {
await nextTick();
document.querySelectorAll('.ressource-item > div').forEach(el => {
const card = el.__vueParentComponent.exposed;
if (card && card.setClickableElements) {
card.setClickableElements();
}
});
}, { deep: true });
watch(selectedType, () => {
setClickableElements();
watch(selectedType, async () => {
await nextTick();
document.querySelectorAll('.ressource-item > div').forEach(el => {
const card = el.__vueParentComponent.exposed;
if (card && card.setClickableElements) {
card.setClickableElements();
}
});
});
onMounted(() => {
baseUrl = window.location.protocol + "//" + window.location.host;
setClickableElements();
});
function setClickableElements() {
setTimeout(() => {
relatedItemCards = document.querySelectorAll('.ressource-item');
handleClickableElements(relatedItemCards, store, router, baseUrl, siteName, mapStore);
}, 50);
}
</script>
<style scoped>
<style lang="scss" scoped>
.itemFade-enter-active, .itemFade-leave-active {
transition: all 0.3s ease !important;
transition-delay: calc(0.1s * var(--index)) !important;
@ -150,4 +129,27 @@ function setClickableElements() {
opacity: 1;
transform: translateY(0px);
}
.filters {
margin: 3rem 0;
margin-top: 5rem;
> .search-bar{
margin-right: 2rem;
border: solid 1px var(--couleur);
padding: 0.5rem 1rem;
border-radius: 1rem;
font-family: 'Marianne', sans-serif;
}
> select {
appearance: none;
border: solid 1px var(--couleur);
padding: 0.5rem 1rem;
font-family: 'Marianne', sans-serif;
border-radius: 1rem;
background-color: white;
background: url("data:image/svg+xml,<svg height='10px' width='10px' viewBox='0 0 16 16' fill='rgba(128, 200, 191)' xmlns='http://www.w3.org/2000/svg'><path d='M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z'/></svg>") no-repeat;
background-position: calc(100% - 1rem) center !important;
padding-right: 2.5rem !important;
}
}
</style>

View File

@ -0,0 +1,39 @@
<template>
<div class="card" :class="direction ? (direction === 'previous' ? 'previous' : 'next') : 'solo'" :data-href="relatedContent.url">
<div class="icon" :style="{ backgroundColor: relatedContent.couleur }"></div>
<div class="card-content">
<div class="infos">
<div class="titre">{{ relatedContent.title }} <span>({{ relatedContent.postalCode.slice(0, 2) }})</span></div>
<div class="date">Du {{ relatedContent.dates.start.d }} {{ relatedContent.dates.start.m }}<br>au {{ relatedContent.dates.end.d }} {{ relatedContent.dates.end.m }} {{ relatedContent.dates.end.y }}</div>
</div>
<div class="vignette">
<img :src="relatedContent.vignette.url.small" :alt="relatedContent.vignette.alt">
</div>
</div>
</div>
</template>
<script setup>
const props = defineProps({
relatedContent: Object,
direction: String,
});
import { onMounted } from 'vue';
import router from '../../router/router';
import { useContentStore } from '../../stores/content';
import { useMapStore } from '../../stores/map';
import { handleClickableElements } from '../../utils/handle-navigation.js';
const store = useContentStore();
const mapStore = useMapStore();
const siteName = document.querySelector('#site_name').innerText;
onMounted(() => {
const relatedEtapesCards = document.querySelectorAll('.card');
const baseUrl = window.location.protocol + "//" + window.location.host;
handleClickableElements(relatedEtapesCards, store, router, baseUrl, siteName, mapStore);
});
</script>

View File

@ -16,6 +16,7 @@
:navigation="true"
:pagination="true"
:initialSlide="currentSlideIndex"
:keyboard="{ enabled: true }"
:injectStyles="[`
.swiper-button-next, .swiper-button-prev {
color: white;

View File

@ -5,49 +5,20 @@
</div>
<div v-if="contentType === 'etape' && (content.previous || content.next)" class="related-etape-links">
<div v-if="content.previous" class="card previous" :data-href="content.previous.url">
<div class="icon" :style="{ backgroundColor: content.previous.couleur }"></div>
<div class="card-content">
<div class="infos">
<div class="titre">{{ content.previous.title }} <span>({{ content.previous.postalCode.slice(0, 2) }})</span></div>
<div class="date">Du {{ content.previous.dates.start.d }} {{ content.previous.dates.start.m }}<br>au {{ content.previous.dates.end.d }} {{ content.previous.dates.end.m }} {{ content.previous.dates.end.y }}</div>
</div>
<div class="vignette">
<img :src="content.previous.vignette.url.small" :alt="content.previous.vignette.alt">
</div>
</div>
</div>
<div v-if="content.next" class="card next" :data-href="content.next.url">
<div class="icon" :style="{ backgroundColor: content.next.couleur }"></div>
<div class="card-content">
<div class="infos">
<div class="titre">{{ content.next.title }} <span>({{ content.next.postalCode.slice(0, 2) }})</span></div>
<div class="date">Du {{ content.next.dates.start.d }} {{ content.next.dates.start.m }}<br>au {{ content.next.dates.end.d }} {{ content.next.dates.end.m }} {{ content.next.dates.end.y }}</div>
</div>
<div class="vignette">
<img :src="content.next.vignette.url.small" :alt="content.next.vignette.alt">
</div>
</div>
<EtapeCard v-if="content.previous" :relatedContent="content.previous" :direction="'previous'"/>
<EtapeCard v-if="content.next" :relatedContent="content.next" :direction="'next'"/>
</div>
<div v-if="contentType === 'ressourceItem' && content.relatedEtape" >
<div class="related-etape-label" :style="{ backgroundColor: couleur }">Étape de la ressource</div>
<div class="related-etape-links">
<EtapeCard :relatedContent="content.relatedEtape" :direction="''" />
</div>
</div>
</footer>
</template>
<script setup>
import { onMounted } from 'vue';
import router from '../../router/router';
import { useContentStore } from '../../stores/content';
import { useMapStore } from '../../stores/map';
import { handleClickableElements } from '../../utils/handle-navigation.js';
const brandColor = "#80c8bf";
const store = useContentStore();
const mapStore = useMapStore();
const siteName = document.querySelector('#site_name').innerText;
import EtapeCard from './EtapeCard.vue';
const props = defineProps({
contentType: String,
@ -55,10 +26,4 @@ const props = defineProps({
couleur: String,
map: Object,
});
onMounted(() => {
const relatedEtapesCards = document.querySelectorAll('.card');
const baseUrl = window.location.protocol + "//" + window.location.host;
handleClickableElements(relatedEtapesCards, store, router, baseUrl, siteName, mapStore);
});
</script>

View File

@ -0,0 +1,23 @@
<template>
<div class="partie related-ressources">
<h3>
<p :style="{ background: `linear-gradient(transparent 70%, ${couleur} 70%)` }">
Ressources liées
</p>
</h3>
<div class="ressource-list sm-ressource-list">
<div class="ressource-item" v-for="(relatedRessource, index) in relatedRessources">
<RessourceCard :ressource="relatedRessource" :index="index" />
</div>
</div>
</div>
</template>
<script setup>
import RessourceCard from './RessourceCard.vue';
const props = defineProps({
relatedRessources: Object,
couleur: String,
});
</script>

View File

@ -0,0 +1,49 @@
<template>
<div
:data-href="ressource.url"
:id="`ressource-${index}`">
<figure>
<img :src="ressource.vignette.url" :alt="ressource.vignette.alt" />
</figure>
<div>
<h4>{{ ressource.title }}</h4>
<p>Le {{ ressource.date.d }} {{ ressource.date.m }} {{ ressource.date.y }}</p>
<p>Par {{ ressource.auteurice }}</p>
</div>
</div>
</template>
<script setup>
import { onMounted, defineExpose } from 'vue';
import router from '../../router/router';
import { handleClickableElements } from '../../utils/handle-navigation.js';
import { useContentStore } from '../../stores/content';
import { useMapStore } from '../../stores/map';
const store = useContentStore();
const mapStore = useMapStore();
const siteName = document.querySelector('#site_name').innerText;
let relatedItemCards, baseUrl;
onMounted(() => {
baseUrl = window.location.protocol + "//" + window.location.host;
setClickableElements();
});
const setClickableElements = () => {
relatedItemCards = document.querySelector(`#ressource-${props.index}`);
console.log(relatedItemCards);
handleClickableElements([relatedItemCards], store, router, baseUrl, siteName, mapStore);
}
defineExpose({
setClickableElements,
});
const props = defineProps({
ressource: Object,
index: String,
});
</script>

View File

@ -47,6 +47,9 @@ function setDisplayedType() {
case 'videos':
props.content.displayedType = 'Vidéo';
break;
case 'reportages':
props.content.displayedType = 'Reportage';
break;
}
}

View File

@ -21,6 +21,10 @@
import { onMounted } from 'vue';
import { useImageModal } from '../../composables/useImageModale';
import ImageModale from '../ImageModale.vue';
// WebComponent
// https://swiperjs.com/element
import { register } from 'swiper/element/bundle';
register();
const props = defineProps({
partie: Object,
@ -36,10 +40,10 @@ const {
} = useImageModal();
const handleImageClick = (event) => {
const img = event.target;
if (img.tagName === 'IMG') {
const clickedImg = event.target;
if (clickedImg.tagName === 'IMG') {
let swiperMedia = [];
const imgGrid = img.closest('.images-grid');
const imgGrid = clickedImg.closest('.images-grid');
imgGrid.querySelectorAll('figure').forEach((figure) => {
const img = figure.querySelector('img');
@ -50,15 +54,13 @@ const handleImageClick = (event) => {
}
});
});
console.log(swiperMedia);
openImageModale(img.src, img.alt, swiperMedia);
openImageModale(clickedImg.src, clickedImg.alt, swiperMedia);
}
}
onMounted(() => {
setVerticalImages();
document.documentElement.style.setProperty('--etape-couleur', props.couleur);
});
function setVerticalImages() {
@ -70,4 +72,13 @@ function setVerticalImages() {
}
});
}
</script>
</script>
<style lang="scss" scoped>
:root {
--swiper-navigation-color: #1a1918; /* cf main.scss */
--swiper-pagination-color: var(--etape-couleur);
--swiper-navigation-top-offset: calc(100% - 1.5rem);
--swiper-navigation-sides-offset: 5vw; /* cf main.scss */
}
</style>

View File

@ -959,7 +959,8 @@ body{
.partie-title,
> .chiffres-cles,
> .entretien,
> .gallerie {
> .gallerie,
&.related-ressources {
> h3 {
position: relative;
display: inline-block;
@ -1228,7 +1229,12 @@ body{
}
#centre-de-ressource {
> .intro {
font-size: $sm-font-size-mobile;
width: 66%;
margin-bottom: 4rem;
@media screen and (min-width: $desktop-min-width) {
font-size: $sm-font-size-desktop;
}
}
> .type-section {
> h3 {
@ -1245,46 +1251,6 @@ body{
font-size: $l-font-size-desktop;
}
}
> .ressource-list > div {
display: grid;
grid-template-columns: repeat(4, 1fr);
align-items: start;
gap: 2rem;
margin-bottom: 2.5rem;
> .ressource-item {
display: flex;
gap: 1.5rem;
cursor: pointer;
transform: scale(1);
transition: transform 0.2s ease-in-out;
&:hover {
transform: scale(1.05);
}
> figure {
width: 40%;
margin: 0;
}
> div {
width: 50%;
> h4 {
font-size: $m-font-size-mobile;
font-family: 'Joost', sans-serif;
margin: 0;
margin-bottom: 0.5rem;
@media screen and (min-width: $desktop-min-width) {
font-size: $m-font-size-desktop;
}
}
> p {
margin: 0;
font-size: $sm-font-size-mobile;
@media screen and (min-width: $desktop-min-width) {
font-size: $sm-font-size-desktop;
}
}
}
}
}
> .button-container {
display: flex;
justify-content: center;
@ -1410,15 +1376,27 @@ body{
background-size: 300px;
background-size: repeat;
}
.related-etape-label {
display: inline-block;
padding: 0.5rem 1rem;
padding-left: calc($modale-x-padding / 2);
font-size: $sm-font-size-mobile;
@media screen and (min-width: $desktop-min-width) {
font-size: $sm-font-size-desktop;
}
}
.related-etape-links {
position: absolute;
//bottom: calc(($modale-bottom-padding / 2) * -1);
width: 100%;
box-sizing: border-box;
padding: 0 calc($modale-x-padding / 2);
display: grid;
grid-template-rows: 1fr 1fr;
grid-template-columns: 1fr;
width: 75%;
&:not(:has(.solo)) {
width: 100%;
position: absolute;
grid-template-rows: 1fr 1fr;
}
@media screen and (min-width: $desktop-min-width) {
grid-template-columns: 1fr 1fr;
margin-top: 2.5rem;
@ -1508,6 +1486,60 @@ body{
}
}
}
.ressource-list > div:not(.ressource-item),
.ressource-list.sm-ressource-list {
display: grid;
grid-template-columns: repeat(4, 1fr);
align-items: start;
gap: 2rem;
margin-bottom: 2.5rem;
&.sm-ressource-list {
margin-top: 2rem;
grid-template-columns: repeat(2, 1fr);
}
> .ressource-item > div {
display: flex;
gap: 1.5rem;
cursor: pointer;
transform: scale(1);
transition: transform 0.2s ease-in-out;
&:hover {
transform: scale(1.05);
}
> figure {
width: 40%;
margin: 0;
}
> div {
width: 50%;
> h4 {
font-size: $m-font-size-mobile;
font-family: 'Joost', sans-serif;
margin: 0;
margin-bottom: 0.5rem;
@media screen and (min-width: $desktop-min-width) {
font-size: $m-font-size-desktop;
}
}
> p {
margin: 0;
font-size: $sm-font-size-mobile;
@media screen and (min-width: $desktop-min-width) {
font-size: $sm-font-size-desktop;
}
}
}
}
}
}
> #animation-toggle {
transition: opacity 0.3s ease-out;