Files
librodrome/app/pages/numerique/[slug].vue
T
YvvandClaude Opus 5.5 bb8fad9ff3 identité 5 : la liste fermée des règles mesurées, appliquée à toutes les pages
Ordre IDENTITE-DA-5 (03/11 14h41, relayé par la session orch ; seuils confirmés par DA) :
- docs/da-oiseaux.md : « Règles mesurées (liste fermée) », règles a à i avec leurs seuils, telles
  que le contrôle les mesure (pixels peints, ligne de base), et la phrase juste sur l'acrobate ;
- UiOiseauPose : pose `bout`, une ancre sans taille dans le texte, sur sa ligne de base, pour un
  oiseau au bout (ou au début) d'un titre ;
- au bout de leur titre : trapéziste, clown, marionnettiste, DJ (/en-musique), droneuse, vigie,
  alchimiste, relieur, et l'oiseau de l'intitulé au téléphone ;
- authentification : le pompeur de Ğ1 perché sur la carte du projet (plus de colonne en marge) ;
- accueil : l'accordéoniste au bout du titre du format (plus de paire avec la lectrice, plus de
  trait sur le lien) ;
- soudeuse sur la ligne de base de son titre ; administrateur réseau et cartographe sortent par le
  côté de leur carte, pas par un coin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 15:07:35 +01:00

1754 lines
48 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: {
// feet on the last line of the steps: the block ends with the step's padding (16 px)
1: [{ nom: 'debogueur', pose: 'marge-g', ecart: '24px', bottom: '16px', w: 132 }],
// sections 2 (and cloud-libre 1 and 5) are wide: 152 px of margin at 1200 px
2: [{ nom: 'le-manchot', pose: 'marge-g', ecart: '40px', w: 84 }],
3: [{ nom: 'canard-en-plastique', pose: 'marge-g', ecart: '110px', w: 76 }],
},
},
'authentification-wot': {
projet: [
{ nom: 'porteur-de-serveur', pose: 'derriere', left: '-130px', bottom: '-30px', w: 210 },
// the Ğ1 pump (Duniter) perched on the project card: 74 px of void above it at every width,
// and the left margin keeps no column of three (rule d)
{ nom: 'pompeur-de-g1', pose: 'perche', right: '14px', w: 96 },
],
sections: {
// the steps have no background: in the margin, on their last line, small and close to the
// text, out of the band shared by the server carrier and the pump
1: [{ nom: 'toile-de-confiance', pose: 'marge-g', ecart: '24px', bottom: '16px', w: 76 }],
// not right under the server carrier and the web of trust: no column of three
2: [{ nom: 'cryptographe', pose: 'marge-g', ecart: '96px', w: 84 }],
},
},
'cloud-libre': {
projet: [{ nom: 'porteur-de-serveur', pose: 'derriere', left: '-130px', bottom: '-30px', w: 210 }],
sections: {
1: [{ nom: 'admin-reseau', pose: 'derriere', left: '-110px', bottom: '-20px', w: 220 }],
5: [{ nom: 'imprimeuse-3d', pose: 'marge-g', ecart: '48px', w: 84 }],
},
},
}
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>