Files
librodrome/app/pages/numerique/[slug].vue
T
YvvandClaude Opus 5.5 fb8c71fc59 identité 2 : un oiseau perché seulement où il y a du vide au-dessus, l'en-tête tient à 768 px
Avis du studio DA du 03/11 (07h00) et relecture de 8ccdda3 :
- règle du perché : il faut au-dessus de l'arête un vide au moins égal à la hauteur
  de l'oiseau, sinon il va en marge ou derrière la carte. Les perchés entre cartes
  rapprochées (numérique, logiciel-libre, économique, sommaire du livre, commande,
  citoyenne, événement) passent en marge, derrière, ou partent ;
- productions-collectives : 8 oiseaux ramenés à 4, sans colonne ni chevauchement.
  Les oiseaux retirés vont sur des pages qui en avaient peu : monnaie-libre
  (boulangère, vigneronne), tarifs-eau (potier), décision (tisserand),
  cloud-libre (imprimeuse 3D). Les 61 restent rendus ;
- pas trois oiseaux sur la même verticale (commande, numérique) ;
- au téléphone, l'oiseau de la page se tient au bout de la ligne de l'intitulé ;
- seuil 1024 pour un perché dont le bloc n'a de vide au-dessus qu'en deux colonnes
  (accordéoniste et factrice de l'accueil) ;
- numerique/[slug] : les oiseaux des sections larges tiennent dans 152 px de marge
  à 1200 px ;
- en-tête : de 768 à 1023 px, boutons resserrés au lieu d'un débordement ; le
  logotype du kit ne change pas.

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

1746 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 }],
// 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: '28px', bottom: '18%', w: 108 }],
3: [{ nom: 'canard-en-plastique', pose: 'marge-g', ecart: '72px', bottom: '6%', w: 84 }],
},
},
'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: '28px', bottom: '40%', w: 104 }],
5: [{ nom: 'imprimeuse-3d', pose: 'marge-g', ecart: '28px', bottom: '62%', w: 100 }],
},
},
}
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>