Compare commits

...

2 commits

Author SHA1 Message Date
LIABEUF, Stephane (External)
3d8087dd5a fix: prevent service card titles from overlapping green checkmarks
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 09:39:30 +02:00
LIABEUF, Stephane (External)
7044983bde feat: add ecosystem Coolify page with favicon and hero button
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 09:32:50 +02:00
3 changed files with 728 additions and 1 deletions

19
ecosystem-favicon.svg Normal file
View file

@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<defs>
<linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#00d8ff"/>
<stop offset="100%" stop-color="#36f55d"/>
</linearGradient>
</defs>
<rect width="32" height="32" rx="6" fill="#03070c"/>
<rect width="32" height="32" rx="6" fill="url(#g)" opacity="0.18"/>
<circle cx="16" cy="16" r="4" fill="none" stroke="url(#g)" stroke-width="2"/>
<circle cx="7" cy="10" r="2.2" fill="url(#g)"/>
<circle cx="25" cy="10" r="2.2" fill="url(#g)"/>
<circle cx="7" cy="22" r="2.2" fill="url(#g)"/>
<circle cx="25" cy="22" r="2.2" fill="url(#g)"/>
<line x1="9" y1="11" x2="13" y2="14" stroke="url(#g)" stroke-width="1.5" opacity="0.8"/>
<line x1="23" y1="11" x2="19" y2="14" stroke="url(#g)" stroke-width="1.5" opacity="0.8"/>
<line x1="9" y1="21" x2="13" y2="18" stroke="url(#g)" stroke-width="1.5" opacity="0.8"/>
<line x1="23" y1="21" x2="19" y2="18" stroke="url(#g)" stroke-width="1.5" opacity="0.8"/>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

703
ecosystem.html Normal file
View file

@ -0,0 +1,703 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Mon écosystème Coolify — Stéphane Liabeuf</title>
<link rel="icon" type="image/svg+xml" href="ecosystem-favicon.svg">
<link rel="shortcut icon" href="ecosystem-favicon.svg">
<script defer src="https://analytics.stephaneliabeuf.fr/script.js" data-website-id="e2570832-3cf4-4645-8d02-2e9676f123be"></script>
<style>
:root {
--bg: #03070c;
--panel: rgba(7, 18, 29, 0.78);
--panel-2: rgba(5, 14, 23, 0.9);
--line: rgba(0, 215, 255, 0.28);
--line-soft: rgba(83, 196, 255, 0.16);
--cyan: #00d8ff;
--cyan-soft: #3ce7ff;
--green: #36f55d;
--green-soft: rgba(54, 245, 93, 0.18);
--yellow: #f3c12b;
--orange: #f27b2f;
--purple: #a46dff;
--blue: #228cff;
--text: #f4f7fb;
--muted: #9aa8b8;
--muted-2: #6e7d8d;
--radius-xl: 22px;
--radius-lg: 16px;
--shadow-cyan: 0 0 28px rgba(0, 216, 255, 0.18);
--shadow-green: 0 0 24px rgba(54, 245, 93, 0.16);
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
background:
radial-gradient(circle at 50% 4%, rgba(0, 216, 255, 0.16), transparent 22%),
radial-gradient(circle at 18% 72%, rgba(0, 216, 255, 0.08), transparent 25%),
radial-gradient(circle at 82% 78%, rgba(54, 245, 93, 0.06), transparent 24%),
linear-gradient(180deg, #02050a 0%, #04101b 52%, #01050a 100%);
color: var(--text);
overflow-x: hidden;
}
body::before {
content: "";
position: fixed;
inset: 0;
pointer-events: none;
background-image:
linear-gradient(rgba(255,255,255,0.025) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,0.018) 1px, transparent 1px);
background-size: 44px 44px;
mask-image: radial-gradient(circle at center, #000 0%, transparent 75%);
opacity: 0.35;
}
.page {
width: min(100vw, 1920px);
aspect-ratio: 16 / 9;
min-height: 900px;
margin: 0 auto;
padding: 46px 26px 34px;
position: relative;
}
.page::after {
content: "";
position: absolute;
inset: 28px;
border-radius: 28px;
border: 1px solid rgba(0, 216, 255, 0.04);
pointer-events: none;
}
header {
display: grid;
grid-template-columns: 1.08fr 0.72fr 1.05fr;
gap: 26px;
align-items: start;
position: relative;
z-index: 1;
margin-bottom: 48px;
}
.title h1 {
font-size: clamp(42px, 4.1vw, 72px);
line-height: 0.95;
letter-spacing: -0.05em;
margin: 0 0 18px;
font-weight: 850;
text-shadow: 0 0 24px rgba(255,255,255,0.13);
}
.title p {
margin: 0;
color: #c3cad4;
font-size: clamp(16px, 1.25vw, 21px);
letter-spacing: -0.02em;
}
.orchestration {
display: flex;
flex-direction: column;
align-items: center;
margin-top: -18px;
position: relative;
}
.orchestration::before,
.orchestration::after {
content: "";
position: absolute;
top: 118px;
width: 190px;
height: 56px;
border-top: 2px solid rgba(0, 216, 255, 0.55);
border-bottom: 2px solid rgba(0, 216, 255, 0.08);
filter: drop-shadow(0 0 6px rgba(0,216,255,.6));
}
.orchestration::before {
right: 74%;
clip-path: polygon(0 62%, 25% 62%, 33% 34%, 58% 34%, 65% 10%, 100% 10%, 100% 18%, 69% 18%, 62% 42%, 37% 42%, 29% 70%, 0 70%);
}
.orchestration::after {
left: 74%;
transform: scaleX(-1);
clip-path: polygon(0 62%, 25% 62%, 33% 34%, 58% 34%, 65% 10%, 100% 10%, 100% 18%, 69% 18%, 62% 42%, 37% 42%, 29% 70%, 0 70%);
}
.hub {
width: 218px;
height: 154px;
display: grid;
place-items: center;
text-align: center;
background: linear-gradient(180deg, rgba(8, 25, 39, .98), rgba(4, 12, 22, .9));
border: 2px solid rgba(0, 216, 255, 0.72);
box-shadow: inset 0 0 32px rgba(0,216,255,.13), 0 0 30px rgba(0,216,255,.18);
clip-path: polygon(24% 0, 76% 0, 100% 24%, 100% 78%, 78% 100%, 22% 100%, 0 78%, 0 24%);
position: relative;
z-index: 2;
}
.hub .logo {
font-size: 48px;
line-height: 1;
color: var(--cyan);
text-shadow: 0 0 18px rgba(0,216,255,.85);
margin-bottom: 6px;
font-weight: 800;
}
.hub strong {
display: block;
font-size: 20px;
letter-spacing: -0.03em;
}
.hub span {
display: block;
color: var(--cyan-soft);
font-size: 30px;
font-weight: 850;
margin-top: 4px;
letter-spacing: -0.04em;
}
.server-chip {
margin-top: -8px;
display: inline-flex;
gap: 10px;
align-items: center;
padding: 12px 24px;
background: rgba(8, 22, 34, 0.92);
border: 1px solid rgba(0,216,255,.32);
border-radius: 12px;
color: #c6d1dd;
box-shadow: var(--shadow-cyan);
font-size: 18px;
z-index: 3;
}
.server-chip b { color: var(--cyan); font-weight: 700; }
.stats {
height: 118px;
border: 1px solid var(--line);
border-radius: 18px;
background: linear-gradient(180deg, rgba(8, 18, 29, .88), rgba(4, 11, 20, .82));
display: grid;
grid-template-columns: repeat(3, 1fr);
align-items: center;
box-shadow: inset 0 0 22px rgba(255,255,255,.025);
overflow: hidden;
}
.stat {
height: 70%;
display: grid;
grid-template-columns: 46px minmax(0, 1fr);
gap: 14px;
align-items: center;
padding: 0 24px;
border-right: 1px solid rgba(150, 180, 210, 0.18);
min-width: 0;
}
.stat:last-child { border-right: 0; }
.stat svg {
width: 44px;
height: 44px;
color: var(--cyan);
filter: drop-shadow(0 0 8px rgba(0,216,255,.58));
}
.stat .num {
display: block;
font-size: clamp(30px, 2.35vw, 42px);
line-height: 0.92;
font-weight: 850;
letter-spacing: -0.05em;
white-space: nowrap;
}
.stat .label {
display: block;
margin-top: 8px;
color: #c8d1dc;
font-size: clamp(13px, 0.95vw, 17px);
white-space: nowrap;
}
.section {
position: relative;
z-index: 1;
border: 1px solid rgba(0, 216, 255, 0.24);
border-radius: var(--radius-xl);
background: linear-gradient(180deg, rgba(6, 17, 28, 0.82), rgba(2, 9, 16, 0.72));
box-shadow: inset 0 0 40px rgba(0,216,255,.035), 0 0 25px rgba(0,0,0,.25);
padding: 26px 34px;
margin-bottom: 18px;
}
.section-title {
display: grid;
grid-template-columns: auto auto 1fr;
align-items: center;
gap: 18px;
margin: -6px 0 24px;
color: white;
font-weight: 850;
font-size: 30px;
letter-spacing: -0.04em;
}
.section-title svg {
color: var(--cyan);
filter: drop-shadow(0 0 7px rgba(0,216,255,.5));
}
.section-title::after {
content: "";
height: 2px;
background: linear-gradient(90deg, rgba(0,216,255,.7), transparent);
margin-left: 10px;
}
.apps-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 52px;
}
.app-card,
.service-card {
background: linear-gradient(180deg, rgba(9, 24, 38, 0.78), rgba(3, 10, 18, 0.88));
border: 1px solid rgba(116, 190, 220, 0.22);
border-radius: var(--radius-lg);
box-shadow: inset 0 0 24px rgba(255,255,255,.018), 0 0 20px rgba(0,0,0,.16);
position: relative;
}
.app-card { overflow: hidden; }
.app-card::before {
content: "";
position: absolute;
inset: 0 auto 0 0;
width: 3px;
background: var(--green);
box-shadow: 0 0 18px var(--green);
}
.app-card {
min-height: 148px;
display: grid;
grid-template-columns: 118px 1fr;
align-items: center;
padding: 22px 30px;
}
.icon-circle {
width: 76px;
height: 76px;
border-radius: 50%;
display: grid;
place-items: center;
background: radial-gradient(circle at 50% 45%, rgba(0,216,255,.18), rgba(0,216,255,.04) 65%, transparent 66%);
border: 1px solid rgba(0, 216, 255, 0.25);
color: var(--cyan);
filter: drop-shadow(0 0 14px rgba(0,216,255,.16));
}
.app-card h3 {
margin: 0 0 10px;
font-size: 29px;
letter-spacing: -0.04em;
}
.url {
color: var(--cyan);
font-size: 20px;
margin-bottom: 12px;
text-shadow: 0 0 8px rgba(0,216,255,.3);
}
.status-pill {
display: inline-flex;
align-items: center;
gap: 9px;
color: #b7ffc5;
background: var(--green-soft);
border-radius: 999px;
padding: 8px 18px;
font-weight: 700;
font-size: 17px;
}
.check {
width: 20px;
height: 20px;
display: inline-grid;
place-items: center;
border-radius: 50%;
background: var(--green);
color: #08210e;
font-size: 13px;
font-weight: 900;
box-shadow: 0 0 14px rgba(54,245,93,.8);
flex: none;
}
.services-section { padding-bottom: 34px; }
.service-groups {
display: grid;
grid-template-columns: 0.95fr 1fr 1.32fr;
gap: 22px;
}
.group {
border: 1px solid rgba(0, 216, 255, 0.22);
border-radius: 18px;
background: rgba(2, 10, 17, 0.34);
padding: 22px 14px 20px;
min-height: 322px;
}
.group-title {
height: 36px;
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
margin-bottom: 18px;
font-size: 19px;
font-weight: 800;
letter-spacing: -0.03em;
}
.teal { color: #44f1d3; }
.purple { color: var(--purple); }
.yellow { color: var(--yellow); }
.service-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 18px 14px;
}
.productivity .service-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.service-card {
min-height: 102px;
display: grid;
grid-template-columns: 74px 1fr 28px;
align-items: center;
gap: 0 8px;
padding: 14px 14px;
}
.productivity .service-card {
grid-template-columns: 62px 1fr 24px;
}
.service-card h4 {
margin: 0;
font-size: clamp(13px, 1.2vw, 20px);
line-height: 1.22;
letter-spacing: -0.025em;
font-weight: 780;
}
.mini-icon {
width: 58px;
height: 58px;
border-radius: 50%;
display: grid;
place-items: center;
background:
radial-gradient(circle at 48% 44%, rgba(255,255,255,.10), transparent 18%),
radial-gradient(circle, rgba(0,216,255,.22), rgba(0,216,255,.045) 67%);
border: 1px solid rgba(0,216,255,.22);
box-shadow:
inset 0 0 16px rgba(255,255,255,.035),
inset 0 0 22px rgba(0,216,255,.055),
0 0 16px rgba(0,216,255,.10);
}
.mini-icon svg,
.icon-circle svg,
.feature svg,
.stat svg {
stroke-linecap: round;
stroke-linejoin: round;
}
.mini-icon svg {
filter: drop-shadow(0 0 8px currentColor);
}
.green-dot {
width: 20px;
height: 20px;
border-radius: 999px;
display: grid;
place-items: center;
background: var(--green);
color: #06240e;
font-size: 13px;
font-weight: 950;
box-shadow: 0 0 15px rgba(54,245,93,.85);
}
.cyan-icon { color: var(--cyan); }
.green-icon { color: #73ff64; }
.yellow-icon { color: var(--yellow); }
.orange-icon { color: var(--orange); }
.blue-icon { color: var(--blue); }
.pink-icon { color: #ff6e92; }
.footer-strip {
position: relative;
z-index: 1;
margin-top: 38px;
min-height: 116px;
border: 1px solid rgba(0, 216, 255, 0.2);
border-radius: 18px;
background: linear-gradient(180deg, rgba(6, 17, 28, 0.78), rgba(2, 9, 16, 0.76));
display: grid;
grid-template-columns: repeat(4, 1fr);
align-items: center;
overflow: hidden;
}
.feature {
display: grid;
grid-template-columns: 72px 1fr;
align-items: center;
gap: 18px;
padding: 0 50px;
min-height: 72px;
border-right: 1px solid rgba(180, 210, 230, 0.13);
}
.feature:last-child { border-right: 0; }
.feature svg {
color: var(--cyan);
filter: drop-shadow(0 0 8px rgba(0,216,255,.56));
}
.feature h3 {
margin: 0 0 6px;
font-size: 21px;
letter-spacing: -0.035em;
}
.feature p {
margin: 0;
color: #aeb8c6;
font-size: 16px;
}
svg { stroke: currentColor; }
.back-bar {
position: relative;
z-index: 1;
text-align: left;
padding: 0 0 28px;
}
.back-bar a {
color: var(--muted);
font-size: 13px;
text-decoration: none;
letter-spacing: .08em;
text-transform: uppercase;
transition: color .2s;
}
.back-bar a:hover { color: var(--cyan); }
.bottom-spacer { height: 60px; }
@media (max-width: 1180px) {
.page { aspect-ratio: auto; min-height: 100vh; padding: 28px 18px; }
header { grid-template-columns: 1fr; text-align: center; }
.stats { max-width: 720px; margin: 0 auto; }
.orchestration { order: -1; }
.orchestration::before, .orchestration::after { display: none; }
.apps-grid, .service-groups, .footer-strip { grid-template-columns: 1fr; }
.productivity .service-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.feature { border-right: 0; border-bottom: 1px solid rgba(180, 210, 230, 0.13); padding: 24px; }
}
@media (max-width: 700px) {
.stats { height: auto; grid-template-columns: 1fr; }
.stat { border-right: 0; border-bottom: 1px solid rgba(150, 180, 210, 0.18); padding: 22px; }
.app-card { grid-template-columns: 78px 1fr; padding: 18px; }
.icon-circle { width: 60px; height: 60px; }
.service-grid, .productivity .service-grid { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<main class="page">
<div class="back-bar"><a id="backBtn" href="index.html">← Retour au CV</a></div>
<header>
<div class="title">
<h1>Mon écosystème Coolify</h1>
<p>Vue d'ensemble des services déployés dans l'environnement production</p>
</div>
<div class="orchestration" aria-label="Orchestration Coolify">
<div class="hub">
<div>
<div class="logo"></div>
<strong>Orchestration</strong>
<span>Coolify</span>
</div>
</div>
<div class="server-chip">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke-width="1.8"><rect x="3" y="4" width="18" height="6" rx="1.5"/><rect x="3" y="14" width="18" height="6" rx="1.5"/><path d="M7 7h.01M7 17h.01M11 7h6M11 17h6"/></svg>
Serveur : <b>localhost</b>
</div>
</div>
<aside class="stats" aria-label="Synthèse des ressources">
<div class="stat">
<svg width="50" height="50" viewBox="0 0 48 48" fill="none" stroke-width="2.2"><path d="M8 10h32v7H8zM8 21h32v7H8zM8 32h32v7H8z"/><path d="M13 13.5h.01M13 24.5h.01M13 35.5h.01"/></svg>
<div><span class="num">14</span><span class="label">ressources</span></div>
</div>
<div class="stat">
<svg width="50" height="50" viewBox="0 0 48 48" fill="none" stroke-width="2.2"><path d="m24 5 16 9v20l-16 9-16-9V14z"/><path d="M8 14l16 9 16-9M24 43V23"/></svg>
<div><span class="num">2</span><span class="label">applications</span></div>
</div>
<div class="stat">
<svg width="50" height="50" viewBox="0 0 48 48" fill="none" stroke-width="2.2"><path d="M24 16a8 8 0 1 0 0 16 8 8 0 0 0 0-16z"/><path d="M24 4v7M24 37v7M6.7 14l6 3.5M35.3 30.5l6 3.5M6.7 34l6-3.5M35.3 17.5l6-3.5M10 8l5 5M33 35l5 5M10 40l5-5M33 13l5-5"/></svg>
<div><span class="num">12</span><span class="label">services</span></div>
</div>
</aside>
</header>
<section class="section">
<div class="section-title">
<svg width="34" height="34" viewBox="0 0 24 24" fill="none" stroke-width="1.8"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18"/></svg>
<span>1. Applications</span>
</div>
<div class="apps-grid">
<article class="app-card">
<div class="icon-circle">
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" stroke-width="2.4"><rect x="7" y="11" width="34" height="26" rx="2"/><path d="M7 18h34M13 15h.01M18 15h.01"/></svg>
</div>
<div>
<h3>CV</h3>
<div class="url">https://cv.stephaneliabeuf.fr</div>
<div class="status-pill"><span class="check"></span> En ligne</div>
</div>
</article>
<article class="app-card">
<div class="icon-circle">
<svg width="50" height="50" viewBox="0 0 48 48" fill="none" stroke-width="2.4"><circle cx="24" cy="24" r="17"/><path d="M7 24h34M24 7a26 26 0 0 1 0 34M24 7a26 26 0 0 0 0 34"/></svg>
</div>
<div>
<h3>Site principal</h3>
<div class="url">https://www.stephaneliabeuf.fr</div>
<div class="status-pill"><span class="check"></span> En ligne</div>
</div>
</article>
</div>
</section>
<section class="section services-section">
<div class="section-title">
<svg width="34" height="34" viewBox="0 0 24 24" fill="none" stroke-width="1.8"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.8 1.8 0 0 0 .36 2l.04.04a2.1 2.1 0 0 1-3 3l-.04-.04a1.8 1.8 0 0 0-2-.36 1.8 1.8 0 0 0-1 1.64V21a2.1 2.1 0 0 1-4.2 0v-.06a1.8 1.8 0 0 0-1-1.64 1.8 1.8 0 0 0-2 .36l-.04.04a2.1 2.1 0 0 1-3-3l.04-.04a1.8 1.8 0 0 0 .36-2 1.8 1.8 0 0 0-1.64-1H3a2.1 2.1 0 0 1 0-4.2h.06a1.8 1.8 0 0 0 1.64-1 1.8 1.8 0 0 0-.36-2l-.04-.04a2.1 2.1 0 0 1 3-3l.04.04a1.8 1.8 0 0 0 2 .36 1.8 1.8 0 0 0 1-1.64V3a2.1 2.1 0 0 1 4.2 0v.06a1.8 1.8 0 0 0 1 1.64 1.8 1.8 0 0 0 2-.36l.04-.04a2.1 2.1 0 0 1 3 3l-.04.04a1.8 1.8 0 0 0-.36 2 1.8 1.8 0 0 0 1.64 1H21a2.1 2.1 0 0 1 0 4.2h-.06a1.8 1.8 0 0 0-1.54.46z"/></svg>
<span>2. Services</span>
</div>
<div class="service-groups">
<div class="group">
<div class="group-title teal">
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke-width="1.8"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75"/></svg>
Collaboration & design
</div>
<div class="service-grid">
<article class="service-card"><div class="mini-icon cyan-icon"><svg width="36" height="36" viewBox="0 0 48 48" fill="none" stroke-width="2.4"><path d="M24 5 42 15 24 25 6 15z"/><path d="m6 24 18 10 18-10M6 33l18 10 18-10"/></svg></div><h4>AFFiNE</h4><span class="green-dot"></span></article>
<article class="service-card"><div class="mini-icon cyan-icon"><svg width="36" height="36" viewBox="0 0 48 48" fill="none" stroke-width="2.4"><path d="m13 35 5-2 18-18a4 4 0 0 0-6-6L12 27l-2 8z"/><path d="m28 11 9 9M9 39h30"/></svg></div><h4>Excalidraw</h4><span class="green-dot"></span></article>
<article class="service-card"><div class="mini-icon cyan-icon"><svg width="35" height="35" viewBox="0 0 48 48" fill="none" stroke-width="2.2"><path d="M12 39V19a12 12 0 1 1 24 0v20l-5-4-4 4-4-4-4 4-4-4z"/><path d="M20 23h.01M28 23h.01"/></svg></div><h4>Ghost</h4><span class="green-dot"></span></article>
</div>
</div>
<div class="group">
<div class="group-title purple">
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke-width="1.8"><path d="m16 18 6-6-6-6M8 6l-6 6 6 6M14 4l-4 16"/></svg>
DevOps & code
</div>
<div class="service-grid">
<article class="service-card"><div class="mini-icon green-icon"><svg width="35" height="35" viewBox="0 0 48 48" fill="none" stroke-width="2.3"><path d="M12 10h24v15c0 9-6 15-12 17-6-2-12-8-12-17z"/><path d="M22 17v12l8 4V13"/></svg></div><h4>Forgejo +<br>PostgreSQL</h4><span class="green-dot"></span></article>
<article class="service-card"><div class="mini-icon blue-icon"><svg width="36" height="36" viewBox="0 0 48 48" fill="none" stroke-width="2.4"><path d="M9 24 24 12l15 12v17H9z"/><path d="M19 41V29h10v12"/></svg></div><h4>Homebox</h4><span class="green-dot"></span></article>
<article class="service-card"><div class="mini-icon green-icon"><svg width="36" height="36" viewBox="0 0 48 48" fill="none" stroke-width="2.4"><circle cx="12" cy="24" r="4"/><circle cx="36" cy="12" r="4"/><circle cx="36" cy="36" r="4"/><path d="M16 22 32 14M16 26l16 8"/></svg></div><h4>n8n + Postgres<br>+ Worker</h4><span class="green-dot"></span></article>
<article class="service-card"><div class="mini-icon blue-icon"><svg width="36" height="36" viewBox="0 0 48 48" fill="none" stroke-width="2.4"><ellipse cx="24" cy="12" rx="14" ry="6"/><path d="M10 12v12c0 3.3 6.3 6 14 6s14-2.7 14-6V12"/><path d="M10 24v12c0 3.3 6.3 6 14 6s14-2.7 14-6V24"/></svg></div><h4>NocoDB</h4><span class="green-dot"></span></article>
</div>
</div>
<div class="group productivity">
<div class="group-title yellow">
<svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke-width="1.8"><path d="M14.7 6.3a5 5 0 0 0-6.6 6.6L3 18l3 3 5.1-5.1a5 5 0 0 0 6.6-6.6l-3.4 3.4-3-3z"/></svg>
Outils de productivité
</div>
<div class="service-grid">
<article class="service-card"><div class="mini-icon yellow-icon"><svg width="34" height="34" viewBox="0 0 48 48" fill="none" stroke-width="2.4"><path d="m15 9 8 8M11 13l8 8M28 20l11 11-8 8-11-11M8 36l11-11"/></svg></div><h4>IT-Tools</h4><span class="green-dot"></span></article>
<article class="service-card"><div class="mini-icon yellow-icon"><svg width="34" height="34" viewBox="0 0 48 48" fill="none" stroke-width="2.4"><rect x="8" y="13" width="32" height="24" rx="2"/><path d="m8 15 16 13 16-13"/></svg></div><h4>Mailpit</h4><span class="green-dot"></span></article>
<article class="service-card"><div class="mini-icon green-icon"><svg width="34" height="34" viewBox="0 0 48 48" fill="none" stroke-width="2.4"><path d="M12 37V25M24 37V16M36 37V9M8 37h32"/></svg></div><h4>Umami</h4><span class="green-dot"></span></article>
<article class="service-card"><div class="mini-icon orange-icon"><svg width="34" height="34" viewBox="0 0 48 48" fill="none" stroke-width="2.4"><path d="M10 15h28v26H10zM14 8h20l4 7H10z"/><path d="M19 26h10l-4-4M29 30H19l4 4"/></svg></div><h4>Open-Archiver</h4><span class="green-dot"></span></article>
<article class="service-card"><div class="mini-icon cyan-icon"><svg width="34" height="34" viewBox="0 0 48 48" fill="none" stroke-width="2.4"><path d="M8 40 20 9l20 19-32 12z"/><path d="m20 9 4 15 16 4M18 30a4 4 0 1 0 8 0 4 4 0 0 0-8 0z"/></svg></div><h4>Penpot</h4><span class="green-dot"></span></article>
</div>
</div>
</div>
</section>
<section class="footer-strip" aria-label="Bénéfices de l'infrastructure">
<div class="feature">
<svg width="54" height="54" viewBox="0 0 48 48" fill="none" stroke-width="2"><path d="M24 5 39 11v11c0 10-6.5 17-15 21C15.5 39 9 32 9 22V11z"/><path d="m18 24 4 4 8-9"/></svg>
<div><h3>Auto-hébergé & souverain</h3><p>Vos données restent chez vous.</p></div>
</div>
<div class="feature">
<svg width="54" height="54" viewBox="0 0 48 48" fill="none" stroke-width="2"><path d="M9 39h30M13 39V27M22 39V19M31 39V11"/></svg>
<div><h3>Orchestré & centralisé</h3><p>Déploiements, variables, logs et plus.</p></div>
</div>
<div class="feature">
<svg width="54" height="54" viewBox="0 0 48 48" fill="none" stroke-width="2"><rect x="12" y="21" width="24" height="20" rx="2"/><path d="M17 21v-7a7 7 0 0 1 14 0v7"/></svg>
<div><h3>Sécurisé</h3><p>HTTPS, secrets chiffrés, mises à jour.</p></div>
</div>
<div class="feature">
<svg width="54" height="54" viewBox="0 0 48 48" fill="none" stroke-width="2"><path d="M27 7c8 1 13 6 14 14L28 34l-14-14z"/><path d="M18 30 9 39l9-2 2-9M30 17h.01"/></svg>
<div><h3>Rapide & fiable</h3><p>Services toujours prêts, 24/7.</p></div>
</div>
</section>
<div class="bottom-spacer"></div>
</main>
<script>
const cv = new URLSearchParams(window.location.search).get('cv');
if (cv) document.getElementById('backBtn').href = `index.html?cv=${cv}`;
if (typeof umami !== 'undefined') umami.track('ecosystem-view');
</script>
</body>
</html>

View file

@ -3180,7 +3180,10 @@
${esc(id.nom)}<span class="hero-name-line2">${esc(id.prenom)}</span>
</h1>
<p class="hero-subtitle">${esc(id.accroche)}</p>
<div style="margin-top:20px;display:inline-block"><a id="infraBtn" href="infrastructure.html" class="infra-btn" data-umami-event="infra-visit">⚡ Voir mon infrastructure</a></div>
<div style="margin-top:20px;display:inline-flex;gap:14px;flex-wrap:wrap;justify-content:center">
<a id="infraBtn" href="infrastructure.html" class="infra-btn" data-umami-event="infra-visit">⚡ Voir mon infrastructure</a>
<a id="ecosystemBtn" href="ecosystem.html" class="infra-btn" data-umami-event="ecosystem-visit">◈ Mon écosystème Coolify</a>
</div>
<div class="hero-stats">
${d.stats.map(s => `
<div class="stat-box">
@ -3361,6 +3364,8 @@
// Mettre à jour le lien infra APRÈS render (l'élément existe maintenant dans le DOM)
const infraBtn = document.getElementById('infraBtn');
if (infraBtn && cvParam) infraBtn.href = `infrastructure.html?cv=${cvParam}`;
const ecosystemBtn = document.getElementById('ecosystemBtn');
if (ecosystemBtn && cvParam) ecosystemBtn.href = `ecosystem.html?cv=${cvParam}`;
// Lang dynamique depuis le JSON (défaut : fr)
document.documentElement.lang = data.identity.lang || 'fr';