Files
librodrome/app/pages/numerique/[slug].vue
T
YvvandClaude Opus 5.5 28a6daf382 identité 6 : la règle f mesurée au-dessus du dessin, les chapitres posés sur leurs filets, les coins arrondis dégagés
Règle f, selon la précision de DA du 03/11 après 17h35 : au-dessus d'un oiseau perché, le vide
se mesure du haut de son dessin jusqu'à la plus basse ligne de texte ou au plus bas bloc qui
passe au-dessus de sa largeur peinte. Les lignes de son propre titre comptent, et l'en-tête
collant aussi en haut de page. Quinze perchés en défaut sur bb8fad9 changent de pose, sans
sortir de la liste fermée :
- accueil :
  - le DJ se tient derrière le pupitre de la console, seule sa tête dépasse ;
  - l'accordéoniste passe en S, visible à partir de 1200 px ;
  - la factrice passe à 76 px ;
  - la lectrice sort 20 px plus bas ;
- au téléphone, l'oiseau de l'intitulé est un oiseau plat : codeuse, maraîchère, potier, et le
  cycliste livreur sur productions collectives ;
- authentification, tarifs de l'eau, commande : en marge droite, sur l'arête haute de la carte ;
- en musique : le danseur passe en S ;
- évènement :
  - l'acrobate se tient avant la devise ;
  - le régisseur passe en S, posé sur l'arête des cartes ;
  - le clown passe en M ;
  - le marionnettiste se tient sur le filet du pied de page (option 1 d'orch). Son décalage
    est tiré de --seal-block, le bloc du sceau défini dans le layout ;
- chapitres : le calligraphe se tient sur le filet du titre, le typographe sur celui de la
  navigation, le cartonnier en marge des boutons. Plus aucune hauteur en %.

Règle g, rectangle du bloc (décision d'orch) : le porteur de serveur et l'administrateur réseau
sortent de la découpe des coins arrondis.

La liste fermée (docs/da-oiseaux.md) reçoit les précisions d'orch :
- e vaut pour un oiseau « libre » dont le centre sort de son bloc ;
- g se mesure sur le rectangle du bloc ;
- un oiseau derrière une carte reste « derrière ».

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 19:24:44 +01:00

1758 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: [
// behind the card, out of its bottom-left corner: no painted pixel in the cut of the rounded
// corner, inside the card's rectangle (rule g, orch 03/11 after 17h58)
{ nom: 'porteur-de-serveur', pose: 'derriere', left: '-142px', bottom: '-24px', w: 210 },
// the Ğ1 pump (Duniter) in the right margin, feet on the card's top edge: perched on the
// card it stood 11 px under the third line of the introduction (rule f). The summary starts
// under the card, and the left margin keeps no column of three (rule d)
{ nom: 'pompeur-de-g1', pose: 'marge-d', ecart: '24px', bottom: '100%', 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': {
// both out of the cut of their card's rounded corner (rule g, as on the web of trust)
projet: [{ nom: 'porteur-de-serveur', pose: 'derriere', left: '-142px', bottom: '-24px', w: 210 }],
sections: {
1: [{ nom: 'admin-reseau', pose: 'derriere', left: '-110px', bottom: '-32px', 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>