feat(theme): color package Electric Teal + bascule nom IA qu'à
This commit is contained in:
parent
229f7e879e
commit
cd6a641d72
@ -1,4 +1,4 @@
|
||||
# IA qu'à... — Blog
|
||||
# IA qu'à — Blog
|
||||
|
||||
Blog statique personnel sur l'IA en PME.
|
||||
|
||||
|
||||
@ -3,12 +3,104 @@ import mdx from "@astrojs/mdx";
|
||||
import sitemap from "@astrojs/sitemap";
|
||||
import tailwind from "@astrojs/tailwind";
|
||||
|
||||
/**
|
||||
* "IA qu'à" Atom Syntax theme — Electric Teal color package §3/§4.
|
||||
* Dark Slate canvas with the 7-color diagnostic spectrum mapped onto
|
||||
* standard code tokens. Used for all fenced code blocks (light & dark site).
|
||||
*/
|
||||
const iaQuaSyntax = {
|
||||
name: "ia-qua-atom-syntax",
|
||||
type: "dark",
|
||||
colors: {
|
||||
"editor.background": "#0F172A",
|
||||
"editor.foreground": "#F1F5F9",
|
||||
},
|
||||
settings: [
|
||||
{ settings: { background: "#0F172A", foreground: "#F1F5F9" } },
|
||||
// comment / metadata → Orchid
|
||||
{
|
||||
scope: ["comment", "punctuation.definition.comment", "string.comment"],
|
||||
settings: { foreground: "#8B5CF6", fontStyle: "italic" },
|
||||
},
|
||||
// keyword / control → Ember
|
||||
{
|
||||
scope: [
|
||||
"keyword",
|
||||
"keyword.control",
|
||||
"storage",
|
||||
"storage.type",
|
||||
"storage.modifier",
|
||||
"keyword.operator.new",
|
||||
"entity.name.tag",
|
||||
],
|
||||
settings: { foreground: "#FF6B00" },
|
||||
},
|
||||
// string / regex → Gold
|
||||
{
|
||||
scope: [
|
||||
"string",
|
||||
"string.quoted",
|
||||
"string.template",
|
||||
"constant.other.symbol",
|
||||
"string.regexp",
|
||||
],
|
||||
settings: { foreground: "#F59E0B" },
|
||||
},
|
||||
// support / type / class → Emerald
|
||||
{
|
||||
scope: [
|
||||
"entity.name.class",
|
||||
"entity.name.type",
|
||||
"support.type",
|
||||
"support.class",
|
||||
"storage.type.class",
|
||||
"entity.other.attribute-name",
|
||||
],
|
||||
settings: { foreground: "#10B981" },
|
||||
},
|
||||
// entity / function → Indigo
|
||||
{
|
||||
scope: [
|
||||
"entity.name.function",
|
||||
"support.function",
|
||||
"meta.function-call",
|
||||
"variable.function",
|
||||
],
|
||||
settings: { foreground: "#6366F1" },
|
||||
},
|
||||
// variable / parameter & numerics → Sky
|
||||
{
|
||||
scope: [
|
||||
"variable",
|
||||
"variable.other",
|
||||
"variable.parameter",
|
||||
"support.variable",
|
||||
"constant.numeric",
|
||||
"constant.language",
|
||||
"constant",
|
||||
],
|
||||
settings: { foreground: "#0EA5E9" },
|
||||
},
|
||||
// exception / invalid → Rose
|
||||
{
|
||||
scope: ["invalid", "invalid.illegal", "invalid.deprecated"],
|
||||
settings: { foreground: "#F43F5E" },
|
||||
},
|
||||
// punctuation kept muted
|
||||
{
|
||||
scope: ["punctuation", "meta.brace", "punctuation.separator"],
|
||||
settings: { foreground: "#94A3B8" },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default defineConfig({
|
||||
site: "https://ia-qua.fr", // À changer si autre domaine
|
||||
integrations: [mdx(), sitemap(), tailwind()],
|
||||
markdown: {
|
||||
shikiConfig: {
|
||||
theme: "github-dark",
|
||||
theme: iaQuaSyntax,
|
||||
wrap: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# ─── deploy.sh — Blog IA qu'à... ───
|
||||
# ─── deploy.sh — Blog IA qu'à ───
|
||||
# Usage : sudo bash deploy.sh
|
||||
# Depuis le répertoire du repo sur Cloudbreak
|
||||
|
||||
|
||||
@ -1,4 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<rect width="32" height="32" rx="6" fill="#1B2A4A"/>
|
||||
<text x="16" y="22" text-anchor="middle" font-family="Georgia, serif" font-weight="bold" font-size="16" fill="#6B8F71">IA</text>
|
||||
<defs>
|
||||
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0%" stop-color="#06B6D4"/>
|
||||
<stop offset="100%" stop-color="#3B82F6"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="32" height="32" rx="7" fill="url(#g)"/>
|
||||
<text x="16" y="22" text-anchor="middle" font-family="Inter, system-ui, sans-serif" font-weight="800" font-size="17" fill="#FFFFFF">IA</text>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 254 B After Width: | Height: | Size: 460 B |
14
public/logo.svg
Normal file
14
public/logo.svg
Normal file
@ -0,0 +1,14 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 196 48" role="img" aria-label="IA qu'à">
|
||||
<defs>
|
||||
<linearGradient id="iaGrad" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0%" stop-color="#06B6D4"/>
|
||||
<stop offset="100%" stop-color="#3B82F6"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<text x="0" y="37" font-family="Inter, system-ui, -apple-system, sans-serif" font-size="38" letter-spacing="-0.5">
|
||||
<tspan fill="url(#iaGrad)" font-weight="800">IA</tspan>
|
||||
<tspan fill="#334155" font-weight="700" dx="6">qu</tspan>
|
||||
<tspan fill="url(#iaGrad)" font-weight="600">’</tspan>
|
||||
<tspan fill="#334155" font-weight="700">à</tspan>
|
||||
</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 669 B |
@ -20,21 +20,44 @@ const formattedDate = pubDate.toLocaleDateString("fr-FR", {
|
||||
});
|
||||
---
|
||||
|
||||
<article class="bg-white rounded-xl border border-border overflow-hidden transition-all hover:-translate-y-1 hover:shadow-lg hover:shadow-blue-night/5">
|
||||
<article
|
||||
class="bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-800 overflow-hidden transition-all hover:-translate-y-1 hover:border-brand-cyan/50 hover:shadow-lg hover:shadow-brand-cyan/10"
|
||||
>
|
||||
<a href={`/blog/${slug}`} class="no-underline block">
|
||||
{heroImage ? (
|
||||
{
|
||||
heroImage ? (
|
||||
<img src={heroImage} alt={title} class="w-full h-48 object-cover" />
|
||||
) : (
|
||||
<div class="w-full h-48 bg-gradient-to-br from-blue-night to-blue-mid flex items-center justify-center">
|
||||
<span class="text-white/10 text-6xl">◉</span>
|
||||
<div class="w-full h-48 bg-brand-gradient flex items-center justify-center">
|
||||
<span class="text-white/20 text-6xl">◉</span>
|
||||
</div>
|
||||
)}
|
||||
)
|
||||
}
|
||||
<div class="p-5">
|
||||
{series && <div class="mb-2"><SeriesBadge name={series.name} part={series.part} total={series.total} /></div>}
|
||||
<span class="text-xs font-semibold text-sage uppercase tracking-wide">{category}</span>
|
||||
<h3 class="font-heading text-blue-night text-lg font-semibold leading-snug mt-1 mb-2">{title}</h3>
|
||||
<p class="text-sm text-text-light line-clamp-2 mb-3">{description}</p>
|
||||
<span class="text-xs text-text-light">{formattedDate}</span>
|
||||
{
|
||||
series && (
|
||||
<div class="mb-2">
|
||||
<SeriesBadge
|
||||
name={series.name}
|
||||
part={series.part}
|
||||
total={series.total}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<span
|
||||
class="text-xs font-semibold text-brand-deep dark:text-brand-cyan uppercase tracking-wide"
|
||||
>{category}</span
|
||||
>
|
||||
<h3
|
||||
class="font-heading text-slate-900 dark:text-slate-50 text-lg font-semibold leading-snug mt-1 mb-2"
|
||||
>
|
||||
{title}
|
||||
</h3>
|
||||
<p class="text-sm text-slate-500 dark:text-slate-400 line-clamp-2 mb-3">
|
||||
{description}
|
||||
</p>
|
||||
<span class="text-xs text-slate-400 dark:text-slate-500">{formattedDate}</span>
|
||||
</div>
|
||||
</a>
|
||||
</article>
|
||||
|
||||
44
src/components/BrandLogo.astro
Normal file
44
src/components/BrandLogo.astro
Normal file
@ -0,0 +1,44 @@
|
||||
---
|
||||
/**
|
||||
* BrandLogo — wordmark "IA qu'à" (color package §1, palette Classic Electric Teal).
|
||||
* "IA" + apostrophe en dégradé cyan→bleu, "qu'à" en slate adaptatif (clair/sombre).
|
||||
* Double sens assumé : "IA qu'à" ↔ "Y'a qu'à". Rendu en Inter (police du site).
|
||||
*/
|
||||
interface Props {
|
||||
class?: string;
|
||||
}
|
||||
const { class: className = "h-7 w-auto" } = Astro.props;
|
||||
// id unique pour éviter les collisions de gradient si plusieurs instances
|
||||
const gid = "iaGrad-" + Math.random().toString(36).slice(2, 8);
|
||||
---
|
||||
|
||||
<svg
|
||||
viewBox="0 0 196 48"
|
||||
role="img"
|
||||
aria-label="IA qu'à"
|
||||
class={className}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<defs>
|
||||
<linearGradient id={gid} x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0%" stop-color="#06B6D4"></stop>
|
||||
<stop offset="100%" stop-color="#3B82F6"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<text
|
||||
x="0"
|
||||
y="37"
|
||||
font-family="Inter, system-ui, -apple-system, sans-serif"
|
||||
font-size="38"
|
||||
letter-spacing="-0.5"
|
||||
>
|
||||
<tspan fill={`url(#${gid})`} font-weight="800">IA</tspan>
|
||||
<tspan
|
||||
class="fill-slate-700 dark:fill-slate-100"
|
||||
font-weight="700"
|
||||
dx="6">qu</tspan
|
||||
>
|
||||
<tspan fill={`url(#${gid})`} font-weight="600">’</tspan>
|
||||
<tspan class="fill-slate-700 dark:fill-slate-100" font-weight="700">à</tspan>
|
||||
</text>
|
||||
</svg>
|
||||
@ -2,12 +2,20 @@
|
||||
const year = new Date().getFullYear();
|
||||
---
|
||||
|
||||
<footer class="max-w-6xl mx-auto mt-16 px-6 py-8 border-t border-border text-center text-sm text-text-light">
|
||||
<footer
|
||||
class="max-w-6xl mx-auto mt-16 px-6 py-8 border-t border-slate-200 dark:border-slate-800 text-center text-sm text-slate-500 dark:text-slate-400"
|
||||
>
|
||||
<p>
|
||||
<strong class="font-heading text-blue-night">IA qu'à...</strong> — Journal de bord d'un DSI en PME face à l'IA
|
||||
<strong class="font-heading text-slate-900 dark:text-slate-100"
|
||||
>IA qu'à</strong
|
||||
> — Journal d'un DSI de PME
|
||||
</p>
|
||||
<p class="mt-2">
|
||||
© {year} — Fait avec <a href="https://astro.build" class="text-sage no-underline hover:underline">Astro</a>
|
||||
© {year} — Fait avec <a
|
||||
href="https://astro.build"
|
||||
class="text-brand-deep dark:text-brand-cyan no-underline hover:underline"
|
||||
>Astro</a
|
||||
>
|
||||
· Self-hosted
|
||||
</p>
|
||||
</footer>
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
---
|
||||
import BrandLogo from "./BrandLogo.astro";
|
||||
|
||||
const currentPath = Astro.url.pathname;
|
||||
|
||||
const links = [
|
||||
@ -11,57 +13,132 @@ const links = [
|
||||
];
|
||||
---
|
||||
|
||||
<nav class="bg-white border-b border-border sticky top-0 z-50">
|
||||
<nav
|
||||
class="sticky top-0 z-50 border-b border-slate-200 dark:border-slate-800 bg-canvas-light/80 dark:bg-canvas-dark/80 backdrop-blur-md"
|
||||
>
|
||||
<div class="max-w-6xl mx-auto px-6 py-4 flex items-center justify-between">
|
||||
<a href="/" class="font-heading font-extrabold text-xl text-blue-night no-underline">
|
||||
IA qu'à<span class="text-sage">...</span>
|
||||
<a href="/" class="no-underline shrink-0" aria-label="Accueil — IA qu'à">
|
||||
<BrandLogo class="h-7 w-auto" />
|
||||
</a>
|
||||
|
||||
<div class="flex items-center gap-6">
|
||||
<!-- Desktop -->
|
||||
<ul class="hidden md:flex gap-8 list-none">
|
||||
{links.map((link) => (
|
||||
<ul class="hidden md:flex gap-7 list-none">
|
||||
{
|
||||
links.map((link) => (
|
||||
<li>
|
||||
<a
|
||||
href={link.href}
|
||||
class:list={[
|
||||
"text-sm font-medium no-underline transition-colors",
|
||||
currentPath === link.href
|
||||
? "text-blue-night border-b-2 border-sage pb-0.5"
|
||||
: "text-text-light hover:text-blue-night",
|
||||
? "text-slate-900 dark:text-white border-b-2 border-brand-cyan pb-0.5"
|
||||
: "text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white",
|
||||
]}
|
||||
>
|
||||
{link.label}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
|
||||
<!-- Theme toggle -->
|
||||
<button
|
||||
id="theme-toggle"
|
||||
class="text-slate-500 dark:text-slate-400 hover:text-brand-cyan dark:hover:text-brand-cyan transition-colors"
|
||||
aria-label="Basculer le thème clair / sombre"
|
||||
>
|
||||
<!-- soleil (visible en mode sombre) -->
|
||||
<svg
|
||||
class="hidden dark:block"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<circle cx="12" cy="12" r="4"></circle>
|
||||
<path
|
||||
d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"
|
||||
></path>
|
||||
</svg>
|
||||
<!-- lune (visible en mode clair) -->
|
||||
<svg
|
||||
class="block dark:hidden"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Mobile toggle -->
|
||||
<button id="menu-toggle" class="md:hidden text-blue-night" aria-label="Menu">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<button
|
||||
id="menu-toggle"
|
||||
class="md:hidden text-slate-700 dark:text-slate-200"
|
||||
aria-label="Menu"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<line x1="3" y1="12" x2="21" y2="12"></line>
|
||||
<line x1="3" y1="6" x2="21" y2="6"></line>
|
||||
<line x1="3" y1="18" x2="21" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile menu -->
|
||||
<div id="mobile-menu" class="hidden md:hidden border-t border-border px-6 py-4">
|
||||
<div
|
||||
id="mobile-menu"
|
||||
class="hidden md:hidden border-t border-slate-200 dark:border-slate-800 px-6 py-4"
|
||||
>
|
||||
<ul class="flex flex-col gap-4 list-none">
|
||||
{links.map((link) => (
|
||||
{
|
||||
links.map((link) => (
|
||||
<li>
|
||||
<a href={link.href} class="text-sm font-medium text-text-light no-underline hover:text-blue-night">
|
||||
<a
|
||||
href={link.href}
|
||||
class="text-sm font-medium text-slate-500 dark:text-slate-400 no-underline hover:text-slate-900 dark:hover:text-white"
|
||||
>
|
||||
{link.label}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<script>
|
||||
// Menu mobile
|
||||
document.getElementById("menu-toggle")?.addEventListener("click", () => {
|
||||
document.getElementById("mobile-menu")?.classList.toggle("hidden");
|
||||
});
|
||||
|
||||
// Bascule de thème clair / sombre (persistée)
|
||||
document.getElementById("theme-toggle")?.addEventListener("click", () => {
|
||||
const root = document.documentElement;
|
||||
const isDark = root.classList.toggle("dark");
|
||||
localStorage.setItem("theme", isDark ? "dark" : "light");
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -9,15 +9,30 @@ const { name, part, total } = Astro.props;
|
||||
const dots = Array.from({ length: total }, (_, i) => i < part);
|
||||
---
|
||||
|
||||
<div class="inline-flex items-center gap-2 bg-sage-light text-sage text-xs font-semibold px-3 py-1 rounded-full">
|
||||
<svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20" />
|
||||
<path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" />
|
||||
<div
|
||||
class="inline-flex items-center gap-2 bg-brand-gradient-soft text-brand-deep dark:text-brand-cyan ring-1 ring-brand-cyan/20 text-xs font-semibold px-3 py-1 rounded-full"
|
||||
>
|
||||
<svg
|
||||
class="w-3.5 h-3.5"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path>
|
||||
<path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path>
|
||||
</svg>
|
||||
<span>Série : {name}</span>
|
||||
<div class="flex gap-0.5 ml-1">
|
||||
{dots.map((filled) => (
|
||||
<div class:list={["w-1.5 h-1.5 rounded-full bg-sage", filled ? "opacity-100" : "opacity-30"]} />
|
||||
))}
|
||||
{
|
||||
dots.map((filled) => (
|
||||
<div
|
||||
class:list={[
|
||||
"w-1.5 h-1.5 rounded-full bg-brand-cyan",
|
||||
filled ? "opacity-100" : "opacity-30",
|
||||
]}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -23,18 +23,19 @@ const seriesPosts = allPosts.sort(
|
||||
);
|
||||
---
|
||||
|
||||
{seriesPosts.length > 1 && (
|
||||
<aside class="my-8 p-6 bg-white border border-border rounded-xl">
|
||||
<h3 class="font-heading text-blue-night text-lg font-semibold mb-1">
|
||||
{
|
||||
seriesPosts.length > 1 && (
|
||||
<aside class="my-8 p-6 bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-xl">
|
||||
<h3 class="font-heading text-slate-900 dark:text-slate-50 text-lg font-semibold mb-1">
|
||||
Série : {seriesName}
|
||||
</h3>
|
||||
<p class="text-text-light text-sm mb-4">
|
||||
<p class="text-slate-500 dark:text-slate-400 text-sm mb-4">
|
||||
Article {currentPart} sur {seriesPosts.length}
|
||||
</p>
|
||||
|
||||
<div class="w-full h-1 bg-border rounded-full mb-4 overflow-hidden">
|
||||
<div class="w-full h-1 bg-slate-200 dark:bg-slate-800 rounded-full mb-4 overflow-hidden">
|
||||
<div
|
||||
class="h-full bg-sage rounded-full transition-all"
|
||||
class="h-full bg-brand-gradient rounded-full transition-all"
|
||||
style={`width: ${(currentPart / seriesPosts.length) * 100}%`}
|
||||
/>
|
||||
</div>
|
||||
@ -49,16 +50,16 @@ const seriesPosts = allPosts.sort(
|
||||
class:list={[
|
||||
"flex items-center gap-3 py-2 px-3 rounded-lg text-sm no-underline transition-colors",
|
||||
isCurrent
|
||||
? "bg-sage-light text-sage font-semibold"
|
||||
: "text-text-light hover:bg-sage-light/50 hover:text-blue-night",
|
||||
? "bg-brand-gradient-soft text-brand-deep dark:text-brand-cyan font-semibold"
|
||||
: "text-slate-500 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800 hover:text-slate-900 dark:hover:text-white",
|
||||
]}
|
||||
>
|
||||
<span
|
||||
class:list={[
|
||||
"flex-shrink-0 w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold",
|
||||
isCurrent
|
||||
? "bg-sage text-white"
|
||||
: "bg-border text-text-light",
|
||||
? "bg-brand-gradient text-white"
|
||||
: "bg-slate-200 dark:bg-slate-700 text-slate-500 dark:text-slate-300",
|
||||
]}
|
||||
>
|
||||
{post.data.series?.part}
|
||||
@ -70,4 +71,5 @@ const seriesPosts = allPosts.sort(
|
||||
})}
|
||||
</ol>
|
||||
</aside>
|
||||
)}
|
||||
)
|
||||
}
|
||||
|
||||
@ -23,11 +23,26 @@ const canonicalURL = new URL(Astro.url.pathname, Astro.site);
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<!-- Thème clair/sombre : appliqué avant le paint pour éviter le flash -->
|
||||
<script is:inline>
|
||||
(() => {
|
||||
const stored = localStorage.getItem("theme");
|
||||
const prefersDark = window.matchMedia(
|
||||
"(prefers-color-scheme: dark)"
|
||||
).matches;
|
||||
if (stored === "dark" || (!stored && prefersDark)) {
|
||||
document.documentElement.classList.add("dark");
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<meta name="theme-color" content="#06B6D4" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="sitemap" href="/sitemap-index.xml" />
|
||||
|
||||
<!-- SEO -->
|
||||
<title>{title} | IA qu'à...</title>
|
||||
<title>{title} | IA qu'à</title>
|
||||
<meta name="description" content={description} />
|
||||
<link rel="canonical" href={canonicalURL} />
|
||||
|
||||
|
||||
@ -32,14 +32,14 @@ const formattedDate = pubDate.toLocaleDateString("fr-FR", {
|
||||
<SeriesBadge name={series.name} part={series.part} total={series.total} />
|
||||
</div>
|
||||
)}
|
||||
<span class="text-xs font-semibold text-sage uppercase tracking-wide">
|
||||
<span class="text-xs font-semibold text-brand-deep dark:text-brand-cyan uppercase tracking-wide">
|
||||
{category}
|
||||
</span>
|
||||
<h1 class="font-heading text-blue-night text-4xl font-extrabold leading-tight mt-2 mb-3">
|
||||
<h1 class="font-heading text-slate-900 dark:text-slate-50 text-4xl font-extrabold leading-tight mt-2 mb-3">
|
||||
{title}
|
||||
</h1>
|
||||
<p class="text-text-light">{description}</p>
|
||||
<div class="flex items-center gap-2 mt-4 text-sm text-text-light">
|
||||
<p class="text-slate-500 dark:text-slate-400">{description}</p>
|
||||
<div class="flex items-center gap-2 mt-4 text-sm text-slate-400 dark:text-slate-500">
|
||||
<time datetime={pubDate.toISOString()}>{formattedDate}</time>
|
||||
{updatedDate && (
|
||||
<>
|
||||
@ -62,7 +62,7 @@ const formattedDate = pubDate.toLocaleDateString("fr-FR", {
|
||||
<img
|
||||
src={heroImage}
|
||||
alt={title}
|
||||
class="w-full rounded-xl mb-8"
|
||||
class="w-full rounded-xl mb-8 ring-1 ring-slate-200 dark:ring-slate-800"
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ import BaseLayout from "../layouts/BaseLayout.astro";
|
||||
|
||||
<BaseLayout title="À propos">
|
||||
<article class="max-w-3xl mx-auto px-6 py-16">
|
||||
<h1 class="font-heading text-blue-night text-4xl font-extrabold leading-tight mb-6">
|
||||
<h1 class="font-heading text-slate-900 dark:text-slate-50 text-4xl font-extrabold leading-tight mb-6">
|
||||
À propos
|
||||
</h1>
|
||||
|
||||
|
||||
@ -34,34 +34,41 @@ const seriesList = Array.from(seriesMap.values());
|
||||
<BaseLayout title="Accueil">
|
||||
<!-- Hero -->
|
||||
<section class="max-w-3xl mx-auto text-center px-6 pt-16 pb-10">
|
||||
<h1 class="font-heading text-blue-night text-4xl md:text-5xl font-extrabold leading-tight mb-4">
|
||||
L'IA en PME,<br />sans bullshit.
|
||||
<h1
|
||||
class="font-heading text-slate-900 dark:text-slate-50 text-4xl md:text-5xl font-extrabold leading-tight mb-4"
|
||||
>
|
||||
L'IA en PME,<br /><span class="text-gradient">sans bullshit.</span>
|
||||
</h1>
|
||||
<p class="text-lg text-text-light max-w-xl mx-auto">
|
||||
Journal de bord d'un DSI qui teste, bidouille, plante et recommence.
|
||||
Ce qui marche vraiment, ce qui ne marche pas, et pourquoi.
|
||||
<p class="text-lg text-slate-500 dark:text-slate-400 max-w-xl mx-auto">
|
||||
Journal de bord d'un DSI qui teste, bidouille, plante et recommence. Ce
|
||||
qui marche vraiment, ce qui ne marche pas, et pourquoi.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- Catégories -->
|
||||
<div class="flex justify-center gap-3 flex-wrap px-6 mb-10">
|
||||
{categories.map((cat) => (
|
||||
{
|
||||
categories.map((cat) => (
|
||||
<a
|
||||
href={`/categorie/${cat.toLowerCase().replace(/ & /g, "-").replace(/ /g, "-")}`}
|
||||
class="px-4 py-1.5 rounded-full text-xs font-medium border border-border bg-white text-text-light no-underline hover:bg-sage-light hover:border-sage hover:text-sage transition-colors"
|
||||
class="px-4 py-1.5 rounded-full text-xs font-medium border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 text-slate-500 dark:text-slate-400 no-underline hover:border-brand-cyan hover:text-brand-deep dark:hover:text-brand-cyan transition-colors"
|
||||
>
|
||||
{cat}
|
||||
</a>
|
||||
))}
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Articles -->
|
||||
<section class="max-w-6xl mx-auto px-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="font-heading text-xl text-blue-night">Articles récents</h2>
|
||||
<h2 class="font-heading text-xl text-slate-900 dark:text-slate-50">
|
||||
Articles récents
|
||||
</h2>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{posts.map((post) => (
|
||||
{
|
||||
posts.map((post) => (
|
||||
<ArticleCard
|
||||
title={post.data.title}
|
||||
description={post.data.description}
|
||||
@ -71,36 +78,45 @@ const seriesList = Array.from(seriesMap.values());
|
||||
slug={post.id}
|
||||
series={post.data.series}
|
||||
/>
|
||||
))}
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Séries en cours -->
|
||||
{seriesList.length > 0 && (
|
||||
{
|
||||
seriesList.length > 0 && (
|
||||
<section class="max-w-6xl mx-auto px-6 mt-16">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="font-heading text-xl text-blue-night">Séries en cours</h2>
|
||||
<a href="/series" class="text-sm text-sage no-underline font-medium hover:underline">
|
||||
<h2 class="font-heading text-xl text-slate-900 dark:text-slate-50">
|
||||
Séries en cours
|
||||
</h2>
|
||||
<a
|
||||
href="/series"
|
||||
class="text-sm text-brand-deep dark:text-brand-cyan no-underline font-medium hover:underline"
|
||||
>
|
||||
Toutes les séries →
|
||||
</a>
|
||||
</div>
|
||||
{seriesList.map((s) => (
|
||||
<a
|
||||
href={`/series/${s.name.toLowerCase().replace(/ /g, "-")}`}
|
||||
class="block bg-white border border-border rounded-xl p-5 mb-3 no-underline hover:translate-x-1 transition-transform"
|
||||
class="block bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-xl p-5 mb-3 no-underline hover:border-brand-cyan/50 hover:translate-x-1 transition-all"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 class="font-heading text-blue-night text-base font-semibold">{s.name}</h3>
|
||||
<h3 class="font-heading text-slate-900 dark:text-slate-50 text-base font-semibold">
|
||||
{s.name}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-28 h-1 bg-border rounded-full overflow-hidden">
|
||||
<div class="w-28 h-1 bg-slate-200 dark:bg-slate-800 rounded-full overflow-hidden">
|
||||
<div
|
||||
class="h-full bg-sage rounded-full"
|
||||
class="h-full bg-brand-gradient rounded-full"
|
||||
style={`width: ${(s.count / s.total) * 100}%`}
|
||||
/>
|
||||
</div>
|
||||
<span class="text-xs text-text-light whitespace-nowrap">
|
||||
<span class="text-xs text-slate-400 dark:text-slate-500 whitespace-nowrap">
|
||||
{s.count} / {s.total} articles
|
||||
</span>
|
||||
</div>
|
||||
@ -108,5 +124,6 @@ const seriesList = Array.from(seriesMap.values());
|
||||
</a>
|
||||
))}
|
||||
</section>
|
||||
)}
|
||||
)
|
||||
}
|
||||
</BaseLayout>
|
||||
|
||||
@ -32,31 +32,38 @@ const seriesList = Array.from(seriesMap.values()).map((s) => ({
|
||||
|
||||
<BaseLayout title="Séries">
|
||||
<section class="max-w-3xl mx-auto px-6 py-16">
|
||||
<h1 class="font-heading text-blue-night text-4xl font-extrabold leading-tight mb-3">
|
||||
<h1
|
||||
class="font-heading text-slate-900 dark:text-slate-50 text-4xl font-extrabold leading-tight mb-3"
|
||||
>
|
||||
Séries
|
||||
</h1>
|
||||
<p class="text-text-light mb-10">
|
||||
<p class="text-slate-500 dark:text-slate-400 mb-10">
|
||||
Des articles qui se suivent pour explorer un sujet en profondeur.
|
||||
</p>
|
||||
|
||||
{seriesList.length === 0 && (
|
||||
<p class="text-text-light italic">Aucune série pour le moment.</p>
|
||||
)}
|
||||
{
|
||||
seriesList.length === 0 && (
|
||||
<p class="text-slate-500 dark:text-slate-400 italic">
|
||||
Aucune série pour le moment.
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
||||
{seriesList.map((series) => (
|
||||
<div class="bg-white border border-border rounded-xl p-6 mb-6">
|
||||
{
|
||||
seriesList.map((series) => (
|
||||
<div class="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-xl p-6 mb-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="font-heading text-blue-night text-xl font-semibold">
|
||||
<h2 class="font-heading text-slate-900 dark:text-slate-50 text-xl font-semibold">
|
||||
{series.name}
|
||||
</h2>
|
||||
<span class="text-xs text-text-light">
|
||||
<span class="text-xs text-slate-400 dark:text-slate-500">
|
||||
{series.posts.length} / {series.total} articles
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="w-full h-1 bg-border rounded-full mb-5 overflow-hidden">
|
||||
<div class="w-full h-1 bg-slate-200 dark:bg-slate-800 rounded-full mb-5 overflow-hidden">
|
||||
<div
|
||||
class="h-full bg-sage rounded-full"
|
||||
class="h-full bg-brand-gradient rounded-full"
|
||||
style={`width: ${(series.posts.length / series.total) * 100}%`}
|
||||
/>
|
||||
</div>
|
||||
@ -66,9 +73,9 @@ const seriesList = Array.from(seriesMap.values()).map((s) => ({
|
||||
<li>
|
||||
<a
|
||||
href={`/blog/${post.id}`}
|
||||
class="flex items-center gap-3 py-2 px-3 rounded-lg text-sm text-text-light no-underline hover:bg-sage-light/50 hover:text-blue-night transition-colors"
|
||||
class="flex items-center gap-3 py-2 px-3 rounded-lg text-sm text-slate-500 dark:text-slate-400 no-underline hover:bg-slate-100 dark:hover:bg-slate-800 hover:text-slate-900 dark:hover:text-white transition-colors"
|
||||
>
|
||||
<span class="flex-shrink-0 w-6 h-6 rounded-full bg-sage text-white flex items-center justify-center text-xs font-bold">
|
||||
<span class="flex-shrink-0 w-6 h-6 rounded-full bg-brand-gradient text-white flex items-center justify-center text-xs font-bold">
|
||||
{post.data.series?.part}
|
||||
</span>
|
||||
<span>{post.data.title}</span>
|
||||
@ -79,8 +86,8 @@ const seriesList = Array.from(seriesMap.values()).map((s) => ({
|
||||
{Array.from(
|
||||
{ length: series.total - series.posts.length },
|
||||
(_, i) => (
|
||||
<li class="flex items-center gap-3 py-2 px-3 text-sm text-text-light/50">
|
||||
<span class="flex-shrink-0 w-6 h-6 rounded-full bg-border text-text-light/50 flex items-center justify-center text-xs font-bold">
|
||||
<li class="flex items-center gap-3 py-2 px-3 text-sm text-slate-400/60 dark:text-slate-500/60">
|
||||
<span class="flex-shrink-0 w-6 h-6 rounded-full bg-slate-200 dark:bg-slate-800 text-slate-400 dark:text-slate-500 flex items-center justify-center text-xs font-bold">
|
||||
{series.posts.length + i + 1}
|
||||
</span>
|
||||
<span class="italic">À venir...</span>
|
||||
@ -89,6 +96,7 @@ const seriesList = Array.from(seriesMap.values()).map((s) => ({
|
||||
)}
|
||||
</ol>
|
||||
</div>
|
||||
))}
|
||||
))
|
||||
}
|
||||
</section>
|
||||
</BaseLayout>
|
||||
|
||||
@ -2,60 +2,86 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@import url("https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,300;0,9..144,600;0,9..144,800;1,9..144,400&family=Inter:wght@400;500;600&display=swap");
|
||||
/* Polices Fraunces + Inter : chargées via <link> dans BaseLayout.astro */
|
||||
|
||||
@layer base {
|
||||
html {
|
||||
@apply scroll-smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-bg-warm text-text-dark font-body leading-relaxed;
|
||||
@apply bg-canvas-light text-slate-700 font-body leading-relaxed antialiased;
|
||||
@apply dark:bg-canvas-dark dark:text-slate-300;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
@apply font-heading text-blue-night;
|
||||
@apply font-heading text-slate-900 dark:text-slate-50;
|
||||
}
|
||||
|
||||
/* Selection picks up the brand teal */
|
||||
::selection {
|
||||
@apply bg-brand-cyan/25 text-slate-900 dark:text-white;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── Prose (articles MDX) ─── */
|
||||
@layer components {
|
||||
/* Reusable gradient text helper for the wordmark / accents */
|
||||
.text-gradient {
|
||||
@apply bg-brand-gradient bg-clip-text text-transparent;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────
|
||||
Prose (articles MDX) — light + dark
|
||||
───────────────────────────────────────────── */
|
||||
.prose {
|
||||
@apply max-w-none text-text-dark leading-relaxed;
|
||||
@apply max-w-none text-slate-700 dark:text-slate-300 leading-relaxed;
|
||||
}
|
||||
|
||||
.prose h2 {
|
||||
@apply font-heading text-blue-night text-2xl font-semibold mt-10 mb-4;
|
||||
@apply font-heading text-slate-900 dark:text-slate-50 text-2xl font-semibold mt-10 mb-4;
|
||||
}
|
||||
|
||||
.prose h3 {
|
||||
@apply font-heading text-blue-night text-xl font-semibold mt-8 mb-3;
|
||||
@apply font-heading text-slate-900 dark:text-slate-50 text-xl font-semibold mt-8 mb-3;
|
||||
}
|
||||
|
||||
.prose p {
|
||||
@apply mb-5;
|
||||
}
|
||||
|
||||
.prose strong {
|
||||
@apply text-slate-900 dark:text-slate-100 font-semibold;
|
||||
}
|
||||
|
||||
.prose a {
|
||||
@apply text-sage underline underline-offset-2 hover:text-blue-night transition-colors;
|
||||
@apply text-brand-deep dark:text-brand-cyan underline underline-offset-2 decoration-brand-cyan/40 hover:decoration-brand-cyan transition-colors;
|
||||
}
|
||||
|
||||
.prose blockquote {
|
||||
@apply border-l-4 border-sage pl-4 italic text-text-light my-6;
|
||||
@apply border-l-4 border-brand-cyan pl-4 italic text-slate-500 dark:text-slate-400 my-6;
|
||||
}
|
||||
|
||||
.prose code {
|
||||
@apply bg-sage-light text-blue-night px-1.5 py-0.5 rounded text-sm;
|
||||
/* Inline code (not inside a fenced block) */
|
||||
.prose :not(pre) > code {
|
||||
@apply bg-slate-100 dark:bg-slate-800 text-brand-deep dark:text-brand-teal px-1.5 py-0.5 rounded text-[0.9em] font-medium;
|
||||
}
|
||||
|
||||
/* Fenced code blocks: Shiki sets the Slate background via the theme.
|
||||
We only handle spacing, rounding and horizontal scroll. */
|
||||
.prose pre {
|
||||
@apply bg-blue-night text-white rounded-lg p-4 overflow-x-auto my-6;
|
||||
@apply rounded-xl p-4 overflow-x-auto my-6 text-sm leading-relaxed ring-1 ring-slate-800/60;
|
||||
}
|
||||
|
||||
.prose pre code {
|
||||
@apply bg-transparent text-white p-0;
|
||||
@apply bg-transparent p-0;
|
||||
}
|
||||
|
||||
.prose img {
|
||||
@apply rounded-lg my-6;
|
||||
@apply rounded-xl my-6 ring-1 ring-slate-200 dark:ring-slate-800;
|
||||
}
|
||||
|
||||
.prose ul,
|
||||
@ -63,6 +89,22 @@
|
||||
@apply my-4 pl-6;
|
||||
}
|
||||
|
||||
.prose ul {
|
||||
@apply list-disc;
|
||||
}
|
||||
|
||||
.prose ol {
|
||||
@apply list-decimal;
|
||||
}
|
||||
|
||||
.prose li {
|
||||
@apply mb-2;
|
||||
}
|
||||
|
||||
.prose li::marker {
|
||||
@apply text-brand-cyan;
|
||||
}
|
||||
|
||||
.prose hr {
|
||||
@apply my-10 border-slate-200 dark:border-slate-800;
|
||||
}
|
||||
|
||||
@ -1,22 +1,48 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"],
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
"blue-night": "#1B2A4A",
|
||||
"blue-mid": "#2d4a7a",
|
||||
sage: "#6B8F71",
|
||||
"sage-light": "#E8F0E9",
|
||||
"bg-warm": "#FAFAF7",
|
||||
"text-dark": "#2D2D2D",
|
||||
"text-light": "#6B6B6B",
|
||||
border: "#E5E5E0",
|
||||
// ─── Brand "Electric Teal" ───
|
||||
brand: {
|
||||
cyan: "#06B6D4", // accent primary
|
||||
blue: "#3B82F6", // accent secondary
|
||||
teal: "#22D3EE", // deep marine highlight
|
||||
deep: "#0891B2", // deep teal (hover / prestige)
|
||||
indigo: "#4F46E5",
|
||||
},
|
||||
// ─── Canvas (Slate) ───
|
||||
canvas: {
|
||||
dark: "#0F172A", // dark body / terminal backdrop
|
||||
deep: "#090D16", // dark footer / deepest sheet
|
||||
light: "#F8FAFC", // light body (alpine snow)
|
||||
},
|
||||
// ─── 7-color diagnostic & syntax spectrum ───
|
||||
diag: {
|
||||
rose: "#F43F5E", // exception / invalid
|
||||
orange: "#FF6B00", // keyword / control
|
||||
amber: "#F59E0B", // string / regex
|
||||
emerald: "#10B981", // support / type / class
|
||||
sky: "#0EA5E9", // variable / parameter
|
||||
indigo: "#6366F1", // entity / function
|
||||
violet: "#8B5CF6", // comment / metadata
|
||||
},
|
||||
},
|
||||
fontFamily: {
|
||||
heading: ["Fraunces", "Georgia", "serif"],
|
||||
body: ["Inter", "-apple-system", "BlinkMacSystemFont", "sans-serif"],
|
||||
},
|
||||
backgroundImage: {
|
||||
// 135° brand gradient (logo ligatures, CTAs, highlights)
|
||||
"brand-gradient": "linear-gradient(135deg, #06B6D4 0%, #3B82F6 100%)",
|
||||
"brand-gradient-soft":
|
||||
"linear-gradient(135deg, rgba(6,182,212,0.12) 0%, rgba(59,130,246,0.12) 100%)",
|
||||
},
|
||||
boxShadow: {
|
||||
glow: "0 8px 30px -8px rgba(6,182,212,0.35)",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
|
||||
Loading…
Reference in New Issue
Block a user