Files
librodrome/app/pages/numerique/[slug].vue
T
YvvandClaude Opus 5.5 d2bc0d9e46 identité : le kit du studio DA intégré, les 61 oiseaux posés sur la page
Copie du kit kit-site (empreinte vérifiée) : logotype dans l'en-tête, signe §,
favicons et icônes, og-default (?v=3), frise du pied de page, 61 oiseaux.
Les shadoks positionnés à la main (HomeShadoks et les règles des 10 pages)
cèdent la place à UiOiseauPose : chaque oiseau se pose sur une arête réelle
(perché, marge gauche ou droite, derrière une carte, libre), currentColor,
largeur 72 px au moins, opacité 0,18-0,28 près du texte et 0,10-0,14 derrière,
jamais deux fois sur une page, calligraphe et typographe jamais en miroir.
Sous 768 px, un oiseau par page au plus ; seuls ceux derrière une carte
dérivent un peu, et plus du tout en mouvement réduit.

Consignes d'Yvv du 03/11 inscrites dans docs/consignes-yvv.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 06:39:34 +01:00

1744 lines
47 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="axis-scope axis-scope--num oiseaux-scene page-outer section-padding">
<div class="container-content">
<!-- Header — centré, max-2xl -->
<div class="mx-auto max-w-2xl text-center mb-10">
<div class="section-icon mx-auto mb-6">
<div :class="`i-lucide-${content?.icon ?? 'monitor'}`" class="h-12 w-12" />
</div>
<p class="mb-2 font-mono text-sm tracking-widest text-primary uppercase">
{{ content?.kicker }}
</p>
<h1 class="font-display text-3xl font-bold mb-4" style="color: hsl(var(--color-text))">
{{ content?.title ?? slug }}
</h1>
<p class="text-lg leading-relaxed" style="color: hsl(var(--color-text-muted))">
{{ content?.description }}
</p>
</div>
<!-- Project card (legacy pages) -->
<div v-if="content?.project" class="mx-auto max-w-2xl mb-8">
<div class="project-card">
<UiOiseauPose v-for="o in oiseaux.projet ?? []" :key="`${o.nom}-${o.pose}`" v-bind="o" />
<div class="project-icon">
<div class="i-lucide-rocket h-5 w-5" />
</div>
<h2 class="font-display text-xl font-semibold mb-2" style="color: hsl(var(--color-text))">
{{ content.project.name }}
</h2>
<p style="color: hsl(var(--color-text-muted))" class="leading-relaxed">
{{ content.project.text }}
</p>
<UiStatusBadge
class="mt-3"
:status="content.project.status"
:gestation="content.project.gestation || content?.gestation"
:since="content.project.since"
/>
<div v-if="content.project.action?.href" class="project-action mt-4">
<UiBaseButton :href="content.project.action.href" target="_blank">
<div class="i-lucide-external-link mr-2 h-4 w-4" />
{{ content.project.action.label }}
</UiBaseButton>
<span v-if="content.project.action.note" class="project-action-note">{{ content.project.action.note }}</span>
</div>
</div>
</div>
<!-- Extended content (legacy) -->
<div v-if="content?.content" class="mx-auto max-w-2xl mb-10">
<div class="prose-block">
<p class="leading-relaxed whitespace-pre-line" style="color: hsl(var(--color-text-muted))">
{{ content.content }}
</p>
</div>
</div>
<!-- Navigation prev / index / next — en haut -->
<nav class="ctx-nav mx-auto max-w-2xl mb-10">
<NuxtLink v-if="prevItem?.to" :to="prevItem.to" class="ctx-nav-btn ctx-nav-prev">
<div class="i-lucide-arrow-left h-4 w-4 shrink-0" />
<span>{{ prevItem.label }}</span>
</NuxtLink>
<div v-else class="ctx-nav-spacer" />
<NuxtLink to="/numerique" class="ctx-nav-btn ctx-nav-index">
<div class="i-lucide-layout-grid h-4 w-4" />
<span>Numérique</span>
</NuxtLink>
<NuxtLink v-if="nextItem?.to" :to="nextItem.to" class="ctx-nav-btn ctx-nav-next">
<span>{{ nextItem.label }}</span>
<div class="i-lucide-arrow-right h-4 w-4 shrink-0" />
</NuxtLink>
<div v-else class="ctx-nav-spacer" />
</nav>
<!-- Zone sections — position:relative pour ancrer le sidebar absolu -->
<div class="sections-area">
<!-- Sidebar sommaire — absolu, ne perturbe pas le flux, sticky en scroll -->
<aside v-if="sommaire.length > 1" class="page-sidebar">
<nav class="sommaire-sidebar">
<p class="sommaire-sidebar-title">Sur cette page</p>
<ol class="sommaire-sidebar-list">
<li v-for="(entry, ei) in sommaire" :key="ei">
<a :href="`#${entry.id}`" class="sommaire-sidebar-link">
<span class="sommaire-n">{{ ei + 1 }}</span>
<span>{{ entry.title }}</span>
</a>
</li>
</ol>
</nav>
</aside>
<!-- Rich sections — mx-auto conservé, centrage intact -->
<template v-if="content?.sections?.length">
<div
v-for="(section, si) in content.sections"
:key="si"
:id="`s${si}`"
class="relative mb-12 section-anchor"
:class="['equivalents', 'tiers', 'territoire'].includes(section.type) ? 'max-w-4xl mx-auto' : 'max-w-2xl mx-auto'"
>
<UiOiseauPose v-for="o in oiseaux.sections?.[si] ?? []" :key="`${o.nom}-${o.pose}`" v-bind="o" />
<!-- ── ARGUMENTS ── -->
<template v-if="section.type === 'arguments'">
<h2 class="section-title">{{ section.title }}</h2>
<div class="args-grid">
<div v-for="(arg, i) in section.items" :key="i" class="arg-card">
<div class="arg-icon">
<div :class="`i-lucide-${arg.icon}`" class="h-5 w-5" />
</div>
<h3 class="arg-title">{{ arg.title }}</h3>
<p class="arg-text">{{ arg.text }}</p>
</div>
</div>
</template>
<!-- ── FICHE ── -->
<template v-else-if="section.type === 'fiche'">
<h2 class="section-title">{{ section.title }}</h2>
<div class="fiche-steps">
<div v-for="(step, i) in section.steps" :key="i" class="fiche-step">
<div class="step-num">{{ step.n }}</div>
<div class="step-body">
<h3 class="step-title">{{ step.title }}</h3>
<p class="step-text">{{ step.text }}</p>
<p v-if="step.tip" class="step-tip">
<span class="i-lucide-lightbulb h-3.5 w-3.5 inline-block mr-1 align-middle" />
{{ step.tip }}
</p>
</div>
</div>
</div>
</template>
<!-- ── EQUIVALENTS ── -->
<template v-else-if="section.type === 'equivalents'">
<h2 class="section-title">{{ section.title }}</h2>
<div class="equiv-categories">
<div v-for="(cat, ci) in section.categories" :key="ci" class="equiv-cat">
<h3 class="equiv-cat-label">{{ cat.label }}</h3>
<div class="equiv-rows">
<component
:is="item.url ? 'a' : 'div'"
v-for="(item, ii) in cat.items"
:key="ii"
v-bind="item.url ? { href: item.url, target: '_blank', rel: 'noopener' } : {}"
class="equiv-row"
:class="{ 'equiv-row--no-link': !item.url }"
>
<span class="equiv-from">{{ item.from }}</span>
<span class="equiv-arrow">→</span>
<span class="equiv-to">{{ item.to }}</span>
<span class="equiv-note">{{ item.note }}</span>
<span v-if="item.url" class="i-lucide-external-link equiv-ext h-3.5 w-3.5" />
</component>
</div>
</div>
</div>
</template>
<!-- ── LLM ── -->
<template v-else-if="section.type === 'llm'">
<h2 class="section-title">{{ section.title }}</h2>
<p class="llm-intro">{{ section.text }}</p>
<div class="llm-meta">
<span class="llm-badge">
<span class="i-lucide-cpu h-3.5 w-3.5" />
{{ section.tool }}
</span>
<span class="llm-badge">
<span class="i-lucide-zap h-3.5 w-3.5" />
{{ section.model }}
</span>
<span class="llm-badge">
<span class="i-lucide-memory-stick h-3.5 w-3.5" />
{{ section.ram }}
</span>
</div>
<div class="llm-commands">
<code v-for="(cmd, ci) in section.commands" :key="ci" class="llm-cmd">{{ cmd }}</code>
</div>
<ul class="llm-rules">
<li v-for="(rule, ri) in section.rules" :key="ri" class="llm-rule">
<span class="i-lucide-shield-check h-3.5 w-3.5 shrink-0 mt-0.5" />
{{ rule }}
</li>
</ul>
</template>
<!-- ── ATELIER ── -->
<template v-else-if="section.type === 'atelier'">
<h2 class="section-title">{{ section.title }}</h2>
<p class="atelier-text">{{ section.text }}</p>
<p class="atelier-format">
<span class="i-lucide-calendar h-4 w-4 inline-block mr-1 align-middle" />
{{ section.format }}
</p>
<ul class="atelier-programme">
<li v-for="(item, pi) in section.programme" :key="pi" class="atelier-item">
<div :class="`i-lucide-${item.icon}`" class="h-4 w-4 shrink-0 mt-0.5 text-primary" />
{{ item.label }}
</li>
</ul>
</template>
<!-- ── INSIGHT ── -->
<template v-else-if="section.type === 'insight'">
<div class="insight-box" :class="`insight-box--${section.variant || 'info'}`">
<div class="insight-header">
<div :class="`i-lucide-${section.icon || 'info'}`" class="h-5 w-5 shrink-0" />
<h2 class="insight-title">{{ section.title }}</h2>
</div>
<p class="insight-text">{{ section.text }}</p>
<ul v-if="section.points?.length" class="insight-points">
<li v-for="(pt, pi) in section.points" :key="pi">{{ pt }}</li>
</ul>
</div>
</template>
<!-- ── TIERS ── -->
<template v-else-if="section.type === 'tiers'">
<h2 class="section-title">{{ section.title }}</h2>
<div class="tiers-grid">
<div v-for="(tier, ti) in section.tiers" :key="ti" class="tier-card">
<div class="tier-top">
<span class="tier-level">{{ tier.level }}</span>
<span class="tier-badge">{{ tier.badge }}</span>
</div>
<div class="tier-icon">
<div :class="`i-lucide-${tier.icon}`" class="h-5 w-5" />
</div>
<h3 class="tier-title">{{ tier.title }}</h3>
<p class="tier-text">{{ tier.text }}</p>
<ul class="tier-tools">
<li v-for="(tool, gi) in tier.tools" :key="gi">{{ tool }}</li>
</ul>
</div>
</div>
</template>
<!-- ── TERRITOIRE ── -->
<template v-else-if="section.type === 'territoire'">
<div class="territoire-card">
<!-- Header -->
<div class="territoire-header">
<div class="territoire-icon">
<div :class="`i-lucide-${section.icon || 'map-pin'}`" class="h-6 w-6" />
</div>
<div>
<h2 class="territoire-title">{{ section.title }}</h2>
<p class="territoire-text">{{ section.text }}</p>
</div>
</div>
<!-- Modèles économiques -->
<div v-if="section.economies?.length" class="territoire-economies">
<div v-for="(eco, ei) in section.economies" :key="ei" class="territoire-eco">
<div class="territoire-eco-icon">
<span v-if="eco.icon === 'g1'" class="territoire-g1">Ğ1</span>
<div v-else :class="`i-lucide-${eco.icon}`" class="h-4 w-4" />
</div>
<div>
<p class="territoire-eco-label">{{ eco.label }}</p>
<p class="territoire-eco-desc">{{ eco.desc }}</p>
</div>
</div>
</div>
<!-- Bouquet sweetHome -->
<div v-if="section.bouquet" class="territoire-bouquet">
<h3 class="territoire-bouquet-title">
<div class="i-lucide-package h-4 w-4" />
{{ section.bouquet.title }}
</h3>
<p v-if="section.bouquet.provider" class="territoire-bouquet-provider">{{ section.bouquet.provider }}</p>
<ul v-if="section.bouquet.statuses" class="bouquet-legend" aria-label="Légende des statuts">
<li v-for="(label, key) in section.bouquet.statuses" :key="key">
<span class="bouquet-dot" :class="`bouquet-dot--${key}`" aria-hidden="true" />{{ label }}
</li>
</ul>
<div class="territoire-bouquet-grid">
<div
v-for="(cat, ci) in section.bouquet.categories"
:key="ci"
class="territoire-bouquet-cat"
>
<div class="territoire-bouquet-cat-header">
<div :class="`i-lucide-${cat.icon}`" class="h-3.5 w-3.5" />
<span>{{ cat.label }}</span>
</div>
<ul class="territoire-bouquet-items">
<template v-for="(item, ii) in cat.items" :key="ii">
<li v-if="typeof item === 'string'">{{ item }}</li>
<li v-else class="bouquet-item">
<span
class="bouquet-dot"
:class="`bouquet-dot--${item.status}`"
:title="section.bouquet.statuses?.[item.status]"
/>
<span class="bouquet-role">{{ item.role }}</span>
<template v-if="item.name">
<a v-if="item.url" :href="item.url" target="_blank" rel="noopener" class="bouquet-name">{{ item.name }}</a>
<span v-else class="bouquet-name">{{ item.name }}</span>
</template>
<span class="sr-only">— {{ section.bouquet.statuses?.[item.status] }}</span>
</li>
</template>
</ul>
<p v-if="cat.note" class="bouquet-note">{{ cat.note }}</p>
</div>
</div>
</div>
<!-- Estimation matérielle (toggle natif) -->
<details v-if="section.estimate" class="territoire-estimate">
<summary class="territoire-estimate-toggle">
<div class="i-lucide-server h-4 w-4" />
{{ section.estimate.toggle }}
<div class="i-lucide-chevron-down territoire-chevron h-4 w-4" />
</summary>
<div class="territoire-estimate-body">
<p v-if="section.estimate.note" class="territoire-estimate-note">
<span class="i-lucide-info h-3.5 w-3.5 inline-block mr-1 align-middle" />
{{ section.estimate.note }}
</p>
<div class="territoire-hw-table">
<div class="territoire-hw-header">
<span>Rôle</span>
<span>Matériel</span>
<span>Coût indicatif</span>
</div>
<div
v-for="(hw, hi) in section.estimate.hardware"
:key="hi"
class="territoire-hw-row"
>
<div class="territoire-hw-role">{{ hw.role }}</div>
<div class="territoire-hw-detail">
<span>{{ hw.detail }}</span>
<span class="territoire-hw-usage">{{ hw.usage }}</span>
<span v-if="hw.note" class="territoire-hw-note">{{ hw.note }}</span>
</div>
<div class="territoire-hw-cost">{{ hw.cost }}</div>
</div>
</div>
<div v-if="section.estimate.totals" class="territoire-totals">
<div class="territoire-total-row">
<span class="territoire-total-label">Investissement initial</span>
<span class="territoire-total-value">{{ section.estimate.totals.invest }}</span>
</div>
<div class="territoire-total-row">
<span class="territoire-total-label">Fonctionnement</span>
<span class="territoire-total-value">{{ section.estimate.totals.annual }}</span>
</div>
<div class="territoire-total-row territoire-total-row--highlight">
<span class="territoire-total-label">Par habitant</span>
<span class="territoire-total-value">{{ section.estimate.totals.per_person }}</span>
</div>
<p v-if="section.estimate.totals.note" class="territoire-totals-note">
{{ section.estimate.totals.note }}
</p>
</div>
</div>
</details>
</div>
</template>
<!-- ── PROJET (gestation) ── -->
<template v-else-if="section.type === 'projet'">
<div class="projet-card">
<div class="projet-header">
<div class="projet-icon">
<div :class="`i-lucide-${section.icon || 'rocket'}`" class="h-6 w-6" />
</div>
<div class="projet-header-text">
<p class="projet-kicker">{{ section.kicker }}</p>
<h2 class="projet-title">{{ section.title }}</h2>
</div>
<span class="projet-badge">
<div class="i-lucide-flask-conical h-3 w-3" />
{{ section.badge || 'En gestation' }}
</span>
</div>
<p class="projet-text">{{ section.text }}</p>
<ul v-if="section.features?.length" class="projet-features">
<li v-for="(feat, fi) in section.features" :key="fi" class="projet-feature">
<div :class="`i-lucide-${feat.icon}`" class="h-4 w-4 shrink-0" />
{{ feat.label }}
</li>
</ul>
</div>
</template>
<!-- ── LINKS ── -->
<template v-else-if="section.type === 'links'">
<h2 class="section-title">{{ section.title }}</h2>
<ul class="links-list">
<li v-for="(lnk, li) in section.items" :key="li">
<a :href="lnk.url" target="_blank" rel="noopener" class="link-row">
<span class="link-label">{{ lnk.label }}</span>
<span class="link-desc">{{ lnk.desc }}</span>
<span class="i-lucide-external-link h-3.5 w-3.5 text-primary/50 shrink-0" />
</a>
</li>
</ul>
</template>
</div>
</template>
</div><!-- /sections-area -->
<div v-if="slug === 'tarifs-eau'" class="text-center mt-6">
<UiBaseButton :href="sejeteral0Url" target="_blank">
<div class="i-lucide-external-link mr-2 h-4 w-4" />
Lancer SejeteralO
</UiBaseButton>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import type { OiseauPlace } from '~/utils/oiseauPose'
const route = useRoute()
const slug = route.params.slug as string
// Birds of each page, by block: the project card and the sections (by index). Left side
// only: the right margin holds the sticky summary on wide screens.
interface OiseauxPage { projet?: OiseauPlace[], sections?: Record<number, OiseauPlace[]> }
const OISEAUX_PAGE: Record<string, OiseauxPage> = {
'logiciel-libre': {
projet: [{ nom: 'cafeine', pose: 'derriere', left: '-130px', bottom: '-30px', w: 200 }],
sections: {
1: [{ nom: 'debogueur', pose: 'marge-g', ecart: '48px', bottom: '62%', w: 120 }],
2: [{ nom: 'le-manchot', pose: 'marge-g', ecart: '40px', bottom: '18%', w: 132 }],
3: [{ nom: 'canard-en-plastique', pose: 'perche', right: '8%', w: 80 }],
},
},
'authentification-wot': {
projet: [{ nom: 'porteur-de-serveur', pose: 'derriere', left: '-130px', bottom: '-30px', w: 210 }],
sections: {
0: [{ nom: 'cryptographe', pose: 'marge-g', ecart: '48px', bottom: '45%', w: 104 }],
1: [{ nom: 'toile-de-confiance', pose: 'marge-g', ecart: '44px', bottom: '30%', w: 150 }],
4: [{ nom: 'pompeur-de-g1', pose: 'marge-g', ecart: '40px', bottom: '22%', w: 108 }],
},
},
'cloud-libre': {
projet: [{ nom: 'porteur-de-serveur', pose: 'derriere', left: '-130px', bottom: '-30px', w: 210 }],
sections: {
1: [{ nom: 'admin-reseau', pose: 'marge-g', ecart: '44px', bottom: '40%', w: 124 }],
},
},
}
const oiseaux = computed<OiseauxPage>(() => OISEAUX_PAGE[slug] ?? {})
const [{ data: content }, { data: homeData }] = await Promise.all([
usePageContent(`numerique/${slug}`),
usePageContent('home'),
])
const appConfig = useAppConfig()
const sejeteral0Url = (appConfig.sejeteral0 as { url: string })?.url ?? '#'
// Sommaire — titres des sections ayant un titre
interface Section { type: string; title?: string }
const sommaire = computed(() =>
(content.value?.sections as Section[] ?? [])
.map((s, i) => ({ title: s.title, id: `s${i}` }))
.filter(s => !!s.title),
)
// Prev / next dans la section numérique
interface AxisItem { label: string; to?: string; icon?: string }
const sectionItems = computed<AxisItem[]>(
() => (homeData.value as Record<string, unknown> | null)?.axes?.numerique?.items as AxisItem[] ?? [],
)
const currentPath = `/numerique/${slug}`
const currentIdx = computed(() => sectionItems.value.findIndex(i => i.to === currentPath))
const prevItem = computed(() => currentIdx.value > 0 ? sectionItems.value[currentIdx.value - 1] : null)
const nextItem = computed(() => currentIdx.value < sectionItems.value.length - 1 ? sectionItems.value[currentIdx.value + 1] : null)
useSeoPage({
title: content.value?.meta?.title ?? slug,
description: content.value?.description as string ?? undefined,
})
</script>
<style scoped>
.section-icon {
display: flex;
align-items: center;
justify-content: center;
width: 5rem;
height: 5rem;
border-radius: 1rem;
background: hsl(var(--color-primary) / 0.1);
border: 1px solid hsl(var(--color-primary) / 0.2);
color: hsl(var(--color-primary));
}
.project-card {
position: relative;
padding: 1.5rem;
border-radius: 0.75rem;
border: 1px solid hsl(var(--color-primary) / 0.15);
background: hsl(var(--color-surface));
}
.project-icon {
display: flex;
align-items: center;
justify-content: center;
width: 2.5rem;
height: 2.5rem;
border-radius: 0.5rem;
background: hsl(var(--color-primary) / 0.1);
color: hsl(var(--color-primary));
margin-bottom: 1rem;
}
.gestation-badge {
display: inline-flex;
align-items: center;
gap: 0.25rem;
padding: 0.125rem 0.5rem;
border-radius: 9999px;
background: hsl(var(--color-accent) / 0.12);
color: hsl(var(--color-accent));
font-size: 0.7rem;
font-weight: 500;
font-family: var(--font-mono);
}
.prose-block {
padding: 1.5rem;
border-radius: 0.75rem;
background: hsl(var(--color-surface));
}
/* scroll-margin-top pour compenser le header fixe */
.section-anchor {
scroll-margin-top: 5.5rem;
}
/* ── Navigation prev/next (en haut) ── */
.ctx-nav {
display: grid;
grid-template-columns: 1fr auto 1fr;
gap: 0.5rem;
align-items: center;
}
.ctx-nav-spacer { min-width: 0; }
.ctx-nav-btn {
display: flex;
align-items: center;
gap: 0.45rem;
padding: 0.5rem 0.875rem;
border-radius: 20px;
text-decoration: none;
font-size: 0.8rem;
font-weight: 500;
color: hsl(var(--color-text-muted));
background: hsl(var(--color-surface));
border: 1px solid hsl(var(--color-text) / 0.08);
transition: all 0.15s;
min-width: 0;
}
.ctx-nav-btn:hover {
color: hsl(var(--color-primary));
border-color: hsl(var(--color-primary) / 0.25);
background: hsl(var(--color-primary) / 0.05);
}
.ctx-nav-prev { justify-content: flex-start; }
.ctx-nav-next { justify-content: flex-end; }
.ctx-nav-prev span,
.ctx-nav-next span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ctx-nav-index {
justify-content: center;
background: hsl(var(--color-primary) / 0.08);
border-color: hsl(var(--color-primary) / 0.15);
color: hsl(var(--color-primary));
font-size: 0.75rem;
padding: 0.45rem 0.75rem;
white-space: nowrap;
flex-shrink: 0;
}
.ctx-nav-index:hover { background: hsl(var(--color-primary) / 0.15); }
@media (max-width: 480px) {
.ctx-nav { grid-template-columns: auto 1fr auto; }
.ctx-nav-prev span, .ctx-nav-next span { display: none; }
.ctx-nav-prev, .ctx-nav-next { padding: 0.5rem; }
}
/* ── Zone sections — relative pour ancrer le sidebar ── */
.sections-area {
position: relative;
}
/* ── Sidebar sommaire — position absolue, ne décale pas le contenu ── */
.page-sidebar {
display: none; /* caché par défaut (mobile) */
position: absolute;
top: 3.5rem;
right: 0;
width: 10.5rem;
height: 100%;
}
/* Activé uniquement quand il y a assez de place (>= largeur max-w-4xl + sidebar) */
@media (min-width: 1300px) {
.page-sidebar {
display: block;
}
}
.sommaire-sidebar {
position: sticky;
top: 5.5rem;
padding: 0.875rem;
border-radius: 14px;
background: hsl(var(--color-surface));
border: 1px solid hsl(var(--color-text) / 0.07);
}
.sommaire-sidebar-title {
font-family: var(--font-mono);
font-size: 0.65rem;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
color: hsl(var(--color-text-muted));
margin: 0 0 0.6rem;
}
.sommaire-sidebar-list {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 0.1rem;
}
.sommaire-sidebar-link {
display: flex;
align-items: baseline;
gap: 0.45rem;
padding: 0.28rem 0.4rem;
border-radius: 8px;
text-decoration: none;
font-size: 0.75rem;
color: hsl(var(--color-text-muted));
line-height: 1.4;
transition: color 0.12s, background 0.12s;
}
.sommaire-sidebar-link:hover {
color: hsl(var(--color-primary));
background: hsl(var(--color-primary) / 0.06);
}
.sommaire-n {
display: inline-flex;
align-items: center;
justify-content: center;
width: 1.2rem;
height: 1.2rem;
border-radius: 50%;
background: hsl(var(--color-primary) / 0.1);
color: hsl(var(--color-primary));
font-family: var(--font-mono);
font-size: 0.6rem;
font-weight: 700;
flex-shrink: 0;
}
/* ── Section titles ── */
.section-title {
font-family: var(--font-display);
font-size: clamp(1.1rem, 2.5vw, 1.35rem);
font-weight: 700;
color: hsl(var(--color-text));
margin: 0 0 1.25rem;
padding-bottom: 0.5rem;
border-bottom: 2px solid hsl(var(--color-primary) / 0.15);
}
/* ── Arguments ── */
.args-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 0.875rem;
}
.arg-card {
padding: 1rem 1.1rem;
border-radius: 14px;
background: hsl(var(--color-surface));
border: 1px solid hsl(var(--color-primary) / 0.08);
transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.arg-card:hover {
transform: translateY(-3px);
box-shadow: 0 8px 24px hsl(var(--color-primary) / 0.1);
}
.arg-icon {
width: 2rem;
height: 2rem;
border-radius: 8px;
background: hsl(var(--color-primary) / 0.1);
color: hsl(var(--color-primary));
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 0.6rem;
}
.arg-title {
font-family: var(--font-display);
font-size: 0.9rem;
font-weight: 600;
color: hsl(var(--color-text));
margin: 0 0 0.4rem;
}
.arg-text {
font-size: 0.82rem;
line-height: 1.55;
color: hsl(var(--color-text-muted));
margin: 0;
}
/* ── Fiche steps ── */
.fiche-steps {
display: flex;
flex-direction: column;
gap: 0;
}
.fiche-step {
display: flex;
gap: 1rem;
padding: 1rem 0;
border-bottom: 1px solid hsl(var(--color-text) / 0.06);
}
.fiche-step:last-child {
border-bottom: none;
}
.step-num {
width: 2rem;
height: 2rem;
border-radius: 50%;
background: hsl(var(--color-primary) / 0.12);
color: hsl(var(--color-primary));
font-family: var(--font-mono);
font-size: 0.8rem;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
margin-top: 0.1rem;
}
.step-body {
flex: 1;
}
.step-title {
font-family: var(--font-display);
font-size: 0.95rem;
font-weight: 600;
color: hsl(var(--color-text));
margin: 0 0 0.3rem;
}
.step-text {
font-size: 0.85rem;
line-height: 1.55;
color: hsl(var(--color-text-muted));
margin: 0;
}
.step-tip {
margin: 0.5rem 0 0;
font-size: 0.78rem;
color: hsl(var(--color-accent));
font-style: italic;
}
/* ── Equivalents ── */
.equiv-categories {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.equiv-cat-label {
font-family: var(--font-mono);
font-size: 0.72rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: hsl(var(--color-primary));
margin: 0 0 0.5rem;
}
.equiv-rows {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.equiv-row {
display: grid;
grid-template-columns: 1fr auto 1fr 1.5fr auto;
align-items: center;
gap: 0.5rem 0.75rem;
padding: 0.5rem 0.75rem;
border-radius: 10px;
background: hsl(var(--color-surface));
border: 1px solid transparent;
text-decoration: none;
transition: border-color 0.12s, background 0.12s;
}
.equiv-row:hover {
border-color: hsl(var(--color-primary) / 0.2);
background: hsl(var(--color-primary) / 0.04);
}
.equiv-from {
font-size: 0.82rem;
color: hsl(var(--color-text-muted));
text-decoration: line-through;
text-decoration-color: hsl(var(--color-text) / 0.2);
}
.equiv-arrow {
font-size: 0.75rem;
color: hsl(var(--color-primary) / 0.4);
}
.equiv-to {
font-size: 0.85rem;
font-weight: 600;
color: hsl(var(--color-primary));
}
.equiv-note {
font-size: 0.78rem;
color: hsl(var(--color-text-muted));
}
.equiv-ext {
color: hsl(var(--color-primary) / 0.4);
flex-shrink: 0;
}
@media (max-width: 600px) {
.equiv-row {
grid-template-columns: 1fr auto 1fr;
grid-template-rows: auto auto;
}
.equiv-note { grid-column: 1 / -1; font-size: 0.74rem; }
.equiv-ext { display: none; }
}
/* ── LLM ── */
.llm-intro {
font-size: 0.9rem;
line-height: 1.6;
color: hsl(var(--color-text-muted));
margin: 0 0 1rem;
}
.llm-meta {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-bottom: 1rem;
}
.llm-badge {
display: inline-flex;
align-items: center;
gap: 0.35rem;
padding: 0.25rem 0.7rem;
border-radius: 20px;
font-size: 0.78rem;
font-weight: 500;
font-family: var(--font-mono);
background: hsl(var(--color-primary) / 0.1);
color: hsl(var(--color-primary));
}
.llm-commands {
display: flex;
flex-direction: column;
gap: 0.35rem;
margin-bottom: 1rem;
}
.llm-cmd {
display: block;
padding: 0.5rem 0.875rem;
border-radius: 10px;
background: hsl(var(--color-bg));
border: 1px solid hsl(var(--color-surface-light));
font-family: var(--font-mono);
font-size: 0.78rem;
color: hsl(var(--color-text));
white-space: pre;
overflow-x: auto;
}
.llm-rules {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 0.4rem;
}
.llm-rule {
display: flex;
align-items: flex-start;
gap: 0.5rem;
font-size: 0.82rem;
color: hsl(var(--color-text-muted));
line-height: 1.5;
}
.llm-rule .i-lucide-shield-check {
color: hsl(var(--color-accent));
}
/* ── Atelier ── */
.atelier-text {
font-size: 0.9rem;
line-height: 1.6;
color: hsl(var(--color-text-muted));
margin: 0 0 0.75rem;
}
.atelier-format {
font-size: 0.82rem;
font-family: var(--font-mono);
color: hsl(var(--color-primary));
margin: 0 0 1rem;
}
.atelier-programme {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 0;
}
.atelier-item {
display: flex;
align-items: flex-start;
gap: 0.625rem;
padding: 0.625rem 0;
border-bottom: 1px solid hsl(var(--color-text) / 0.06);
font-size: 0.85rem;
color: hsl(var(--color-text));
}
.atelier-item:last-child { border-bottom: none; }
/* ── Links ── */
.links-list {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.link-row {
display: grid;
grid-template-columns: 9rem 1fr auto;
align-items: center;
gap: 0.75rem;
padding: 0.625rem 0.875rem;
border-radius: 10px;
text-decoration: none;
background: hsl(var(--color-surface));
border: 1px solid transparent;
transition: border-color 0.12s, background 0.12s;
}
.link-row:hover {
border-color: hsl(var(--color-primary) / 0.2);
background: hsl(var(--color-primary) / 0.04);
}
.link-label {
font-size: 0.85rem;
font-weight: 600;
color: hsl(var(--color-primary));
}
.link-desc {
font-size: 0.8rem;
color: hsl(var(--color-text-muted));
}
@media (max-width: 480px) {
.link-row { grid-template-columns: 1fr auto; grid-template-rows: auto auto; }
.link-desc { grid-column: 1; font-size: 0.74rem; }
}
/* ── Insight ── */
.insight-box {
padding: 1.25rem 1.5rem;
border-radius: 14px;
background: hsl(var(--color-surface));
border-left: 3px solid hsl(var(--color-primary) / 0.5);
}
.insight-box--warning {
border-left-color: hsl(var(--color-accent) / 0.6);
background: hsl(var(--color-accent) / 0.05);
}
.insight-header {
display: flex;
align-items: flex-start;
gap: 0.6rem;
margin-bottom: 0.75rem;
color: hsl(var(--color-primary));
}
.insight-box--warning .insight-header {
color: hsl(var(--color-accent));
}
.insight-title {
font-family: var(--font-display);
font-size: 1rem;
font-weight: 700;
color: inherit;
margin: 0;
line-height: 1.3;
}
.insight-text {
font-size: 0.875rem;
line-height: 1.6;
color: hsl(var(--color-text-muted));
margin: 0 0 0.75rem;
}
.insight-points {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 0.35rem;
}
.insight-points li {
font-size: 0.82rem;
color: hsl(var(--color-text-muted));
padding-left: 1rem;
position: relative;
line-height: 1.5;
}
.insight-points li::before {
content: '·';
position: absolute;
left: 0;
color: hsl(var(--color-primary) / 0.5);
font-weight: 700;
}
/* ── Tiers ── */
.tiers-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
}
@media (max-width: 768px) {
.tiers-grid { grid-template-columns: 1fr; }
}
.tier-card {
padding: 1.25rem;
border-radius: 14px;
background: hsl(var(--color-surface));
border: 1px solid hsl(var(--color-primary) / 0.08);
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.tier-top {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.25rem;
}
.tier-level {
font-family: var(--font-mono);
font-size: 0.7rem;
font-weight: 600;
color: hsl(var(--color-text-muted));
text-transform: uppercase;
letter-spacing: 0.06em;
}
.tier-badge {
font-size: 0.7rem;
font-weight: 500;
padding: 0.15rem 0.5rem;
border-radius: 20px;
background: hsl(var(--color-primary) / 0.1);
color: hsl(var(--color-primary));
}
.tier-icon {
width: 2rem;
height: 2rem;
border-radius: 8px;
background: hsl(var(--color-primary) / 0.1);
color: hsl(var(--color-primary));
display: flex;
align-items: center;
justify-content: center;
}
.tier-title {
font-family: var(--font-display);
font-size: 0.95rem;
font-weight: 700;
color: hsl(var(--color-text));
margin: 0;
}
.tier-text {
font-size: 0.8rem;
line-height: 1.55;
color: hsl(var(--color-text-muted));
margin: 0;
flex: 1;
}
.tier-tools {
list-style: none;
padding: 0;
margin: 0.25rem 0 0;
display: flex;
flex-direction: column;
gap: 0.2rem;
border-top: 1px solid hsl(var(--color-text) / 0.06);
padding-top: 0.5rem;
}
.tier-tools li {
font-size: 0.76rem;
color: hsl(var(--color-text-muted));
padding-left: 0.75rem;
position: relative;
}
.tier-tools li::before {
content: '→';
position: absolute;
left: 0;
color: hsl(var(--color-primary) / 0.4);
font-size: 0.65rem;
}
/* ── Projet (gestation) ── */
.projet-card {
padding: 1.75rem;
border-radius: 16px;
background: hsl(var(--color-surface));
border: 1px solid hsl(var(--color-accent) / 0.2);
box-shadow: 0 0 32px hsl(var(--color-accent) / 0.05);
}
.projet-header {
display: flex;
align-items: flex-start;
gap: 1rem;
margin-bottom: 1rem;
}
.projet-icon {
width: 3rem;
height: 3rem;
border-radius: 12px;
background: hsl(var(--color-accent) / 0.12);
color: hsl(var(--color-accent));
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.projet-header-text {
flex: 1;
}
.projet-kicker {
font-family: var(--font-mono);
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.1em;
text-transform: uppercase;
color: hsl(var(--color-accent));
margin: 0 0 0.2rem;
}
.projet-title {
font-family: var(--font-display);
font-size: clamp(1.25rem, 3vw, 1.6rem);
font-weight: 800;
color: hsl(var(--color-text));
margin: 0;
}
.projet-badge {
display: inline-flex;
align-items: center;
gap: 0.25rem;
padding: 0.2rem 0.6rem;
border-radius: 20px;
background: hsl(var(--color-accent) / 0.12);
color: hsl(var(--color-accent));
font-size: 0.7rem;
font-weight: 500;
font-family: var(--font-mono);
white-space: nowrap;
flex-shrink: 0;
}
.projet-text {
font-size: 0.875rem;
line-height: 1.65;
color: hsl(var(--color-text-muted));
margin: 0 0 1.25rem;
}
.projet-features {
list-style: none;
padding: 0;
margin: 0;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 0.5rem;
border-top: 1px solid hsl(var(--color-text) / 0.06);
padding-top: 1rem;
}
.projet-feature {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.82rem;
color: hsl(var(--color-text-muted));
padding: 0.4rem 0.6rem;
border-radius: 8px;
background: hsl(var(--color-bg) / 0.5);
}
.projet-feature .i-lucide-hard-drive,
.projet-feature .i-lucide-video,
.projet-feature .i-lucide-message-circle,
.projet-feature .i-lucide-bot,
.projet-feature .i-lucide-globe-2,
.projet-feature .i-lucide-share-2 {
color: hsl(var(--color-accent));
}
/* ── Territoire ── */
.territoire-card {
border-radius: 16px;
background: hsl(var(--color-surface));
border: 1px solid hsl(var(--color-primary) / 0.12);
overflow: hidden;
}
.territoire-header {
display: flex;
align-items: flex-start;
gap: 1rem;
padding: 1.75rem 1.75rem 1.25rem;
}
.territoire-icon {
width: 3rem;
height: 3rem;
border-radius: 12px;
background: hsl(var(--color-primary) / 0.1);
color: hsl(var(--color-primary));
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
margin-top: 0.1rem;
}
.territoire-title {
font-family: var(--font-display);
font-size: clamp(1.15rem, 2.5vw, 1.4rem);
font-weight: 800;
color: hsl(var(--color-text));
margin: 0 0 0.5rem;
}
.territoire-text {
font-size: 0.875rem;
line-height: 1.65;
color: hsl(var(--color-text-muted));
margin: 0;
}
/* Modèles économiques */
.territoire-economies {
display: flex;
gap: 0.75rem;
padding: 0 1.75rem 1.5rem;
flex-wrap: wrap;
}
.territoire-eco {
flex: 1;
min-width: 220px;
display: flex;
align-items: flex-start;
gap: 0.75rem;
padding: 0.875rem 1rem;
border-radius: 12px;
background: hsl(var(--color-bg) / 0.6);
border: 1px solid hsl(var(--color-primary) / 0.1);
}
.territoire-eco-icon {
width: 2rem;
height: 2rem;
border-radius: 8px;
background: hsl(var(--color-primary) / 0.12);
color: hsl(var(--color-primary));
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.territoire-g1 {
font-family: var(--font-display);
font-weight: 700;
font-size: 1rem;
}
.territoire-eco-label {
font-size: 0.82rem;
font-weight: 700;
color: hsl(var(--color-text));
margin: 0 0 0.2rem;
}
.territoire-eco-desc {
font-size: 0.78rem;
line-height: 1.5;
color: hsl(var(--color-text-muted));
margin: 0;
}
/* Bouquet */
.territoire-bouquet {
padding: 1.25rem 1.75rem;
border-top: 1px solid hsl(var(--color-text) / 0.06);
}
.territoire-bouquet-title {
display: flex;
align-items: center;
gap: 0.5rem;
font-family: var(--font-mono);
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: hsl(var(--color-primary));
margin: 0 0 1rem;
}
.territoire-bouquet-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 0.75rem;
}
.territoire-bouquet-cat {
background: hsl(var(--color-bg) / 0.5);
border-radius: 10px;
padding: 0.75rem;
}
.territoire-bouquet-cat-header {
display: flex;
align-items: center;
gap: 0.375rem;
font-size: 0.75rem;
font-weight: 700;
color: hsl(var(--color-accent));
margin-bottom: 0.5rem;
}
.territoire-bouquet-items {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 0.2rem;
}
.territoire-bouquet-items li {
font-size: 0.76rem;
color: hsl(var(--color-text-muted));
line-height: 1.4;
padding-left: 0.6rem;
position: relative;
}
.territoire-bouquet-items li::before {
content: '›';
position: absolute;
left: 0;
color: hsl(var(--color-primary) / 0.5);
}
.project-action {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.5rem 1rem;
}
.project-action-note {
font-family: var(--font-mono);
font-size: 0.72rem;
color: hsl(var(--axis-cit));
}
/* Bouquet — status of each stack */
.territoire-bouquet-provider {
font-size: 0.8rem;
color: hsl(var(--color-text-muted));
margin: -0.25rem 0 0.75rem;
}
.bouquet-legend {
list-style: none;
display: flex;
flex-wrap: wrap;
gap: 0.35rem 1rem;
padding: 0;
margin: 0 0 1rem;
font-family: var(--font-mono);
font-size: 0.7rem;
color: hsl(var(--color-text-muted));
}
.bouquet-legend li {
display: inline-flex;
align-items: center;
gap: 0.4rem;
}
.territoire-bouquet-items li.bouquet-item {
padding-left: 0;
display: grid;
grid-template-columns: 0.5rem minmax(0, 1fr);
column-gap: 0.45rem;
align-items: baseline;
}
.bouquet-item > .bouquet-dot { grid-row: 1; align-self: center; }
.bouquet-item > .bouquet-role { grid-column: 2; }
.bouquet-item > .bouquet-name { grid-column: 2; }
.territoire-bouquet-items li.bouquet-item::before { content: none; }
.bouquet-dot {
display: inline-block;
flex-shrink: 0;
width: 0.5rem;
height: 0.5rem;
border-radius: 50%;
align-self: center;
}
.bouquet-dot--service { background: hsl(var(--axis-num)); }
.bouquet-dot--catalogue { box-shadow: inset 0 0 0 1.5px hsl(var(--axis-num)); }
.bouquet-dot--avenir { box-shadow: inset 0 0 0 1px hsl(var(--color-text) / 0.3); }
.bouquet-role { color: hsl(var(--color-text-muted)); }
.bouquet-name {
color: hsl(var(--color-text));
font-weight: 600;
}
a.bouquet-name:hover { color: hsl(var(--axis-num)); }
.bouquet-note {
margin: 0.4rem 0 0;
font-size: 0.72rem;
font-style: italic;
color: hsl(var(--color-text-muted));
}
/* Estimation toggled */
.territoire-estimate {
border-top: 1px solid hsl(var(--color-text) / 0.06);
}
.territoire-estimate-toggle {
display: flex;
align-items: center;
gap: 0.6rem;
padding: 1rem 1.75rem;
cursor: pointer;
list-style: none;
font-size: 0.85rem;
font-weight: 600;
color: hsl(var(--color-text) / 0.7);
transition: color 0.15s;
user-select: none;
}
.territoire-estimate-toggle:hover {
color: hsl(var(--color-primary));
}
.territoire-estimate-toggle::-webkit-details-marker { display: none; }
.territoire-chevron {
margin-left: auto;
transition: transform 0.2s;
}
details[open] .territoire-chevron {
transform: rotate(180deg);
}
.territoire-estimate-body {
padding: 0 1.75rem 1.75rem;
}
.territoire-estimate-note {
font-size: 0.8rem;
color: hsl(var(--color-text-muted));
font-style: italic;
margin: 0 0 1.25rem;
line-height: 1.55;
}
/* HW table */
.territoire-hw-table {
display: flex;
flex-direction: column;
gap: 0;
border-radius: 12px;
overflow: hidden;
border: 1px solid hsl(var(--color-text) / 0.06);
margin-bottom: 1.25rem;
}
.territoire-hw-header {
display: grid;
grid-template-columns: 1.5fr 3fr 1.2fr;
gap: 0.75rem;
padding: 0.5rem 1rem;
background: hsl(var(--color-bg) / 0.8);
font-family: var(--font-mono);
font-size: 0.7rem;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
color: hsl(var(--color-text-muted));
border-bottom: 1px solid hsl(var(--color-text) / 0.06);
}
.territoire-hw-row {
display: grid;
grid-template-columns: 1.5fr 3fr 1.2fr;
gap: 0.75rem;
padding: 0.75rem 1rem;
border-bottom: 1px solid hsl(var(--color-text) / 0.04);
align-items: start;
}
.territoire-hw-row:last-child { border-bottom: none; }
.territoire-hw-row:nth-child(even) { background: hsl(var(--color-bg) / 0.3); }
.territoire-hw-role {
font-size: 0.82rem;
font-weight: 600;
color: hsl(var(--color-text));
line-height: 1.4;
}
.territoire-hw-detail {
display: flex;
flex-direction: column;
gap: 0.15rem;
}
.territoire-hw-detail span:first-child {
font-size: 0.82rem;
color: hsl(var(--color-text) / 0.85);
}
.territoire-hw-usage {
font-size: 0.75rem;
color: hsl(var(--color-text-muted));
line-height: 1.4;
}
.territoire-hw-note {
font-size: 0.72rem;
color: hsl(var(--color-accent) / 0.8);
font-style: italic;
line-height: 1.4;
}
.territoire-hw-cost {
font-family: var(--font-mono);
font-size: 0.8rem;
font-weight: 600;
color: hsl(var(--color-primary));
white-space: nowrap;
}
/* Totaux */
.territoire-totals {
border-radius: 12px;
overflow: hidden;
border: 1px solid hsl(var(--color-primary) / 0.15);
}
.territoire-total-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.6rem 1rem;
border-bottom: 1px solid hsl(var(--color-primary) / 0.08);
}
.territoire-total-row:last-of-type { border-bottom: none; }
.territoire-total-row--highlight {
background: hsl(var(--color-primary) / 0.06);
}
.territoire-total-label {
font-size: 0.82rem;
color: hsl(var(--color-text-muted));
}
.territoire-total-value {
font-family: var(--font-mono);
font-size: 0.88rem;
font-weight: 700;
color: hsl(var(--color-primary));
}
.territoire-totals-note {
padding: 0.6rem 1rem;
font-size: 0.8rem;
color: hsl(var(--color-text-muted));
font-style: italic;
border-top: 1px solid hsl(var(--color-primary) / 0.08);
margin: 0;
background: hsl(var(--color-primary) / 0.03);
}
@media (max-width: 640px) {
.territoire-header { flex-direction: column; gap: 0.75rem; }
.territoire-hw-header { display: none; }
.territoire-hw-row { grid-template-columns: 1fr; gap: 0.25rem; }
.territoire-hw-cost { font-size: 0.85rem; }
.territoire-bouquet-grid { grid-template-columns: 1fr; }
}
/* overflow:clip coupe sans créer de scroll container — préserve position:sticky */
.page-outer {
position: relative;
overflow: clip;
}
/* equiv-row without link */
.equiv-row--no-link {
cursor: default;
opacity: 0.75;
}
.equiv-row--no-link:hover {
border-color: transparent;
background: hsl(var(--color-surface));
}
</style>