feat(outils): sélection open source, sommaire d'ancres et garde-fous (B6-M4)
28 outils vérifiés en 4 rubriques (open_source.selection), affichés en premier sous un sommaire d'ancres ; grille 2 colonnes dès 1024 px. Le test contrôle la sélection (champs, https, liste noire) et les doublons d'URL. Co-Authored-By: Claude Sonnet <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0136n5xMLHuaBDLcPJYCDjRW
This commit is contained in:
co-authored by
Claude Sonnet
parent
dfbac7dd12
commit
43ae065e26
+131
-6
@@ -95,8 +95,38 @@
|
||||
<template v-else-if="tab === 'open-source'">
|
||||
<p class="outils-intention">{{ outils.open_source.intention }}</p>
|
||||
|
||||
<section class="outils-section" aria-labelledby="sec-recommandes">
|
||||
<h2 id="sec-recommandes" class="outils-section__title">Recommandés</h2>
|
||||
<!-- Sommaire d'ancres : tout est déployé d'office, on saute au bloc voulu -->
|
||||
<nav class="os-sommaire" aria-label="Sommaire de l'onglet Open source">
|
||||
<ul class="os-sommaire__liste">
|
||||
<li v-for="a in sommaire" :key="a.id">
|
||||
<a :href="'#' + a.id" class="os-sommaire__lien" @click="allerAncre($event, a.id)">{{ a.libelle }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<!-- Sélection AEP : outils utiles à un architecte indépendant (B6-M3/M4) -->
|
||||
<template v-if="outils.open_source.selection">
|
||||
<p class="outils-section__desc">{{ outils.open_source.selection.intention }}</p>
|
||||
<section
|
||||
v-for="r in outils.open_source.selection.rubriques"
|
||||
:key="r.id"
|
||||
class="outils-section outils-section--ancre"
|
||||
:aria-labelledby="'sel-' + r.id"
|
||||
>
|
||||
<h2 :id="'sel-' + r.id" class="outils-section__title" tabindex="-1">{{ r.titre }}</h2>
|
||||
<p v-if="r.note" class="outils-section__desc">{{ r.note }}</p>
|
||||
<ul class="sel-grid">
|
||||
<li v-for="l in r.liens" :key="l.url" class="sel-item">
|
||||
<a :href="l.url" target="_blank" rel="noopener noreferrer" class="sel-item__nom">{{ l.nom }}</a>
|
||||
<p class="sel-item__desc">{{ l.desc }}</p>
|
||||
<p class="sel-item__meta">{{ metaSelection(l) }}</p>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<section class="outils-section outils-section--ancre" aria-labelledby="sec-recommandes">
|
||||
<h2 id="sec-recommandes" class="outils-section__title" tabindex="-1">Nos recommandations</h2>
|
||||
<div class="outil-cards-grid">
|
||||
<OutilCard
|
||||
v-for="outil in outils.open_source.recommandes"
|
||||
@@ -110,8 +140,8 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="outils-section outils-section--fmhy" aria-labelledby="sec-fmhy">
|
||||
<h2 id="sec-fmhy" class="outils-section__title">{{ outils.open_source.fmhy.titre }}</h2>
|
||||
<section class="outils-section outils-section--fmhy outils-section--ancre" aria-labelledby="sec-fmhy">
|
||||
<h2 id="sec-fmhy" class="outils-section__title" tabindex="-1">{{ outils.open_source.fmhy.titre }}</h2>
|
||||
<p class="outils-section__desc">{{ outils.open_source.fmhy.texte_intro }}</p>
|
||||
|
||||
<div v-for="g in outils.open_source.rubriques" :key="g.id" class="fmhy-usage">
|
||||
@@ -155,10 +185,10 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section v-if="outils.open_source.logiciels_pro" class="outils-section outils-section--placeholder" aria-labelledby="sec-logiciels">
|
||||
<section v-if="outils.open_source.logiciels_pro" class="outils-section outils-section--placeholder outils-section--ancre" aria-labelledby="sec-logiciels">
|
||||
<div class="placeholder-block">
|
||||
<span class="placeholder-block__badge">{{ outils.open_source.logiciels_pro.badge }}</span>
|
||||
<h2 id="sec-logiciels" class="placeholder-block__title">{{ outils.open_source.logiciels_pro.titre }}</h2>
|
||||
<h2 id="sec-logiciels" class="placeholder-block__title" tabindex="-1">{{ outils.open_source.logiciels_pro.titre }}</h2>
|
||||
<p class="placeholder-block__desc">{{ outils.open_source.logiciels_pro.description }}</p>
|
||||
</div>
|
||||
</section>
|
||||
@@ -220,6 +250,31 @@ function onTabKeydown(e: KeyboardEvent) {
|
||||
nextTick(() => tablistRef.value?.querySelector<HTMLElement>(`#outils-tab-${TABS[j].id}`)?.focus())
|
||||
}
|
||||
|
||||
// Open source : sommaire d'ancres = rubriques de la sélection + les trois autres blocs.
|
||||
const sommaire = computed(() => {
|
||||
const os = outils.value?.open_source
|
||||
if (!os) return []
|
||||
return [
|
||||
...(os.selection?.rubriques ?? []).map((r: any) => ({ id: `sel-${r.id}`, libelle: r.titre })),
|
||||
{ id: 'sec-recommandes', libelle: 'Nos recommandations' },
|
||||
{ id: 'sec-fmhy', libelle: 'Annuaire fmhy' },
|
||||
...(os.logiciels_pro ? [{ id: 'sec-logiciels', libelle: 'Logiciels pro' }] : []),
|
||||
]
|
||||
})
|
||||
|
||||
const metaSelection = (l: any) => [l.licence, (l.plateformes ?? []).join(', ')].filter(Boolean).join(' · ')
|
||||
|
||||
// Saut vers une ancre : défilement doux sauf préférence « moins d'animations », focus sur le titre
|
||||
// (clavier et lecteurs d'écran arrivent au bon endroit). L'URL ne bouge pas (?tab= intact).
|
||||
function allerAncre(e: Event, id: string) {
|
||||
e.preventDefault()
|
||||
const el = document.getElementById(id)
|
||||
if (!el) return
|
||||
const reduit = window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
||||
el.scrollIntoView({ behavior: reduit ? 'auto' : 'smooth', block: 'start' })
|
||||
el.focus({ preventScroll: true })
|
||||
}
|
||||
|
||||
useSeoMeta({
|
||||
title: () => `Outils — AEP${tab.value !== TAB_DEFAUT ? ' — ' + TABS.find((t) => t.id === tab.value)!.libelle : ''}`,
|
||||
description: 'Outils partagés entre architectes : simulateurs, open source, ressources libres pour partir de soi.',
|
||||
@@ -397,6 +452,76 @@ useSeoMeta({
|
||||
gap: var(--space-md);
|
||||
}
|
||||
|
||||
/* ── Open source : sommaire d'ancres et sélection ───────────── */
|
||||
.outils-section--ancre { scroll-margin-top: var(--space-lg); }
|
||||
.outils-section--ancre :is(h2):focus { outline: none; }
|
||||
.outils-section--ancre :is(h2):focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; border-radius: var(--radius-sm); }
|
||||
|
||||
.os-sommaire__liste {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-xs) var(--space-sm);
|
||||
}
|
||||
.os-sommaire__lien {
|
||||
display: inline-block;
|
||||
padding: var(--space-xs) var(--space-md);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--bg-alt);
|
||||
color: var(--text);
|
||||
font-size: 0.85rem;
|
||||
font-weight: var(--fw-medium);
|
||||
text-decoration: none;
|
||||
}
|
||||
.os-sommaire__lien:hover { border-color: var(--nav-primary-solid); }
|
||||
.os-sommaire__lien:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
|
||||
|
||||
.sel-grid {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: var(--space-md);
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.sel-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
}
|
||||
.sel-item {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.3rem;
|
||||
padding: var(--space-md);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--surface);
|
||||
}
|
||||
.sel-item__nom {
|
||||
align-self: flex-start;
|
||||
font-size: 0.95rem;
|
||||
font-weight: var(--fw-semibold);
|
||||
color: var(--nav-primary-solid);
|
||||
text-underline-offset: 2px;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.sel-item__nom:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; border-radius: var(--radius-sm); }
|
||||
.sel-item__desc {
|
||||
margin: 0;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.55;
|
||||
color: var(--text);
|
||||
}
|
||||
.sel-item__meta {
|
||||
margin: auto 0 0;
|
||||
padding-top: 0.2rem;
|
||||
font-size: 0.78rem;
|
||||
color: color-mix(in srgb, var(--text) 78%, transparent);
|
||||
}
|
||||
|
||||
/* ── Section FMHY ───────────────────────────────────────────── */
|
||||
.outils-section--fmhy {
|
||||
background: var(--bg-alt);
|
||||
|
||||
Reference in New Issue
Block a user