diff --git a/parcoursup/api.js b/parcoursup/api.js index 789a836..72f1e5c 100644 --- a/parcoursup/api.js +++ b/parcoursup/api.js @@ -1,21 +1,131 @@ -export function buildURL(query) { - let url = - "https://data.enseignementsup-recherche.gouv.fr/api/explore/v2.1/catalog/datasets/fr-esr-parcoursup/records?limit=10" - - if (query && query.trim() !== "") { - url += "&where=search(lib_for_voe_ins, '" + query + "')" - } - - return url +// Échapper les apostrophes dans les valeurs injectées dans la clause where +function echapperValeur(valeur) { + return String(valeur).replace(/'/g, "\\'"); } -export async function fetchFormations(query) { - const url = buildURL(query) - const response = await fetch(url) +// Construire l'URL de requête vers l'API Parcoursup +export function construireURL(requete, limite = 20, decalage = 0, filtres = {}) { - if (!response.ok) { - throw new Error("Erreur HTTP") + var url = "https://data.enseignementsup-recherche.gouv.fr/api/explore/v2.1/catalog/datasets/fr-esr-parcoursup/records?"; + + url += "limit=" + limite; + url += "&offset=" + decalage; + + var conditions = []; + + if (requete && requete.trim() !== "") { + conditions.push("search(lib_for_voe_ins, '" + echapperValeur(requete.trim()) + "')"); } - return await response.json() + if (filtres.filiere && filtres.filiere !== "") { + conditions.push("fili='" + echapperValeur(filtres.filiere) + "'"); + } + + if (filtres.selectivite && filtres.selectivite !== "") { + conditions.push("select_form='" + echapperValeur(filtres.selectivite) + "'"); + } + + if (filtres.region && filtres.region !== "") { + conditions.push("region_etab_aff='" + echapperValeur(filtres.region) + "'"); + } + + if (filtres.tauxMin && filtres.tauxMin > 0) { + conditions.push("taux_acces_ens>=" + filtres.tauxMin); + } + + if (filtres.tauxMax && filtres.tauxMax < 100) { + conditions.push("taux_acces_ens<=" + filtres.tauxMax); + } + + if (conditions.length > 0) { + url += "&where=" + encodeURIComponent(conditions.join(" AND ")); + } + + return url; +} + +// Charger les formations depuis l'API Parcoursup +export async function chargerFormations(requete, limite = 20, decalage = 0, filtres = {}) { + + var url = construireURL(requete, limite, decalage, filtres); + var reponse = await fetch(url); + + if (!reponse.ok) { + throw new Error("Erreur HTTP " + reponse.status); + } + + return await reponse.json(); +} + +// Charger l'historique d'une formation sur plusieurs années +export async function chargerHistoriqueFormation(codUai, nomFormation) { + + var jeuDeDonnees = { + 2020: "fr-esr-parcoursup_2020", + 2021: "fr-esr-parcoursup_2021", + 2022: "fr-esr-parcoursup_2022", + 2023: "fr-esr-parcoursup_2023", + 2024: "fr-esr-parcoursup_2024", + 2025: "fr-esr-parcoursup" + }; + + var historique = []; + var nomCourt = echapperValeur((nomFormation || "").substring(0, 40)); + var codeUai = echapperValeur(codUai); + var annees = [2020, 2021, 2022, 2023, 2024, 2025]; + + for (var i = 0; i < annees.length; i++) { + + var annee = annees[i]; + var dataset = jeuDeDonnees[annee]; + + try { + + var where = + "cod_uai='" + codeUai + "' AND search(lib_for_voe_ins, '" + nomCourt + "')"; + + var url = "https://data.enseignementsup-recherche.gouv.fr/api/explore/v2.1/catalog/datasets/" + + dataset + "/records?" + + "limit=5" + + "&where=" + encodeURIComponent(where) + + "&select=" + encodeURIComponent("cod_uai,lib_for_voe_ins,voe_tot,acc_tot,pct_sansmention,pct_ab,pct_b,pct_tb,pct_tbf,pct_bg,pct_bt,pct_bp"); + + var reponse = await fetch(url); + + if (reponse.ok) { + + var donnees = await reponse.json(); + + if (donnees.results && donnees.results.length > 0) { + + var ligne = donnees.results[0]; + var taux = 0; + + if (ligne.voe_tot && ligne.voe_tot > 0) { + taux = Math.round((ligne.acc_tot / ligne.voe_tot) * 100); + } + + historique.push({ + annee: annee, + tauxAcces: taux, + candidats: ligne.voe_tot || 0, + admis: ligne.acc_tot || 0, + pctSansMention: ligne.pct_sansmention || 0, + pctAB: ligne.pct_ab || 0, + pctB: ligne.pct_b || 0, + pctTB: ligne.pct_tb || 0, + pctTBF: ligne.pct_tbf || 0, + pctGeneral: ligne.pct_bg || 0, + pctTechno: ligne.pct_bt || 0, + pctPro: ligne.pct_bp || 0 + }); + } + } + + } catch (e) { + console.warn("Erreur pour l'année " + annee + " :", e); + } + } + + return historique; } \ No newline at end of file diff --git a/parcoursup/app.riot b/parcoursup/app.riot index 26c4cbb..28382a3 100644 --- a/parcoursup/app.riot +++ b/parcoursup/app.riot @@ -1,83 +1,574 @@ -
-

Mini projet Parcoursup

-

Recherche de formations Parcoursup avec Riot

+ -
- +
+ + +
+ + +

+ { state.query } — { state.total } résultat(s) +

+ +
- - +
+ + - + +
-
+ +
+ onback={ retourRecherche }>
+ +
+ Chargement de la formation... +
+ + +
+ + + +
0 }> +

Comparateur de formations

+ +

Choisis ton profil pour estimer tes chances d'intégration.

+ +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+ + + +
+ +
+

{ f.nom }

+ +

Établissement : { f.etablissement }

+

Ville : { f.ville }

+

Filière : { f.filiere }

+

Capacité : { f.capacite }

+

Taux d'accès : { f.tauxAcces }%

+ +

+ Intégrés : + Général { f.pctGeneral }% / + Techno { f.pctTechno }% / + Pro { f.pctPro }% +

+ +

+ + { estimerFormation(f) } + + { detailEstimation(f) } +

+ + +
+
+ +
+

Aucune formation sélectionnée

+

Retourne à la recherche et clique sur "Ajouter à la sélection" pour comparer des formations.

+
+
+
- \ No newline at end of file + diff --git a/parcoursup/components/detail-view.riot b/parcoursup/components/detail-view.riot index 47ae643..fa62d88 100644 --- a/parcoursup/components/detail-view.riot +++ b/parcoursup/components/detail-view.riot @@ -1,15 +1,428 @@ -
-

{ props.formation.nom }

-

Établissement : { props.formation.etablissement }

-

Ville : { props.formation.ville }

-

Département : { props.formation.departement }

-

Filière : { props.formation.filiere }

-

Sélectivité : { props.formation.selectivite }

-

Capacité : { props.formation.capacite }

-

Candidats : { props.formation.candidats }

-

Admis : { props.formation.admis }

-

Taux d'accès : { props.formation.tauxAcces }%

- +
+

Formation

+

{ props.formation.etablissement } - { props.formation.nom }

+
+

Ville : { props.formation.ville }

+

Département : { props.formation.departement } { props.formation.departementLib }

+

Académie : { props.formation.academie }

+

{ props.formation.contrat }

+

Capacité : { props.formation.capacite }

+
+ +
+
+

Phase principale d'admission

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BacVoeuxClassésPropositionsAcceptés
Gén{ props.formation.voePPGeneral }{ props.formation.classesPPGeneral }{ props.formation.propositionsPPGeneral }{ props.formation.acceptesPPGeneral }
Techno{ props.formation.voePPTechno }{ props.formation.classesPPTechno }{ props.formation.propositionsPPTechno }{ props.formation.acceptesPPTechno }
Pro{ props.formation.voePPPro }{ props.formation.classesPPPro }{ props.formation.propositionsPPPro }{ props.formation.acceptesPPPro }
Autres{ props.formation.voePPAutres }{ props.formation.classesPPAutres }{ props.formation.propositionsPPAutres }{ props.formation.acceptesPPAutres }
Total{ props.formation.voePPTotal }{ props.formation.classesPPTotal }{ props.formation.propositionsPPTotal }{ props.formation.acceptesPPTotal }
+
+ +
+

Vitesse de remplissage

+
+
+
+
+ Ouverture 30 mai
+ { props.formation.pctDebutPhase }% +
+
+
+
+
+ 16 juin
+ { props.formation.pctDateBac }% +
+
+
+
+
+ 11 juillet
+ { props.formation.pctFinPhase }% +
+
+
+
+
+ +

Phase complémentaire d'admission

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BacVoeuxClassésPropositionsAcceptés
Gén{ props.formation.voePCGeneral }
Techno{ props.formation.voePCTechno }
Pro{ props.formation.voePCPro }
Autres{ props.formation.voePCAutres }
Total{ props.formation.voePCTotal }{ props.formation.classesPCTotal }{ props.formation.acceptesPCTotal }{ props.formation.acceptesPCTotal }
+ + + +

Profil des admis

+ +
+
+

Répartition par type de bac

+
+
+ +
+

Mentions au bac des admis

+
+
+ +
+

Profil sociologique

+
+
+
+ + + +

Évolution depuis 2020

+ +
+ Chargement de l'historique... +
+ +
+ Aucune donnée historique disponible pour cette formation. +
+ +
0 }> +
+

Taux d'accès par année

+
+
+ +
+

Nombre de candidats et admis

+
+
+ +
+

Évolution des mentions au bac

+
+
+
+ +
- \ No newline at end of file + + + + diff --git a/parcoursup/components/map-view.riot b/parcoursup/components/map-view.riot index 3bfd26d..d719bba 100644 --- a/parcoursup/components/map-view.riot +++ b/parcoursup/components/map-view.riot @@ -1,53 +1,120 @@

Carte des formations

-
+
-
\ No newline at end of file + + diff --git a/parcoursup/components/result-list.riot b/parcoursup/components/result-list.riot index 9d12a38..28d25cb 100644 --- a/parcoursup/components/result-list.riot +++ b/parcoursup/components/result-list.riot @@ -1,5 +1,6 @@
+
Aucun résultat trouvé
@@ -8,13 +9,41 @@ Chargement...
-
-

{ f.nom }

-

Établissement : { f.etablissement }

-

Ville : { f.ville } ({ f.departement })

-

Filière : { f.filiere }

-

Taux d'accès : { f.tauxAcces }%

- +
+

{ formation.nom }

+

Établissement : { formation.etablissement }

+

Ville : { formation.ville } ({ formation.departement })

+

Filière : { formation.filiere }

+

Taux d'accès : { formation.tauxAcces }%

+ + + +
+
-
\ No newline at end of file + + + + diff --git a/parcoursup/components/search-bar.riot b/parcoursup/components/search-bar.riot index 01861a4..ebc5337 100644 --- a/parcoursup/components/search-bar.riot +++ b/parcoursup/components/search-bar.riot @@ -2,26 +2,146 @@ +
+ +
+ +
+
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+
+ + +
+ +
+ + +
+
+
+ - \ No newline at end of file + diff --git a/parcoursup/formation.js b/parcoursup/formation.js index b03f51f..fbe770e 100644 --- a/parcoursup/formation.js +++ b/parcoursup/formation.js @@ -1,23 +1,97 @@ -export function createFormation(raw) { - let taux = 0 +// Créer un objet formation à partir des données brutes de l'API +export function creerFormation(brut) { - if (raw.voe_tot && raw.voe_tot > 0) { - taux = Math.round((raw.acc_tot / raw.voe_tot) * 100) + var taux = 0; + var latitude = null; + var longitude = null; + + if (brut.voe_tot && brut.voe_tot > 0) { + taux = Math.round((brut.acc_tot / brut.voe_tot) * 100); + } + + if (brut.g_olocalisation_des_formations) { + latitude = brut.g_olocalisation_des_formations.lat; + longitude = brut.g_olocalisation_des_formations.lon; } return { - id: raw.cod_uai + "-" + raw.lib_for_voe_ins, - nom: raw.lib_for_voe_ins, - etablissement: raw.g_ea_lib_vx, - ville: raw.ville_etab, - departement: raw.dep, - filiere: raw.fili, - selectivite: raw.select_form, - capacite: raw.capa_fin, - candidats: raw.voe_tot, - admis: raw.acc_tot, - tauxAcces: taux, - latitude: raw.g_olocalisation_des_formations ? raw.g_olocalisation_des_formations.lat : null, - longitude: raw.g_olocalisation_des_formations ? raw.g_olocalisation_des_formations.lon : null - } + id: brut.cod_uai + "-" + brut.lib_for_voe_ins, + + nom: brut.lib_for_voe_ins, + etablissement: brut.g_ea_lib_vx, + ville: brut.ville_etab, + departement: brut.dep, + departementLib: brut.dep_lib, + region: brut.region_etab_aff, + academie: brut.acad_mies, + contrat: brut.contrat_etab, + + filiere: brut.fili, + selectivite: brut.select_form, + + capacite: brut.capa_fin, + candidats: brut.voe_tot, + admis: brut.acc_tot, + tauxAcces: taux, + + latitude: latitude, + longitude: longitude, + + pctFemmes: brut.pct_f, + pctBoursiers: brut.pct_bours, + pctNeoBac: brut.pct_neobac, + + pctGeneral: brut.pct_bg, + pctTechno: brut.pct_bt, + pctPro: brut.pct_bp, + + pctSansMention: brut.pct_sansmention, + pctAB: brut.pct_ab, + pctB: brut.pct_b, + pctTB: brut.pct_tb, + pctTBF: brut.pct_tbf, + + pctDebutPhase: brut.pct_acc_debutpp, + pctDateBac: brut.pct_acc_datebac, + pctFinPhase: brut.pct_acc_finpp, + + admisDebutPhase: brut.acc_debutpp, + admisDateBac: brut.acc_datebac, + admisFinPhase: brut.acc_finpp, + + // Phase principale + voePPGeneral: brut.nb_voe_pp_bg, + voePPTechno: brut.nb_voe_pp_bt, + voePPPro: brut.nb_voe_pp_bp, + voePPAutres: brut.nb_voe_pp_at, + voePPTotal: brut.nb_voe_pp, + + classesPPGeneral: brut.nb_cla_pp_bg, + classesPPTechno: brut.nb_cla_pp_bt, + classesPPPro: brut.nb_cla_pp_bp, + classesPPAutres: brut.nb_cla_pp_at, + classesPPTotal: brut.nb_cla_pp, + + propositionsPPGeneral: brut.prop_tot_bg, + propositionsPPTechno: brut.prop_tot_bt, + propositionsPPPro: brut.prop_tot_bp, + propositionsPPAutres: brut.prop_tot_at, + propositionsPPTotal: brut.prop_tot, + + acceptesPPGeneral: brut.acc_bg, + acceptesPPTechno: brut.acc_bt, + acceptesPPPro: brut.acc_bp, + acceptesPPAutres: brut.acc_at, + acceptesPPTotal: brut.acc_pp, + + // Phase complémentaire + voePCGeneral: brut.nb_voe_pc_bg, + voePCTechno: brut.nb_voe_pc_bt, + voePCPro: brut.nb_voe_pc_bp, + voePCAutres: brut.nb_voe_pc_at, + voePCTotal: brut.nb_voe_pc, + + classesPCTotal: brut.nb_cla_pc, + acceptesPCTotal: brut.acc_pc + }; } \ No newline at end of file diff --git a/parcoursup/index.html b/parcoursup/index.html index 349c182..da008b8 100644 --- a/parcoursup/index.html +++ b/parcoursup/index.html @@ -3,38 +3,56 @@ - Parcoursup Riot - + Parcoursup Explorer + - - + + + - - + + - - - - - + + + + + + - + window.chargerFormations = chargerFormations + window.creerFormation = creerFormation + window.chargerHistoriqueFormation = chargerHistoriqueFormation - - + + \ No newline at end of file diff --git a/parcoursup/style.css b/parcoursup/style.css index da935e5..9f4e3b6 100644 --- a/parcoursup/style.css +++ b/parcoursup/style.css @@ -1,97 +1,901 @@ -body { - font-family: Arial, sans-serif; - margin: 0; - background: #f5f5f5; - color: #222; - } - - .page { - max-width: 1100px; - margin: 0 auto; - padding: 20px; - } - - h1 { - margin-bottom: 8px; - } - - .subtitle { - margin-top: 0; - color: #555; - } - - .search-bar { - display: flex; - gap: 10px; - margin: 20px 0; - } - - .search-bar input { - flex: 1; - padding: 10px; - font-size: 16px; - } - - .search-bar button, - .card button, - .detail-card button { - padding: 10px 14px; - font-size: 14px; - cursor: pointer; - } - - .layout { - display: grid; - grid-template-columns: 1fr; - gap: 20px; - } - - .results { - display: grid; - gap: 16px; - } - - .card { - border: 1px solid #d8d8d8; - background: white; - padding: 16px; - border-radius: 8px; - } - - .card h3 { - margin-top: 0; - } - - .detail-card { - border: 1px solid #d8d8d8; - background: white; - padding: 20px; - border-radius: 8px; - } - - .map-box { - border: 1px solid #d8d8d8; - background: white; - border-radius: 8px; - padding: 12px; - } - - #map { - height: 420px; - width: 100%; - border-radius: 6px; - } - - .message { - padding: 12px; - background: white; - border: 1px solid #d8d8d8; - border-radius: 8px; - } - - @media (min-width: 900px) { - .layout { - grid-template-columns: 1.2fr 1fr; - align-items: start; - } - } \ No newline at end of file +/* ============================================================ + PARCOURSUP EXPLORER — Charte Parcoursup officielle + ============================================================ */ + + :root { + --vert: #1a936f; + --vert-fonce: #147a5c; + --vert-clair: #e8f5ef; + --bleu: #2a5298; + --bleu-clair: #eaf0fa; + --orange: #e67e22; + --rouge: #c0392b; + --gris-50: #f9fafb; + --gris-100: #f3f4f6; + --gris-200: #e5e7eb; + --gris-300: #d1d5db; + --gris-500: #6b7280; + --gris-700: #374151; + --gris-900: #111827; + --rayon: 6px; + } + + *, *::before, *::after { + box-sizing: border-box; + } + + body { + font-family: 'Marianne', 'Segoe UI', system-ui, -apple-system, sans-serif; + margin: 0; + background: var(--gris-50); + color: var(--gris-900); + line-height: 1.6; + font-size: 15px; + } + + /* =========================================================== + HEADER + =========================================================== */ + + .site-header { + background: white; + border-bottom: 3px solid var(--vert); + padding: 0 24px; + height: 56px; + display: flex; + align-items: center; + position: sticky; + top: 0; + z-index: 1000; + box-shadow: 0 1px 4px rgba(0,0,0,0.06); + } + + .header-inner { + max-width: 1100px; + width: 100%; + margin: 0 auto; + display: flex; + align-items: center; + justify-content: space-between; + } + + .logo { + display: flex; + align-items: center; + gap: 10px; + text-decoration: none; + color: inherit; + } + + .logo-icon { font-size: 22px; } + + .logo-text { + font-size: 18px; + font-weight: 700; + color: var(--vert); + letter-spacing: -0.3px; + } + + .logo-light { + font-weight: 400; + color: var(--gris-700); + } + + .header-badge { + background: var(--vert-clair); + color: var(--vert-fonce); + padding: 4px 14px; + border-radius: 20px; + font-size: 13px; + font-weight: 600; + border: 1px solid #b8e0cd; + text-decoration: none; + } + + /* =========================================================== + PAGE + =========================================================== */ + + .page { + max-width: 1100px; + margin: 0 auto; + padding: 24px 20px 40px; + } + + /* =========================================================== + BOUTONS + =========================================================== */ + + .btn { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 8px 16px; + font-size: 14px; + font-weight: 600; + border: none; + border-radius: var(--rayon); + cursor: pointer; + transition: all 0.15s; + } + + .btn:disabled { opacity: 0.4; cursor: not-allowed; } + + .btn-primary { background: var(--vert); color: white; } + .btn-primary:hover:not(:disabled) { background: var(--vert-fonce); } + + .btn-outline { background: white; color: var(--vert); border: 1.5px solid var(--vert); } + .btn-outline:hover:not(:disabled) { background: var(--vert-clair); } + + .btn-danger { background: #fef2f2; color: var(--rouge); border: 1px solid #f5c6cb; } + .btn-danger:hover { background: #fde8e8; } + + .btn-small { padding: 5px 12px; font-size: 13px; } + + /* =========================================================== + SEARCH BAR + =========================================================== */ + + .search-bar { + display: flex; + gap: 10px; + margin: 0 0 20px; + } + + .search-bar input { + flex: 1; + padding: 10px 14px; + font-size: 15px; + border: 1.5px solid var(--gris-300); + border-radius: var(--rayon); + background: white; + outline: none; + transition: border-color 0.2s; + } + + .search-bar input:focus { + border-color: var(--vert); + box-shadow: 0 0 0 2px rgba(26, 147, 111, 0.12); + } + + .search-bar button { + padding: 10px 22px; + font-size: 15px; + font-weight: 600; + cursor: pointer; + background: var(--vert); + color: white; + border: none; + border-radius: var(--rayon); + transition: background 0.15s; + } + + .search-bar button:hover { background: var(--vert-fonce); } + + .result-count { + font-size: 14px; + color: var(--gris-500); + margin: 0 0 14px; + } + + /* =========================================================== + CARDS + =========================================================== */ + + .card { + background: white; + padding: 18px 20px; + border-radius: var(--rayon); + border: 1px solid var(--gris-200); + transition: border-color 0.15s; + } + + .card:hover { border-color: var(--vert); } + + .card h3, .card h4 { + margin-top: 0; + margin-bottom: 6px; + color: var(--bleu); + font-size: 1rem; + } + + .card p { + margin: 4px 0; + font-size: 14px; + color: var(--gris-700); + } + + .card button { + margin-top: 10px; + padding: 6px 14px; + font-size: 13px; + font-weight: 600; + cursor: pointer; + border-radius: var(--rayon); + border: 1.5px solid var(--vert); + background: white; + color: var(--vert); + transition: all 0.15s; + } + + .card button:hover { background: var(--vert-clair); } + .card button + button { margin-left: 8px; } + + /* --- Cards colorées du comparateur --- */ + .card-tres-favorable { + background: #e8f5ef; + border-color: #6ec89b; + border-left: 4px solid #147a5c; + } + + .card-favorable { + background: #eaf7ed; + border-color: #a3d9b8; + border-left: 4px solid #1a936f; + } + + .card-possible { + background: #fef9e7; + border-color: #f6e05e; + border-left: 4px solid #d69e2e; + } + + .card-difficile { + background: #fef2f2; + border-color: #f5c6cb; + border-left: 4px solid #e67e22; + } + + .card-tres-difficile { + background: #fde8e8; + border-color: #f5a3a3; + border-left: 4px solid #c0392b; + } + + /* =========================================================== + BADGES ESTIMATION — 5 niveaux + =========================================================== */ + + .estimate { + display: inline-block; + padding: 4px 14px; + border-radius: 4px; + font-size: 13px; + font-weight: 700; + } + + .tres-favorable { + background: #147a5c; + color: white; + } + + .favorable { + background: var(--vert-clair); + color: var(--vert-fonce); + border: 1px solid #a3d9c0; + } + + .possible { + background: #fef9e7; + color: #b7791f; + border: 1px solid #f6e05e; + } + + .difficile { + background: #fef2f2; + color: #c05621; + border: 1px solid #f5c6cb; + } + + .tres-difficile { + background: var(--rouge); + color: white; + } + + .estimation-result { + margin-top: 10px; + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; + } + + .estimation-detail { + font-size: 12px; + color: var(--gris-500); + } + + /* =========================================================== + DETAIL CARD — Comparateur + =========================================================== */ + + .detail-card { + background: white; + padding: 22px 24px; + border-radius: var(--rayon); + border: 1px solid var(--gris-200); + margin-bottom: 20px; + } + + .detail-card hr { + margin: 18px 0; + border: none; + border-top: 1px solid var(--gris-200); + } + + .detail-card h2, .detail-card h3 { + margin-top: 0; + color: var(--gris-900); + } + + .detail-card p { + margin: 6px 0; + color: var(--gris-700); + } + + .comparateur-card { + border-left: 4px solid var(--vert); + } + + /* =========================================================== + COMPARE CONTROLS + =========================================================== */ + + .compare-controls { + display: flex; + gap: 16px; + flex-wrap: wrap; + margin-bottom: 14px; + align-items: flex-end; + } + + .compare-controls label { + font-size: 13px; + color: var(--gris-500); + font-weight: 600; + } + + .compare-controls input, + .compare-controls select { + padding: 8px 12px; + font-size: 14px; + min-width: 130px; + border: 1.5px solid var(--gris-300); + border-radius: var(--rayon); + background: white; + outline: none; + transition: border-color 0.2s; + } + + .compare-controls input:focus, + .compare-controls select:focus { + border-color: var(--vert); + } + + /* =========================================================== + PAGINATION + =========================================================== */ + + .pagination { + display: flex; + align-items: center; + justify-content: center; + gap: 14px; + margin: 20px 0; + } + + .page-info { + font-size: 14px; + font-weight: 600; + color: var(--gris-500); + } + + /* =========================================================== + LAYOUT + =========================================================== */ + + .layout { + display: grid; + grid-template-columns: 1fr; + gap: 16px; + } + + .results { + display: grid; + gap: 12px; + } + + .message { + padding: 16px; + background: white; + border: 1px solid var(--gris-200); + border-radius: var(--rayon); + text-align: center; + color: var(--gris-500); + } + + /* =========================================================== + MAP + =========================================================== */ + + .map-box { + overflow: hidden; + margin-bottom: 20px; + } + + .map-box h3 { + margin: 0 0 8px; + font-size: 0.95rem; + color: var(--gris-700); + } + + .map { + height: 400px; + width: 100%; + border-radius: var(--rayon); + border: 1px solid var(--gris-200); + } + + /* =========================================================== + DETAIL VIEW + =========================================================== */ + + .detail-page { background: transparent; } + + .formation-title { + color: var(--bleu); + font-size: 1.6rem; + font-weight: 700; + line-height: 1.25; + margin-bottom: 16px; + } + + .formation-meta { + background: white; + padding: 18px 22px; + border-radius: var(--rayon); + border: 1px solid var(--gris-200); + margin-bottom: 24px; + } + + .formation-meta p { + margin: 5px 0; + font-size: 14px; + color: var(--gris-700); + } + + .detail-grid { + display: grid; + grid-template-columns: 2fr 1fr; + gap: 24px; + align-items: start; + margin-top: 8px; + margin-bottom: 28px; + } + + .detail-table { + width: 100%; + border-collapse: collapse; + background: white; + border-radius: var(--rayon); + overflow: hidden; + } + + .detail-table th, + .detail-table td { + border: 1px solid var(--gris-200); + padding: 10px 12px; + text-align: left; + font-size: 14px; + } + + .detail-table thead th { + background: var(--gris-100); + font-weight: 700; + color: var(--gris-700); + font-size: 13px; + } + + .total-row td { + font-weight: 700; + background: var(--gris-100); + } + + .timeline-box { + background: white; + padding: 18px 22px; + border-radius: var(--rayon); + border: 1px solid var(--gris-200); + } + + .timeline-box h3 { margin-top: 0; color: var(--gris-900); } + + .timeline { + position: relative; + margin-top: 20px; + padding-left: 26px; + border-left: 3px solid #b8e0cd; + } + + .timeline-item { position: relative; margin-bottom: 32px; } + + .timeline-dot { + position: absolute; + left: -35px; + top: 5px; + width: 12px; + height: 12px; + background: var(--vert); + border-radius: 50%; + border: 3px solid var(--vert-clair); + } + + /* =========================================================== + CHARTS CSS + =========================================================== */ + + .charts-heading { + margin-top: 32px; + margin-bottom: 4px; + color: var(--gris-900); + } + + .charts-section { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 20px; + margin: 16px 0 28px; + } + + .chart-wrapper { + background: white; + border: 1px solid var(--gris-200); + border-radius: var(--rayon); + padding: 18px 20px 14px; + } + + .chart-wrapper h3 { + margin: 0 0 14px; + font-size: 0.9rem; + color: var(--gris-700); + font-weight: 600; + } + + .chart-full { grid-column: 1 / -1; } + + #chart-bac .column, + #chart-mentions .column { + height: 240px; + max-width: 100%; + margin: 0 auto; + } + + #chart-profil .bar { + height: 170px; + max-width: 560px; + margin: 0; + } + + #chart-profil .bar th { + width: 85px; + font-size: 13px; + } + + #chart-evolution-taux .column { + height: 220px; + max-width: 100%; + margin: 0 auto; + } + + #chart-evolution-candidats .column { + height: 220px; + max-width: 100%; + margin: 0 auto; + } + + .chart-wrapper .data { + font-size: 11px; + font-weight: 700; + color: #fff; + text-shadow: 0 1px 2px rgba(0,0,0,0.25); + } + + .btn-retour { + display: inline-flex; + align-items: center; + gap: 6px; + margin: 12px 0 28px; + padding: 9px 20px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + background: var(--vert); + color: white; + border: none; + border-radius: var(--rayon); + transition: background 0.15s; + } + + .btn-retour:hover { background: var(--vert-fonce); } + + /* =========================================================== + RESPONSIVE + =========================================================== */ + + /* --- Badge cliquable --- */ + .badge-clickable { + cursor: pointer; + transition: all 0.15s; + } + + .badge-clickable:hover { + background: #c6f0dc; + border-color: #6ec89b; + } + + /* --- Filtres avancés --- */ + .filters-toggle { + margin: -10px 0 16px; + } + + .filters-panel { + background: white; + border: 1px solid var(--gris-200); + border-radius: var(--rayon); + padding: 16px 20px; + margin-bottom: 20px; + } + + .filter-row { + display: flex; + gap: 16px; + flex-wrap: wrap; + margin-bottom: 12px; + } + + .filter-row:last-child { + margin-bottom: 0; + } + + .filter-item { + display: flex; + flex-direction: column; + gap: 4px; + min-width: 160px; + flex: 1; + } + + .filter-item label { + font-size: 12px; + font-weight: 600; + color: var(--gris-500); + } + + .filter-item select, + .filter-item input { + padding: 8px 10px; + font-size: 14px; + border: 1.5px solid var(--gris-300); + border-radius: var(--rayon); + background: white; + outline: none; + } + + .filter-item select:focus, + .filter-item input:focus { + border-color: var(--vert); + } + + @media (max-width: 900px) { + .detail-grid { grid-template-columns: 1fr; } + } + + @media (max-width: 700px) { + .charts-section { grid-template-columns: 1fr; } + .site-header { padding: 0 14px; } + .logo-text { font-size: 16px; } + .page { padding: 16px 12px 30px; } + .search-bar { flex-direction: column; } + .search-bar button { width: 100%; } + } + + /* =========================================================== + HEADER RIGHT (badge + auth) + =========================================================== */ + + .header-right { + display: flex; + align-items: center; + gap: 10px; + } + + /* =========================================================== + AUTH PANEL + =========================================================== */ + + .btn-auth { + background: var(--vert); + color: white; + padding: 6px 14px; + font-size: 13px; + font-weight: 600; + border: none; + border-radius: var(--rayon); + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 6px; + transition: background 0.15s; + } + + .btn-auth:hover { background: var(--vert-fonce); } + + .auth-icon { font-size: 15px; } + + .auth-user-info { + display: flex; + align-items: center; + gap: 10px; + } + + .auth-email { + font-size: 13px; + color: var(--gris-700); + font-weight: 500; + max-width: 160px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .btn-auth-logout { + background: white; + color: var(--rouge); + border: 1.5px solid #f5c6cb; + padding: 5px 12px; + font-size: 13px; + font-weight: 600; + border-radius: var(--rayon); + cursor: pointer; + transition: all 0.15s; + } + + .btn-auth-logout:hover { + background: #fef2f2; + } + + /* --- Modale --- */ + .auth-modal-overlay { + position: fixed; + inset: 0; + background: rgba(17, 24, 39, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; + backdrop-filter: blur(2px); + animation: fadeIn 0.15s ease; + } + + @keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } + } + + .auth-modal { + background: white; + border-radius: 10px; + padding: 32px 36px; + width: 100%; + max-width: 400px; + position: relative; + box-shadow: 0 20px 60px rgba(0,0,0,0.18); + animation: slideUp 0.2s ease; + } + + @keyframes slideUp { + from { transform: translateY(16px); opacity: 0; } + to { transform: translateY(0); opacity: 1; } + } + + .auth-modal-close { + position: absolute; + top: 14px; + right: 16px; + background: none; + border: none; + font-size: 18px; + cursor: pointer; + color: var(--gris-500); + line-height: 1; + padding: 4px 6px; + border-radius: 4px; + transition: background 0.15s; + } + + .auth-modal-close:hover { background: var(--gris-100); } + + .auth-modal-title { + margin: 0 0 20px; + font-size: 1.3rem; + font-weight: 700; + color: var(--gris-900); + } + + /* --- Onglets --- */ + .auth-tabs { + display: flex; + margin-bottom: 24px; + border-bottom: 2px solid var(--gris-200); + } + + .auth-tab { + flex: 1; + background: none; + border: none; + padding: 8px 0; + font-size: 14px; + font-weight: 600; + cursor: pointer; + color: var(--gris-500); + border-bottom: 2px solid transparent; + margin-bottom: -2px; + transition: all 0.15s; + } + + .auth-tab.active { + color: var(--vert); + border-bottom-color: var(--vert); + } + + .auth-tab:hover:not(.active) { color: var(--gris-700); } + + /* --- Formulaire --- */ + .auth-form { + display: flex; + flex-direction: column; + gap: 16px; + } + + .auth-field { + display: flex; + flex-direction: column; + gap: 6px; + } + + .auth-field label { + font-size: 13px; + font-weight: 600; + color: var(--gris-700); + } + + .auth-field input { + padding: 10px 12px; + font-size: 14px; + border: 1.5px solid var(--gris-300); + border-radius: var(--rayon); + outline: none; + transition: border-color 0.2s; + background: white; + } + + .auth-field input:focus { + border-color: var(--vert); + box-shadow: 0 0 0 3px rgba(26, 147, 111, 0.12); + } + + .auth-error { + background: #fef2f2; + border: 1px solid #f5c6cb; + color: var(--rouge); + padding: 10px 14px; + border-radius: var(--rayon); + font-size: 13px; + } + + .auth-submit { + width: 100%; + justify-content: center; + padding: 11px; + font-size: 15px; + } \ No newline at end of file