Restructure Engagement Forgeron + fix GenesisBlock + InertiaSlider

- Seed: restructure Engagement Forgeron (51→59 items) avec 3 nouvelles
  sections: Engagements fondamentaux (EF1-EF3), Engagements techniques
  (ET1-ET3), Qualification (Q0-Q1) liée au protocole Embarquement
- Seed: ajout protocole Embarquement Forgeron (5 jalons: candidature,
  miroir, évaluation, certification Smith, mise en ligne)
- GenesisBlock: fix lisibilité — fond mood-surface teinté accent au lieu
  de mood-text inversé, texte mood-aware au lieu de rgba blanc hardcodé
- InertiaSlider: mini affiche "Inertie" sous le curseur, compact en
  width:fit-content pour s'adapter au label
- Frontend: ajout section qualification dans SECTION_META/SECTION_ORDER
- Pages, composants et tests des sprints précédents

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-03-03 03:44:33 +01:00
parent 4212e847d4
commit c19c1aa55e
16 changed files with 3002 additions and 361 deletions

View File

@@ -45,6 +45,9 @@ const SECTION_META: Record<string, { label: string; icon: string }> = {
introduction: { label: 'Introduction', icon: 'i-lucide-scroll-text' },
fondamental: { label: 'Engagements fondamentaux', icon: 'i-lucide-shield-check' },
technique: { label: 'Engagements techniques', icon: 'i-lucide-wrench' },
qualification: { label: 'Qualification', icon: 'i-lucide-graduation-cap' },
aspirant: { label: 'Aspirant forgeron', icon: 'i-lucide-user-plus' },
certificateur: { label: 'Certificateur forgeron', icon: 'i-lucide-stamp' },
conclusion: { label: 'Conclusion', icon: 'i-lucide-bookmark' },
annexe: { label: 'Annexes', icon: 'i-lucide-paperclip' },
formule: { label: 'Formule de vote', icon: 'i-lucide-calculator' },
@@ -52,7 +55,7 @@ const SECTION_META: Record<string, { label: string; icon: string }> = {
ordonnancement: { label: 'Ordonnancement', icon: 'i-lucide-list-ordered' },
}
const SECTION_ORDER = ['introduction', 'fondamental', 'technique', 'conclusion', 'annexe', 'formule', 'inertie', 'ordonnancement']
const SECTION_ORDER = ['introduction', 'fondamental', 'technique', 'qualification', 'aspirant', 'certificateur', 'conclusion', 'annexe', 'formule', 'inertie', 'ordonnancement']
const sections = computed((): Section[] => {
const grouped: Record<string, DocumentItem[]> = {}
@@ -140,11 +143,36 @@ async function archiveToSanctuary() {
const activeSection = ref<string | null>(null)
function scrollToSection(tag: string) {
const el = document.getElementById(`section-${tag}`)
if (el) {
el.scrollIntoView({ behavior: 'smooth', block: 'start' })
activeSection.value = tag
// Expand the section if collapsed
if (collapsedSections.value[tag]) {
collapsedSections.value[tag] = false
}
nextTick(() => {
const el = document.getElementById(`section-${tag}`)
if (el) {
el.scrollIntoView({ behavior: 'smooth', block: 'start' })
activeSection.value = tag
}
})
}
// ─── Collapsible sections ────────────────────────────────────
// First 2 sections open by default, rest collapsed
const collapsedSections = ref<Record<string, boolean>>({})
watch(sections, (newSections) => {
if (newSections.length > 0 && Object.keys(collapsedSections.value).length === 0) {
const map: Record<string, boolean> = {}
newSections.forEach((s, i) => {
map[s.tag] = i >= 2 // collapsed if index >= 2
})
collapsedSections.value = map
}
}, { immediate: true })
function toggleSection(tag: string) {
collapsedSections.value[tag] = !collapsedSections.value[tag]
}
</script>
@@ -280,8 +308,11 @@ function scrollToSection(tag: string) {
:id="`section-${section.tag}`"
class="doc-page__section"
>
<!-- Section header -->
<div class="doc-page__section-header">
<!-- Section header (clickable toggle) -->
<button
class="doc-page__section-header"
@click="toggleSection(section.tag)"
>
<div class="flex items-center gap-2">
<UIcon :name="section.icon" style="color: var(--mood-accent)" />
<h2 class="doc-page__section-title">
@@ -291,20 +322,29 @@ function scrollToSection(tag: string) {
{{ section.items.length }}
</UBadge>
</div>
<InertiaSlider :preset="section.inertiaPreset" compact class="max-w-48" />
</div>
<div class="flex items-center gap-2">
<InertiaSlider :preset="section.inertiaPreset" compact mini />
<UIcon
name="i-lucide-chevron-down"
class="doc-page__section-chevron"
:class="{ 'doc-page__section-chevron--open': !collapsedSections[section.tag] }"
/>
</div>
</button>
<!-- Items -->
<div class="doc-page__section-items">
<EngagementCard
v-for="item in section.items"
:key="item.id"
:item="item"
:document-slug="slug"
:show-actions="auth.isAuthenticated"
@propose="handlePropose"
/>
</div>
<!-- Items (collapsible) -->
<Transition name="section-collapse">
<div v-show="!collapsedSections[section.tag]" class="doc-page__section-items">
<EngagementCard
v-for="item in section.items"
:key="item.id"
:item="item"
:document-slug="slug"
:show-actions="auth.isAuthenticated"
@propose="handlePropose"
/>
</div>
</Transition>
</div>
</div>
</template>
@@ -449,6 +489,27 @@ function scrollToSection(tag: string) {
gap: 1rem;
padding: 0.75rem 0;
border-bottom: 2px solid color-mix(in srgb, var(--mood-accent) 15%, transparent);
width: 100%;
background: none;
cursor: pointer;
user-select: none;
transition: opacity 0.15s;
}
.doc-page__section-header:hover {
opacity: 0.85;
}
.doc-page__section-chevron {
font-size: 1rem;
color: var(--mood-text-muted);
transform: rotate(-90deg);
transition: transform 0.25s ease;
flex-shrink: 0;
}
.doc-page__section-chevron--open {
transform: rotate(0deg);
}
.doc-page__section-title {
@@ -469,4 +530,22 @@ function scrollToSection(tag: string) {
flex-direction: column;
gap: 0.75rem;
}
/* Section collapse transition */
.section-collapse-enter-active,
.section-collapse-leave-active {
transition: all 0.3s ease;
overflow: hidden;
}
.section-collapse-enter-from,
.section-collapse-leave-to {
opacity: 0;
max-height: 0;
}
.section-collapse-enter-to,
.section-collapse-leave-from {
opacity: 1;
}
</style>