feat(brand): refonte favicon + og-image, reskin /soutenir
All checks were successful
Deploy info-canicule / deploy (push) Successful in 1m31s

- favicon.svg : nouveau logo (sun + brand + droplet) cohérent avec le header
- favicon-32 / favicon-192 / apple-touch-icon générés par scripts/build-favicon.mjs (sharp)
- og-image refait avec la nouvelle identité (paper, Manrope-like, pills vigilance avec glyphes)
- Base.astro : link rel icon + apple-touch-icon ajoutés
- /soutenir : reskin (kicker + h1 accentué + barre d'objectif en brand-tint, blocs ic-card), fonctionnalité Ko-fi conservée

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Florian 2026-05-27 19:13:30 +02:00
parent 0723ee10e3
commit d5c0b0968d
9 changed files with 153 additions and 81 deletions

BIN
public/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

BIN
public/favicon-192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

BIN
public/favicon-32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 892 B

View file

@ -1,5 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" role="img" aria-label="Info Canicule">
<circle cx="32" cy="32" r="30" fill="#ea580c"/> <title>Info Canicule</title>
<text x="32" y="44" font-size="34" text-anchor="middle" font-family="system-ui,sans-serif">🌡</text> <circle cx="16" cy="16" r="14" fill="#f4c97d"/>
<circle cx="16" cy="16" r="11" fill="#d97757"/>
<path d="M16 7 C12 11 12 16 16 21 C20 16 20 11 16 7 Z" fill="#ffffff"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 262 B

After

Width:  |  Height:  |  Size: 350 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Before After
Before After

View file

@ -2,14 +2,45 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 630"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 630">
<defs> <defs>
<linearGradient id="bg" x1="0" y1="0" x2="0" y2="1"> <linearGradient id="bg" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#fff7ed"/> <stop offset="0%" stop-color="#fdfaf2"/>
<stop offset="100%" stop-color="#ffedd5"/> <stop offset="100%" stop-color="#f8f1e1"/>
</linearGradient> </linearGradient>
</defs> </defs>
<rect width="1200" height="630" fill="url(#bg)"/> <rect width="1200" height="630" fill="url(#bg)"/>
<text x="80" y="200" font-family="system-ui,-apple-system,sans-serif" font-size="120" font-weight="800" fill="#c2410c">🌡️ Info</text>
<text x="80" y="340" font-family="system-ui,-apple-system,sans-serif" font-size="120" font-weight="800" fill="#c2410c">Canicule</text> <!-- Logo : grand cercle (sun) + cercle brand + droplet -->
<text x="80" y="440" font-family="system-ui,-apple-system,sans-serif" font-size="38" fill="#7c2d12">Vigilance Météo France en temps réel</text> <g transform="translate(80 90)">
<text x="80" y="495" font-family="system-ui,-apple-system,sans-serif" font-size="28" fill="#9a3412">Carte interactive · 96 départements · conseils officiels</text> <circle cx="60" cy="60" r="56" fill="#f4c97d"/>
<text x="80" y="570" font-family="system-ui,-apple-system,sans-serif" font-size="22" fill="#a16207">Gratuit · sans publicité · non lucratif · données ouvertes</text> <circle cx="60" cy="60" r="44" fill="#d97757"/>
<path d="M60 28 C44 44 44 64 60 80 C76 64 76 44 60 28 Z" fill="#ffffff"/>
</g>
<!-- Wordmark -->
<text x="220" y="160" font-family="'Manrope','Public Sans',system-ui,-apple-system,sans-serif" font-size="56" font-weight="700" fill="#6b6457" letter-spacing="0.04em">VIGILANCE MÉTÉO FRANCE</text>
<text x="80" y="320" font-family="'Manrope','Public Sans',system-ui,-apple-system,sans-serif" font-size="132" font-weight="800" fill="#1b1814" letter-spacing="-0.025em">Info Canicule.</text>
<text x="80" y="400" font-family="'Manrope','Public Sans',system-ui,-apple-system,sans-serif" font-size="50" font-weight="600" fill="#b45a3a" letter-spacing="-0.015em">Toutes les alertes météo, en clair.</text>
<!-- Pills vigilance pour le visuel -->
<g transform="translate(80 470)">
<g>
<rect x="0" y="0" rx="999" ry="999" width="170" height="56" fill="#e8f3e6" stroke="#2f7d3a" stroke-width="3"/>
<text x="22" y="36" font-family="'Public Sans',system-ui,sans-serif" font-size="22" font-weight="700" fill="#1b4a22">● Vert</text>
</g>
<g transform="translate(190 0)">
<rect x="0" y="0" rx="999" ry="999" width="180" height="56" fill="#fff5cf" stroke="#b08a00" stroke-width="3"/>
<text x="22" y="36" font-family="'Public Sans',system-ui,sans-serif" font-size="22" font-weight="700" fill="#5e4900">▲ Jaune</text>
</g>
<g transform="translate(390 0)">
<rect x="0" y="0" rx="999" ry="999" width="200" height="56" fill="#ffd9b3" stroke="#c25f00" stroke-width="3"/>
<text x="22" y="36" font-family="'Public Sans',system-ui,sans-serif" font-size="22" font-weight="700" fill="#5a2a00">◆ Orange</text>
</g>
<g transform="translate(610 0)">
<rect x="0" y="0" rx="999" ry="999" width="190" height="56" fill="#ffb3b3" stroke="#c01818" stroke-width="3"/>
<text x="22" y="36" font-family="'Public Sans',system-ui,sans-serif" font-size="22" font-weight="700" fill="#5a0a0a">■ Rouge</text>
</g>
</g>
<text x="80" y="580" font-family="'Public Sans',system-ui,sans-serif" font-size="24" font-weight="500" fill="#6b6457">Gratuit · sans publicité · données ouvertes Météo France</text>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Before After
Before After

27
scripts/build-favicon.mjs Normal file
View file

@ -0,0 +1,27 @@
#!/usr/bin/env node
// Génère les fallbacks PNG du favicon (apple-touch-icon + 32px) depuis public/favicon.svg.
// Lancé à la demande quand le SVG change : `node scripts/build-favicon.mjs`.
import sharp from 'sharp';
import { readFileSync } from 'node:fs';
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const SVG = resolve(__dirname, '../public/favicon.svg');
const buf = readFileSync(SVG);
const outputs = [
{ path: '../public/apple-touch-icon.png', size: 180 },
{ path: '../public/favicon-32.png', size: 32 },
{ path: '../public/favicon-192.png', size: 192 },
];
for (const o of outputs) {
const dest = resolve(__dirname, o.path);
await sharp(buf, { density: 384 })
.resize(o.size, o.size, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } })
.png({ compressionLevel: 9 })
.toFile(dest);
console.log(`Wrote ${dest} (${o.size}×${o.size})`);
}

View file

@ -77,6 +77,8 @@ const jsonLd = {
{noindex ? <meta name="robots" content="noindex, nofollow" /> : <meta name="robots" content="index, follow, max-image-preview:large" />} {noindex ? <meta name="robots" content="noindex, nofollow" /> : <meta name="robots" content="index, follow, max-image-preview:large" />}
<link rel="canonical" href={canonicalUrl} /> <link rel="canonical" href={canonicalUrl} />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="alternate" type="application/rss+xml" title="Info Canicule" href={`${SITE}/sitemap-index.xml`} /> <link rel="alternate" type="application/rss+xml" title="Info Canicule" href={`${SITE}/sitemap-index.xml`} />
<title>{title}</title> <title>{title}</title>

View file

@ -8,105 +8,115 @@ const goalTarget = 10;
const goalRaised = 0; const goalRaised = 0;
const goalCurrency = '€'; const goalCurrency = '€';
const goalPercent = Math.min(100, Math.round((goalRaised / goalTarget) * 100)); const goalPercent = Math.min(100, Math.round((goalRaised / goalTarget) * 100));
const usages = [
{ title: "Hébergement", text: "VPS mutualisé chez OVH : ~30 € / mois pour faire tourner le site, le cache Valkey et le pipeline de données." },
{ title: "Nom de domaine", text: "Renouvellement annuel du domaine dédié (objectif en cours)." },
{ title: "Surprise café", text: "De quoi tenir les soirées de maintenance et les rotations d'API key 🥲." },
];
const otherWays = [
{ title: "Signaler un bug ou une typo", text: "Par mail à florian@nocleus.com. Toute remontée d'erreur dans un conseil ou une donnée incohérente est précieuse." },
{ title: "Partager le site", text: "Surtout autour des personnes fragiles (proches âgés, isolés), pour qui les conseils canicule peuvent faire une vraie différence." },
{ title: "Diffuser un kit", text: "Imprimer un kit phénomène et l'afficher dans un lieu collectif (mairie, médiathèque, hall d'immeuble)." },
];
--- ---
<Base <Base
title="Soutenir le projet — Info Canicule" title="Soutenir le projet — Info Canicule"
description="Info Canicule est un service gratuit, sans publicité, à but non lucratif. Si le site vous est utile, vous pouvez soutenir son hébergement via Ko-fi." description="Info Canicule est un service gratuit, sans publicité, à but non lucratif. Si le site vous est utile, vous pouvez soutenir son hébergement via Ko-fi."
> >
<section class="bg-gradient-to-b from-canicule-50 to-white"> <section class="container-tight" style="padding-block: clamp(28px, 5vw, 56px) 32px;">
<div class="container-tight py-10"> <div class="flex flex-col gap-3" style="margin-bottom: 28px; max-width: 720px;">
<h1 class="text-3xl font-bold sm:text-4xl">Soutenir Info Canicule</h1> <div class="kicker">Soutenir</div>
<p class="mt-2 max-w-2xl text-slate-600"> <h1>Si le site vous est utile, <span style="color: var(--brand-deep);">un café suffit</span>.</h1>
Le site est gratuit, sans publicité et sans traceurs commerciaux. Si vous le trouvez utile, <p style="font-size: clamp(1rem, 0.9rem + 0.4vw, 1.18rem); color: var(--ink-2); line-height: 1.55;">
vous pouvez contribuer aux frais d'infrastructure (~30 €/mois). Info Canicule est gratuit, sans publicité, sans traceur commercial. Si vous le trouvez utile,
vous pouvez contribuer aux frais d'infrastructure (~30&nbsp;€/mois).
</p> </p>
</div> </div>
</section>
<section class="container-tight pt-6"> {/* Objectif de don */}
<div class="rounded-xl border border-canicule-200 bg-white p-5 shadow-sm"> <div class="ic-card" style="padding: 22px; margin-bottom: 24px; border-color: var(--brand); background: var(--brand-tint);">
<div class="flex items-baseline justify-between gap-3"> <div class="flex items-baseline justify-between gap-3" style="flex-wrap: wrap;">
<div> <div>
<p class="text-xs font-semibold uppercase tracking-wide text-canicule-700"> <div class="kicker" style="color: var(--brand-deep);">Objectif de don en cours</div>
Objectif de don en cours <p style="margin-top: 6px; font-weight: 600; color: var(--brand-ink); font-size: 1.05rem;">{goalLabel}</p>
</p>
<p class="mt-1 text-base font-semibold text-slate-900">{goalLabel}</p>
</div> </div>
<p class="shrink-0 text-sm tabular-nums text-slate-600"> <p style="flex-shrink: 0; font-size: 0.95rem; font-variant-numeric: tabular-nums; color: var(--ink-2);">
<span class="font-bold text-slate-900">{goalRaised} {goalCurrency}</span> <span style="font-weight: 700; color: var(--brand-ink); font-family: var(--font-display); font-size: 1.15rem;">{goalRaised} {goalCurrency}</span>
<span class="text-slate-400"> / {goalTarget} {goalCurrency}</span> <span style="color: var(--ink-mute);"> / {goalTarget} {goalCurrency}</span>
</p> </p>
</div> </div>
<div class="mt-3 h-3 w-full overflow-hidden rounded-full bg-slate-100">
<div <div
class="h-full rounded-full bg-canicule-500 transition-all" style="margin-top: 14px; height: 10px; width: 100%; overflow: hidden; border-radius: 999px; background: rgba(180, 90, 58, 0.15);"
style={`width: ${goalPercent}%`}
role="progressbar" role="progressbar"
aria-valuenow={goalPercent} aria-valuenow={goalPercent}
aria-valuemin="0" aria-valuemin="0"
aria-valuemax="100" aria-valuemax="100"
aria-label={`${goalPercent}% de l'objectif atteint`} aria-label={`${goalPercent}% de l'objectif atteint`}
> >
<div style={`height: 100%; width: ${goalPercent}%; background: var(--brand); transition: width .3s;`}></div>
</div> </div>
</div> <p style="margin-top: 10px; font-size: 0.85rem; color: var(--brand-ink);">
{goalPercent}% atteint — une fois l'objectif rempli, le nom de domaine dédié sera réservé pour un an.
<p class="mt-2 text-xs text-slate-500">
{goalPercent}% atteint — une fois l'objectif rempli, le nom de domaine dédié sera réservé
pour un an.
</p> </p>
</div> </div>
</section>
<section class="container-tight py-8"> {/* CTA principal */}
<div class="prose prose-slate max-w-none"> <div class="ic-card flex flex-wrap items-center justify-between gap-4" style="padding: 28px; margin-bottom: 32px;">
<h2>Don libre via Ko-fi</h2> <div style="max-width: 540px;">
<p> <h2 style="margin-bottom: 8px;">Don libre via Ko-fi</h2>
Le moyen le plus simple : un don libre, sans inscription, par carte bancaire ou PayPal, via <p style="color: var(--ink-soft);">
<a href="https://ko-fi.com/daelwizhit" rel="noopener" class="font-semibold">ko-fi.com/daelwizhit</a>. Sans inscription, par carte bancaire ou PayPal. Anonyme par défaut et entièrement optionnel.
Les dons sont anonymes par défaut et entièrement optionnels.
</p> </p>
</div>
<div class="not-prose my-6">
<a <a
href="https://ko-fi.com/daelwizhit" href="https://ko-fi.com/daelwizhit"
rel="noopener" rel="noopener"
class="inline-flex items-center gap-2 rounded-lg bg-canicule-600 px-6 py-3 text-base font-semibold text-white no-underline hover:bg-canicule-700" class="ic-btn ic-btn-brand ic-btn-lg"
style="text-decoration: none;"
> >
☕ Soutenir sur Ko-fi ☕ Soutenir sur Ko-fi
</a> </a>
</div> </div>
<h2>Ce que les dons financent</h2> {/* Ce que ça finance */}
<ul> <section style="margin-bottom: 48px;">
<li>Frais d'infrastructure mensuels (~30 €/mois).</li> <h2 style="margin-bottom: 16px;">Ce que les dons financent</h2>
<li>Nom de domaine annuel.</li> <div class="grid gap-3" style="grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));">
<li>Café pour les soirées de maintenance 🥲.</li> {usages.map((u) => (
</ul> <div class="ic-card-soft">
<p> <h3>{u.title}</h3>
<strong>Ce site n'a aucune vocation commerciale.</strong> Aucun chiffre d'affaires, aucun salaire <p style="margin-top: 8px; line-height: 1.5; color: var(--ink-soft);">{u.text}</p>
versé. Les dons couvrent les frais techniques ou, en cas d'excédent, financent d'autres petits </div>
projets d'utilité publique du même développeur. ))}
</div>
<p style="margin-top: 16px; color: var(--ink-soft); font-size: 0.92rem; line-height: 1.5;">
<strong>Ce site n'a aucune vocation commerciale.</strong> Aucun chiffre d'affaires, aucun salaire versé.
Les dons couvrent les frais techniques ou, en cas d'excédent, financent d'autres petits projets d'utilité publique du même développeur.
</p> </p>
</section>
<h2>Autres manières d'aider</h2> {/* Autres manières d'aider */}
<ul> <section style="margin-bottom: 32px;">
<li> <h2 style="margin-bottom: 16px;">Autres manières d'aider</h2>
<strong>Signaler un bug ou une typo</strong> : par mail à <div class="grid gap-3" style="grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));">
<a href="mailto:florian@nocleus.com">florian@nocleus.com</a>. {otherWays.map((w) => (
</li> <div class="ic-card flex items-start gap-3" style="padding: 18px;">
<li> <span style="color: var(--v-vert); flex-shrink: 0; font-weight: 700; font-size: 1.1rem;" aria-hidden="true">✓</span>
<strong>Partager le site</strong> autour de vous, en particulier auprès de personnes fragiles <div>
(proches âgés, personnes isolées) pour qui les conseils canicule peuvent faire une vraie <strong>{w.title}</strong>
différence. <p style="margin-top: 6px; font-size: 0.92rem; line-height: 1.5; color: var(--ink-soft);">{w.text}</p>
</li> </div>
</ul> </div>
))}
<p class="text-sm text-slate-500">
Pour les questions juridiques (mention de l'éditeur, statut), voir les
<a href="/mentions-legales">mentions légales</a>.
</p>
</div> </div>
</section> </section>
<p style="color: var(--ink-soft); font-size: 0.88rem;">
Pour les questions juridiques (mention de l'éditeur, statut), voir les
<a href="/mentions-legales" style="color: var(--brand-deep);">mentions légales</a>.
</p>
</section>
</Base> </Base>