SPB Git forge

spb/auto-ka

Public
61commits 1branches 0releases
14.4 MBsize
maindefault branch
12 days agolast push
Python 61.6% TypeScript 20.9% CSS 11.4% JavaScript 5.1% HTML 1.1%

Harmonisation Groupe KA : socle ka-ui, badge, KaFooter, page /contact

- Vendorisation du package ka-ui dans frontend/src/ka/ (tokens.css,
  ecosystem.json, KaFooter, GroupeKaBadge) ; tokens importés AVANT le CSS
  local dans main.tsx
- styles.css allégé : le :root ne garde que l accent orange brûlé d Auto-Ka
  (--accent/--accent-soft/--accent-deep/--on-accent + alias --lime), socle
  dédupliqué, couleurs en dur pointées vers les tokens, cibles tactiles 44 px
- Header : badge « Un service Groupe KA » à côté du logo (desktop) + barre
  mobile sous le ticker ; nav défilante sans débordement
- Footer : remplacé par le KaFooter commun (siteId auto-ka)
- Nouvelle page /contact (ecosystem.json : 3 courriels + rôles, avertissement
  agrégateur, lien hub, liste des sites) + rendu serveur SEO + sitemap
- Profil déconnecté : lien « Créer un compte » vers groupe-ka.com/connexion
- index.html : titre « Auto-Ka — Un service Groupe KA »

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

11 changed files +587 −111

modified autoka/seo.py +14 −1
@@ -952,6 +952,18 @@ def page_profil():
952 952 description="Votre profil Auto-Ka.", robots="noindex, nofollow")
953 953
954 954
955 +@router.get("/contact", response_model=None, include_in_schema=False)
956 +def page_contact():
957 + """Page contact — coordonnées officielles du Groupe KA (rendu React)."""
958 + return render_page(
959 + path="/contact",
960 + title="Contact — Auto-Ka, un service Groupe KA",
961 + description="Joindre le Groupe KA : contact@groupe-ka.com (projets, "
962 + "partenariats et données), info@groupe-ka.com (médias) et "
963 + "admin@groupe-ka.com (légal, vie privée et Loi 25). Auto-Ka est un "
964 + "service Groupe KA.")
965 +
966 +
955 967 # --- robots.txt et sitemaps -----------------------------------------------------------
956 968
957 969 @router.get("/robots.txt", response_model=None, include_in_schema=False)
@@ -992,7 +1004,8 @@ def sitemap_pages():
992 1004 m = _maps()
993 1005 today = datetime.now(tz=timezone.utc).strftime("%Y-%m-%d")
994 1006 urls: list[tuple[str, str]] = [("/", today), ("/motos", today), ("/scooters", today),
995 − ("/stats", today), ("/sources", today)]
1007 + ("/stats", today), ("/sources", today),
1008 + ("/contact", today)]
996 1009 for kind, prefix in (("auto", "/usagees/"), ("moto", "/motos/"), ("scooter", "/scooters/")):
997 1010 for slug, info in m["make"].get(kind, {}).items():
998 1011 urls.append((prefix + slug, _date(info["lm"])))
modified frontend/index.html +1 −1
@@ -5,7 +5,7 @@
5 5 <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
6 6 <meta name="description" content="Auto-Ka — toutes les voitures usagées à vendre au Québec, agrégées directement depuis les sites des concessionnaires. Un seul endroit, toujours à jour." />
7 7 <meta name="theme-color" content="#17181c" />
8 − <title>Auto·Ka — Voitures usagées à vendre au Québec</title>
8 + <title>Auto-Ka — Un service Groupe KA</title>
9 9 <link rel="preconnect" href="https://fonts.googleapis.com" />
10 10 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
11 11 <link
modified frontend/src/App.tsx +13 −23
@@ -9,7 +9,10 @@ import {
9 9 fetchFacets, fetchSources, fetchStats, logout, registerSourceNames,
10 10 } from "./api";
11 11 import { AccountProvider, useAccount } from "./account";
12 +import GroupeKaBadge from "./ka/GroupeKaBadge";
13 +import KaFooter from "./ka/KaFooter";
12 14 import Home from "./pages/Home";
15 +import ContactPage from "./pages/Contact";
13 16 import VehiclePage from "./pages/Vehicle";
14 17 import SourcesPage from "./pages/Sources";
15 18 import StatsPage from "./pages/Stats";
@@ -138,6 +141,7 @@ function Header() {
138 141 Auto<span className="ka">Ka</span>
139 142 <span className="brand-tag">Voitures usagées · tout le Québec · toujours à jour</span>
140 143 </NavLink>
144 + <GroupeKaBadge />
141 145 <nav className="nav" aria-label="Navigation principale">
142 146 <NavLink to="/" end className={({ isActive }) => (isActive ? "active" : "")}>
143 147 Autos
@@ -154,34 +158,19 @@ function Header() {
154 158 <NavLink to="/sources" className={({ isActive }) => (isActive ? "active" : "")}>
155 159 Sources
156 160 </NavLink>
161 + <NavLink to="/contact" className={({ isActive }) => (isActive ? "active" : "")}>
162 + Contact
163 + </NavLink>
157 164 </nav>
158 165 <AccountMenu />
159 166 </div>
160 167 </header>
161 168 <Ticker />
162 − </>
163 − );
164 −}
165 −
166 −function Footer() {
167 − return (
168 − <footer className="footer">
169 − <div className="container">
170 − <div className="fbrand">
171 − Auto<span className="ka">Ka</span>
172 − </div>
173 − <div className="frow">
174 − <b>Agrégateur indépendant</b> de voitures usagées à vendre dans la
175 − province de Québec. Les annonces proviennent directement des sites
176 − publics des concessionnaires — pas des plateformes d'annonces — et
177 − sont rafraîchies automatiquement. Chaque fiche renvoie vers l'annonce
178 − originale du commerçant.
179 − </div>
180 − <div className="fmono">
181 − © {new Date().getFullYear()} Simon-Pierre Boucher — contact@spboucher.ai
182 − </div>
169 + {/* variante mobile du badge (le badge du header est masqué < 1024 px) */}
170 + <div className="gk-mobile">
171 + <GroupeKaBadge />
183 172 </div>
184 − </footer>
173 + </>
185 174 );
186 175 }
187 176
@@ -206,6 +195,7 @@ export default function App() {
206 195 <Route path="/vehicule/:uid/:slug" element={<VehiclePage />} />
207 196 <Route path="/stats" element={<StatsPage />} />
208 197 <Route path="/sources" element={<SourcesPage />} />
198 + <Route path="/contact" element={<ContactPage />} />
209 199 <Route path="/profil" element={<ProfilPage />} />
210 200 <Route
211 201 path="*"
@@ -219,7 +209,7 @@ export default function App() {
219 209 />
220 210 </Routes>
221 211 </main>
222 − <Footer />
212 + <KaFooter siteId="auto-ka" />
223 213 </AccountProvider>
224 214 );
225 215 }
added frontend/src/ka/GroupeKaBadge.tsx +21 −0
@@ -0,0 +1,21 @@
1 +// Auteur : Simon-Pierre Boucher — contact@spboucher.ai
2 +// ka-ui/react/GroupeKaBadge.tsx — badge « Un service Groupe KA », cliquable
3 +// vers le hub. À placer dans le header de chaque site (à côté du logo de la
4 +// marque). Variante dark pour les surfaces encre. Zone de protection : le
5 +// padding interne du badge suffit ; ne rien coller à moins de 8 px du badge.
6 +export default function GroupeKaBadge({ dark = false }: { dark?: boolean }) {
7 + return (
8 + <a
9 + className={`gk-badge${dark ? " gk-badge--dark" : ""}`}
10 + href="https://www.groupe-ka.com"
11 + target="_blank"
12 + rel="noopener noreferrer"
13 + aria-label="Un service Groupe KA — visiter le portail de l'écosystème"
14 + >
15 + Un service
16 + <b>
17 + Groupe<span className="ka">KA</span>
18 + </b>
19 + </a>
20 + );
21 +}
added frontend/src/ka/KaFooter.tsx +91 −0
@@ -0,0 +1,91 @@
1 +// Auteur : Simon-Pierre Boucher — contact@spboucher.ai
2 +// ka-ui/react/KaFooter.tsx — footer commun Groupe KA (fond encre), à vendorer
3 +// dans chaque app Vite/React sous frontend/src/ka/. Consomme ecosystem.json :
4 +// toute modification des infos de contact se fait dans ka-ui/ecosystem.json
5 +// puis se resynchronise sur les 12 sites (voir ka-ui/README.md).
6 +import eco from "./ecosystem.json";
7 +
8 +type LocalLink = { label: string; href: string };
9 +
10 +export default function KaFooter({
11 + siteId,
12 + localLegal = [],
13 +}: {
14 + /** id du site courant dans ecosystem.json (ex. "resto-ka") */
15 + siteId: string;
16 + /** pages légales PROPRES au site (ex. /conditions), affichées avant celles du hub */
17 + localLegal?: LocalLink[];
18 +}) {
19 + const year = new Date().getFullYear();
20 + const site = eco.sites.find((s) => s.id === siteId);
21 + const others = eco.sites.filter((s) => s.id !== siteId);
22 + return (
23 + <footer className="ka-footer" id="contact">
24 + <div className="container">
25 + <a
26 + className="wordmark"
27 + href={eco.hub.url}
28 + target={siteId === "groupe-ka" ? undefined : "_blank"}
29 + rel="noopener noreferrer"
30 + aria-label="Groupe KA — le portail de l'écosystème"
31 + >
32 + Groupe <span className="ka">KA</span>
33 + </a>
34 + <p className="desc">
35 + {eco.org.tagline} Des centaines de connecteurs lisent les sites à la
36 + source, normalisent la donnée et se resynchronisent seuls.{" "}
37 + {site ? (
38 + <>
39 + <b style={{ color: "var(--paper)" }}>{site.wordmark}</b> —{" "}
40 + {site.tagline} — est un service Groupe KA.
41 + </>
42 + ) : null}
43 + </p>
44 + <p className="notice">
45 + <b>{eco.org.disclaimer}</b>
46 + </p>
47 + <ul className="sites">
48 + {others.map((s) => (
49 + <li key={s.id}>
50 + <a href={`https://${s.domain}`} target="_blank" rel="noopener noreferrer">
51 + {s.wordmark}
52 + </a>
53 + </li>
54 + ))}
55 + {eco.extraFooterLinks.map((l) => (
56 + <li key={l.href}>
57 + <a href={l.href} target="_blank" rel="noopener noreferrer">
58 + {l.label}
59 + </a>
60 + </li>
61 + ))}
62 + </ul>
63 + <div className="contacts">
64 + {eco.contacts.map((c) => (
65 + <p key={c.email} style={{ margin: 0 }}>
66 + <a href={`mailto:${c.email}`}>{c.email}</a>
67 + <span>{c.role}</span>
68 + </p>
69 + ))}
70 + </div>
71 + <p className="legal">
72 + © {year} {eco.org.copyrightHolder}
73 + {localLegal.map((l) => (
74 + <span key={l.href}>
75 + {" · "}
76 + <a href={l.href}>{l.label}</a>
77 + </span>
78 + ))}
79 + {eco.legal.map((l) => (
80 + <span key={l.href}>
81 + {" · "}
82 + <a href={l.href} target="_blank" rel="noopener noreferrer">
83 + {l.label}
84 + </a>
85 + </span>
86 + ))}
87 + </p>
88 + </div>
89 + </footer>
90 + );
91 +}
added frontend/src/ka/ecosystem.json +45 −0
@@ -0,0 +1,45 @@
1 +{
2 + "org": {
3 + "name": "Groupe KA",
4 + "legalName": "Groupe KA — Simon-Pierre Boucher",
5 + "tagline": "Holding québécois d'agrégateurs de produits et services entièrement automatisés.",
6 + "disclaimer": "Groupe KA est un agrégateur de contenu : nous ne vendons rien, ne louons rien et ne sommes partie à aucune transaction.",
7 + "copyrightHolder": "Groupe KA — Simon-Pierre Boucher"
8 + },
9 + "hub": {
10 + "url": "https://www.groupe-ka.com",
11 + "loginPath": "/connexion",
12 + "signupNote": "La création de compte KA ID se fait sur le hub groupe-ka.com ; chaque site délègue sa connexion via /api/auth/ka/login."
13 + },
14 + "contacts": [
15 + { "email": "contact@groupe-ka.com", "role": "Projets, partenariats & données" },
16 + { "email": "info@groupe-ka.com", "role": "Médias & questions générales" },
17 + { "email": "admin@groupe-ka.com", "role": "Légal, vie privée & Loi 25" }
18 + ],
19 + "legal": [
20 + { "label": "Conditions d'utilisation", "href": "https://www.groupe-ka.com/conditions" },
21 + { "label": "Politique de confidentialité", "href": "https://www.groupe-ka.com/confidentialite" },
22 + { "label": "Protection des renseignements personnels (Loi 25)", "href": "https://www.groupe-ka.com/loi-25" },
23 + { "label": "Transparence des robots d'indexation", "href": "https://www.groupe-ka.com/bots" }
24 + ],
25 + "sites": [
26 + { "id": "groupe-ka", "wordmark": "Groupe KA", "domain": "www.groupe-ka.com", "accent": "#d9f26b", "accentSoft": "#f0f9d2", "accentDeep": "#123f2e", "onAccent": "#141814", "tagline": "Le portail de l'écosystème ·Ka" },
27 + { "id": "trouve-ka", "wordmark": "Trouve·Ka", "domain": "www.trouve-ka.com", "accent": "#1c7ed6", "accentSoft": "#e7f2fd", "accentDeep": "#14508f", "onAccent": "#ffffff", "tagline": "Le moteur de recherche du web québécois" },
28 + { "id": "lou-ka", "wordmark": "Lou·Ka", "domain": "www.lou-ka.com", "accent": "#ff6a00", "accentSoft": "#fff1e6", "accentDeep": "#cc5500", "onAccent": "#ffffff", "tagline": "Tous les logements à louer" },
29 + { "id": "immo-ka", "wordmark": "Immo·Ka", "domain": "www.immo-ka.com", "accent": "#e23744", "accentSoft": "#fbe0e2", "accentDeep": "#a8232e", "onAccent": "#ffffff", "tagline": "Toutes les propriétés à vendre" },
30 + { "id": "vrai-prix", "wordmark": "Vrai-Prix", "domain": "www.vrai-prix.com", "accent": "#ff5148", "accentSoft": "#ffe3e0", "accentDeep": "#9e2a25", "onAccent": "#ffffff", "tagline": "La valeur réelle de chaque propriété" },
31 + { "id": "auto-ka", "wordmark": "Auto·Ka", "domain": "www.auto-ka.com", "accent": "#ff5a2a", "accentSoft": "#ffe8de", "accentDeep": "#cc3f16", "onAccent": "#ffffff", "tagline": "Les voitures usagées du Québec" },
32 + { "id": "fabri-ka", "wordmark": "Fabri·Ka", "domain": "www.fabri-ka.com", "accent": "#c4532e", "accentSoft": "#f7e3da", "accentDeep": "#a94525", "onAccent": "#ffffff", "tagline": "Les produits fabriqués au Québec" },
33 + { "id": "food-ka", "wordmark": "Food·Ka", "domain": "www.food-ka.com", "accent": "#1f9d55", "accentSoft": "#e2f5ea", "accentDeep": "#157a40", "onAccent": "#ffffff", "tagline": "Les prix d'épicerie, suivis à la source" },
34 + { "id": "resto-ka", "wordmark": "Resto·Ka", "domain": "www.resto-ka.com", "accent": "#f08c00", "accentSoft": "#fdeed7", "accentDeep": "#b96a00", "onAccent": "#141814", "tagline": "Chaque resto, chaque plat, chaque prix" },
35 + { "id": "sorti-ka", "wordmark": "Sorti·Ka", "domain": "www.sorti-ka.com", "accent": "#d6336c", "accentSoft": "#fbe0eb", "accentDeep": "#a12551", "onAccent": "#ffffff", "tagline": "Toutes les sorties, dans les 17 régions" },
36 + { "id": "crea-ka", "wordmark": "Créa·Ka", "domain": "www.crea-ka.com", "accent": "#7048e8", "accentSoft": "#ece5fc", "accentDeep": "#5433b8", "onAccent": "#ffffff", "tagline": "Les créateurs d'ici, tous leurs liens" },
37 + { "id": "api-ka", "wordmark": "API·Ka", "domain": "www.api-ka.com", "accent": "#3b5bdb", "accentSoft": "#e4eafb", "accentDeep": "#2b44a8", "onAccent": "#ffffff", "tagline": "La donnée de l'écosystème, par API" }
38 + ],
39 + "extraFooterLinks": [
40 + { "label": "ValoPlex", "href": "https://www.valoplex.com" },
41 + { "label": "Ka2", "href": "https://www.ka2.bot" },
42 + { "label": "Ka4", "href": "https://www.ka4.bot" },
43 + { "label": "Ka6", "href": "https://www.ka6.bot" }
44 + ]
45 +}
added frontend/src/ka/tokens.css +229 −0
@@ -0,0 +1,229 @@
1 +/* -----------------------------------------------------------------------------
2 + Auteur : Simon-Pierre Boucher — contact@spboucher.ai
3 + Fichier : ka-ui/tokens.css — SOURCE CANONIQUE (repo ka-ui sur spbgit)
4 + Desc. : Design system commun GROUPE KA « éditorial sharp » — tokens + socle
5 + de composants partagés par les 12 plateformes. Chaque site importe ce
6 + fichier PUIS surcharge uniquement son accent (voir accents.css).
7 +
8 + · Typo : display Space Grotesk / texte Inter / micro-étiquettes JetBrains Mono
9 + · Palette: papier #f5f3ee · encre #141814 · vert profond #1c5c41 + ACCENT du site
10 + · Signature : bordures encre 1,5–2 px + ombres décalées dures, grain de film,
11 + surlignés d'accent inclinés (néo-brutalisme raffiné)
12 + · Breakpoints communs : 360 / 768 / 1024 / 1440 px (mobile-first)
13 + · Zones tactiles ≥ 44×44 px, safe-areas iOS, typographie fluide (clamp)
14 +----------------------------------------------------------------------------- */
15 +
16 +:root {
17 + /* ---- Palette fixe Groupe KA (identique sur les 12 sites) ---- */
18 + --paper: #f5f3ee;
19 + --surface: #ffffff;
20 + --surface-2: #faf9f5;
21 + --ink: #141814;
22 + --ink-2: #4d5551;
23 + --ink-3: #8b928c;
24 + --line: rgba(20, 24, 20, 0.14);
25 + --line-strong: rgba(20, 24, 20, 0.85);
26 + --green: #1c5c41;
27 + --green-deep: #123f2e;
28 + --amber: #e8a33d;
29 + --amber-soft: #fdf3e2;
30 + --danger: #b3423a;
31 + --danger-soft: #fbe9e7;
32 +
33 + /* ---- ACCENT du site — SEULES variables surchargées par marque ---- */
34 + --accent: #d9f26b; /* défaut : lime Groupe KA */
35 + --accent-soft: #f0f9d2;
36 + --accent-deep: #123f2e;
37 + --on-accent: var(--ink); /* couleur du texte posé SUR l'accent */
38 +
39 + /* alias rétro-compatibles (les satellites historiques utilisent --lime) */
40 + --lime: var(--accent);
41 + --lime-soft: var(--accent-soft);
42 +
43 + /* ---- Géométrie sharp ---- */
44 + --r-card: 10px;
45 + --r-ctl: 6px;
46 + --r-pill: 999px;
47 +
48 + /* ---- Ombres décalées — la signature du groupe ---- */
49 + --shadow-flat: 0 1px 2px rgba(20, 24, 20, 0.05);
50 + --shadow-off: 6px 6px 0 var(--ink);
51 + --shadow-off-soft: 8px 8px 0 rgba(20, 24, 20, 0.08);
52 + --shadow-off-mid: 4px 4px 0 rgba(20, 24, 20, 0.18);
53 +
54 + /* ---- Typographie ---- */
55 + --font-display: "Space Grotesk", system-ui, sans-serif;
56 + --font-body: "Inter", system-ui, sans-serif;
57 + --font-mono: "JetBrains Mono", ui-monospace, monospace;
58 +
59 + /* Échelle fluide (mobile-first, clamp) */
60 + --fs-h1: clamp(30px, 3.2vw + 18px, 54px);
61 + --fs-h2: clamp(23px, 1.8vw + 14px, 34px);
62 + --fs-h3: clamp(17px, 0.9vw + 12px, 22px);
63 + --fs-body: 15px;
64 + --fs-small: 13px;
65 +
66 + /* Espacements */
67 + --sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px;
68 + --sp-5: 24px; --sp-6: 32px; --sp-7: 48px; --sp-8: 64px;
69 +
70 + /* Cible tactile minimale */
71 + --touch: 44px;
72 +}
73 +
74 +/* ---- Socle ---- */
75 +* { box-sizing: border-box; }
76 +html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }
77 +body {
78 + margin: 0;
79 + background: var(--paper);
80 + color: var(--ink);
81 + font-family: var(--font-body);
82 + font-size: var(--fs-body);
83 + line-height: 1.55;
84 + -webkit-font-smoothing: antialiased;
85 + padding-bottom: env(safe-area-inset-bottom);
86 + overflow-x: clip; /* jamais de débordement horizontal */
87 +}
88 +img, svg, video { max-width: 100%; height: auto; display: block; }
89 +h1, h2, h3, h4 { font-family: var(--font-display); letter-spacing: -0.03em; margin: 0 0 0.5em; }
90 +h1 { font-size: var(--fs-h1); line-height: 1.06; }
91 +h2 { font-size: var(--fs-h2); line-height: 1.15; }
92 +h3 { font-size: var(--fs-h3); line-height: 1.25; }
93 +a { color: inherit; }
94 +::selection { background: var(--accent); color: var(--on-accent); }
95 +:focus-visible { outline: 2px solid var(--green); outline-offset: 2px; }
96 +
97 +/* Grain de film pleine page — ⚠️ jamais de z-index sur body > * (position:
98 + relative seulement), sinon les utilitaires z-* sont écrasés. */
99 +body::before {
100 + content: "";
101 + position: fixed; inset: 0; pointer-events: none; opacity: 0.35; z-index: 9999;
102 + 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' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='linear' slope='0.06'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)'/%3E%3C/svg%3E");
103 +}
104 +body > * { position: relative; }
105 +
106 +/* ---- Conteneur commun ---- */
107 +.container { max-width: 1152px; margin: 0 auto; padding: 0 var(--sp-4); }
108 +@media (min-width: 768px) { .container { padding: 0 var(--sp-5); } }
109 +
110 +/* ---- Micro-typographie signature ---- */
111 +.kicker {
112 + display: inline-flex; align-items: center; gap: 10px;
113 + font-family: var(--font-mono); font-size: 11.5px; font-weight: 500;
114 + text-transform: uppercase; letter-spacing: 0.12em; color: var(--green);
115 +}
116 +.kicker::before { content: ""; width: 22px; height: 2px; background: var(--green); }
117 +.klabel {
118 + font-family: var(--font-mono); font-size: 10px; font-weight: 700;
119 + text-transform: uppercase; letter-spacing: 0.1em; color: var(--ink-3);
120 +}
121 +.hl {
122 + display: inline-block; background: var(--accent); color: var(--on-accent);
123 + border-radius: 8px; padding: 0 10px 2px; transform: rotate(-1deg);
124 +}
125 +.outline-txt { color: transparent; -webkit-text-stroke: 2px var(--ink); }
126 +
127 +/* ---- Boutons (cible ≥ 44 px) ---- */
128 +.btn {
129 + display: inline-flex; align-items: center; justify-content: center; gap: 8px;
130 + min-height: var(--touch); padding: 10px 18px;
131 + font-family: var(--font-display); font-weight: 700; font-size: 14px;
132 + border: 1.5px solid var(--ink); border-radius: var(--r-ctl);
133 + background: var(--surface); color: var(--ink);
134 + cursor: pointer; text-decoration: none;
135 + transition: transform 0.15s, box-shadow 0.15s, background 0.15s, color 0.15s;
136 +}
137 +.btn:hover { transform: translate(-2px, -2px); box-shadow: var(--shadow-off-mid); }
138 +.btn:active { transform: translate(0, 0); box-shadow: none; }
139 +.btn:disabled { opacity: 0.45; pointer-events: none; }
140 +.btn-primary { background: var(--ink); color: var(--accent); }
141 +.btn-primary:hover { background: var(--accent-deep); }
142 +.btn-accent { background: var(--accent); color: var(--on-accent); }
143 +.btn-ghost { background: transparent; border-color: var(--line); }
144 +.btn-ghost:hover { border-color: var(--ink); }
145 +
146 +/* ---- Cartes ---- */
147 +.card {
148 + background: var(--surface); border: 1.5px solid var(--ink);
149 + border-radius: var(--r-card); box-shadow: var(--shadow-off-soft);
150 + overflow: hidden;
151 +}
152 +.card-hover { transition: transform 0.15s, box-shadow 0.15s; }
153 +.card-hover:hover { transform: translate(-2px, -2px); box-shadow: var(--shadow-off); }
154 +
155 +/* ---- Chips / badges ---- */
156 +.chip {
157 + display: inline-flex; align-items: center; gap: 6px;
158 + padding: 4px 11px; font-family: var(--font-mono); font-size: 11px; font-weight: 700;
159 + text-transform: uppercase; letter-spacing: 0.06em;
160 + border: 1.5px solid var(--ink); border-radius: var(--r-pill);
161 + background: var(--surface); color: var(--ink);
162 +}
163 +.chip-accent { background: var(--accent); color: var(--on-accent); }
164 +.chip-soft { background: var(--accent-soft); border-color: var(--line); }
165 +
166 +/* ---- Champs ---- */
167 +.input, .select, .textarea {
168 + width: 100%; min-height: var(--touch); padding: 10px 14px;
169 + font: inherit; color: var(--ink); background: var(--surface);
170 + border: 1.5px solid var(--ink); border-radius: var(--r-ctl);
171 +}
172 +.input:focus, .select:focus, .textarea:focus {
173 + outline: none; box-shadow: 3px 3px 0 var(--accent); border-color: var(--ink);
174 +}
175 +.input::placeholder { color: var(--ink-3); }
176 +
177 +/* ---- Badge « Un service Groupe KA » (header, cliquable → hub) ---- */
178 +.gk-badge {
179 + display: inline-flex; align-items: center; gap: 7px;
180 + min-height: 30px; padding: 3px 10px 4px;
181 + font-family: var(--font-mono); font-size: 10px; font-weight: 700;
182 + letter-spacing: 0.08em; text-transform: uppercase; text-decoration: none;
183 + border: 1.5px solid var(--ink); border-radius: var(--r-pill);
184 + background: var(--surface); color: var(--ink-2); white-space: nowrap;
185 +}
186 +.gk-badge b {
187 + font-family: var(--font-display); font-size: 12px; letter-spacing: -0.02em;
188 + text-transform: none; color: var(--ink);
189 +}
190 +.gk-badge b .ka {
191 + display: inline-block; background: var(--ink); color: var(--accent);
192 + border-radius: 5px; padding: 0 5px 1px; margin-left: 3px; transform: rotate(-2deg);
193 +}
194 +.gk-badge:hover .ka { transform: rotate(0); }
195 +.gk-badge--dark { background: transparent; border-color: rgba(245,243,238,0.4); color: rgba(245,243,238,0.75); }
196 +.gk-badge--dark b { color: var(--paper); }
197 +
198 +/* ---- Footer commun (fond encre — voir react/KaFooter.tsx) ---- */
199 +.ka-footer { margin-top: var(--sp-8); background: var(--ink); padding: 44px 0; font-size: 13px; color: rgba(245,243,238,0.75); }
200 +.ka-footer a { text-decoration: none; }
201 +.ka-footer .wordmark { font-family: var(--font-display); font-weight: 700; font-size: 30px; letter-spacing: -0.04em; color: var(--paper); text-decoration: none; }
202 +.ka-footer .wordmark .ka { color: var(--accent); }
203 +.ka-footer .desc { max-width: 640px; margin-top: var(--sp-4); }
204 +.ka-footer .notice { max-width: 640px; margin-top: var(--sp-4); border-left: 2px solid var(--accent); padding-left: var(--sp-4); }
205 +.ka-footer .notice b { color: var(--paper); }
206 +.ka-footer .sites { list-style: none; display: flex; flex-wrap: wrap; gap: 10px 26px; margin: 26px 0 0; padding: 0; font-family: var(--font-mono); font-size: 11px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; }
207 +.ka-footer .sites a { color: rgba(245,243,238,0.65); }
208 +.ka-footer .sites a:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 4px; }
209 +.ka-footer .contacts { display: grid; gap: 14px 32px; border-top: 1px solid rgba(245,243,238,0.15); margin-top: 30px; padding-top: 26px; }
210 +@media (min-width: 768px) { .ka-footer .contacts { grid-template-columns: repeat(3, 1fr); } }
211 +.ka-footer .contacts a { font-family: var(--font-mono); font-weight: 700; font-size: 12px; color: rgba(245,243,238,0.85); }
212 +.ka-footer .contacts a:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 4px; }
213 +.ka-footer .contacts span { display: block; margin-top: 3px; font-size: 11px; color: rgba(245,243,238,0.5); }
214 +.ka-footer .legal { margin-top: 30px; font-family: var(--font-mono); font-size: 11px; color: rgba(245,243,238,0.45); }
215 +.ka-footer .legal a { color: inherit; }
216 +.ka-footer .legal a:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 4px; }
217 +
218 +/* ---- Tableaux → cartes empilées sous 768 px ---- */
219 +.tbl-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
220 +@media (max-width: 767px) {
221 + .tbl-stack thead { display: none; }
222 + .tbl-stack tr { display: block; border: 1.5px solid var(--ink); border-radius: var(--r-card); margin-bottom: var(--sp-3); background: var(--surface); }
223 + .tbl-stack td { display: flex; justify-content: space-between; gap: var(--sp-3); padding: 8px 12px; border: 0; }
224 + .tbl-stack td::before { content: attr(data-label); font-family: var(--font-mono); font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-3); }
225 +}
226 +
227 +/* ---- Utilitaires responsive ---- */
228 +.only-mobile { display: initial; } .only-desktop { display: none; }
229 +@media (min-width: 768px) { .only-mobile { display: none; } .only-desktop { display: initial; } }
modified frontend/src/main.tsx +1 −0
@@ -6,6 +6,7 @@ import React from "react";
6 6 import ReactDOM from "react-dom/client";
7 7 import { BrowserRouter } from "react-router-dom";
8 8 import App from "./App";
9 +import "./ka/tokens.css"; // socle Groupe KA (tokens + composants partagés) — AVANT le CSS local
9 10 import "./styles.css";
10 11
11 12 ReactDOM.createRoot(document.getElementById("root")!).render(
added frontend/src/pages/Contact.tsx +73 −0
@@ -0,0 +1,73 @@
1 +// -----------------------------------------------------------------------------
2 +// Auto-Ka — Agrégateur de voitures usagées à vendre (province de Québec)
3 +// Auteur : Simon-Pierre Boucher — contact@spboucher.ai
4 +// pages/Contact.tsx : page /contact — coordonnées officielles du Groupe KA,
5 +// lues d'ecosystem.json (source canonique ka-ui) : 3 courriels + rôles,
6 +// avertissement agrégateur, lien vers le hub et liste des sites du groupe.
7 +// -----------------------------------------------------------------------------
8 +import { useEffect } from "react";
9 +import eco from "../ka/ecosystem.json";
10 +
11 +export default function ContactPage() {
12 + useEffect(() => {
13 + document.title = "Contact — Auto-Ka, un service Groupe KA";
14 + window.scrollTo(0, 0);
15 + }, []);
16 +
17 + const others = eco.sites.filter((s) => s.id !== "auto-ka");
18 +
19 + return (
20 + <div className="page container">
21 + <span className="kicker">Nous joindre</span>
22 + <h1>Contact</h1>
23 + <p className="lead">
24 + Auto·Ka est un service du <b>{eco.org.name}</b> — {eco.org.tagline}{" "}
25 + Toutes les demandes passent par les adresses officielles du groupe.
26 + </p>
27 +
28 + <div className="contact-grid">
29 + {eco.contacts.map((c) => (
30 + <div className="contact-card" key={c.email}>
31 + <a className="mail" href={`mailto:${c.email}`}>{c.email}</a>
32 + <span className="role">{c.role}</span>
33 + </div>
34 + ))}
35 + </div>
36 +
37 + <div className="contact-note">
38 + <b>{eco.org.disclaimer}</b> Les annonces affichées sur Auto·Ka
39 + proviennent des sites publics des concessionnaires ; chaque fiche
40 + renvoie vers l'annonce originale du commerçant.
41 + </div>
42 +
43 + <div className="contact-hub">
44 + <a
45 + className="btn"
46 + href={eco.hub.url}
47 + target="_blank"
48 + rel="noopener noreferrer"
49 + >
50 + Visiter le portail Groupe KA ↗
51 + </a>
52 + </div>
53 +
54 + <div className="seo-block">
55 + <h2>Les autres plateformes du Groupe KA</h2>
56 + <div className="contact-sites">
57 + {others.map((s) => (
58 + <a
59 + key={s.id}
60 + className="spec-chip"
61 + href={`https://${s.domain}`}
62 + target="_blank"
63 + rel="noopener noreferrer"
64 + title={s.tagline}
65 + >
66 + {s.wordmark}
67 + </a>
68 + ))}
69 + </div>
70 + </div>
71 + </div>
72 + );
73 +}
modified frontend/src/pages/Profil.tsx +13 −3
@@ -115,9 +115,19 @@ export default function ProfilPage() {
115 115 Connectez-vous avec votre <b>KA&nbsp;ID</b> — l'identifiant de membre
116 116 du Groupe&nbsp;KA, le même sur toutes les plateformes du groupe.
117 117 </p>
118 − <a className="btn login-cta" href="/api/auth/ka/login?next=%2Fprofil">
119 − Se connecter avec KA ID
120 − </a>
118 + <div className="profil-actions">
119 + <a className="btn login-cta" href="/api/auth/ka/login?next=%2Fprofil">
120 + Se connecter avec KA ID
121 + </a>
122 + <a
123 + className="btn ghost login-cta"
124 + href="https://www.groupe-ka.com/connexion"
125 + target="_blank"
126 + rel="noopener noreferrer"
127 + >
128 + Créer un compte sur groupe-ka.com ↗
129 + </a>
130 + </div>
121 131 </div>
122 132 );
123 133 }
modified frontend/src/styles.css +86 −83
@@ -1,79 +1,43 @@
1 1 /* -----------------------------------------------------------------------------
2 2 Auto-Ka — Agrégateur de voitures usagées à vendre (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
5 − · Typo display Space Grotesk / texte Inter / micro-étiquettes JetBrains Mono
6 − · Signature : bordures encre + ombres décalées (néo-brutalisme raffiné)
7 − · Accent orange racing #ff5a2a sur encre charbon
4 + styles.css : classes MÉTIER par-dessus le socle Groupe KA (ka/tokens.css).
5 + · Le socle (palette papier/encre, typo, ombres décalées, breakpoints,
6 + composants partagés) vient de ka/tokens.css, importé AVANT ce fichier.
7 + · Ici : SEUL l'accent du site est surchargé + les classes propres à
8 + Auto-Ka (fiches véhicules, filtres, stats, compte KA ID).
9 + · Accent Auto-Ka : orange brûlé #ff5a2a sur encre.
8 10 ----------------------------------------------------------------------------- */
9 11 :root {
10 − --paper: #f4f2ec;
11 − --surface: #ffffff;
12 − --surface-2: #faf8f4;
13 − --ink: #17181c;
14 − --ink-2: #4c4f57;
15 − --ink-3: #8b8e96;
16 − --line: rgba(23, 24, 28, 0.14);
17 − --line-strong: rgba(23, 24, 28, 0.85);
12 + /* ---- ACCENT Auto-Ka — seules variables surchargées des tokens ka-ui ---- */
18 13 --accent: #ff5a2a;
19 − --accent-deep: #cc3f16;
20 14 --accent-soft: #ffe8de;
15 + --accent-deep: #cc3f16;
16 + --on-accent: #ffffff;
17 + /* alias rétro-compatibles avec le socle */
18 + --lime: var(--accent);
19 + --lime-soft: var(--accent-soft);
20 + /* variables métier locales (absentes des tokens) */
21 21 --good: #1c7a4d;
22 22 --good-soft: #e2f4e9;
23 − --amber: #e8a33d;
24 − --amber-soft: #fdf3e2;
25 − --danger: #b3423a;
26 − --r-card: 10px;
27 − --r-ctl: 6px;
28 − --shadow-flat: 0 1px 2px rgba(23, 24, 28, 0.05);
29 − --shadow-off: 6px 6px 0 var(--ink);
30 − --shadow-off-soft: 8px 8px 0 rgba(23, 24, 28, 0.08);
31 − --font-display: "Space Grotesk", system-ui, sans-serif;
32 − --font-body: "Inter", system-ui, sans-serif;
33 − --font-mono: "JetBrains Mono", ui-monospace, monospace;
34 −}
35 −
36 −* { box-sizing: border-box; }
37 −html { scroll-behavior: smooth; }
38 −body {
39 − margin: 0;
40 − background: var(--paper);
41 − color: var(--ink);
42 − font-family: var(--font-body);
43 − font-size: 15px;
44 − line-height: 1.55;
45 − -webkit-font-smoothing: antialiased;
46 − padding-bottom: env(safe-area-inset-bottom);
47 −}
48 −/* grain subtil — texture signature */
49 −body::before {
50 − content: "";
51 − position: fixed; inset: 0; z-index: 0; pointer-events: none; opacity: 0.35;
52 − 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");
53 −}
54 −#root { position: relative; z-index: 1; }
55 −
56 −h1, h2, h3, h4 { font-family: var(--font-display); letter-spacing: -0.03em; margin: 0; }
57 −a { color: inherit; text-decoration: none; }
23 +}
24 +
25 +/* — surcharges locales du socle (layout historique d'Auto-Ka) — */
26 +h1, h2, h3, h4 { margin: 0; }
27 +a { text-decoration: none; }
58 28 button { font-family: inherit; }
59 −img { display: block; }
60 −::selection { background: var(--accent); color: #fff; }
61 29
62 30 .mono { font-family: var(--font-mono); }
63 −.kicker {
64 − font-family: var(--font-mono); font-size: 11.5px; font-weight: 500;
65 − text-transform: uppercase; letter-spacing: 0.14em; color: var(--accent-deep);
66 − display: inline-flex; align-items: center; gap: 8px;
67 −}
68 −.kicker::before { content: ""; width: 22px; height: 2px; background: var(--accent); }
31 +.kicker { color: var(--accent-deep); }
32 +.kicker::before { background: var(--accent); }
69 33
70 −.container { max-width: 1280px; margin: 0 auto; padding: 0 24px; }
34 +.container { max-width: 1280px; padding: 0 24px; }
71 35 @media (max-width: 640px) { .container { padding: 0 16px; } }
72 36
73 37 /* ================= Header ================= */
74 38 .header {
75 39 position: sticky; top: 0; z-index: 50;
76 − background: rgba(244, 242, 236, 0.88);
40 + background: rgba(245, 243, 238, 0.88); /* var(--paper) à 88 % */
77 41 backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
78 42 border-bottom: 2px solid var(--ink);
79 43 }
@@ -93,11 +57,16 @@ img { display: block; }
93 57 letter-spacing: 0.08em; text-transform: uppercase; margin-left: 12px;
94 58 }
95 59 @media (max-width: 860px) { .brand-tag { display: none; } }
96 −.nav { margin-left: auto; display: flex; gap: 4px; }
60 +.nav {
61 + margin-left: auto; display: flex; gap: 4px;
62 + overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none;
63 +}
64 +.nav::-webkit-scrollbar { display: none; }
65 +.nav a { flex: none; }
97 66 .nav a {
98 67 padding: 9px 16px; border-radius: 999px; font-weight: 600; font-size: 14px;
99 68 color: var(--ink-2); border: 1.5px solid transparent;
100 − transition: all 0.15s ease; min-height: 40px; display: inline-flex; align-items: center;
69 + transition: all 0.15s ease; min-height: 44px; display: inline-flex; align-items: center;
101 70 }
102 71 .nav a:hover { border-color: var(--ink); color: var(--ink); }
103 72 .nav a.active { background: var(--ink); color: var(--accent); }
@@ -105,6 +74,17 @@ img { display: block; }
105 74 .nav a { padding: 8px 11px; font-size: 13px; }
106 75 }
107 76
77 +/* — badge « Un service Groupe KA » (composant ka/GroupeKaBadge) — */
78 +.header .gk-badge { flex: none; margin-left: 2px; }
79 +@media (max-width: 1023px) { .header .gk-badge { display: none; } }
80 +/* variante mobile : petite barre sous le ticker, tactile ≥ 44 px */
81 +.gk-mobile {
82 + display: none; align-items: center; justify-content: center;
83 + min-height: 44px; padding: 5px 16px;
84 + background: var(--surface-2); border-bottom: 1.5px solid var(--line);
85 +}
86 +@media (max-width: 1023px) { .gk-mobile { display: flex; } }
87 +
108 88 /* ================= Ticker ================= */
109 89 .ticker {
110 90 background: var(--ink); color: var(--accent); overflow: hidden;
@@ -152,7 +132,7 @@ img { display: block; }
152 132 .f-field select, .f-field input {
153 133 border: 1.5px solid var(--line-strong); border-radius: var(--r-ctl);
154 134 background: var(--surface); font: inherit; font-size: 14px;
155 − padding: 9px 10px; min-height: 40px; color: var(--ink);
135 + padding: 9px 10px; min-height: 44px; color: var(--ink);
156 136 }
157 137 .f-field select:focus, .f-field input:focus {
158 138 outline: 2px solid var(--accent); outline-offset: 1px;
@@ -161,8 +141,8 @@ img { display: block; }
161 141 .btn {
162 142 font-family: var(--font-display); font-weight: 600; font-size: 14.5px;
163 143 border: 2px solid var(--ink); border-radius: var(--r-ctl);
164 − background: var(--ink); color: #fff; cursor: pointer;
165 − padding: 9px 18px; min-height: 40px;
144 + background: var(--ink); color: var(--paper); cursor: pointer;
145 + padding: 9px 18px; min-height: 44px;
166 146 transition: transform 0.12s ease, box-shadow 0.12s ease;
167 147 }
168 148 .btn:hover { transform: translate(-2px, -2px); box-shadow: 4px 4px 0 var(--accent); }
@@ -205,19 +185,19 @@ img { display: block; }
205 185 }
206 186 .vcard .year-chip {
207 187 position: absolute; top: 10px; left: 10px;
208 − background: var(--ink); color: #fff; font-family: var(--font-mono);
188 + background: var(--ink); color: var(--paper); font-family: var(--font-mono);
209 189 font-size: 11.5px; font-weight: 600; padding: 3px 9px; border-radius: 999px;
210 190 }
211 191 .vcard .drop-chip {
212 192 position: absolute; top: 10px; right: 10px;
213 − background: var(--good); color: #fff; font-family: var(--font-mono);
193 + background: var(--good); color: var(--paper); font-family: var(--font-mono);
214 194 font-size: 10.5px; font-weight: 600; padding: 3px 9px; border-radius: 999px;
215 195 }
216 196
217 197 /* ===== Favoris — cœur rond « Mon univers Ka » en coin de photo ===== */
218 198 .fav-btn {
219 199 position: absolute; right: 10px; bottom: 10px; z-index: 2;
220 − width: 34px; height: 34px; padding: 0;
200 + width: 44px; height: 44px; padding: 0;
221 201 display: flex; align-items: center; justify-content: center;
222 202 background: rgba(255, 255, 255, 0.94); border: 2px solid var(--ink);
223 203 border-radius: 999px; cursor: pointer; color: var(--ink);
@@ -227,7 +207,7 @@ img { display: block; }
227 207 .fav-btn.on { background: var(--accent-soft); color: var(--accent-deep); }
228 208 .fav-btn.on svg { filter: drop-shadow(0 0 3px rgba(204, 63, 22, 0.35)); }
229 209 .gallery .main { position: relative; }
230 −.gallery .main .fav-btn { right: 14px; bottom: 14px; width: 42px; height: 42px; }
210 +.gallery .main .fav-btn { right: 14px; bottom: 14px; width: 44px; height: 44px; }
231 211 .gallery .main .fav-btn svg { width: 20px; height: 20px; }
232 212 .vcard .body { padding: 14px 16px 16px; display: flex; flex-direction: column; gap: 8px; flex: 1; }
233 213 .vcard h3 { font-size: 16.5px; line-height: 1.25; }
@@ -296,7 +276,7 @@ img { display: block; }
296 276
297 277 .cta-source {
298 278 display: block; text-align: center; font-family: var(--font-display);
299 − font-weight: 700; font-size: 16px; background: var(--accent); color: #fff;
279 + font-weight: 700; font-size: 16px; background: var(--accent); color: var(--on-accent);
300 280 border: 2px solid var(--ink); border-radius: var(--r-ctl);
301 281 padding: 13px 18px; margin-top: 4px;
302 282 transition: transform 0.12s ease, box-shadow 0.12s ease;
@@ -359,7 +339,7 @@ img { display: block; }
359 339
360 340 .chart-tip {
361 341 position: absolute; transform: translate(-50%, -100%); pointer-events: none;
362 − background: var(--ink); color: #fff; font-size: 12.5px; line-height: 1.35;
342 + background: var(--ink); color: var(--paper); font-size: 12.5px; line-height: 1.35;
363 343 padding: 6px 10px; border-radius: 6px; white-space: nowrap; z-index: 5;
364 344 box-shadow: 0 4px 14px rgba(23,24,28,0.25);
365 345 }
@@ -466,20 +446,20 @@ img { display: block; }
466 446 font-family: var(--font-display); font-weight: 600; font-size: 13.5px;
467 447 border: 2px solid var(--ink); border-radius: 999px;
468 448 background: var(--surface); color: var(--ink);
469 − padding: 8px 15px; min-height: 40px; flex: none;
449 + padding: 8px 15px; min-height: 44px; flex: none;
470 450 transition: transform 0.12s ease, box-shadow 0.12s ease;
471 451 }
472 452 .login-btn:hover { transform: translate(-2px, -2px); box-shadow: 4px 4px 0 var(--accent); }
473 453 .login-ka {
474 454 display: inline-flex; align-items: center; justify-content: center;
475 455 width: 18px; height: 16px; border-radius: 4px;
476 − background: #141814; color: #d9f26b;
456 + background: var(--ink); color: #d9f26b; /* lime KA ID — identité du groupe */
477 457 font: 700 9px/1 var(--font-display); letter-spacing: -0.02em;
478 458 transform: rotate(-2deg);
479 459 }
480 460 .account { position: relative; flex: none; }
481 461 .account-btn {
482 − width: 40px; height: 40px; padding: 0; cursor: pointer;
462 + width: 44px; height: 44px; padding: 0; cursor: pointer;
483 463 border: 2px solid var(--ink); border-radius: 999px; overflow: hidden;
484 464 background: var(--surface); display: flex; align-items: center; justify-content: center;
485 465 transition: transform 0.12s ease, box-shadow 0.12s ease;
@@ -488,7 +468,7 @@ img { display: block; }
488 468 .account-btn img { width: 100%; height: 100%; object-fit: cover; }
489 469 .account-initial {
490 470 font-family: var(--font-display); font-weight: 700; font-size: 17px;
491 − background: var(--accent); color: #fff; width: 100%; height: 100%;
471 + background: var(--accent); color: var(--on-accent); width: 100%; height: 100%;
492 472 display: flex; align-items: center; justify-content: center;
493 473 }
494 474 .account-backdrop { position: fixed; inset: 0; z-index: 60; }
@@ -608,19 +588,42 @@ a.hub-chip:hover { border-color: var(--ink); color: var(--ink); }
608 588 .profil-note a { text-decoration: underline; text-underline-offset: 3px; }
609 589 .profil-actions { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 26px; }
610 590
611 −/* ================= Footer ================= */
612 −.footer {
613 − background: var(--ink); color: #c9cbd1; margin-top: 30px;
614 − padding: 42px 0 30px; border-top: 3px solid var(--accent);
591 +/* ================= Footer =================
592 + Le footer est désormais le KaFooter commun (ka/KaFooter.tsx + .ka-footer
593 + dans ka/tokens.css). Bordure accent au-dessus, signature Auto-Ka. */
594 +.ka-footer { border-top: 3px solid var(--accent); }
595 +
596 +/* ================= Page /contact ================= */
597 +.contact-grid {
598 + display: grid; gap: 14px; margin: 22px 0 6px;
599 + grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); max-width: 900px;
615 600 }
616 −.fbrand { font-family: var(--font-display); font-weight: 700; font-size: 24px; color: #fff; margin-bottom: 14px; }
617 −.fbrand .ka { background: var(--accent); color: var(--ink); padding: 1px 6px 3px; border-radius: 5px; margin-left: 2px; display: inline-block; transform: rotate(-2deg); }
618 −.frow { font-size: 13.5px; max-width: 560px; line-height: 1.6; }
619 −.fmono { font-family: var(--font-mono); font-size: 11px; color: #82858d; margin-top: 22px; }
620 −.fmono a { text-decoration: underline; }
601 +.contact-card {
602 + background: var(--surface); border: 2px solid var(--ink); border-radius: var(--r-card);
603 + padding: 16px 18px; display: flex; flex-direction: column; gap: 6px;
604 + box-shadow: var(--shadow-flat);
605 + transition: transform 0.14s ease, box-shadow 0.14s ease;
606 +}
607 +.contact-card:hover { transform: translate(-2px, -2px); box-shadow: var(--shadow-off); }
608 +.contact-card a.mail {
609 + font-family: var(--font-mono); font-weight: 700; font-size: 14px;
610 + color: var(--accent-deep); word-break: break-all;
611 + display: inline-flex; align-items: center; min-height: 44px;
612 +}
613 +.contact-card a.mail:hover { text-decoration: underline; text-underline-offset: 3px; }
614 +.contact-card .role { font-size: 13px; color: var(--ink-2); }
615 +.contact-note {
616 + max-width: 720px; margin: 24px 0; padding: 14px 18px;
617 + background: var(--accent-soft); border: 2px solid var(--ink);
618 + border-left-width: 6px; border-radius: var(--r-card); font-size: 14px;
619 +}
620 +.contact-sites { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; max-width: 900px; }
621 +.contact-sites a.spec-chip { text-decoration: none; min-height: 32px; display: inline-flex; align-items: center; }
622 +.contact-sites a.spec-chip:hover { border-color: var(--accent); color: var(--ink); }
623 +.contact-hub { margin-top: 26px; }
621 624
622 625 /* --- blocs SEO (pages programmatiques rendues serveur + React) --------------- */
623 −.breadcrumbs { font-size: 12px; color: var(--muted, #82858d); margin: 14px 0 4px; }
626 +.breadcrumbs { font-size: 12px; color: var(--ink-3); margin: 14px 0 4px; }
624 627 .breadcrumbs a { text-decoration: underline; }
625 628 .seo-block { margin: 30px 0; }
626 629 .seo-block h2 { font-size: 17px; margin: 0 0 12px; }
627 630