Add dark mode palettes + Woodpecker CI pipeline
- Add 2 dark palettes (Nuit, Ocean) to DisplaySettings with full SVG theme tokens — all hardcoded SVG colors (grids, legends, text fills, pills, dot strokes, drag handles) replaced with reactive bindings - Update scoped CSS to use var(--color-*) and var(--svg-*) throughout - Add Woodpecker CI pipeline (.woodpecker.yml): build → docker push → deploy - Add multi-stage Dockerfiles for backend (Python) and frontend (Nuxt) - Add production docker-compose with Traefik labels + dev override - Remove old single-stage Dockerfiles and root docker-compose.yml - Update Makefile with docker-dev target - Exclude data files (pdf, xls, ipynb) from git Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -65,7 +65,7 @@
|
||||
<input type="number" v-model.number="citizenAbo" min="0" step="10" class="abo-input" /> €
|
||||
</label>
|
||||
<span class="zoom-separator"></span>
|
||||
<span class="zoom-info" style="font-weight: 500; color: #475569;">
|
||||
<span class="zoom-info" style="font-weight: 500;" :style="{ color: t.textCount }">
|
||||
Recettes : {{ Math.round(recettes).toLocaleString() }} €
|
||||
</span>
|
||||
</div>
|
||||
@@ -92,16 +92,16 @@
|
||||
|
||||
<!-- Background -->
|
||||
<rect :x="margin.left" :y="margin.top" :width="plotW" :height="plotH"
|
||||
fill="#f8fafc" rx="4" />
|
||||
:fill="t.plotBg" rx="4" />
|
||||
|
||||
<!-- Grid -->
|
||||
<g>
|
||||
<line v-for="v in gridVols" :key="'gv'+v"
|
||||
:x1="cx(v)" :y1="margin.top" :x2="cx(v)" :y2="margin.top + plotH"
|
||||
stroke="#e2e8f0" stroke-width="0.5" />
|
||||
:stroke="t.grid" stroke-width="0.5" />
|
||||
<line v-for="p in gridPrices" :key="'gp'+p"
|
||||
:x1="margin.left" :y1="cy(p)" :x2="margin.left + plotW" :y2="cy(p)"
|
||||
stroke="#e2e8f0" stroke-width="0.5" />
|
||||
:stroke="t.grid" stroke-width="0.5" />
|
||||
<!-- Volume labels (bottom) -->
|
||||
<text v-for="v in gridVols" :key="'lv'+v"
|
||||
:x="cx(v)" :y="margin.top + plotH + 16" text-anchor="middle"
|
||||
@@ -113,11 +113,11 @@
|
||||
<text v-for="bk in visibleBuckets30Main" :key="'hc'+bk.low"
|
||||
:x="(cx(bk.low) + cx(bk.high)) / 2"
|
||||
:y="margin.top + plotH + 32"
|
||||
text-anchor="middle" font-size="9.5" fill="#64748b" font-weight="500">
|
||||
text-anchor="middle" font-size="9.5" :fill="t.textMuted" font-weight="500">
|
||||
{{ bk.count }}
|
||||
</text>
|
||||
<text :x="margin.left + plotW + 6" :y="margin.top + plotH + 32"
|
||||
text-anchor="start" font-size="8.5" fill="#64748b">foyers/30m³</text>
|
||||
text-anchor="start" font-size="8.5" :fill="t.textMuted">foyers/30m³</text>
|
||||
</g>
|
||||
<!-- Price labels (RIGHT side, since Y axis is on right at vol=0) -->
|
||||
<text v-for="p in gridPrices" :key="'lp'+p"
|
||||
@@ -160,11 +160,11 @@
|
||||
|
||||
<!-- Bezier curve: population (blue gradient, thick) -->
|
||||
<path :d="tier1Path" fill="none"
|
||||
:stroke="showHouseholds ? '#cbd5e1' : '#2563eb'"
|
||||
:stroke="showHouseholds ? (isDark ? '#475569' : '#cbd5e1') : '#2563eb'"
|
||||
:stroke-width="showHouseholds ? 2 : 3.5" stroke-linecap="round" />
|
||||
<!-- Bezier curve: cas exceptionnels (orange) -->
|
||||
<path :d="tier2Path" fill="none"
|
||||
:stroke="showHouseholds ? '#cbd5e1' : '#ea580c'"
|
||||
:stroke="showHouseholds ? (isDark ? '#475569' : '#cbd5e1') : '#ea580c'"
|
||||
:stroke-width="showHouseholds ? 2 : 2.5" stroke-linecap="round" />
|
||||
|
||||
<!-- Household dots on curves -->
|
||||
@@ -174,7 +174,7 @@
|
||||
r="3.5"
|
||||
:fill="hh.volume <= bp.vinf ? '#2563eb' : '#ea580c'"
|
||||
:opacity="0.65"
|
||||
stroke="white" stroke-width="0.8"
|
||||
:stroke="t.plotBg" stroke-width="0.8"
|
||||
/>
|
||||
</g>
|
||||
|
||||
@@ -185,8 +185,8 @@
|
||||
stroke="#94a3b8" stroke-width="1" stroke-dasharray="4 4" />
|
||||
|
||||
<!-- p0 label (pill style) -->
|
||||
<rect :x="cx(bp.vinf) + 8" :y="cy(localP0) - 18" width="180" height="20" rx="10" fill="white" stroke="#e2e8f0" />
|
||||
<text :x="cx(bp.vinf) + 98" :y="cy(localP0) - 5" text-anchor="middle" font-size="10.5" fill="#1e293b" font-weight="600">
|
||||
<rect :x="cx(bp.vinf) + 8" :y="cy(localP0) - 18" width="180" height="20" rx="10" :fill="t.pillBg" :stroke="t.pillBorder" />
|
||||
<text :x="cx(bp.vinf) + 98" :y="cy(localP0) - 5" text-anchor="middle" font-size="10.5" :fill="t.pillText" font-weight="600">
|
||||
Prix au palier : {{ localP0.toFixed(2) }} €/m³
|
||||
</text>
|
||||
|
||||
@@ -205,7 +205,7 @@
|
||||
<circle
|
||||
:cx="margin.left + 14" :cy="cy(medianPrice)"
|
||||
:r="dragging === 'medianBar' ? 10 : 7"
|
||||
fill="#059669" stroke="white" :stroke-width="dragging === 'medianBar' ? 3 : 2"
|
||||
fill="#059669" :stroke="t.plotBg" :stroke-width="dragging === 'medianBar' ? 3 : 2"
|
||||
class="drag-handle"
|
||||
@mousedown.prevent="startDrag('medianBar')"
|
||||
@touchstart.prevent="startDrag('medianBar')"
|
||||
@@ -219,7 +219,7 @@
|
||||
<circle v-for="(pt, key) in dragPoints" :key="key"
|
||||
:cx="cx(pt.x)" :cy="cy(pt.y)"
|
||||
:r="dragging === key ? 10 : 7"
|
||||
:fill="ptColors[key]" stroke="white" :stroke-width="dragging === key ? 3 : 2"
|
||||
:fill="ptColors[key]" :stroke="t.plotBg" :stroke-width="dragging === key ? 3 : 2"
|
||||
class="drag-handle"
|
||||
@mousedown.prevent="startDrag(key)"
|
||||
@touchstart.prevent="startDrag(key)"
|
||||
@@ -232,18 +232,18 @@
|
||||
|
||||
<!-- Legend box (top-right) -->
|
||||
<g :transform="`translate(${margin.left + plotW - 232}, ${margin.top + 8})`">
|
||||
<rect x="0" y="0" width="220" :height="citizenAbo > 0 ? 80 : 62" rx="6" fill="white" fill-opacity="0.92" stroke="#e2e8f0" />
|
||||
<rect x="0" y="0" width="220" :height="citizenAbo > 0 ? 80 : 62" rx="6" :fill="t.legendBg" fill-opacity="0.92" :stroke="t.legendBorder" />
|
||||
<line x1="10" y1="14" x2="28" y2="14" stroke="#2563eb" stroke-width="3" stroke-linecap="round" />
|
||||
<text x="34" y="18" font-size="11" fill="#334155">Consommations foyers</text>
|
||||
<text x="34" y="18" font-size="11" :fill="t.text">Consommations foyers</text>
|
||||
<line x1="10" y1="32" x2="28" y2="32" stroke="#ea580c" stroke-width="2.5" stroke-linecap="round" />
|
||||
<text x="34" y="36" font-size="11" fill="#334155">Consommations exceptionnelles</text>
|
||||
<text x="34" y="36" font-size="11" :fill="t.text">Consommations exceptionnelles</text>
|
||||
<line x1="10" y1="50" x2="28" y2="50" stroke="#059669" stroke-width="2" stroke-dasharray="6 3" stroke-linecap="round" />
|
||||
<text x="34" y="54" font-size="11" fill="#334155">
|
||||
<text x="34" y="54" font-size="11" :fill="t.text">
|
||||
Prix median ({{ medianPrice.toFixed(2) }}€/m³)
|
||||
</text>
|
||||
<g v-if="citizenAbo > 0">
|
||||
<line x1="10" y1="68" x2="28" y2="68" stroke="#059669" stroke-width="2" stroke-dasharray="3 2" stroke-linecap="round" />
|
||||
<text x="34" y="72" font-size="11" fill="#334155">Prix moyen avec abo.</text>
|
||||
<text x="34" y="72" font-size="11" :fill="t.text">Prix moyen avec abo.</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
@@ -268,7 +268,7 @@
|
||||
class="form-input" style="flex: 1; text-transform: uppercase; font-family: monospace; letter-spacing: 0.12em; font-size: 0.85rem;" />
|
||||
<button type="submit" class="btn btn-primary" :disabled="authLoading" style="padding: 0.35rem 0.75rem;">OK</button>
|
||||
</form>
|
||||
<p v-if="isDev" style="margin-top: 0.4rem; padding: 0.3rem 0.5rem; background: #fef3c7; border: 1px solid #f59e0b; border-radius: 5px; font-size: 0.7rem;">
|
||||
<p v-if="isDev" :style="{ marginTop: '0.4rem', padding: '0.3rem 0.5rem', background: t.devBg, border: '1px solid ' + t.devBorder, borderRadius: '5px', fontSize: '0.7rem', color: isDark ? '#fcd34d' : 'inherit' }">
|
||||
<strong>Dev:</strong> QPF5L9ZK (60m³)
|
||||
</p>
|
||||
</div>
|
||||
@@ -337,13 +337,13 @@
|
||||
<div class="chart-container">
|
||||
<svg :viewBox="`0 0 ${histW} ${histH}`" preserveAspectRatio="xMidYMid meet">
|
||||
<rect :x="histMargin.left" :y="histMargin.top" :width="histPlotW" :height="histPlotH"
|
||||
fill="#f8fafc" rx="4" />
|
||||
:fill="t.plotBg" rx="4" />
|
||||
|
||||
<!-- Y grid lines -->
|
||||
<g>
|
||||
<template v-for="y in histGridY" :key="'hgy'+y">
|
||||
<line :x1="histMargin.left" :y1="histCy(y)" :x2="histMargin.left + histPlotW" :y2="histCy(y)"
|
||||
stroke="#e2e8f0" stroke-width="0.5" />
|
||||
:stroke="t.grid" stroke-width="0.5" />
|
||||
<text :x="histMargin.left - 6" :y="histCy(y) + 4" text-anchor="end" class="axis-label-sm">{{ y }}</text>
|
||||
</template>
|
||||
</g>
|
||||
@@ -363,7 +363,7 @@
|
||||
<text v-if="bk.count > 0"
|
||||
:x="(histCx(bk.low) + histCx(bk.high)) / 2"
|
||||
:y="histCy(bk.count) - 4"
|
||||
text-anchor="middle" font-size="10" fill="#334155" font-weight="600">
|
||||
text-anchor="middle" font-size="10" :fill="t.text" font-weight="600">
|
||||
{{ bk.count }}
|
||||
</text>
|
||||
</g>
|
||||
@@ -387,11 +387,11 @@
|
||||
|
||||
<!-- Legend (top-right) -->
|
||||
<g :transform="`translate(${histMargin.left + histPlotW - 222}, ${histMargin.top + 6})`">
|
||||
<rect x="0" y="0" width="210" height="44" rx="6" fill="white" fill-opacity="0.9" stroke="#e2e8f0" />
|
||||
<rect x="0" y="0" width="210" height="44" rx="6" :fill="t.legendBg" fill-opacity="0.9" :stroke="t.legendBorder" />
|
||||
<rect x="10" y="8" width="14" height="10" rx="2" fill="#2563eb" opacity="0.7" />
|
||||
<text x="30" y="17" font-size="10" fill="#334155">Consommations foyers</text>
|
||||
<text x="30" y="17" font-size="10" :fill="t.text">Consommations foyers</text>
|
||||
<rect x="10" y="26" width="14" height="10" rx="2" fill="#ea580c" opacity="0.7" />
|
||||
<text x="30" y="35" font-size="10" fill="#334155">Consommations exceptionnelles</text>
|
||||
<text x="30" y="35" font-size="10" :fill="t.text">Consommations exceptionnelles</text>
|
||||
</g>
|
||||
|
||||
<!-- Axis titles -->
|
||||
@@ -409,7 +409,7 @@
|
||||
<!-- ═══════════════════════════════════════════════════════
|
||||
INDICATEURS CLES
|
||||
═══════════════════════════════════════════════════════ -->
|
||||
<div v-if="params" class="key-metrics-banner" style="margin-bottom: 1.5rem;">
|
||||
<div v-if="params" class="key-metrics-banner" :style="{ marginBottom: '1.5rem', background: t.metricBg }">
|
||||
<div class="key-metric key-metric-input">
|
||||
<span class="key-metric-value">{{ params.recettes.toLocaleString() }} €</span>
|
||||
<span class="key-metric-label">Recettes cibles</span>
|
||||
@@ -469,7 +469,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div style="border-top: 1px solid var(--color-border); padding-top: 0.75rem; margin-top: 0.5rem;">
|
||||
<p style="font-size: 0.82rem; color: #475569; font-weight: 600; margin-bottom: 0.35rem;">Votre vote donne :</p>
|
||||
<p style="font-size: 0.82rem; color: var(--color-text-muted); font-weight: 600; margin-bottom: 0.35rem;">Votre vote donne :</p>
|
||||
<div class="vote-result-row">
|
||||
<span>un prix au palier de</span>
|
||||
<strong>{{ localP0.toFixed(2) }} €/m³</strong>
|
||||
@@ -520,16 +520,16 @@
|
||||
<div class="baseline-charts">
|
||||
<!-- Left: Facture totale -->
|
||||
<div class="chart-container">
|
||||
<h4 style="text-align: center; font-size: 0.85rem; margin-bottom: 0.25rem; color: #475569;">Facture totale (€)</h4>
|
||||
<h4 style="text-align: center; font-size: 0.85rem; margin-bottom: 0.25rem; color: var(--color-text-muted);">Facture totale (€)</h4>
|
||||
<svg :viewBox="`0 0 ${W2} ${H2}`" preserveAspectRatio="xMidYMid meet">
|
||||
<rect :x="margin2.left" :y="margin2.top" :width="plotW2" :height="plotH2" fill="#f8fafc" rx="3" />
|
||||
<rect :x="margin2.left" :y="margin2.top" :width="plotW2" :height="plotH2" :fill="t.plotBg" rx="3" />
|
||||
<g>
|
||||
<line v-for="v in gridVols2" :key="'bg1v'+v"
|
||||
:x1="cx2(v)" :y1="margin2.top" :x2="cx2(v)" :y2="margin2.top + plotH2"
|
||||
stroke="#e2e8f0" stroke-width="0.5" />
|
||||
:stroke="t.grid" stroke-width="0.5" />
|
||||
<line v-for="b in gridBills" :key="'bg1b'+b"
|
||||
:x1="margin2.left" :y1="cy2bill(b)" :x2="margin2.left + plotW2" :y2="cy2bill(b)"
|
||||
stroke="#e2e8f0" stroke-width="0.5" />
|
||||
:stroke="t.grid" stroke-width="0.5" />
|
||||
<text v-for="v in gridVols2" :key="'bg1lv'+v"
|
||||
:x="cx2(v)" :y="margin2.top + plotH2 + 14" text-anchor="middle" class="axis-label-sm">{{ v }}</text>
|
||||
<text v-for="b in gridBills" :key="'bg1lb'+b"
|
||||
@@ -539,11 +539,11 @@
|
||||
<text v-for="bk in visibleBuckets30Baseline" :key="'b1hc'+bk.low"
|
||||
:x="(cx2(bk.low) + cx2(bk.high)) / 2"
|
||||
:y="margin2.top + plotH2 + 28"
|
||||
text-anchor="middle" font-size="9" fill="#64748b" font-weight="500">
|
||||
text-anchor="middle" font-size="9" :fill="t.textMuted" font-weight="500">
|
||||
{{ bk.count }}
|
||||
</text>
|
||||
<text :x="margin2.left + plotW2 + 4" :y="margin2.top + plotH2 + 28"
|
||||
text-anchor="start" font-size="8" fill="#64748b">foy.</text>
|
||||
text-anchor="start" font-size="8" :fill="t.textMuted">foy.</text>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
@@ -553,35 +553,35 @@
|
||||
</defs>
|
||||
<g clip-path="url(#baseline-clip-1)">
|
||||
<polyline :points="baselineBillRP" fill="none"
|
||||
:stroke="showHouseholds ? '#cbd5e1' : '#2563eb'" :stroke-width="showHouseholds ? 1.5 : 2" stroke-linecap="round" />
|
||||
:stroke="showHouseholds ? (isDark ? '#475569' : '#cbd5e1') : '#2563eb'" :stroke-width="showHouseholds ? 1.5 : 2" stroke-linecap="round" />
|
||||
<polyline v-if="differentiatedTariff" :points="baselineBillRS" fill="none"
|
||||
:stroke="showHouseholds ? '#e2e8f0' : '#f59e0b'" :stroke-width="showHouseholds ? 1.5 : 2" stroke-linecap="round" stroke-dasharray="6 3" />
|
||||
:stroke="showHouseholds ? (isDark ? '#334155' : '#e2e8f0') : '#f59e0b'" :stroke-width="showHouseholds ? 1.5 : 2" stroke-linecap="round" stroke-dasharray="6 3" />
|
||||
<!-- Household dots: bill = abo + p0_linear * volume -->
|
||||
<g v-if="showHouseholds && householdVolumes.length">
|
||||
<circle v-for="(hh, i) in householdDotsBill" :key="'hdb'+i"
|
||||
:cx="cx2(hh.volume)" :cy="cy2bill(hh.bill)"
|
||||
r="2.5" fill="#2563eb" opacity="0.5"
|
||||
stroke="white" stroke-width="0.5"
|
||||
:stroke="t.plotBg" stroke-width="0.5"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<text :x="W2/2" :y="margin2.top + plotH2 + 42" text-anchor="middle" class="axis-label-sm">m³ → reduire</text>
|
||||
<!-- Legend when differentiated (top-right) -->
|
||||
<g v-if="differentiatedTariff" :transform="`translate(${margin2.left + plotW2 - 92}, ${margin2.top + 4})`">
|
||||
<rect x="0" y="0" width="80" height="32" rx="4" fill="white" fill-opacity="0.9" stroke="#e2e8f0" />
|
||||
<rect x="0" y="0" width="80" height="32" rx="4" :fill="t.legendBg" fill-opacity="0.9" :stroke="t.legendBorder" />
|
||||
<line x1="6" y1="11" x2="18" y2="11" stroke="#2563eb" stroke-width="2" />
|
||||
<text x="22" y="14" font-size="8" fill="#334155">RP/PRO</text>
|
||||
<text x="22" y="14" font-size="8" :fill="t.text">RP/PRO</text>
|
||||
<line x1="6" y1="24" x2="18" y2="24" stroke="#f59e0b" stroke-width="2" stroke-dasharray="4 2" />
|
||||
<text x="22" y="27" font-size="8" fill="#334155">RS</text>
|
||||
<text x="22" y="27" font-size="8" :fill="t.text">RS</text>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- Right: Prix au m3 -->
|
||||
<div class="chart-container">
|
||||
<h4 style="text-align: center; font-size: 0.85rem; margin-bottom: 0.25rem; color: #475569;">Prix au m³ (€)</h4>
|
||||
<h4 style="text-align: center; font-size: 0.85rem; margin-bottom: 0.25rem; color: var(--color-text-muted);">Prix au m³ (€)</h4>
|
||||
<svg :viewBox="`0 0 ${W2} ${H2}`" preserveAspectRatio="xMidYMid meet">
|
||||
<rect :x="margin2.left" :y="margin2.top" :width="plotW2" :height="plotH2" fill="#f8fafc" rx="3" />
|
||||
<rect :x="margin2.left" :y="margin2.top" :width="plotW2" :height="plotH2" :fill="t.plotBg" rx="3" />
|
||||
<defs>
|
||||
<clipPath id="baseline-clip-2">
|
||||
<rect :x="margin2.left" :y="margin2.top" :width="plotW2" :height="plotH2" />
|
||||
@@ -590,10 +590,10 @@
|
||||
<g>
|
||||
<line v-for="v in gridVols2" :key="'bg2v'+v"
|
||||
:x1="cx2(v)" :y1="margin2.top" :x2="cx2(v)" :y2="margin2.top + plotH2"
|
||||
stroke="#e2e8f0" stroke-width="0.5" />
|
||||
:stroke="t.grid" stroke-width="0.5" />
|
||||
<line v-for="p in gridPrices2" :key="'bg2p'+p"
|
||||
:x1="margin2.left" :y1="cy2price(p)" :x2="margin2.left + plotW2" :y2="cy2price(p)"
|
||||
stroke="#e2e8f0" stroke-width="0.5" />
|
||||
:stroke="t.grid" stroke-width="0.5" />
|
||||
<text v-for="v in gridVols2" :key="'bg2lv'+v"
|
||||
:x="cx2(v)" :y="margin2.top + plotH2 + 14" text-anchor="middle" class="axis-label-sm">{{ v }}</text>
|
||||
<text v-for="p in gridPrices2" :key="'bg2lp'+p"
|
||||
@@ -603,41 +603,41 @@
|
||||
<text v-for="bk in visibleBuckets30Baseline" :key="'b2hc'+bk.low"
|
||||
:x="(cx2(bk.low) + cx2(bk.high)) / 2"
|
||||
:y="margin2.top + plotH2 + 28"
|
||||
text-anchor="middle" font-size="9" fill="#64748b" font-weight="500">
|
||||
text-anchor="middle" font-size="9" :fill="t.textMuted" font-weight="500">
|
||||
{{ bk.count }}
|
||||
</text>
|
||||
<text :x="margin2.left + plotW2 + 4" :y="margin2.top + plotH2 + 28"
|
||||
text-anchor="start" font-size="8" fill="#64748b">foy.</text>
|
||||
text-anchor="start" font-size="8" :fill="t.textMuted">foy.</text>
|
||||
</g>
|
||||
</g>
|
||||
<g clip-path="url(#baseline-clip-2)">
|
||||
<polyline :points="baselinePriceRP" fill="none"
|
||||
:stroke="showHouseholds ? '#cbd5e1' : '#2563eb'" :stroke-width="showHouseholds ? 1.5 : 2" stroke-linecap="round" />
|
||||
:stroke="showHouseholds ? (isDark ? '#475569' : '#cbd5e1') : '#2563eb'" :stroke-width="showHouseholds ? 1.5 : 2" stroke-linecap="round" />
|
||||
<polyline v-if="differentiatedTariff" :points="baselinePriceRS" fill="none"
|
||||
:stroke="showHouseholds ? '#e2e8f0' : '#f59e0b'" :stroke-width="showHouseholds ? 1.5 : 2" stroke-linecap="round" stroke-dasharray="6 3" />
|
||||
:stroke="showHouseholds ? (isDark ? '#334155' : '#e2e8f0') : '#f59e0b'" :stroke-width="showHouseholds ? 1.5 : 2" stroke-linecap="round" stroke-dasharray="6 3" />
|
||||
<!-- Household dots: price = abo/v + p0_linear -->
|
||||
<g v-if="showHouseholds && householdVolumes.length">
|
||||
<circle v-for="(hh, i) in householdDotsBaselinePrice" :key="'hdp'+i"
|
||||
:cx="cx2(hh.volume)" :cy="cy2price(hh.price)"
|
||||
r="2.5" fill="#2563eb" opacity="0.5"
|
||||
stroke="white" stroke-width="0.5"
|
||||
:stroke="t.plotBg" stroke-width="0.5"
|
||||
/>
|
||||
</g>
|
||||
<line :x1="cx2(baselineVolMax)" :y1="cy2price(curveData.p0_linear)" :x2="cx2(0)" :y2="cy2price(curveData.p0_linear)"
|
||||
stroke="#94a3b8" stroke-width="1" stroke-dasharray="4 4" />
|
||||
</g>
|
||||
<text :x="cx2(0) + 5" :y="cy2price(curveData.p0_linear) - 6" text-anchor="start"
|
||||
font-size="10" fill="#475569" font-weight="500">
|
||||
font-size="10" :fill="t.textCount" font-weight="500">
|
||||
Prix uniforme : {{ curveData.p0_linear?.toFixed(2) }}€/m³
|
||||
</text>
|
||||
<text :x="W2/2" :y="margin2.top + plotH2 + 42" text-anchor="middle" class="axis-label-sm">m³ → reduire</text>
|
||||
<!-- Legend when differentiated (top-right) -->
|
||||
<g v-if="differentiatedTariff" :transform="`translate(${margin2.left + plotW2 - 92}, ${margin2.top + 4})`">
|
||||
<rect x="0" y="0" width="80" height="32" rx="4" fill="white" fill-opacity="0.9" stroke="#e2e8f0" />
|
||||
<rect x="0" y="0" width="80" height="32" rx="4" :fill="t.legendBg" fill-opacity="0.9" :stroke="t.legendBorder" />
|
||||
<line x1="6" y1="11" x2="18" y2="11" stroke="#2563eb" stroke-width="2" />
|
||||
<text x="22" y="14" font-size="8" fill="#334155">RP/PRO</text>
|
||||
<text x="22" y="14" font-size="8" :fill="t.text">RP/PRO</text>
|
||||
<line x1="6" y1="24" x2="18" y2="24" stroke="#f59e0b" stroke-width="2" stroke-dasharray="4 2" />
|
||||
<text x="22" y="27" font-size="8" fill="#334155">RS</text>
|
||||
<text x="22" y="27" font-size="8" :fill="t.text">RS</text>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
@@ -645,22 +645,22 @@
|
||||
|
||||
<!-- Full-width: Prix marginal au m³ + distribution foyers -->
|
||||
<div style="margin-top: 1rem;">
|
||||
<h4 style="text-align: center; font-size: 0.85rem; margin-bottom: 0.25rem; color: #475569;">
|
||||
<h4 style="text-align: center; font-size: 0.85rem; margin-bottom: 0.25rem; color: var(--color-text-muted);">
|
||||
Prix au m³ — situation actuelle
|
||||
</h4>
|
||||
<div class="chart-container">
|
||||
<svg :viewBox="`0 0 ${Wmarg} ${Hmarg}`" preserveAspectRatio="xMidYMid meet">
|
||||
<!-- Background -->
|
||||
<rect :x="margMargin.left" :y="margMargin.top" :width="margPlotW" :height="margPlotH"
|
||||
fill="#f8fafc" rx="4" />
|
||||
:fill="t.plotBg" rx="4" />
|
||||
|
||||
<defs>
|
||||
<clipPath id="marg-clip">
|
||||
<rect :x="margMargin.left" :y="margMargin.top" :width="margPlotW" :height="margPlotH" />
|
||||
</clipPath>
|
||||
<linearGradient id="bar-grad" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#2563eb" stop-opacity="0.35" />
|
||||
<stop offset="100%" stop-color="#2563eb" stop-opacity="0.12" />
|
||||
<stop offset="0%" :stop-color="isDark ? '#60a5fa' : '#2563eb'" stop-opacity="0.35" />
|
||||
<stop offset="100%" :stop-color="isDark ? '#60a5fa' : '#2563eb'" stop-opacity="0.12" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
@@ -668,10 +668,10 @@
|
||||
<g>
|
||||
<line v-for="v in margGridVols" :key="'mgv'+v"
|
||||
:x1="margCx(v)" :y1="margMargin.top" :x2="margCx(v)" :y2="margMargin.top + margPlotH"
|
||||
stroke="#e2e8f0" stroke-width="0.5" />
|
||||
:stroke="t.grid" stroke-width="0.5" />
|
||||
<line v-for="p in margGridPrices" :key="'mgp'+p"
|
||||
:x1="margMargin.left" :y1="margCyPrice(p)" :x2="margMargin.left + margPlotW" :y2="margCyPrice(p)"
|
||||
stroke="#e2e8f0" stroke-width="0.5" />
|
||||
:stroke="t.grid" stroke-width="0.5" />
|
||||
<!-- Volume labels -->
|
||||
<text v-for="v in margGridVols" :key="'mglv'+v"
|
||||
:x="margCx(v)" :y="margMargin.top + margPlotH + 18" text-anchor="middle" class="axis-label-sm">{{ v }}m³</text>
|
||||
@@ -683,7 +683,7 @@
|
||||
<!-- Household count labels (left axis) -->
|
||||
<text v-for="c in margGridCounts" :key="'mgc'+c"
|
||||
:x="margMargin.left - 4" :y="margCyCount(c) + 3" text-anchor="end"
|
||||
font-size="9.5" fill="#64748b" font-weight="500">{{ c }}</text>
|
||||
font-size="9.5" :fill="t.textMuted" font-weight="500">{{ c }}</text>
|
||||
</g>
|
||||
|
||||
<!-- Household histogram bars (behind the curve) -->
|
||||
@@ -699,14 +699,14 @@
|
||||
<text v-if="bk.count > 0"
|
||||
:x="(margCx(bk.low) + margCx(bk.high)) / 2"
|
||||
:y="margCyCount(bk.count) - 3"
|
||||
text-anchor="middle" font-size="9.5" fill="#475569" font-weight="600">
|
||||
text-anchor="middle" font-size="9.5" :fill="t.textCount" font-weight="600">
|
||||
{{ bk.count }}
|
||||
</text>
|
||||
</g>
|
||||
|
||||
<!-- Price line (marginal) -->
|
||||
<polyline :points="margPriceLine" fill="none"
|
||||
:stroke="showHouseholds ? '#cbd5e1' : '#1e40af'"
|
||||
:stroke="showHouseholds ? (isDark ? '#475569' : '#cbd5e1') : (isDark ? '#93c5fd' : '#1e40af')"
|
||||
:stroke-width="showHouseholds ? 1.5 : 2.5" stroke-linecap="round" />
|
||||
|
||||
<!-- Household dots on marginal price chart -->
|
||||
@@ -714,8 +714,8 @@
|
||||
<circle v-for="(hh, i) in householdDotsMarg" :key="'hm'+i"
|
||||
:cx="margCx(hh.volume)" :cy="margCyPrice(hh.price)"
|
||||
r="3"
|
||||
fill="#1e40af" opacity="0.5"
|
||||
stroke="white" stroke-width="0.6"
|
||||
:fill="isDark ? '#93c5fd' : '#1e40af'" opacity="0.5"
|
||||
:stroke="t.plotBg" stroke-width="0.6"
|
||||
/>
|
||||
</g>
|
||||
|
||||
@@ -726,7 +726,7 @@
|
||||
|
||||
<!-- p0 label -->
|
||||
<text :x="margMargin.left + margPlotW + 4" :y="margCyPrice(curveData.p0_linear) - 5" text-anchor="start"
|
||||
font-size="9" fill="#475569" font-weight="600">
|
||||
font-size="9" :fill="t.textCount" font-weight="600">
|
||||
{{ curveData.p0_linear?.toFixed(2) }}€/m³
|
||||
</text>
|
||||
|
||||
@@ -746,11 +746,11 @@
|
||||
|
||||
<!-- Legend -->
|
||||
<g :transform="`translate(${margMargin.left + 8}, ${margMargin.top + 6})`">
|
||||
<rect x="0" y="0" width="230" height="44" rx="6" fill="white" fill-opacity="0.92" stroke="#e2e8f0" />
|
||||
<line x1="10" y1="14" x2="28" y2="14" stroke="#1e40af" stroke-width="2.5" stroke-linecap="round" />
|
||||
<text x="34" y="18" font-size="10" fill="#334155" font-weight="500">Prix au m³ avec abonnement (€/m³)</text>
|
||||
<rect x="10" y="25" width="14" height="10" rx="2" fill="#2563eb" fill-opacity="0.25" />
|
||||
<text x="34" y="34" font-size="10" fill="#334155" font-weight="500">Foyers par tranche de 30m³</text>
|
||||
<rect x="0" y="0" width="230" height="44" rx="6" :fill="t.legendBg" fill-opacity="0.92" :stroke="t.legendBorder" />
|
||||
<line x1="10" y1="14" x2="28" y2="14" :stroke="isDark ? '#93c5fd' : '#1e40af'" stroke-width="2.5" stroke-linecap="round" />
|
||||
<text x="34" y="18" font-size="10" :fill="t.text" font-weight="500">Prix au m³ avec abonnement (€/m³)</text>
|
||||
<rect x="10" y="25" width="14" height="10" rx="2" :fill="isDark ? '#60a5fa' : '#2563eb'" fill-opacity="0.25" />
|
||||
<text x="34" y="34" font-size="10" :fill="t.text" font-weight="500">Foyers par tranche de 30m³</text>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
@@ -787,6 +787,26 @@ import {
|
||||
const route = useRoute()
|
||||
const authStore = useAuthStore()
|
||||
const api = useApi()
|
||||
const isDark = useState('theme-dark', () => false)
|
||||
|
||||
// Reactive SVG theme colors (light/dark aware)
|
||||
const t = computed(() => isDark.value ? {
|
||||
plotBg: '#1e293b', grid: '#334155', legendBg: '#1e293b', legendBorder: '#475569',
|
||||
text: '#cbd5e1', textDark: '#f1f5f9', textMuted: '#94a3b8', textCount: '#94a3b8',
|
||||
pillBg: '#1e293b', pillBorder: '#475569', pillText: '#f1f5f9',
|
||||
gradStart: 'rgba(96,165,250,0.3)', gradEnd: 'rgba(96,165,250,0.08)',
|
||||
metricBg: 'linear-gradient(135deg, #1e293b, #162032)',
|
||||
highlightBg: 'rgba(52,211,153,0.1)',
|
||||
devBg: '#451a03', devBorder: '#92400e',
|
||||
} : {
|
||||
plotBg: '#f8fafc', grid: '#e2e8f0', legendBg: 'white', legendBorder: '#e2e8f0',
|
||||
text: '#334155', textDark: '#1e293b', textMuted: '#64748b', textCount: '#475569',
|
||||
pillBg: 'white', pillBorder: '#e2e8f0', pillText: '#1e293b',
|
||||
gradStart: 'rgba(37,99,235,0.35)', gradEnd: 'rgba(37,99,235,0.12)',
|
||||
metricBg: 'linear-gradient(135deg, #eff6ff, #f0fdf4)',
|
||||
highlightBg: 'rgba(5,150,105,0.06)',
|
||||
devBg: '#fef3c7', devBorder: '#f59e0b',
|
||||
})
|
||||
|
||||
const slug = route.params.slug as string
|
||||
const commune = ref<any>(null)
|
||||
@@ -1576,8 +1596,8 @@ function renderMarkdown(md: string): string {
|
||||
|
||||
/* ── Chart cards ── */
|
||||
.chart-card {
|
||||
background: white;
|
||||
border: 1px solid #e2e8f0;
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: clamp(8px, 2vw, 12px);
|
||||
padding: clamp(0.75rem, 2vw, 1.5rem);
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.06), 0 0 0 1px rgba(0,0,0,0.02);
|
||||
@@ -1595,13 +1615,13 @@ function renderMarkdown(md: string): string {
|
||||
.chart-title {
|
||||
font-size: clamp(0.95rem, 2.5vw, 1.1rem);
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
color: var(--color-text);
|
||||
margin-bottom: 0.15rem;
|
||||
}
|
||||
|
||||
.chart-subtitle {
|
||||
font-size: clamp(0.72rem, 1.8vw, 0.82rem);
|
||||
color: #64748b;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
/* ── Zoom bar ── */
|
||||
@@ -1615,7 +1635,7 @@ function renderMarkdown(md: string): string {
|
||||
|
||||
.zoom-info {
|
||||
font-size: 0.72rem;
|
||||
color: #94a3b8;
|
||||
color: var(--color-text-muted);
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
|
||||
@@ -1639,7 +1659,7 @@ function renderMarkdown(md: string): string {
|
||||
.zoom-separator {
|
||||
width: 1px;
|
||||
height: 18px;
|
||||
background: #e2e8f0;
|
||||
background: var(--color-border);
|
||||
margin: 0 0.25rem;
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
@@ -1658,12 +1678,13 @@ function renderMarkdown(md: string): string {
|
||||
.abo-input {
|
||||
width: 56px;
|
||||
padding: 0.15rem 0.35rem;
|
||||
border: 1px solid #cbd5e1;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 5px;
|
||||
font-size: 0.78rem;
|
||||
text-align: right;
|
||||
font-family: monospace;
|
||||
background: white;
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text);
|
||||
}
|
||||
.abo-input:focus {
|
||||
outline: none;
|
||||
@@ -1672,9 +1693,9 @@ function renderMarkdown(md: string): string {
|
||||
}
|
||||
|
||||
/* ── SVG axes ── */
|
||||
.axis-label { font-size: 10.5px; fill: #475569; font-weight: 500; font-family: system-ui, -apple-system, sans-serif; }
|
||||
.axis-label-sm { font-size: 10px; fill: #64748b; font-weight: 500; font-family: system-ui, -apple-system, sans-serif; }
|
||||
.axis-title { font-size: 11px; fill: #64748b; font-weight: 600; font-family: system-ui, -apple-system, sans-serif; }
|
||||
.axis-label { font-size: 10.5px; fill: var(--svg-text); font-weight: 500; font-family: system-ui, -apple-system, sans-serif; }
|
||||
.axis-label-sm { font-size: 10px; fill: var(--svg-text-light); font-weight: 500; font-family: system-ui, -apple-system, sans-serif; }
|
||||
.axis-title { font-size: 11px; fill: var(--svg-text-light); font-weight: 600; font-family: system-ui, -apple-system, sans-serif; }
|
||||
|
||||
/* ── Chart containers ── */
|
||||
.chart-container svg {
|
||||
@@ -1757,10 +1778,10 @@ function renderMarkdown(md: string): string {
|
||||
.section-title {
|
||||
font-size: clamp(1rem, 2.5vw, 1.15rem);
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
color: var(--color-text);
|
||||
margin: clamp(1rem, 3vw, 1.5rem) 0 clamp(0.75rem, 2vw, 1rem);
|
||||
padding-bottom: 0.4rem;
|
||||
border-bottom: 2px solid #e2e8f0;
|
||||
border-bottom: 2px solid var(--color-border);
|
||||
}
|
||||
|
||||
/* ── Params + impacts ── */
|
||||
@@ -1804,8 +1825,7 @@ function renderMarkdown(md: string): string {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: clamp(0.5rem, 2vw, 1rem);
|
||||
background: linear-gradient(135deg, #eff6ff, #f0fdf4);
|
||||
border: 1px solid #e2e8f0;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: clamp(8px, 2vw, 12px);
|
||||
padding: clamp(0.75rem, 2vw, 1.25rem) clamp(0.75rem, 2vw, 1.5rem);
|
||||
}
|
||||
@@ -1823,11 +1843,11 @@ function renderMarkdown(md: string): string {
|
||||
.key-metric-value {
|
||||
font-size: clamp(1.1rem, 3vw, 1.4rem);
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
color: var(--color-text);
|
||||
}
|
||||
.key-metric-label {
|
||||
font-size: clamp(0.68rem, 1.6vw, 0.78rem);
|
||||
color: #64748b;
|
||||
color: var(--color-text-muted);
|
||||
font-weight: 500;
|
||||
}
|
||||
.key-metric-tag {
|
||||
@@ -1837,14 +1857,15 @@ function renderMarkdown(md: string): string {
|
||||
letter-spacing: 0.05em;
|
||||
padding: 0.1rem 0.5rem;
|
||||
border-radius: 20px;
|
||||
background: #f1f5f9;
|
||||
color: #64748b;
|
||||
border: 1px solid #e2e8f0;
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text-muted);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
.key-metric-tag-calc {
|
||||
background: #eff6ff;
|
||||
color: #2563eb;
|
||||
border-color: #bfdbfe;
|
||||
background: var(--color-primary);
|
||||
color: white;
|
||||
border-color: var(--color-primary);
|
||||
opacity: 0.85;
|
||||
}
|
||||
.key-metric-input {
|
||||
opacity: 0.85;
|
||||
@@ -1866,12 +1887,12 @@ function renderMarkdown(md: string): string {
|
||||
align-items: center;
|
||||
font-size: clamp(0.72rem, 1.8vw, 0.8rem);
|
||||
padding: 0.2rem 0;
|
||||
color: #475569;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
.vote-result-row strong {
|
||||
font-family: monospace;
|
||||
font-size: 0.88rem;
|
||||
color: #1e293b;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
/* ── Alerts ── */
|
||||
|
||||
Reference in New Issue
Block a user