SPB Git forge

spb/food-ka

Public

Food-Ka — agrégateur de produits d'épicerie du Québec — www.food-ka.com

55commits 1branches 0releases
10.2 MBsize
maindefault branch
9 days agolast push
Python 53.9% TypeScript 24% CSS 14.9% JavaScript 5.8% HTML 1.4%

Harmonisation ka-ui : accent vert marché, badge et footer Groupe KA, page /contact, SSO KA ID actif

- main.tsx : import de ka/tokens.css (design system commun) avant styles.css
- styles.css : :root réduit à l accent vert marché (#1f9d55 / #e2f5ea / #157a40,
  texte blanc sur accent) + alias --lime/--lime-soft ; socle dédupliqué
  (palette, typo, grain, container, kicker fournis par tokens.css) ; anciens
  rgba encre/papier/lime remplacés par les valeurs tokens ; états actifs et
  CTA passés sur accent-deep + blanc ; classes métier conservées
- Header : badge « Un service Groupe KA » (desktop + menu mobile), lien Contact
- Footer : remplacé par KaFooter (siteId food-ka, lien local Confidentialité)
- Nouvelle page /contact : 3 courriels + rôles, avertissement agrégateur,
  lien hub, liste des 12 sites (ecosystem.json)
- Profil : bouton « Créer un compte » vers groupe-ka.com/connexion
- index.html : titre « Food-Ka — Un service Groupe KA », theme-color et
  favicon aux couleurs du groupe
- Build + déploiement : SSO /api/auth/ka/login vérifié (307 vers
  groupe-ka.com/sso/authorize)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Simon-Pierre Boucher committed 1 mo ago (Aug 18, 2026) parent 6004efe

6 changed files +224 −161

modified frontend/index.html +4 −4
@@ -7,9 +7,9 @@
7 7 <head>
8 8 <meta charset="UTF-8" />
9 9 <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
10 − <title>Food·Ka — Tous les prix d'épicerie du Québec</title>
11 − <meta name="description" content="Food·Ka agrège les produits et circulaires des grandes bannières d'épicerie du Québec — Metro, IGA, Maxi, Super C, Provigo et plus — prix, soldes et prix unitaires, toujours à jour." />
12 − <meta name="theme-color" content="#faf6ee" />
10 + <title>Food-Ka — Un service Groupe KA</title>
11 + <meta name="description" content="Food·Ka agrège les produits et circulaires des grandes bannières d'épicerie du Québec — Metro, IGA, Maxi, Super C, Provigo et plus — prix, soldes et prix unitaires, toujours à jour. Un service Groupe KA." />
12 + <meta name="theme-color" content="#f5f3ee" />
13 13 <meta name="mobile-web-app-capable" content="yes" />
14 14 <meta name="apple-mobile-web-app-capable" content="yes" />
15 15 <meta name="apple-mobile-web-app-status-bar-style" content="default" />
@@ -18,7 +18,7 @@
18 18 <link rel="preconnect" href="https://fonts.googleapis.com" />
19 19 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
20 20 <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet" />
21 − <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='12' fill='%2314523a'/%3E%3Ctext x='32' y='45' font-family='Arial Black,sans-serif' font-size='32' font-weight='900' fill='%23d9f26b' text-anchor='middle'%3EFK%3C/text%3E%3C/svg%3E" />
21 + <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='12' fill='%23157a40'/%3E%3Ctext x='32' y='45' font-family='Arial Black,sans-serif' font-size='32' font-weight='900' fill='%23ffffff' text-anchor='middle'%3EFK%3C/text%3E%3C/svg%3E" />
22 22 </head>
23 23 <body>
24 24 <div id="root"></div>
modified frontend/src/App.tsx +17 −33
@@ -7,11 +7,14 @@ import { useEffect, useState } from "react";
7 7 import { NavLink, Route, Routes, useLocation } from "react-router-dom";
8 8 import { Me, fetchAuth, fetchFacets, fetchSources, fetchStats, fmtPrice, logout, registerSourceNames, sourceName } from "./api";
9 9 import CookieConsent from "./components/CookieConsent";
10 +import GroupeKaBadge from "./ka/GroupeKaBadge";
11 +import KaFooter from "./ka/KaFooter";
10 12 import { FavProvider } from "./favorites";
11 13 import Home from "./pages/Home";
12 14 import ProductPage from "./pages/Product";
13 15 import PrivacyPage from "./pages/Privacy";
14 16 import ProfilPage from "./pages/Profil";
17 +import ContactPage from "./pages/Contact";
15 18 import SourcesPage from "./pages/Sources";
16 19 import StatsPage from "./pages/Stats";
17 20
@@ -56,6 +59,7 @@ const NAV_LINKS = [
56 59 { to: "/sources", label: "Sources", icon: "🗂", end: false },
57 60 { to: "/stats", label: "Stats", icon: "📊", end: false },
58 61 { to: "/profil", label: "Mon compte", icon: "👤", end: false },
62 + { to: "/contact", label: "Contact", icon: "✉️", end: false },
59 63 { to: "/confidentialite", label: "Confidentialité", icon: "🔒", end: false },
60 64 ];
61 65
@@ -169,6 +173,7 @@ function Header() {
169 173 Food<span className="ka">Ka</span>
170 174 <span className="brand-tag">Épicerie — tout le Québec, toujours à jour</span>
171 175 </NavLink>
176 + <GroupeKaBadge />
172 177 <nav className="nav" aria-label="Navigation principale">
173 178 <NavLink to="/" end className={({ isActive }) => (isActive ? "active" : "")}>
174 179 Produits
@@ -182,6 +187,9 @@ function Header() {
182 187 <NavLink to="/stats" className={({ isActive }) => (isActive ? "active" : "")}>
183 188 Stats
184 189 </NavLink>
190 + <NavLink to="/contact" className={({ isActive }) => (isActive ? "active" : "")}>
191 + Contact
192 + </NavLink>
185 193 </nav>
186 194 <AccountMenu />
187 195 <button
@@ -210,6 +218,9 @@ function Header() {
210 218 <span className="mm-arrow" aria-hidden="true">→</span>
211 219 </NavLink>
212 220 ))}
221 + <div className="mm-badge">
222 + <GroupeKaBadge />
223 + </div>
213 224 <div className="mm-foot">
214 225 Agrégateur indépendant — mis à jour automatiquement, chaque fiche
215 226 renvoie à la page produit originale.
@@ -222,38 +233,7 @@ function Header() {
222 233 );
223 234 }
224 235
225 −function Footer() {
226 − return (
227 − <footer className="footer">
228 − <div className="container">
229 − <div className="fbrand">
230 − Food<span className="ka">Ka</span>
231 − </div>
232 − <div className="frow">
233 − <div>
234 − <b>Agrégateur indépendant</b> de produits d'épicerie de la province de Québec.
235 − Les prix proviennent des sites publics des bannières et sont rafraîchis
236 − automatiquement — chaque fiche renvoie vers la page produit originale.
237 − </div>
238 − </div>
239 − <div className="fmono">
240 − © {new Date().getFullYear()} Simon-Pierre Boucher — contact@spboucher.ai
241 − {" · "}
242 − www.food-ka.com
243 − {" · "}
244 − <NavLink to="/confidentialite">Confidentialité</NavLink>
245 − {" · "}
246 − <button
247 − className="flink"
248 − onClick={() => window.dispatchEvent(new Event("foodka:openConsent"))}
249 − >
250 − Gérer mes témoins
251 − </button>
252 − </div>
253 − </div>
254 − </footer>
255 − );
256 −}
236 +
257 237
258 238 export default function App() {
259 239 return (
@@ -261,6 +241,7 @@ export default function App() {
261 241 <Header />
262 242 <main>
263 243 <Routes>
244 + <Route path="/contact" element={<ContactPage />} />
264 245 <Route path="/" element={<Home />} />
265 246 <Route path="/aubaines" element={<Home aubaines />} />
266 247 <Route path="/produit/:uid" element={<ProductPage />} />
@@ -280,7 +261,10 @@ export default function App() {
280 261 />
281 262 </Routes>
282 263 </main>
283 − <Footer />
264 + <KaFooter
265 + siteId="food-ka"
266 + localLegal={[{ label: "Confidentialité", href: "/confidentialite" }]}
267 + />
284 268 <CookieConsent />
285 269 </FavProvider>
286 270 );
modified frontend/src/main.tsx +1 −0
@@ -7,6 +7,7 @@ import React from "react";
7 7 import ReactDOM from "react-dom/client";
8 8 import { BrowserRouter } from "react-router-dom";
9 9 import App from "./App";
10 +import "./ka/tokens.css";
10 11 import "./styles.css";
11 12
12 13 ReactDOM.createRoot(document.getElementById("root")!).render(
added frontend/src/pages/Contact.tsx +79 −0
@@ -0,0 +1,79 @@
1 +// -----------------------------------------------------------------------------
2 +// Food-Ka — Agrégateur de produits d'épicerie (province de Québec)
3 +// Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 +// pages/Contact.tsx : page contact commune Groupe KA — consomme ka/ecosystem.json
5 +// (courriels + rôles, avertissement agrégateur, lien hub, liste des sites)
6 +// -----------------------------------------------------------------------------
7 +import eco from "../ka/ecosystem.json";
8 +
9 +export default function ContactPage() {
10 + const site = eco.sites.find((s) => s.id === "food-ka");
11 + return (
12 + <div className="container contact-page">
13 + <span className="kicker">Contact</span>
14 + <h1>
15 + Écrire au <span className="hl">Groupe KA</span>
16 + </h1>
17 + <p className="lede">
18 + <b>Food·Ka</b> — {site?.tagline} — est un service du{" "}
19 + <a
20 + href={eco.hub.url}
21 + target="_blank"
22 + rel="noopener noreferrer"
23 + style={{ textDecoration: "underline", textUnderlineOffset: 3 }}
24 + >
25 + Groupe KA
26 + </a>
27 + . Toutes les demandes passent par les trois adresses centrales du
28 + groupe, selon le sujet.
29 + </p>
30 +
31 + <div className="contact-cards">
32 + {eco.contacts.map((c) => (
33 + <div className="contact-card" key={c.email}>
34 + <a href={`mailto:${c.email}`}>{c.email}</a>
35 + <span>{c.role}</span>
36 + </div>
37 + ))}
38 + </div>
39 +
40 + <p className="contact-warning">
41 + <b>{eco.org.disclaimer}</b> Les prix et produits affichés sur Food·Ka
42 + proviennent des sites publics des bannières d'épicerie — pour toute
43 + question sur un produit, un prix en magasin ou une commande, adressez-vous
44 + directement à la bannière concernée.
45 + </p>
46 +
47 + <div className="contact-hub">
48 + <a className="btn btn-primary" href={eco.hub.url} target="_blank" rel="noopener noreferrer">
49 + Visiter le hub groupe-ka.com ↗
50 + </a>{" "}
51 + <a
52 + className="btn btn-ghost"
53 + href={`${eco.hub.url}${eco.hub.loginPath}`}
54 + target="_blank"
55 + rel="noopener noreferrer"
56 + >
57 + Créer un compte KA ID
58 + </a>
59 + </div>
60 +
61 + <h3 style={{ marginTop: 34 }}>Les plateformes de l'écosystème ·Ka</h3>
62 + <ul className="contact-sites">
63 + {eco.sites.map((s) => (
64 + <li key={s.id}>
65 + <a
66 + href={`https://${s.domain}`}
67 + target={s.id === "food-ka" ? undefined : "_blank"}
68 + rel="noopener noreferrer"
69 + >
70 + <span className="dot" style={{ background: s.accent }} aria-hidden="true" />
71 + {s.wordmark}
72 + <small>{s.domain}</small>
73 + </a>
74 + </li>
75 + ))}
76 + </ul>
77 + </div>
78 + );
79 +}
modified frontend/src/pages/Profil.tsx +13 −3
@@ -121,9 +121,19 @@ export default function ProfilPage() {
121 121 Connectez-vous avec votre <b>KA ID</b> — l'identifiant de membre
122 122 unique du Groupe KA, valide sur toutes les plateformes du groupe.
123 123 </p>
124 − <a className="btn btn-primary" href="/api/auth/ka/login?next=/profil">
125 − Se connecter avec KA ID
126 − </a>
124 + <div className="profil-actions">
125 + <a className="btn btn-primary" href="/api/auth/ka/login?next=/profil">
126 + Se connecter avec KA ID
127 + </a>
128 + <a
129 + className="btn btn-ghost"
130 + href="https://www.groupe-ka.com/connexion"
131 + target="_blank"
132 + rel="noopener noreferrer"
133 + >
134 + Créer un compte ↗
135 + </a>
136 + </div>
127 137 </div>
128 138 );
129 139 }
modified frontend/src/styles.css +110 −121
@@ -1,85 +1,41 @@
1 1 /* -----------------------------------------------------------------------------
2 2 Food-Ka — Agrégateur de produits d'épicerie (province de Québec)
3 3 Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 − styles.css : système de design « éditorial sharp » — thème clair « marché frais »
5 − · Typo display Space Grotesk / texte Inter / micro-étiquettes JetBrains Mono
6 − · Signature : bordures encre + ombres décalées (néo-brutalisme raffiné)
7 − · Palette épicerie : papier crème, encre vert-noir, vert marché #1f7a4d,
8 − lime #d9f26b en surlignage ponctuel, tomate #e8542f réservée aux soldes
4 + styles.css : classes MÉTIER food-ka (produits, circulaires, comparateur…).
5 + · Le socle (palette, typo, boutons, cartes, footer KA…) vient de
6 + ka/tokens.css — design system commun « éditorial sharp » Groupe KA,
7 + importé AVANT ce fichier (voir main.tsx).
8 + · Ici : uniquement l'accent local « vert marché » + les composants propres
9 + au site. Tomate #e8542f réservée aux soldes.
9 10 · Packshots produits : object-fit contain sur fond blanc
10 11 · 100 % adaptatif mobile (PWA installable, safe-areas iOS)
11 12 ----------------------------------------------------------------------------- */
12 13 :root {
13 − --paper: #faf6ee;
14 − --surface: #ffffff;
15 − --surface-2: #f7f2e7;
16 − --ink: #14231a;
17 − --ink-2: #47564c;
18 − --ink-3: #839187;
19 − --line: rgba(20, 35, 26, 0.14);
20 − --line-strong: rgba(20, 35, 26, 0.85);
21 − --green: #1f7a4d;
22 − --green-bright: #2e9e63;
23 − --green-deep: #14523a;
24 − --lime: #d9f26b;
25 − --lime-soft: #eef6d4;
14 + /* ---- ACCENT food-ka : vert marché (surcharge des tokens ka-ui) ---- */
15 + --accent: #1f9d55;
16 + --accent-soft: #e2f5ea;
17 + --accent-deep: #157a40;
18 + --on-accent: #ffffff;
19 + /* alias rétro-compatibles — l'historique du site utilisait --lime */
20 + --lime: var(--accent);
21 + --lime-soft: var(--accent-soft);
22 +
23 + /* ---- Couleurs métier locales (soldes / rabais) ---- */
26 24 --tomato: #e8542f;
27 25 --tomato-deep: #b83c1c;
28 26 --tomato-soft: #fcebe3;
29 − --amber: #e8a33d;
30 − --amber-soft: #fdf3e2;
31 − --danger: #b3423a;
32 − --r-card: 10px;
33 − --r-ctl: 6px;
34 − --shadow-flat: 0 1px 2px rgba(20, 35, 26, 0.05);
35 − --shadow-off: 6px 6px 0 var(--ink);
36 − --shadow-off-soft: 8px 8px 0 rgba(20, 35, 26, 0.08);
37 − --font-display: "Space Grotesk", system-ui, sans-serif;
38 − --font-body: "Inter", system-ui, sans-serif;
39 − --font-mono: "JetBrains Mono", ui-monospace, monospace;
40 −}
41 −
42 −* { box-sizing: border-box; }
43 −html { scroll-behavior: smooth; }
44 −body {
45 − margin: 0;
46 − background: var(--paper);
47 − color: var(--ink);
48 − font-family: var(--font-body);
49 − font-size: 15px;
50 − line-height: 1.55;
51 − -webkit-font-smoothing: antialiased;
52 − padding-bottom: env(safe-area-inset-bottom);
53 −}
54 −/* grain subtil — texture signature */
55 −body::before {
56 − content: "";
57 − position: fixed; inset: 0; z-index: 0; pointer-events: none; opacity: 0.35;
58 − background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3CfeColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.02 0'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)'/%3E%3C/svg%3E");
59 −}
60 −#root { position: relative; z-index: 1; }
61 −
62 −h1, h2, h3, h4 { font-family: var(--font-display); letter-spacing: -0.03em; margin: 0; }
63 −a { color: inherit; text-decoration: none; }
27 +}
28 +
29 +h1, h2, h3, h4 { margin: 0; }
30 +a { text-decoration: none; }
64 31 button { font-family: inherit; }
65 −img { display: block; }
66 −::selection { background: var(--lime); color: var(--ink); }
67 32
68 33 .mono { font-family: var(--font-mono); }
69 −.kicker {
70 − font-family: var(--font-mono); font-size: 11.5px; font-weight: 500;
71 − text-transform: uppercase; letter-spacing: 0.14em; color: var(--green);
72 − display: inline-flex; align-items: center; gap: 8px;
73 −}
74 −.kicker::before { content: ""; width: 22px; height: 2px; background: var(--green); }
75 −
76 −.container { max-width: 1240px; margin: 0 auto; padding: 0 24px; }
77 −@media (max-width: 640px) { .container { padding: 0 16px; } }
78 34
79 35 /* ================= Header ================= */
80 36 .header {
81 37 position: sticky; top: 0; z-index: 50;
82 − background: rgba(250, 246, 238, 0.88);
38 + background: rgba(245, 243, 238, 0.88);
83 39 backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
84 40 border-bottom: 2px solid var(--ink);
85 41 }
@@ -89,7 +45,7 @@ img { display: block; }
89 45 letter-spacing: -0.04em; display: flex; align-items: center; line-height: 1;
90 46 }
91 47 .brand .ka {
92 − background: var(--green-deep); color: var(--lime); padding: 2px 7px 4px;
48 + background: var(--accent-deep); color: var(--on-accent); padding: 2px 7px 4px;
93 49 border-radius: 6px; margin-left: 3px; transform: rotate(-2deg);
94 50 transition: transform 0.2s ease;
95 51 }
@@ -98,9 +54,9 @@ img { display: block; }
98 54 .brand-mark {
99 55 width: 30px; height: 30px; margin-right: 10px; flex: none;
100 56 display: inline-flex; align-items: center; justify-content: center;
101 − background: var(--lime); color: var(--ink); border: 1.5px solid var(--ink);
57 + background: var(--accent); color: var(--on-accent); border: 1.5px solid var(--ink);
102 58 border-radius: 8px; transform: rotate(-5deg); transition: transform 0.2s ease;
103 − box-shadow: 2px 2px 0 rgba(20, 35, 26, 0.2);
59 + box-shadow: 2px 2px 0 rgba(20, 24, 20, 0.2);
104 60 }
105 61 .brand:hover .brand-mark { transform: rotate(0deg); }
106 62 .brand-mark svg { width: 17px; height: 17px; }
@@ -117,7 +73,7 @@ img { display: block; }
117 73 transition: all 0.15s ease; min-height: 40px; display: inline-flex; align-items: center;
118 74 }
119 75 .nav a:hover { border-color: var(--ink); color: var(--ink); }
120 −.nav a.active { background: var(--green-deep); color: var(--lime); }
76 +.nav a.active { background: var(--accent-deep); color: var(--on-accent); }
121 77
122 78 /* ================= Ticker — bande « ruban de caisse » ================= */
123 79 .ticker {
@@ -125,7 +81,7 @@ img { display: block; }
125 81 font-family: var(--font-mono); font-size: 11.5px; letter-spacing: 0.1em;
126 82 text-transform: uppercase; padding: 7px 0; white-space: nowrap;
127 83 border-bottom: 2px dashed var(--line-strong);
128 − box-shadow: inset 0 -6px 12px -10px rgba(20, 35, 26, 0.35);
84 + box-shadow: inset 0 -6px 12px -10px rgba(20, 24, 20, 0.35);
129 85 }
130 86 .ticker-track { display: inline-flex; gap: 0; animation: ticker 40s linear infinite; will-change: transform; }
131 87 .ticker span { padding: 0 26px; position: relative; font-variant-numeric: tabular-nums; }
@@ -147,8 +103,8 @@ img { display: block; }
147 103 color: transparent; -webkit-text-stroke: 2px var(--ink);
148 104 }
149 105 .hero h1 .hl {
150 − background: var(--lime); padding: 0 10px; border-radius: 8px; display: inline-block;
151 − transform: rotate(-1deg);
106 + background: var(--accent); color: var(--on-accent); padding: 0 10px; border-radius: 8px;
107 + display: inline-block; transform: rotate(-1deg);
152 108 }
153 109 /* variante « aubaines » : surlignage tomate (rabais) */
154 110 .hero h1 .hl-tomato { background: var(--tomato); color: #fff; }
@@ -160,14 +116,14 @@ img { display: block; }
160 116 background: var(--surface); border: 1.5px solid var(--ink); border-radius: 999px;
161 117 padding: 8px 16px; font-family: var(--font-mono); font-size: 12px;
162 118 color: var(--ink-2); display: flex; gap: 8px; align-items: center;
163 − box-shadow: 3px 3px 0 rgba(20, 35, 26, 0.12);
119 + box-shadow: 3px 3px 0 rgba(20, 24, 20, 0.12);
164 120 }
165 121 .stat-chip b { color: var(--ink); font-weight: 700; }
166 122 .stat-chip .pulse {
167 123 width: 8px; height: 8px; border-radius: 50%; background: var(--green);
168 124 box-shadow: 0 0 0 4px var(--lime-soft); animation: pulse 2.4s ease infinite;
169 125 }
170 −@keyframes pulse { 50% { box-shadow: 0 0 0 7px rgba(217, 242, 107, 0.4); } }
126 +@keyframes pulse { 50% { box-shadow: 0 0 0 7px rgba(31, 157, 85, 0.4); } }
171 127
172 128 /* ================= Filter bar ================= */
173 129 .filterbar {
@@ -223,7 +179,7 @@ img { display: block; }
223 179 transition: all 0.13s ease;
224 180 }
225 181 .f-more:hover { background: var(--lime-soft); }
226 −.f-more.on { background: var(--green-deep); color: var(--lime); box-shadow: 3px 3px 0 rgba(20,35,26,0.22); }
182 +.f-more.on { background: var(--accent-deep); color: var(--on-accent); box-shadow: 3px 3px 0 rgba(20,24,20,0.22); }
227 183
228 184 /* — panneau avancé — */
229 185 .f-adv {
@@ -260,7 +216,7 @@ img { display: block; }
260 216 .seg button:first-child { border-radius: 8px 0 0 8px; margin-left: 0; }
261 217 .seg button:last-child { border-radius: 0 8px 8px 0; }
262 218 .seg button:hover { background: var(--lime-soft); color: var(--ink); }
263 −.seg button.on { background: var(--green-deep); color: var(--lime); position: relative; z-index: 1; }
219 +.seg button.on { background: var(--accent-deep); color: var(--on-accent); position: relative; z-index: 1; }
264 220
265 221 /* pastilles de filtres actifs */
266 222 .pills { display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0 2px; }
@@ -270,7 +226,7 @@ img { display: block; }
270 226 border-radius: 999px; padding: 6px 13px; font-size: 12.5px; font-weight: 600;
271 227 font-family: var(--font-mono); cursor: pointer; transition: all 0.13s ease;
272 228 }
273 −.pill:hover { background: var(--lime); }
229 +.pill:hover { background: var(--accent); color: var(--on-accent); }
274 230 .pill-x { font-size: 10px; opacity: 0.65; }
275 231 .pill-clear { background: var(--surface); border-color: var(--danger); color: var(--danger); }
276 232 .pill-clear:hover { background: var(--danger); color: #fff; }
@@ -302,10 +258,10 @@ img { display: block; }
302 258 font-family: var(--font-display); letter-spacing: 0.01em;
303 259 }
304 260 .btn:active { transform: translate(2px, 2px); box-shadow: none !important; }
305 −.btn-primary { background: var(--green-deep); color: var(--lime); box-shadow: 4px 4px 0 rgba(20,35,26,0.25); }
261 +.btn-primary { background: var(--accent-deep); color: var(--on-accent); box-shadow: 4px 4px 0 rgba(20,24,20,0.25); }
306 262 .btn-primary:hover { background: var(--ink); }
307 263 .btn-ghost { background: transparent; color: var(--ink); }
308 −.btn-ghost:hover { background: var(--lime); box-shadow: 4px 4px 0 rgba(20,35,26,0.2); }
264 +.btn-ghost:hover { background: var(--accent); color: var(--on-accent); box-shadow: 4px 4px 0 rgba(20,24,20,0.2); }
309 265
310 266 /* ================= Chips ================= */
311 267 .chips { display: flex; gap: 8px; margin: 16px 0 4px; overflow-x: auto; padding-bottom: 6px; scrollbar-width: none; }
@@ -317,7 +273,7 @@ img { display: block; }
317 273 transition: all 0.13s ease;
318 274 }
319 275 .chip:hover { background: var(--lime-soft); transform: translateY(-1px); }
320 −.chip.on { background: var(--green-deep); color: var(--lime); box-shadow: 3px 3px 0 rgba(20,35,26,0.2); }
276 +.chip.on { background: var(--accent-deep); color: var(--on-accent); box-shadow: 3px 3px 0 rgba(20,24,20,0.2); }
321 277
322 278 /* ================= Results ================= */
323 279 .results-head { display: flex; align-items: baseline; gap: 14px; margin: 28px 0 18px; }
@@ -382,7 +338,7 @@ img { display: block; }
382 338 @keyframes shimmer { 0% { background-position: -400px 0; } 100% { background-position: 400px 0; } }
383 339 .skel { border-radius: var(--r-card); border: 1.5px solid var(--line); overflow: hidden; background: var(--surface); }
384 340 .skel .sk-img, .skel .sk-line {
385 − background: linear-gradient(90deg, #efe9db 25%, #f9f5eb 50%, #efe9db 75%);
341 + background: linear-gradient(90deg, #ecebe4 25%, #f7f6f1 50%, #ecebe4 75%);
386 342 background-size: 800px 100%; animation: shimmer 1.4s infinite linear;
387 343 }
388 344 .skel .sk-img { aspect-ratio: 1/1; }
@@ -434,7 +390,7 @@ img { display: block; }
434 390 aspect-ratio: 4/3; font-size: 48px; }
435 391 .carousel-count {
436 392 position: absolute; right: 12px; bottom: 12px; z-index: 2;
437 − background: rgba(20, 35, 26, 0.82); color: var(--lime);
393 + background: rgba(20, 24, 20, 0.82); color: var(--lime);
438 394 font-family: var(--font-mono); font-size: 12px; font-weight: 700;
439 395 padding: 4px 10px; border-radius: 999px;
440 396 }
@@ -482,7 +438,7 @@ img { display: block; }
482 438 }
483 439 .chips-scroll::-webkit-scrollbar { display: none; }
484 440 .chip-key {
485 − flex: 0 0 auto; background: var(--green-deep); color: var(--lime);
441 + flex: 0 0 auto; background: var(--accent-deep); color: var(--on-accent);
486 442 border-radius: 999px; padding: 8px 14px; font-family: var(--font-mono);
487 443 font-size: 12px; font-weight: 700; white-space: nowrap; min-height: 34px;
488 444 display: inline-flex; align-items: center;
@@ -501,13 +457,13 @@ img { display: block; }
501 457 .amenity.confirmed { background: var(--lime-soft); border-color: var(--green); color: var(--green-deep); font-weight: 600; }
502 458 .cta {
503 459 display: flex; align-items: center; justify-content: center; gap: 10px;
504 − text-align: center; background: var(--green-deep); color: var(--lime);
460 + text-align: center; background: var(--accent-deep); color: var(--on-accent);
505 461 font-weight: 700; font-family: var(--font-display); border-radius: var(--r-ctl);
506 462 padding: 12px 15px; border: 1.5px solid var(--ink); min-height: 48px;
507 − box-shadow: 4px 4px 0 rgba(20,35,26,0.25); transition: all 0.14s ease;
463 + box-shadow: 4px 4px 0 rgba(20,24,20,0.25); transition: all 0.14s ease;
508 464 margin-top: 14px;
509 465 }
510 −.cta:hover { background: var(--lime); color: var(--ink); }
466 +.cta:hover { background: var(--accent); color: var(--on-accent); }
511 467 .cta:active { transform: translate(2px, 2px); box-shadow: none; }
512 468 .fine { font-size: 11.5px; color: var(--ink-3); margin-top: 14px; font-family: var(--font-mono); letter-spacing: 0.02em; }
513 469
@@ -559,7 +515,7 @@ img { display: block; }
559 515 .cmp-unit { font-family: var(--font-mono); font-size: 10px; color: var(--ink-3); }
560 516 .cmp-best {
561 517 font-family: var(--font-mono); font-size: 9.5px; font-weight: 700; text-transform: uppercase;
562 − letter-spacing: 0.08em; background: var(--green); color: var(--lime);
518 + letter-spacing: 0.08em; background: var(--accent-deep); color: var(--on-accent);
563 519 border-radius: 4px; padding: 2px 7px;
564 520 }
565 521 .baisse-pct {
@@ -592,7 +548,7 @@ img { display: block; }
592 548 .src-table tr:last-child td { border-bottom: none; }
593 549 .src-table tr:hover td { background: var(--surface-2); }
594 550 .src-table a { color: var(--green-deep); font-weight: 600; border-bottom: 1.5px solid var(--lime); }
595 −.pill.ok { background: var(--lime); color: var(--ink); border: 1px solid var(--ink); border-radius: 4px; padding: 3px 10px; font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.05em; }
551 +.pill.ok { background: var(--accent); color: var(--on-accent); border: 1px solid var(--ink); border-radius: 4px; padding: 3px 10px; font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.05em; }
596 552 .pill.todo { background: var(--amber-soft); color: #8a5a12; border: 1px solid var(--amber); border-radius: 4px; padding: 3px 10px; font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.05em; }
597 553 .count-pill { font-weight: 700; font-family: var(--font-display); font-size: 16px; }
598 554
@@ -604,12 +560,12 @@ img { display: block; }
604 560 .tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 14px; margin-bottom: 30px; }
605 561 .tile {
606 562 background: var(--surface); border: 1.5px solid var(--ink); border-radius: var(--r-card);
607 − padding: 18px 20px; box-shadow: 3px 3px 0 rgba(20, 35, 26, 0.1);
563 + padding: 18px 20px; box-shadow: 3px 3px 0 rgba(20, 24, 20, 0.1);
608 564 }
609 565 .tile-v { font-family: var(--font-display); font-weight: 700; font-size: clamp(24px, 3vw, 34px); letter-spacing: -0.03em; line-height: 1.05; }
610 566 .tile-k { font-family: var(--font-mono); font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--ink-3); margin-top: 6px; }
611 −.hero-tile { background: var(--green-deep); color: var(--lime); border-color: var(--green-deep); }
612 −.hero-tile .tile-k { color: rgba(217, 242, 107, 0.7); }
567 +.hero-tile { background: var(--accent-deep); color: var(--on-accent); border-color: var(--accent-deep); }
568 +.hero-tile .tile-k { color: rgba(255, 255, 255, 0.7); }
613 569
614 570 .viz-card {
615 571 background: var(--surface); border: 1.5px solid var(--ink); border-radius: var(--r-card);
@@ -722,10 +678,10 @@ img { display: block; }
722 678 display: flex; align-items: center; gap: 6px;
723 679 position: fixed; left: 50%; transform: translateX(-50%);
724 680 bottom: calc(18px + env(safe-area-inset-bottom)); z-index: 80;
725 − background: var(--green-deep); color: var(--lime); border: 1.5px solid var(--ink);
681 + background: var(--accent-deep); color: var(--on-accent); border: 1.5px solid var(--ink);
726 682 border-radius: 999px; padding: 13px 24px; font-family: var(--font-display);
727 683 font-weight: 700; font-size: 15px; cursor: pointer;
728 − box-shadow: 0 8px 24px rgba(16, 18, 16, 0.35), 4px 4px 0 rgba(20, 35, 26, 0.25);
684 + box-shadow: 0 8px 24px rgba(16, 18, 16, 0.35), 4px 4px 0 rgba(20, 24, 20, 0.25);
729 685 }
730 686 .fab:active { transform: translateX(-50%) scale(0.97); }
731 687
@@ -746,14 +702,7 @@ img { display: block; }
746 702 .field input, .field select { font-size: 16px; }
747 703 }
748 704
749 −/* ================= Footer ================= */
750 −.footer { background: var(--ink); color: rgba(250, 246, 238, 0.75); margin-top: 20px; padding: 44px 0 max(40px, env(safe-area-inset-bottom)); font-size: 13px; }
751 −.footer .fbrand { font-family: var(--font-display); font-weight: 700; font-size: 34px; color: var(--paper); letter-spacing: -0.04em; margin-bottom: 12px; }
752 −.footer .fbrand .ka { color: var(--lime); }
753 −.footer b { color: var(--paper); }
754 −.footer .frow { display: flex; flex-direction: column; gap: 5px; max-width: 720px; }
755 −.footer .fmono { font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: rgba(250,246,238,0.45); margin-top: 18px; }
756 −.footer .fmono a { text-decoration: underline; text-underline-offset: 2px; }
705 +/* ================= Footer : KaFooter commun (voir ka/tokens.css) ================= */
757 706
758 707 /* --- Menu déroulant mobile ------------------------------------------------- */
759 708 .menu-btn {
@@ -762,10 +711,10 @@ img { display: block; }
762 711 border: 1.5px solid var(--ink); border-radius: var(--r-ctl);
763 712 background: var(--surface); cursor: pointer; padding: 0;
764 713 flex-direction: column; align-items: center; justify-content: center; gap: 5px;
765 − box-shadow: 3px 3px 0 rgba(20, 35, 26, 0.15);
714 + box-shadow: 3px 3px 0 rgba(20, 24, 20, 0.15);
766 715 transition: box-shadow 0.15s ease, transform 0.15s ease;
767 716 }
768 −.menu-btn:active { transform: translate(2px, 2px); box-shadow: 1px 1px 0 rgba(20,35,26,0.15); }
717 +.menu-btn:active { transform: translate(2px, 2px); box-shadow: 1px 1px 0 rgba(20,24,20,0.15); }
769 718 .menu-btn span {
770 719 display: block; width: 18px; height: 2px; background: var(--ink);
771 720 border-radius: 2px; transition: transform 0.25s ease, opacity 0.2s ease;
@@ -777,7 +726,7 @@ img { display: block; }
777 726 .mobile-menu {
778 727 display: none; position: absolute; left: 0; right: 0; top: 100%;
779 728 background: var(--paper); border-bottom: 2px solid var(--ink);
780 − box-shadow: 0 18px 30px rgba(20, 35, 26, 0.18);
729 + box-shadow: 0 18px 30px rgba(20, 24, 20, 0.18);
781 730 padding: 8px 16px calc(16px + env(safe-area-inset-bottom));
782 731 max-height: 0; overflow: hidden; opacity: 0;
783 732 transition: max-height 0.3s ease, opacity 0.22s ease;
@@ -803,7 +752,7 @@ img { display: block; }
803 752 }
804 753 .mm-backdrop {
805 754 position: fixed; inset: 0; z-index: 40;
806 − background: rgba(20, 35, 26, 0.35); backdrop-filter: blur(2px);
755 + background: rgba(20, 24, 20, 0.35); backdrop-filter: blur(2px);
807 756 }
808 757 @media (max-width: 760px) {
809 758 .menu-btn { display: flex; }
@@ -854,7 +803,7 @@ img { display: block; }
854 803 .cta-sticky {
855 804 position: fixed; left: 0; right: 0; bottom: 0; z-index: 45;
856 805 display: flex; align-items: center; gap: 12px;
857 − background: rgba(250, 246, 238, 0.94); backdrop-filter: blur(12px);
806 + background: rgba(245, 243, 238, 0.94); backdrop-filter: blur(12px);
858 807 border-top: 2px solid var(--ink);
859 808 padding: 10px 16px calc(10px + env(safe-area-inset-bottom));
860 809 }
@@ -906,7 +855,7 @@ img { display: block; }
906 855 white-space: nowrap;
907 856 }
908 857 .basket-table td.cell-best { color: var(--green-deep); font-weight: 700; }
909 −.basket-table .best-col { background: rgba(46, 158, 99, 0.13); }
858 +.basket-table .best-col { background: var(--accent-soft); }
910 859 .basket-table tfoot th, .basket-table tfoot td {
911 860 border-top: 2px solid var(--ink); border-bottom: none;
912 861 font-family: var(--font-display); font-weight: 700; font-size: 14.5px; padding: 10px 8px;
@@ -917,7 +866,7 @@ img { display: block; }
917 866 display: inline-flex; align-items: center; gap: 9px; margin: 16px 0 2px;
918 867 border: 1.5px solid var(--green); background: var(--lime-soft); color: var(--green-deep);
919 868 border-radius: 999px; padding: 9px 18px; font-weight: 600; font-size: 13.5px;
920 − box-shadow: 3px 3px 0 rgba(20, 35, 26, 0.12);
869 + box-shadow: 3px 3px 0 rgba(20, 24, 20, 0.12);
921 870 }
922 871
923 872 /* ================= Stats — tableau bannières & matrice ================= */
@@ -1019,7 +968,7 @@ html { scroll-padding-top: 76px; } /* header sticky au-dessus des ancres */
1019 968 .login-ka {
1020 969 display: inline-flex; align-items: center; justify-content: center;
1021 970 width: 18px; height: 16px; border-radius: 4px;
1022 − background: var(--green-deep); color: var(--lime);
971 + background: var(--accent-deep); color: var(--on-accent);
1023 972 font: 700 9px/1 var(--font-display); letter-spacing: -0.02em;
1024 973 transform: rotate(-2deg);
1025 974 }
@@ -1030,7 +979,7 @@ html { scroll-padding-top: 76px; } /* header sticky au-dessus des ancres */
1030 979 background: var(--lime); display: flex; align-items: center; justify-content: center;
1031 980 }
1032 981 .account-btn img { width: 100%; height: 100%; object-fit: cover; }
1033 −.account-initial { font-family: var(--font-display); font-weight: 700; font-size: 16px; color: var(--ink); }
982 +.account-initial { font-family: var(--font-display); font-weight: 700; font-size: 16px; color: var(--on-accent); }
1034 983 .account-backdrop { position: fixed; inset: 0; z-index: 90; }
1035 984 .account-menu {
1036 985 position: absolute; right: 0; top: calc(100% + 10px); z-index: 91;
@@ -1066,51 +1015,51 @@ html { scroll-padding-top: 76px; } /* header sticky au-dessus des ancres */
1066 1015 /* ================= Profil — carte de membre Groupe KA ================= */
1067 1016 .profil { padding-top: 40px; padding-bottom: 60px; }
1068 1017 .profil h1 { font-size: clamp(30px, 5vw, 44px); margin: 10px 0 26px; }
1069 −.profil h1 .hl { background: var(--lime); padding: 0 8px; border-radius: 8px; display: inline-block; }
1018 +.profil h1 .hl { background: var(--accent); color: var(--on-accent); padding: 0 8px; border-radius: 8px; display: inline-block; }
1070 1019 .profil .lede { max-width: 520px; color: var(--ink-2); margin-bottom: 22px; }
1071 1020
1072 1021 .pc {
1073 1022 position: relative; max-width: 520px; overflow: hidden;
1074 1023 background: var(--ink); color: var(--paper);
1075 1024 border: 2px solid var(--ink); border-radius: 16px;
1076 − padding: 24px 26px 0; box-shadow: 10px 10px 0 rgba(20, 35, 26, 0.18);
1025 + padding: 24px 26px 0; box-shadow: 10px 10px 0 rgba(20, 24, 20, 0.18);
1077 1026 }
1078 1027 .pc-watermark {
1079 1028 position: absolute; right: -18px; top: -34px; pointer-events: none;
1080 1029 font-family: var(--font-display); font-weight: 700; font-size: 170px;
1081 − letter-spacing: -0.06em; color: rgba(217, 242, 107, 0.07);
1030 + letter-spacing: -0.06em; color: rgba(31, 157, 85, 0.07);
1082 1031 transform: rotate(-8deg); line-height: 1;
1083 1032 }
1084 1033 .pc-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
1085 1034 .pc-brand { font-family: var(--font-display); font-weight: 700; font-size: 24px; letter-spacing: -0.04em; }
1086 −.pc-ka { background: var(--lime); color: var(--ink); padding: 1px 6px 3px; border-radius: 5px; margin-left: 3px; display: inline-block; transform: rotate(-2deg); }
1087 −.pc-label { font-family: var(--font-mono); font-size: 10px; text-transform: uppercase; letter-spacing: 0.16em; color: rgba(250, 246, 238, 0.55); }
1035 +.pc-ka { background: var(--accent); color: var(--on-accent); padding: 1px 6px 3px; border-radius: 5px; margin-left: 3px; display: inline-block; transform: rotate(-2deg); }
1036 +.pc-label { font-family: var(--font-mono); font-size: 10px; text-transform: uppercase; letter-spacing: 0.16em; color: rgba(245, 243, 238, 0.55); }
1088 1037 .pc-id-block { margin: 26px 0 22px; display: flex; flex-direction: column; gap: 4px; }
1089 −.pc-id-label { font-family: var(--font-mono); font-size: 10px; text-transform: uppercase; letter-spacing: 0.2em; color: rgba(217, 242, 107, 0.65); }
1038 +.pc-id-label { font-family: var(--font-mono); font-size: 10px; text-transform: uppercase; letter-spacing: 0.2em; color: rgba(31, 157, 85, 0.65); }
1090 1039 .pc-id {
1091 1040 font-family: var(--font-mono); font-weight: 700; color: var(--lime);
1092 1041 font-size: clamp(22px, 6vw, 34px); letter-spacing: 0.14em;
1093 − text-shadow: 0 0 24px rgba(217, 242, 107, 0.35);
1042 + text-shadow: 0 0 24px rgba(31, 157, 85, 0.35);
1094 1043 }
1095 1044 .pc-foot { display: flex; align-items: flex-end; justify-content: space-between; gap: 14px; padding-bottom: 18px; }
1096 1045 .pc-holder { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
1097 1046 .pc-holder-name { font-weight: 600; font-size: 15px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1098 −.pc-holder-since { font-family: var(--font-mono); font-size: 10.5px; color: rgba(250, 246, 238, 0.5); letter-spacing: 0.06em; }
1047 +.pc-holder-since { font-family: var(--font-mono); font-size: 10.5px; color: rgba(245, 243, 238, 0.5); letter-spacing: 0.06em; }
1099 1048 .pc-role-badge {
1100 1049 display: inline-block; width: fit-content; margin: 3px 0 2px;
1101 − background: var(--lime); color: var(--ink);
1050 + background: var(--accent); color: var(--on-accent);
1102 1051 font-family: var(--font-mono); font-size: 9px; font-weight: 700;
1103 1052 text-transform: uppercase; letter-spacing: 0.12em;
1104 1053 padding: 2px 7px 3px; border-radius: 5px; transform: rotate(-1deg);
1105 1054 }
1106 1055 .pc-avatar { width: 52px; height: 52px; border-radius: 999px; border: 2px solid var(--lime); flex: none; }
1107 −.pc-strip { display: flex; gap: 5px; margin: 0 -26px; padding: 9px 18px; background: rgba(217, 242, 107, 0.1); border-top: 1px solid rgba(217, 242, 107, 0.25); overflow: hidden; }
1056 +.pc-strip { display: flex; gap: 5px; margin: 0 -26px; padding: 9px 18px; background: rgba(31, 157, 85, 0.1); border-top: 1px solid rgba(31, 157, 85, 0.25); overflow: hidden; }
1108 1057 .pc-strip i { display: block; width: 3px; border-radius: 1px; background: var(--lime); opacity: 0.7; }
1109 1058 .pc-strip i:nth-child(3n) { height: 14px; opacity: 0.35; }
1110 1059 .pc-strip i:nth-child(3n+1) { height: 9px; }
1111 1060 .pc-strip i:nth-child(3n+2) { height: 17px; opacity: 0.9; }
1112 1061 .pc-copy { margin-top: 16px; }
1113 −.pc-copy.ok { background: var(--lime); border-color: var(--ink); color: var(--ink); }
1062 +.pc-copy.ok { background: var(--accent); border-color: var(--ink); color: var(--on-accent); }
1114 1063
1115 1064 .profil-grid {
1116 1065 display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
@@ -1142,3 +1091,43 @@ html { scroll-padding-top: 76px; } /* header sticky au-dessus des ancres */
1142 1091 .profil-note a { text-decoration: underline; text-underline-offset: 3px; }
1143 1092 .profil-actions { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 26px; }
1144 1093 .profil-actions .btn { display: inline-flex; align-items: center; gap: 7px; }
1094 +
1095 +
1096 +/* ================= Badge « Un service Groupe KA » (header) ================= */
1097 +.header .gk-badge { margin-left: 4px; flex: none; }
1098 +@media (max-width: 900px) { .header-inner > .gk-badge { display: none; } }
1099 +.mm-badge { padding: 14px 10px 4px; }
1100 +
1101 +/* ================= Page /contact (écosystème Groupe KA) ================= */
1102 +.contact-page { padding: 44px 0 90px; max-width: 860px; }
1103 +.contact-page h1 { font-size: clamp(30px, 5vw, 46px); margin: 10px 0 8px; text-transform: uppercase; }
1104 +.contact-page .lede { color: var(--ink-2); max-width: 640px; margin: 0 0 26px; }
1105 +.contact-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 14px; margin: 22px 0; }
1106 +.contact-card {
1107 + background: var(--surface); border: 1.5px solid var(--ink); border-radius: var(--r-card);
1108 + padding: 18px 20px; box-shadow: var(--shadow-off-soft);
1109 + display: flex; flex-direction: column; gap: 6px; min-width: 0;
1110 +}
1111 +.contact-card a {
1112 + font-family: var(--font-mono); font-weight: 700; font-size: 14px; color: var(--accent-deep);
1113 + word-break: break-all; text-decoration: underline; text-underline-offset: 3px;
1114 + min-height: var(--touch); display: inline-flex; align-items: center;
1115 +}
1116 +.contact-card span { font-size: 13px; color: var(--ink-2); }
1117 +.contact-warning {
1118 + border: 1.5px solid var(--ink); border-left: 6px solid var(--accent);
1119 + background: var(--accent-soft); border-radius: var(--r-ctl);
1120 + padding: 14px 18px; font-size: 14px; color: var(--ink); margin: 22px 0;
1121 +}
1122 +.contact-hub { margin: 22px 0; }
1123 +.contact-sites { list-style: none; margin: 14px 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: 8px; }
1124 +.contact-sites a {
1125 + display: inline-flex; align-items: center; gap: 7px; min-height: var(--touch);
1126 + border: 1.5px solid var(--ink); border-radius: 999px; padding: 8px 15px;
1127 + background: var(--surface); font-weight: 600; font-size: 13.5px;
1128 + transition: all 0.13s ease;
1129 +}
1130 +.contact-sites a:hover { background: var(--accent-soft); transform: translate(-1px, -1px); box-shadow: 3px 3px 0 rgba(20, 24, 20, 0.18); }
1131 +.contact-sites a .dot { width: 9px; height: 9px; border-radius: 3px; border: 1px solid var(--ink); flex: none; }
1132 +.contact-sites a small { font-family: var(--font-mono); font-size: 10px; color: var(--ink-3); }
1133 +@media (max-width: 640px) { .contact-sites a small { display: none; } }
1145 1134