Harmonisation Groupe KA (ka-ui) : tokens, badge, KaFooter, page /contact, SSO
- Vendorisation ka-ui (frontend/src/ka/) : tokens.css importé AVANT le CSS local ; :root réduit à l'accent TERRACOTTA (+ alias --lime/--lime-soft, --accent-dark → --accent-deep) et aux variables métier, socle dédupliqué, couleurs en dur remplacées par les tokens. - Header : badge « Un service Groupe KA » (GroupeKaBadge) à côté du logo ; bouton « Se connecter avec KA » + lien « Créer un compte » (hub /connexion). - Footer : remplacé par KaFooter (siteId fabri-ka) — contacts, sites de l'écosystème, mentions légales du hub ; dégagement de la barre mobile. - Page /contact : route SPA (ecosystem.json — 3 courriels + rôles, avertissement, lien hub, sites) + rendu serveur SEO (seo.py) + sitemap. - SEO : title « Fabri-Ka — Un service Groupe KA » (index.html). - SSO KA ID : FABRIKA_BASE_URL par défaut → https://www.fabri-ka.com (auth.py) — le redirect_uri vers le hub n'est plus localhost. - Tactile ≥ 44 px (bouton connexion, avatar) ; resolveJsonModule (tsconfig). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
12 changed files +796 −236
modified
fabrika/auth.py
+1 −1
@@ -35,7 +35,7 @@ KA_HUB_URL = os.environ.get("KA_HUB_URL", "https://www.groupe-ka.com").rstrip("/ | ||
| 35 | 35 | KA_SSO_SECRET = os.environ.get("KA_SSO_SECRET", "") |
| 36 | 36 | KA_CLIENT_ID = "fabri-ka" |
| 37 | 37 | # base publique de CETTE app (l'URL de rappel en découle) |
| 38 | −BASE_URL = os.environ.get("FABRIKA_BASE_URL", "http://localhost:8097").rstrip("/") | |
| 38 | +BASE_URL = os.environ.get("FABRIKA_BASE_URL", "https://www.fabri-ka.com").rstrip("/") | |
| 39 | 39 | REDIRECT_URI = f"{BASE_URL}/api/auth/ka/callback" |
| 40 | 40 | SECRET = os.environ.get("SESSION_SECRET", "") or hashlib.sha256( |
| 41 | 41 | (KA_SSO_SECRET or "fabrika-dev").encode()).hexdigest() |
modified
fabrika/seo.py
+45 −2
@@ -331,7 +331,9 @@ def page_home() -> HTMLResponse: | ||
| 331 | 331 | <p>{esc(desc)}</p> |
| 332 | 332 | <p><a href="/produits">Voir les {fmt_int(totals["products"])} produits</a> · |
| 333 | 333 | <a href="/boutiques">{fmt_int(totals["stores"])} boutiques</a> · |
| 334 | − <a href="/stats">Statistiques</a> · <a href="/a-propos">À propos</a></p> | |
| 334 | + <a href="/stats">Statistiques</a> · <a href="/a-propos">À propos</a> · | |
| 335 | + <a href="/contact">Contact</a> · | |
| 336 | + <a href="https://www.groupe-ka.com">Un service Groupe KA</a></p> | |
| 335 | 337 | <h2>Parcourir par catégorie</h2> |
| 336 | 338 | {link_list([(f"/categorie/{CAT_SLUG.get(c['key'], slugify(c['key'] or 'autre'))}", |
| 337 | 339 | cat_label(c["key"]), f"{fmt_int(c['n'])} produits") |
@@ -730,6 +732,45 @@ vers la boutique d'origine pour l'achat. Contact : contact@spboucher.ai</p> | ||
| 730 | 732 | return render(title=f"À propos | {SITE}", description=desc, canonical="/a-propos", body=body) |
| 731 | 733 | |
| 732 | 734 | |
| 735 | +def _ecosystem() -> dict: | |
| 736 | + """ka-ui/ecosystem.json vendoré côté frontend — source unique des | |
| 737 | + coordonnées et des sites du Groupe KA (partagée avec la page React).""" | |
| 738 | + p = Path(__file__).resolve().parent.parent / "frontend" / "src" / "ka" / "ecosystem.json" | |
| 739 | + try: | |
| 740 | + return json.loads(p.read_text(encoding="utf-8")) | |
| 741 | + except OSError: | |
| 742 | + return {"org": {}, "hub": {"url": "https://www.groupe-ka.com"}, | |
| 743 | + "contacts": [], "sites": []} | |
| 744 | + | |
| 745 | + | |
| 746 | +def page_contact() -> HTMLResponse: | |
| 747 | + eco = _ecosystem() | |
| 748 | + hub = (eco.get("hub") or {}).get("url", "https://www.groupe-ka.com") | |
| 749 | + contacts = "".join( | |
| 750 | + f'<li><a href="mailto:{esc(c["email"])}">{esc(c["email"])}</a>' | |
| 751 | + f' — {esc(c["role"])}</li>' | |
| 752 | + for c in eco.get("contacts", [])) | |
| 753 | + sites = "".join( | |
| 754 | + f'<li><a href="https://{esc(st["domain"])}">{esc(st["wordmark"])}</a>' | |
| 755 | + f' — {esc(st["tagline"])}</li>' | |
| 756 | + for st in eco.get("sites", [])) | |
| 757 | + disclaimer = (eco.get("org") or {}).get("disclaimer", "") | |
| 758 | + desc = ("Joindre le Groupe KA — Fabri-Ka est un service Groupe KA : " | |
| 759 | + "projets et partenariats, médias, légal et vie privée (Loi 25).") | |
| 760 | + body = f""" | |
| 761 | +<div class="seo-page"> | |
| 762 | +<h1>Contact</h1> | |
| 763 | +<p>Fabri-Ka est un service du <a href="{esc(hub)}">Groupe KA</a>. Les coordonnées | |
| 764 | +ci-dessous sont communes à toutes les plateformes de l'écosystème.</p> | |
| 765 | +<ul>{contacts}</ul> | |
| 766 | +<p>{esc(disclaimer)}</p> | |
| 767 | +<h2>Les sites de l'écosystème</h2> | |
| 768 | +<ul>{sites}</ul> | |
| 769 | +</div>""" | |
| 770 | + return render(title=f"Contact | {SITE} — Un service Groupe KA", | |
| 771 | + description=desc, canonical="/contact", body=body) | |
| 772 | + | |
| 773 | + | |
| 733 | 774 | def page_404() -> HTMLResponse: |
| 734 | 775 | body = """ |
| 735 | 776 | <div class="seo-page"> |
@@ -793,7 +834,7 @@ def sitemap(name: str) -> Response: | ||
| 793 | 834 | today = date.today().isoformat() |
| 794 | 835 | urls: list[tuple[str, str | None]] = [ |
| 795 | 836 | ("/", today), ("/produits", today), ("/boutiques", today), |
| 796 | − ("/stats", today), ("/a-propos", None)] | |
| 837 | + ("/stats", today), ("/a-propos", None), ("/contact", None)] | |
| 797 | 838 | cat_last: dict[str, float] = {} |
| 798 | 839 | reg_last: dict[str, float] = {} |
| 799 | 840 | for c in combos: |
@@ -870,6 +911,8 @@ def render_route(path: str, params) -> Response: | ||
| 870 | 911 | return page_stats() |
| 871 | 912 | if path == "a-propos": |
| 872 | 913 | return page_about() |
| 914 | + if path == "contact": | |
| 915 | + return page_contact() | |
| 873 | 916 | if path == "profil": |
| 874 | 917 | return render(title=f"Mon profil — {SITE}", |
| 875 | 918 | description="Profil du membre Groupe KA.", |
modified
frontend/index.html
+1 −1
@@ -19,7 +19,7 @@ | ||
| 19 | 19 | 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;600&display=swap" |
| 20 | 20 | rel="stylesheet" |
| 21 | 21 | /> |
| 22 | − <title>Fabri-Ka — Tous les produits québécois. Un seul endroit.</title> | |
| 22 | + <title>Fabri-Ka — Un service Groupe KA</title> | |
| 23 | 23 | </head> |
| 24 | 24 | <body> |
| 25 | 25 | <div id="root"></div> |
modified
frontend/src/App.tsx
+25 −37
@@ -4,8 +4,11 @@ import { fetchMe, logout, type Me } from './api' | ||
| 4 | 4 | import BottomNav from './components/BottomNav' |
| 5 | 5 | import SearchBar from './components/SearchBar' |
| 6 | 6 | import { FavoritesProvider } from './favorites' |
| 7 | +import GroupeKaBadge from './ka/GroupeKaBadge' | |
| 8 | +import KaFooter from './ka/KaFooter' | |
| 7 | 9 | import About from './pages/About' |
| 8 | 10 | import Catalog from './pages/Catalog' |
| 11 | +import Contact from './pages/Contact' | |
| 9 | 12 | import Home from './pages/Home' |
| 10 | 13 | import NotFound from './pages/NotFound' |
| 11 | 14 | import ProductDetail from './pages/ProductDetail' |
@@ -16,7 +19,7 @@ import Stores from './pages/Stores' | ||
| 16 | 19 | |
| 17 | 20 | const HEADER_SEARCH_ID = 'header-search-input' |
| 18 | 21 | |
| 19 | −/** Bouton « Connexion » (KA ID — SSO Groupe KA) ou avatar + menu compte. */ | |
| 22 | +/** Bouton « Se connecter avec KA » (KA ID — SSO Groupe KA) ou avatar + menu. */ | |
| 20 | 23 | function AccountMenu() { |
| 21 | 24 | const location = useLocation() |
| 22 | 25 | const navigate = useNavigate() |
@@ -44,7 +47,15 @@ function AccountMenu() { | ||
| 44 | 47 | href={`/api/auth/ka/login?next=${encodeURIComponent(next)}`} |
| 45 | 48 | > |
| 46 | 49 | <span className="login-ka" aria-hidden="true">KA</span> |
| 47 | − Connexion | |
| 50 | + Se connecter avec KA | |
| 51 | + </a> | |
| 52 | + <a | |
| 53 | + className="signup-link" | |
| 54 | + href="https://www.groupe-ka.com/connexion" | |
| 55 | + target="_blank" | |
| 56 | + rel="noopener noreferrer" | |
| 57 | + > | |
| 58 | + Créer un compte | |
| 48 | 59 | </a> |
| 49 | 60 | </div> |
| 50 | 61 | ) |
@@ -144,14 +155,20 @@ function Header() { | ||
| 144 | 155 | return ( |
| 145 | 156 | <header className="site-header"> |
| 146 | 157 | <div className="site-header-inner"> |
| 147 | − <Link to="/" className="logo" aria-label="Fabri-Ka — Accueil"> | |
| 148 | − Fabri<span className="logo-ka">·Ka</span> | |
| 149 | − </Link> | |
| 158 | + <div className="header-brand"> | |
| 159 | + <Link to="/" className="logo" aria-label="Fabri-Ka — Accueil"> | |
| 160 | + Fabri<span className="logo-ka">·Ka</span> | |
| 161 | + </Link> | |
| 162 | + {/* badge Groupe KA — desktop et mobile (masqué < 480 px, repris | |
| 163 | + par le pied de page KaFooter) */} | |
| 164 | + <GroupeKaBadge /> | |
| 165 | + </div> | |
| 150 | 166 | <nav className="site-nav" aria-label="Navigation principale"> |
| 151 | 167 | <NavLink to="/produits">Produits</NavLink> |
| 152 | 168 | <NavLink to="/boutiques">Boutiques</NavLink> |
| 153 | 169 | <NavLink to="/stats">Stats</NavLink> |
| 154 | 170 | <NavLink to="/a-propos">À propos</NavLink> |
| 171 | + <NavLink to="/contact">Contact</NavLink> | |
| 155 | 172 | </nav> |
| 156 | 173 | <div className="site-header-search"> |
| 157 | 174 | {/* remount on navigation so the field reflects the current query */} |
@@ -174,37 +191,6 @@ function Header() { | ||
| 174 | 191 | ) |
| 175 | 192 | } |
| 176 | 193 | |
| 177 | −function Footer() { | |
| 178 | − return ( | |
| 179 | − <footer className="site-footer"> | |
| 180 | − <div className="site-footer-inner"> | |
| 181 | − <div className="footer-brand"> | |
| 182 | − <span className="logo logo-footer"> | |
| 183 | − Fabri<span className="logo-ka">·Ka</span> | |
| 184 | − </span> | |
| 185 | − <p>Tous les produits québécois. Un seul endroit.</p> | |
| 186 | − </div> | |
| 187 | − <nav className="footer-col" aria-label="Explorer"> | |
| 188 | − <span className="footer-col-title">Explorer</span> | |
| 189 | − <Link to="/produits">Produits</Link> | |
| 190 | − <Link to="/boutiques">Boutiques</Link> | |
| 191 | − <Link to="/produits?sort=recent">Nouveautés</Link> | |
| 192 | − <Link to="/stats">Stats</Link> | |
| 193 | − </nav> | |
| 194 | − <nav className="footer-col" aria-label="Fabri-Ka"> | |
| 195 | − <span className="footer-col-title">Fabri-Ka</span> | |
| 196 | − <Link to="/a-propos">À propos</Link> | |
| 197 | − <a href="mailto:contact@spboucher.ai">Contact</a> | |
| 198 | − </nav> | |
| 199 | − </div> | |
| 200 | − <p className="footer-legal"> | |
| 201 | − © 2026 Simon-Pierre Boucher — Fabri-Ka agrège des catalogues publics; | |
| 202 | − les prix et disponibilités appartiennent aux boutiques sources. | |
| 203 | − </p> | |
| 204 | − </footer> | |
| 205 | − ) | |
| 206 | −} | |
| 207 | − | |
| 208 | 194 | export default function App() { |
| 209 | 195 | return ( |
| 210 | 196 | <FavoritesProvider> |
@@ -224,10 +210,12 @@ export default function App() { | ||
| 224 | 210 | <Route path="/stats" element={<Stats />} /> |
| 225 | 211 | <Route path="/profil" element={<Profile />} /> |
| 226 | 212 | <Route path="/a-propos" element={<About />} /> |
| 213 | + <Route path="/contact" element={<Contact />} /> | |
| 227 | 214 | <Route path="*" element={<NotFound />} /> |
| 228 | 215 | </Routes> |
| 229 | 216 | </main> |
| 230 | − <Footer /> | |
| 217 | + {/* Pied de page commun Groupe KA (ka-ui) */} | |
| 218 | + <KaFooter siteId="fabri-ka" /> | |
| 231 | 219 | <BottomNav /> |
| 232 | 220 | </div> |
| 233 | 221 | </FavoritesProvider> |
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
+3 −0
@@ -2,6 +2,9 @@ import React from 'react' | ||
| 2 | 2 | import ReactDOM from 'react-dom/client' |
| 3 | 3 | import { BrowserRouter } from 'react-router-dom' |
| 4 | 4 | import App from './App' |
| 5 | +// Design system Groupe KA (ka-ui) — tokens + socle communs, importés AVANT | |
| 6 | +// le CSS local qui ne surcharge que l'accent terracotta + les classes métier. | |
| 7 | +import './ka/tokens.css' | |
| 5 | 8 | import './styles.css' |
| 6 | 9 | |
| 7 | 10 | ReactDOM.createRoot(document.getElementById('root')!).render( |
added
frontend/src/pages/Contact.tsx
+62 −0
@@ -0,0 +1,62 @@ | ||
| 1 | +// Auteur : Simon-Pierre Boucher — contact@spboucher.ai | |
| 2 | +// pages/Contact.tsx — page /contact commune Groupe KA : les 3 courriels de | |
| 3 | +// l'écosystème (source : ka/ecosystem.json), l'avertissement d'agrégateur, | |
| 4 | +// le lien vers le hub et la liste des sites du groupe. | |
| 5 | +import { useEffect } from 'react' | |
| 6 | +import eco from '../ka/ecosystem.json' | |
| 7 | + | |
| 8 | +export default function Contact() { | |
| 9 | + useEffect(() => { | |
| 10 | + document.title = 'Contact — Fabri-Ka — Un service Groupe KA' | |
| 11 | + }, []) | |
| 12 | + | |
| 13 | + return ( | |
| 14 | + <div className="page contact-page"> | |
| 15 | + <span className="kicker">Nous joindre</span> | |
| 16 | + <h1>Contact</h1> | |
| 17 | + <p className="contact-lede"> | |
| 18 | + Fabri-Ka est un service du <b>Groupe KA</b> — {eco.org.tagline} Les | |
| 19 | + coordonnées ci-dessous sont communes à toutes les plateformes de | |
| 20 | + l’écosystème. | |
| 21 | + </p> | |
| 22 | + | |
| 23 | + <div className="contact-cards"> | |
| 24 | + {eco.contacts.map((c) => ( | |
| 25 | + <article className="contact-card" key={c.email}> | |
| 26 | + <span className="klabel">{c.role}</span> | |
| 27 | + <a href={`mailto:${c.email}`}>{c.email}</a> | |
| 28 | + </article> | |
| 29 | + ))} | |
| 30 | + </div> | |
| 31 | + | |
| 32 | + <p className="contact-note"> | |
| 33 | + <b>{eco.org.disclaimer}</b> | |
| 34 | + </p> | |
| 35 | + | |
| 36 | + <a | |
| 37 | + className="contact-hub" | |
| 38 | + href={eco.hub.url} | |
| 39 | + target="_blank" | |
| 40 | + rel="noopener noreferrer" | |
| 41 | + > | |
| 42 | + Visiter le portail Groupe KA ↗ | |
| 43 | + </a> | |
| 44 | + | |
| 45 | + <h2>Les sites de l’écosystème</h2> | |
| 46 | + <ul className="contact-sites"> | |
| 47 | + {eco.sites.map((s) => ( | |
| 48 | + <li key={s.id}> | |
| 49 | + <a | |
| 50 | + href={`https://${s.domain}`} | |
| 51 | + target="_blank" | |
| 52 | + rel="noopener noreferrer" | |
| 53 | + > | |
| 54 | + <b>{s.wordmark}</b> | |
| 55 | + <span>{s.tagline}</span> | |
| 56 | + </a> | |
| 57 | + </li> | |
| 58 | + ))} | |
| 59 | + </ul> | |
| 60 | + </div> | |
| 61 | + ) | |
| 62 | +} | |
modified
frontend/src/styles.css
+272 −195
@@ -1,44 +1,44 @@ | ||
| 1 | 1 | /* ========================================================================== |
| 2 | − Fabri-Ka — identité famille KA « éditorial sharp » (cf. Food-Ka / Lou-Ka) | |
| 2 | + Fabri-Ka — surcouche locale du design system Groupe KA (ka-ui/tokens.css, | |
| 3 | + importé AVANT ce fichier dans main.tsx) « éditorial sharp » : | |
| 3 | 4 | · Typo display Space Grotesk / texte Inter / micro-étiquettes JetBrains Mono |
| 4 | 5 | · Signature : bordures encre + ombres décalées (néo-brutalisme raffiné) |
| 5 | − · Palette Fabri-Ka conservée : papier crème, encre chaude, terracotta | |
| 6 | − #C4532E en accent, pin #234438 en profond, miel #F2CF63 en surlignage | |
| 6 | + · ACCENT du site : TERRACOTTA #C4532E — seule surcharge de marque ; | |
| 7 | + tout le reste (papier, encre, lignes, ombres, typo) vient des tokens. | |
| 7 | 8 | ========================================================================== */ |
| 8 | 9 | |
| 9 | 10 | :root { |
| 10 | − --bg: #FAF6EE; | |
| 11 | − --ink: #1E1B16; | |
| 12 | − --accent: #C4532E; | |
| 13 | − --accent-dark: #A94525; | |
| 14 | − --pine: #234438; | |
| 15 | − --sand: #F1E9DD; | |
| 16 | − --border: #E4D9C8; | |
| 17 | − --white: #FFFFFF; | |
| 18 | − --muted: #6E6455; | |
| 19 | − | |
| 20 | − /* jetons famille KA (mêmes rôles que Food-Ka/Lou-Ka) */ | |
| 21 | − --line: rgba(30, 27, 22, 0.14); | |
| 22 | − --line-strong: rgba(30, 27, 22, 0.85); | |
| 23 | − --highlight: #F2CF63; | |
| 24 | − --highlight-soft: #F9F0D2; | |
| 25 | − | |
| 26 | − --origin-a: #234438; | |
| 27 | − --origin-b: #C4532E; | |
| 28 | − --origin-c: #8A6D3B; | |
| 29 | − --origin-d: #5B5B8A; | |
| 30 | − --origin-e: #999999; | |
| 31 | − | |
| 32 | − --font-display: 'Space Grotesk', system-ui, sans-serif; | |
| 33 | − --font-body: 'Inter', system-ui, -apple-system, sans-serif; | |
| 34 | − --font-mono: 'JetBrains Mono', ui-monospace, monospace; | |
| 35 | − | |
| 36 | − --radius: 6px; | |
| 37 | − --radius-lg: 10px; | |
| 38 | − --shadow-hover: 0 6px 18px rgba(30, 27, 22, 0.08); | |
| 39 | − --shadow-off: 6px 6px 0 var(--ink); | |
| 40 | − --shadow-off-soft: 8px 8px 0 rgba(30, 27, 22, 0.08); | |
| 41 | − --shadow-sheet: 0 -12px 40px rgba(30, 27, 22, 0.18); | |
| 11 | + /* ---- ACCENT Fabri-Ka (terracotta) — surcharge des tokens ka-ui ---- */ | |
| 12 | + --accent: #c4532e; | |
| 13 | + --accent-soft: #f7e3da; | |
| 14 | + --accent-deep: #a94525; | |
| 15 | + --on-accent: #ffffff; | |
| 16 | + --accent-dark: var(--accent-deep); /* alias historique local */ | |
| 17 | + --lime: var(--accent); | |
| 18 | + --lime-soft: var(--accent-soft); | |
| 19 | + | |
| 20 | + /* ---- Alias locaux → tokens communs (classes métier conservées) ---- */ | |
| 21 | + --bg: var(--paper); | |
| 22 | + --sand: var(--surface-2); | |
| 23 | + --border: var(--line); | |
| 24 | + --white: var(--surface); | |
| 25 | + --muted: var(--ink-2); | |
| 26 | + --pine: var(--green); | |
| 27 | + --highlight: var(--amber); | |
| 28 | + --highlight-soft: var(--amber-soft); | |
| 29 | + | |
| 30 | + /* badges d'origine des produits (métier Fabri-Ka) */ | |
| 31 | + --origin-a: var(--green); | |
| 32 | + --origin-b: var(--accent); | |
| 33 | + --origin-c: #8a6d3b; | |
| 34 | + --origin-d: #5b5b8a; | |
| 35 | + --origin-e: var(--ink-3); | |
| 36 | + | |
| 37 | + /* géométrie / mise en page locales */ | |
| 38 | + --radius: var(--r-ctl); | |
| 39 | + --radius-lg: var(--r-card); | |
| 40 | + --shadow-hover: 0 6px 18px rgba(20, 24, 20, 0.08); | |
| 41 | + --shadow-sheet: 0 -12px 40px rgba(20, 24, 20, 0.18); | |
| 42 | 42 | --maxw: 1280px; |
| 43 | 43 | |
| 44 | 44 | --header-h: 56px; |
@@ -46,33 +46,17 @@ | ||
| 46 | 46 | --safe-b: env(safe-area-inset-bottom, 0px); |
| 47 | 47 | |
| 48 | 48 | --focus-ring: 0 0 0 3px rgba(196, 83, 46, 0.28); |
| 49 | −} | |
| 50 | − | |
| 51 | −* { | |
| 52 | − box-sizing: border-box; | |
| 53 | −} | |
| 54 | 49 | |
| 55 | −html { | |
| 56 | − scroll-behavior: smooth; | |
| 57 | − -webkit-text-size-adjust: 100%; | |
| 50 | + /* marque KA ID (lime du hub — volontairement fixe) */ | |
| 51 | + --ka-ink: var(--ink); | |
| 52 | + --ka-lime: #d9f26b; | |
| 58 | 53 | } |
| 59 | 54 | |
| 55 | +/* socle (*, html, body, img, ::selection…) : fourni par ka/tokens.css */ | |
| 60 | 56 | body { |
| 61 | − margin: 0; | |
| 62 | − background: var(--bg); | |
| 63 | − color: var(--ink); | |
| 64 | − font-family: var(--font-body); | |
| 65 | − font-size: 15px; | |
| 66 | − line-height: 1.55; | |
| 67 | − -webkit-font-smoothing: antialiased; | |
| 68 | 57 | overscroll-behavior-y: none; |
| 69 | 58 | } |
| 70 | 59 | |
| 71 | −img { | |
| 72 | − max-width: 100%; | |
| 73 | − display: block; | |
| 74 | −} | |
| 75 | − | |
| 76 | 60 | a { |
| 77 | 61 | color: var(--accent); |
| 78 | 62 | text-decoration: none; |
@@ -80,10 +64,10 @@ a { | ||
| 80 | 64 | |
| 81 | 65 | h1, h2, h3 { |
| 82 | 66 | font-family: var(--font-display); |
| 83 | − font-weight: 900; | |
| 67 | + font-weight: 700; | |
| 84 | 68 | font-optical-sizing: auto; |
| 85 | 69 | line-height: 1.12; |
| 86 | − letter-spacing: -0.015em; | |
| 70 | + letter-spacing: -0.03em; | |
| 87 | 71 | margin: 0 0 0.5em; |
| 88 | 72 | color: var(--ink); |
| 89 | 73 | } |
@@ -189,7 +173,7 @@ button:focus-visible, | ||
| 189 | 173 | position: sticky; |
| 190 | 174 | top: 0; |
| 191 | 175 | z-index: 60; |
| 192 | − background: rgba(250, 246, 240, 0.94); | |
| 176 | + background: rgba(245, 243, 238, 0.94); | |
| 193 | 177 | backdrop-filter: blur(12px); |
| 194 | 178 | -webkit-backdrop-filter: blur(12px); |
| 195 | 179 | border-bottom: 1px solid var(--border); |
@@ -208,7 +192,7 @@ button:focus-visible, | ||
| 208 | 192 | |
| 209 | 193 | .logo { |
| 210 | 194 | font-family: var(--font-display); |
| 211 | − font-weight: 900; | |
| 195 | + font-weight: 700; | |
| 212 | 196 | font-size: 1.45rem; |
| 213 | 197 | color: var(--ink); |
| 214 | 198 | letter-spacing: -0.02em; |
@@ -357,7 +341,7 @@ button:focus-visible, | ||
| 357 | 341 | z-index: 70; |
| 358 | 342 | display: grid; |
| 359 | 343 | grid-template-columns: repeat(4, 1fr); |
| 360 | − background: rgba(250, 246, 240, 0.96); | |
| 344 | + background: rgba(245, 243, 238, 0.96); | |
| 361 | 345 | backdrop-filter: blur(14px); |
| 362 | 346 | -webkit-backdrop-filter: blur(14px); |
| 363 | 347 | border-top: 1px solid var(--border); |
@@ -484,84 +468,17 @@ button:focus-visible, | ||
| 484 | 468 | } |
| 485 | 469 | |
| 486 | 470 | /* -------------------------------------------------------------------------- |
| 487 | − Footer | |
| 471 | + Footer — KaFooter (ka-ui) : styles .ka-footer dans ka/tokens.css. | |
| 472 | + Localement : dégager la barre d'onglets mobile (BottomNav fixe en bas). | |
| 488 | 473 | -------------------------------------------------------------------------- */ |
| 489 | 474 | |
| 490 | −.site-footer { | |
| 491 | − background: var(--sand); | |
| 492 | − border-top: 1px solid var(--border); | |
| 493 | − padding: 2.25rem 1rem 1.25rem; | |
| 494 | − margin-top: 2.5rem; | |
| 495 | − /* clear the mobile tab bar */ | |
| 496 | − padding-bottom: calc(1.25rem + var(--tabbar-h) + var(--safe-b)); | |
| 497 | −} | |
| 498 | − | |
| 499 | −.site-footer-inner { | |
| 500 | − max-width: var(--maxw); | |
| 501 | − margin: 0 auto; | |
| 502 | − display: grid; | |
| 503 | − grid-template-columns: 1fr; | |
| 504 | − gap: 1.75rem; | |
| 505 | −} | |
| 506 | − | |
| 507 | −.logo-footer { | |
| 508 | − font-size: 1.3rem; | |
| 509 | −} | |
| 510 | − | |
| 511 | −.footer-brand p { | |
| 512 | − margin: 0.4rem 0 0; | |
| 513 | − color: var(--muted); | |
| 514 | − font-size: 0.92rem; | |
| 515 | −} | |
| 516 | − | |
| 517 | −.footer-col { | |
| 518 | − display: flex; | |
| 519 | − flex-direction: column; | |
| 520 | − gap: 0.45rem; | |
| 521 | − align-items: flex-start; | |
| 522 | −} | |
| 523 | − | |
| 524 | −.footer-col-title { | |
| 525 | − font-size: 0.72rem; | |
| 526 | − font-weight: 700; | |
| 527 | − text-transform: uppercase; | |
| 528 | − letter-spacing: 0.16em; | |
| 529 | − color: var(--muted); | |
| 530 | − margin-bottom: 0.2rem; | |
| 531 | −} | |
| 532 | − | |
| 533 | −.footer-col a { | |
| 534 | − color: var(--ink); | |
| 535 | − font-weight: 500; | |
| 536 | − font-size: 0.95rem; | |
| 537 | − padding: 0.15rem 0; | |
| 538 | −} | |
| 539 | − | |
| 540 | −@media (hover: hover) { | |
| 541 | − .footer-col a:hover { | |
| 542 | − color: var(--accent); | |
| 543 | − text-decoration: none; | |
| 544 | − } | |
| 545 | −} | |
| 546 | − | |
| 547 | −.footer-legal { | |
| 548 | − max-width: var(--maxw); | |
| 549 | − margin: 1.75rem auto 0; | |
| 550 | − padding-top: 1.1rem; | |
| 551 | − border-top: 1px solid var(--border); | |
| 552 | − color: var(--muted); | |
| 553 | − font-size: 0.8rem; | |
| 554 | − line-height: 1.5; | |
| 475 | +.ka-footer { | |
| 476 | + padding-bottom: calc(44px + var(--tabbar-h) + var(--safe-b)); | |
| 555 | 477 | } |
| 556 | 478 | |
| 557 | 479 | @media (min-width: 768px) { |
| 558 | − .site-footer { | |
| 559 | − padding: 2.75rem 1.25rem 1.5rem; | |
| 560 | − } | |
| 561 | − | |
| 562 | − .site-footer-inner { | |
| 563 | − grid-template-columns: 2fr 1fr 1fr; | |
| 564 | − gap: 2rem; | |
| 480 | + .ka-footer { | |
| 481 | + padding-bottom: 44px; | |
| 565 | 482 | } |
| 566 | 483 | } |
| 567 | 484 | |
@@ -596,7 +513,7 @@ button:focus-visible, | ||
| 596 | 513 | |
| 597 | 514 | .hero-title { |
| 598 | 515 | font-size: clamp(2.35rem, 8.5vw, 4.75rem); |
| 599 | − font-weight: 900; | |
| 516 | + font-weight: 700; | |
| 600 | 517 | letter-spacing: -0.03em; |
| 601 | 518 | line-height: 1.04; |
| 602 | 519 | margin-bottom: 0.55em; |
@@ -648,7 +565,7 @@ button:focus-visible, | ||
| 648 | 565 | |
| 649 | 566 | .stat-number { |
| 650 | 567 | font-family: var(--font-display); |
| 651 | − font-weight: 900; | |
| 568 | + font-weight: 700; | |
| 652 | 569 | font-optical-sizing: auto; |
| 653 | 570 | font-size: clamp(1.9rem, 5vw, 2.5rem); |
| 654 | 571 | color: var(--pine); |
@@ -863,7 +780,7 @@ a.card:active { | ||
| 863 | 780 | @media (hover: hover) { |
| 864 | 781 | a.card:hover { |
| 865 | 782 | text-decoration: none; |
| 866 | − border-color: #D3C4AC; | |
| 783 | + border-color: rgba(20, 24, 20, 0.3); | |
| 867 | 784 | box-shadow: var(--shadow-hover); |
| 868 | 785 | } |
| 869 | 786 | } |
@@ -1080,7 +997,7 @@ a.card:active { | ||
| 1080 | 997 | color: var(--white); |
| 1081 | 998 | font-size: 0.78rem; |
| 1082 | 999 | line-height: 1; |
| 1083 | − box-shadow: 0 1px 4px rgba(30, 27, 22, 0.18); | |
| 1000 | + box-shadow: 0 1px 4px rgba(20, 24, 20, 0.18); | |
| 1084 | 1001 | } |
| 1085 | 1002 | |
| 1086 | 1003 | .origin-badge-letter { |
@@ -1208,7 +1125,7 @@ a.card:active { | ||
| 1208 | 1125 | z-index: 40; |
| 1209 | 1126 | margin: -0.5rem -1rem 0.9rem; |
| 1210 | 1127 | padding: 0.65rem 1rem; |
| 1211 | − background: rgba(250, 246, 240, 0.94); | |
| 1128 | + background: rgba(245, 243, 238, 0.94); | |
| 1212 | 1129 | backdrop-filter: blur(12px); |
| 1213 | 1130 | -webkit-backdrop-filter: blur(12px); |
| 1214 | 1131 | transition: padding 0.2s ease, box-shadow 0.2s ease; |
@@ -1312,7 +1229,7 @@ a.card:active { | ||
| 1312 | 1229 | |
| 1313 | 1230 | .filters-title { |
| 1314 | 1231 | font-family: var(--font-display); |
| 1315 | − font-weight: 900; | |
| 1232 | + font-weight: 700; | |
| 1316 | 1233 | font-size: 1.15rem; |
| 1317 | 1234 | } |
| 1318 | 1235 | |
@@ -1456,7 +1373,7 @@ a.card:active { | ||
| 1456 | 1373 | font-weight: 600; |
| 1457 | 1374 | font-size: 0.95rem; |
| 1458 | 1375 | cursor: pointer; |
| 1459 | − box-shadow: 0 8px 22px rgba(30, 27, 22, 0.3); | |
| 1376 | + box-shadow: 0 8px 22px rgba(20, 24, 20, 0.3); | |
| 1460 | 1377 | transition: transform 0.12s ease; |
| 1461 | 1378 | } |
| 1462 | 1379 | |
@@ -1501,7 +1418,7 @@ a.card:active { | ||
| 1501 | 1418 | .sheet-backdrop { |
| 1502 | 1419 | position: absolute; |
| 1503 | 1420 | inset: 0; |
| 1504 | − background: rgba(30, 27, 22, 0.45); | |
| 1421 | + background: rgba(20, 24, 20, 0.45); | |
| 1505 | 1422 | animation: backdrop-in 0.2s ease; |
| 1506 | 1423 | } |
| 1507 | 1424 | |
@@ -1924,7 +1841,7 @@ a.card:active { | ||
| 1924 | 1841 | align-items: center; |
| 1925 | 1842 | gap: 0.75rem; |
| 1926 | 1843 | padding: 0.6rem 1rem; |
| 1927 | − background: rgba(250, 246, 240, 0.97); | |
| 1844 | + background: rgba(245, 243, 238, 0.97); | |
| 1928 | 1845 | backdrop-filter: blur(12px); |
| 1929 | 1846 | -webkit-backdrop-filter: blur(12px); |
| 1930 | 1847 | border-top: 1px solid var(--border); |
@@ -2130,7 +2047,7 @@ a.card:active { | ||
| 2130 | 2047 | border: none; |
| 2131 | 2048 | color: var(--white); |
| 2132 | 2049 | font-family: var(--font-display); |
| 2133 | − font-weight: 900; | |
| 2050 | + font-weight: 700; | |
| 2134 | 2051 | font-optical-sizing: auto; |
| 2135 | 2052 | line-height: 1; |
| 2136 | 2053 | user-select: none; |
@@ -2149,7 +2066,7 @@ a.card:active { | ||
| 2149 | 2066 | } |
| 2150 | 2067 | |
| 2151 | 2068 | .store-logo-ring { |
| 2152 | − box-shadow: 0 0 0 3px var(--white), 0 4px 14px rgba(30, 27, 22, 0.18); | |
| 2069 | + box-shadow: 0 0 0 3px var(--white), 0 4px 14px rgba(20, 24, 20, 0.18); | |
| 2153 | 2070 | } |
| 2154 | 2071 | |
| 2155 | 2072 | /* -------------------------------------------------------------------------- |
@@ -2180,7 +2097,7 @@ a.card:active { | ||
| 2180 | 2097 | position: relative; |
| 2181 | 2098 | height: 118px; |
| 2182 | 2099 | overflow: hidden; |
| 2183 | − background: linear-gradient(120deg, var(--sand), #E8DBC4 55%, var(--sand)); | |
| 2100 | + background: linear-gradient(120deg, var(--sand), var(--line) 55%, var(--sand)); | |
| 2184 | 2101 | } |
| 2185 | 2102 | |
| 2186 | 2103 | .store-hero-cover-img { |
@@ -2199,8 +2116,8 @@ a.card:active { | ||
| 2199 | 2116 | |
| 2200 | 2117 | .store-hero-covered .store-hero-scrim { |
| 2201 | 2118 | background: linear-gradient( |
| 2202 | − rgba(30, 27, 22, 0.24), | |
| 2203 | − rgba(30, 27, 22, 0.48) | |
| 2119 | + rgba(20, 24, 20, 0.24), | |
| 2120 | + rgba(20, 24, 20, 0.48) | |
| 2204 | 2121 | ); |
| 2205 | 2122 | } |
| 2206 | 2123 | |
@@ -2374,7 +2291,7 @@ a.card:active { | ||
| 2374 | 2291 | |
| 2375 | 2292 | .store-stat-value { |
| 2376 | 2293 | font-family: var(--font-display); |
| 2377 | − font-weight: 900; | |
| 2294 | + font-weight: 700; | |
| 2378 | 2295 | font-optical-sizing: auto; |
| 2379 | 2296 | font-size: 1.55rem; |
| 2380 | 2297 | color: var(--pine); |
@@ -2474,7 +2391,7 @@ a.card:active { | ||
| 2474 | 2391 | align-items: center; |
| 2475 | 2392 | gap: 0.75rem; |
| 2476 | 2393 | padding: 0.6rem 1rem; |
| 2477 | − background: rgba(250, 246, 240, 0.97); | |
| 2394 | + background: rgba(245, 243, 238, 0.97); | |
| 2478 | 2395 | backdrop-filter: blur(12px); |
| 2479 | 2396 | -webkit-backdrop-filter: blur(12px); |
| 2480 | 2397 | border-top: 1px solid var(--border); |
@@ -2679,10 +2596,6 @@ a.card:active { | ||
| 2679 | 2596 | .page { |
| 2680 | 2597 | padding: 2rem 1.25rem 4rem; |
| 2681 | 2598 | } |
| 2682 | − | |
| 2683 | − .site-footer { | |
| 2684 | − padding-bottom: 1.5rem; | |
| 2685 | − } | |
| 2686 | 2599 | } |
| 2687 | 2600 | |
| 2688 | 2601 | /* -------------------------------------------------------------------------- |
@@ -2763,7 +2676,7 @@ a.card:active { | ||
| 2763 | 2676 | |
| 2764 | 2677 | .stats-kpi-value { |
| 2765 | 2678 | font-family: var(--font-display); |
| 2766 | − font-weight: 900; | |
| 2679 | + font-weight: 700; | |
| 2767 | 2680 | font-optical-sizing: auto; |
| 2768 | 2681 | font-size: clamp(1.35rem, 4vw, 1.7rem); |
| 2769 | 2682 | color: var(--pine); |
@@ -3203,7 +3116,7 @@ a.card:active { | ||
| 3203 | 3116 | .stats-rank { |
| 3204 | 3117 | flex: 0 0 1.6rem; |
| 3205 | 3118 | font-family: var(--font-display); |
| 3206 | − font-weight: 900; | |
| 3119 | + font-weight: 700; | |
| 3207 | 3120 | font-optical-sizing: auto; |
| 3208 | 3121 | color: var(--accent); |
| 3209 | 3122 | font-size: 0.95rem; |
@@ -3604,12 +3517,6 @@ a.card:active { | ||
| 3604 | 3517 | Compte — connexion KA ID (SSO Groupe KA) : bouton d'en-tête + menu |
| 3605 | 3518 | -------------------------------------------------------------------------- */ |
| 3606 | 3519 | |
| 3607 | −:root { | |
| 3608 | − --ka-ink: #141814; | |
| 3609 | − --ka-lime: #d9f26b; | |
| 3610 | − --font-mono: 'SF Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; | |
| 3611 | −} | |
| 3612 | − | |
| 3613 | 3520 | .site-header-account { |
| 3614 | 3521 | margin-left: auto; |
| 3615 | 3522 | position: relative; |
@@ -3629,7 +3536,7 @@ a.card:active { | ||
| 3629 | 3536 | align-items: center; |
| 3630 | 3537 | gap: 0.5rem; |
| 3631 | 3538 | padding: 0.45rem 0.85rem; |
| 3632 | − min-height: 38px; | |
| 3539 | + min-height: var(--touch, 44px); | |
| 3633 | 3540 | border: 1px solid var(--border); |
| 3634 | 3541 | border-radius: 999px; |
| 3635 | 3542 | background: var(--white); |
@@ -3663,8 +3570,8 @@ a.card:active { | ||
| 3663 | 3570 | } |
| 3664 | 3571 | |
| 3665 | 3572 | .account-btn { |
| 3666 | − width: 36px; | |
| 3667 | − height: 36px; | |
| 3573 | + width: 44px; | |
| 3574 | + height: 44px; | |
| 3668 | 3575 | border-radius: 999px; |
| 3669 | 3576 | border: 2px solid var(--accent); |
| 3670 | 3577 | background: var(--sand); |
@@ -3685,7 +3592,7 @@ a.card:active { | ||
| 3685 | 3592 | |
| 3686 | 3593 | .account-initial { |
| 3687 | 3594 | font-family: var(--font-display); |
| 3688 | − font-weight: 900; | |
| 3595 | + font-weight: 700; | |
| 3689 | 3596 | font-size: 1rem; |
| 3690 | 3597 | color: var(--accent); |
| 3691 | 3598 | } |
@@ -3797,11 +3704,11 @@ a.card:active { | ||
| 3797 | 3704 | max-width: 520px; |
| 3798 | 3705 | overflow: hidden; |
| 3799 | 3706 | background: var(--ka-ink); |
| 3800 | − color: #f5f3ee; | |
| 3707 | + color: var(--paper); | |
| 3801 | 3708 | border: 2px solid var(--ka-ink); |
| 3802 | 3709 | border-radius: 16px; |
| 3803 | 3710 | padding: 24px 26px 0; |
| 3804 | − box-shadow: 10px 10px 0 rgba(30, 27, 22, 0.15); | |
| 3711 | + box-shadow: 10px 10px 0 rgba(20, 24, 20, 0.15); | |
| 3805 | 3712 | } |
| 3806 | 3713 | |
| 3807 | 3714 | .pc-watermark { |
@@ -3810,7 +3717,7 @@ a.card:active { | ||
| 3810 | 3717 | top: -34px; |
| 3811 | 3718 | pointer-events: none; |
| 3812 | 3719 | font-family: var(--font-display); |
| 3813 | − font-weight: 900; | |
| 3720 | + font-weight: 700; | |
| 3814 | 3721 | font-size: 170px; |
| 3815 | 3722 | letter-spacing: -0.06em; |
| 3816 | 3723 | color: rgba(217, 242, 107, 0.07); |
@@ -3828,7 +3735,7 @@ a.card:active { | ||
| 3828 | 3735 | |
| 3829 | 3736 | .pc-brand { |
| 3830 | 3737 | font-family: var(--font-display); |
| 3831 | − font-weight: 900; | |
| 3738 | + font-weight: 700; | |
| 3832 | 3739 | font-size: 24px; |
| 3833 | 3740 | letter-spacing: -0.02em; |
| 3834 | 3741 | } |
@@ -4134,7 +4041,7 @@ a.card:active { | ||
| 4134 | 4041 | margin: 1.6em 0 0.5em; |
| 4135 | 4042 | } |
| 4136 | 4043 | .seo-stats b { |
| 4137 | − color: #c4532e; | |
| 4044 | + color: var(--accent); | |
| 4138 | 4045 | } |
| 4139 | 4046 | .seo-links { |
| 4140 | 4047 | list-style: none; |
@@ -4230,7 +4137,7 @@ h1, h2, h3, h4 { | ||
| 4230 | 4137 | |
| 4231 | 4138 | /* ---- header : trait d'encre + chip ·Ka rotée --------------------------- */ |
| 4232 | 4139 | .site-header { |
| 4233 | − background: rgba(250, 246, 238, 0.88); | |
| 4140 | + background: rgba(245, 243, 238, 0.88); | |
| 4234 | 4141 | border-bottom: 2px solid var(--ink); |
| 4235 | 4142 | } |
| 4236 | 4143 | .logo { |
@@ -4296,8 +4203,7 @@ h1, h2, h3, h4 { | ||
| 4296 | 4203 | |
| 4297 | 4204 | /* ---- kicker / entêtes de section : micro-mono -------------------------- */ |
| 4298 | 4205 | .hero-eyebrow, |
| 4299 | −.filter-label, | |
| 4300 | −.footer-col-title { | |
| 4206 | +.filter-label { | |
| 4301 | 4207 | font-family: var(--font-mono); |
| 4302 | 4208 | font-size: 11.5px; |
| 4303 | 4209 | font-weight: 500; |
@@ -4336,7 +4242,7 @@ a.card:active { border-color: var(--ink); } | ||
| 4336 | 4242 | .chip:hover { |
| 4337 | 4243 | border-color: var(--ink); |
| 4338 | 4244 | background: var(--white); |
| 4339 | − box-shadow: 3px 3px 0 rgba(30, 27, 22, 0.12); | |
| 4245 | + box-shadow: 3px 3px 0 rgba(20, 24, 20, 0.12); | |
| 4340 | 4246 | transform: translate(-1px, -1px); |
| 4341 | 4247 | } |
| 4342 | 4248 | } |
@@ -4393,7 +4299,7 @@ a.card:active { border-color: var(--ink); } | ||
| 4393 | 4299 | } |
| 4394 | 4300 | @media (hover: hover) { |
| 4395 | 4301 | .btn-secondary:hover { |
| 4396 | − box-shadow: 3px 3px 0 rgba(30, 27, 22, 0.15); | |
| 4302 | + box-shadow: 3px 3px 0 rgba(20, 24, 20, 0.15); | |
| 4397 | 4303 | transform: translate(-1px, -1px); |
| 4398 | 4304 | } |
| 4399 | 4305 | } |
@@ -4411,32 +4317,203 @@ a.card:active { border-color: var(--ink); } | ||
| 4411 | 4317 | |
| 4412 | 4318 | /* ---- barre d'onglets mobile ---------------------------------------------- */ |
| 4413 | 4319 | .bottom-nav { |
| 4414 | − background: rgba(250, 246, 238, 0.96); | |
| 4320 | + background: rgba(245, 243, 238, 0.96); | |
| 4415 | 4321 | border-top: 2px solid var(--ink); |
| 4416 | 4322 | } |
| 4417 | 4323 | |
| 4418 | −/* ---- footer : bloc pin profond ------------------------------------------- */ | |
| 4419 | −.site-footer { | |
| 4420 | − background: var(--pine); | |
| 4421 | − border-top: 2px solid var(--ink); | |
| 4422 | − color: rgba(250, 246, 238, 0.92); | |
| 4423 | −} | |
| 4424 | −.site-footer .logo, | |
| 4425 | −.site-footer a, | |
| 4426 | −.footer-brand p, | |
| 4427 | −.footer-legal { | |
| 4428 | − color: rgba(250, 246, 238, 0.92); | |
| 4429 | −} | |
| 4430 | −.site-footer .footer-col-title { color: var(--highlight); } | |
| 4431 | −.site-footer a:hover { color: var(--highlight); } | |
| 4432 | −.site-footer .logo-ka { | |
| 4433 | − background: var(--highlight); | |
| 4434 | − color: var(--pine); | |
| 4435 | −} | |
| 4436 | − | |
| 4437 | 4324 | /* ---- contenu SSR (seo.py) aligné sur le thème ---------------------------- */ |
| 4438 | 4325 | .seo-page h1, .seo-page h2 { font-family: var(--font-display); } |
| 4439 | 4326 | .seo-stats b { color: var(--accent); font-family: var(--font-mono); } |
| 4440 | 4327 | .seo-card a { font-weight: 600; } |
| 4441 | 4328 | .seo-card-price { font-family: var(--font-mono); color: var(--accent); } |
| 4442 | 4329 | .seo-card-media img { border: 1.5px solid var(--line); } |
| 4330 | + | |
| 4331 | +/* -------------------------------------------------------------------------- | |
| 4332 | + Groupe KA — badge d'en-tête + « Créer un compte » (KA ID) | |
| 4333 | + -------------------------------------------------------------------------- */ | |
| 4334 | + | |
| 4335 | +.site-header .gk-badge { | |
| 4336 | + flex: none; | |
| 4337 | +} | |
| 4338 | + | |
| 4339 | +@media (max-width: 479px) { | |
| 4340 | + .site-header .gk-badge { | |
| 4341 | + display: none; | |
| 4342 | + } | |
| 4343 | +} | |
| 4344 | + | |
| 4345 | +.header-brand { | |
| 4346 | + display: flex; | |
| 4347 | + align-items: center; | |
| 4348 | + gap: 0.7rem; | |
| 4349 | + min-width: 0; | |
| 4350 | +} | |
| 4351 | + | |
| 4352 | +.signup-link { | |
| 4353 | + display: none; | |
| 4354 | + align-items: center; | |
| 4355 | + min-height: var(--touch, 44px); | |
| 4356 | + padding: 0 0.55rem; | |
| 4357 | + font-size: 0.85rem; | |
| 4358 | + font-weight: 500; | |
| 4359 | + color: var(--muted); | |
| 4360 | + white-space: nowrap; | |
| 4361 | +} | |
| 4362 | + | |
| 4363 | +@media (min-width: 768px) { | |
| 4364 | + .signup-link { | |
| 4365 | + display: inline-flex; | |
| 4366 | + } | |
| 4367 | +} | |
| 4368 | + | |
| 4369 | +/* -------------------------------------------------------------------------- | |
| 4370 | + Page /contact — coordonnées de l'écosystème (ka/ecosystem.json) | |
| 4371 | + -------------------------------------------------------------------------- */ | |
| 4372 | + | |
| 4373 | +.contact-page .contact-lede { | |
| 4374 | + color: var(--muted); | |
| 4375 | + max-width: 620px; | |
| 4376 | + margin: 0.35rem 0 1.6rem; | |
| 4377 | +} | |
| 4378 | + | |
| 4379 | +.contact-cards { | |
| 4380 | + display: grid; | |
| 4381 | + grid-template-columns: 1fr; | |
| 4382 | + gap: 0.9rem; | |
| 4383 | + margin-bottom: 1.8rem; | |
| 4384 | +} | |
| 4385 | + | |
| 4386 | +@media (min-width: 768px) { | |
| 4387 | + .contact-cards { | |
| 4388 | + grid-template-columns: repeat(3, 1fr); | |
| 4389 | + } | |
| 4390 | +} | |
| 4391 | + | |
| 4392 | +.contact-card { | |
| 4393 | + background: var(--surface); | |
| 4394 | + border: 1.5px solid var(--line-strong); | |
| 4395 | + border-radius: var(--r-card); | |
| 4396 | + box-shadow: var(--shadow-off-mid); | |
| 4397 | + padding: 1.1rem 1.15rem 1.2rem; | |
| 4398 | + display: flex; | |
| 4399 | + flex-direction: column; | |
| 4400 | + gap: 0.45rem; | |
| 4401 | + min-width: 0; | |
| 4402 | +} | |
| 4403 | + | |
| 4404 | +.contact-card .klabel { | |
| 4405 | + color: var(--accent); | |
| 4406 | +} | |
| 4407 | + | |
| 4408 | +.contact-card a { | |
| 4409 | + font-family: var(--font-mono); | |
| 4410 | + font-weight: 700; | |
| 4411 | + font-size: 0.92rem; | |
| 4412 | + color: var(--ink); | |
| 4413 | + word-break: break-all; | |
| 4414 | + display: inline-flex; | |
| 4415 | + align-items: center; | |
| 4416 | + min-height: var(--touch, 44px); | |
| 4417 | +} | |
| 4418 | + | |
| 4419 | +@media (hover: hover) { | |
| 4420 | + .contact-card a:hover { | |
| 4421 | + color: var(--accent); | |
| 4422 | + } | |
| 4423 | +} | |
| 4424 | + | |
| 4425 | +.contact-card p { | |
| 4426 | + margin: 0; | |
| 4427 | + color: var(--muted); | |
| 4428 | + font-size: 0.9rem; | |
| 4429 | +} | |
| 4430 | + | |
| 4431 | +.contact-note { | |
| 4432 | + max-width: 720px; | |
| 4433 | + border-left: 3px solid var(--accent); | |
| 4434 | + background: var(--accent-soft); | |
| 4435 | + padding: 0.9rem 1rem; | |
| 4436 | + border-radius: 0 var(--r-ctl) var(--r-ctl) 0; | |
| 4437 | + margin-bottom: 1.8rem; | |
| 4438 | + font-size: 0.92rem; | |
| 4439 | +} | |
| 4440 | + | |
| 4441 | +.contact-hub { | |
| 4442 | + display: inline-flex; | |
| 4443 | + align-items: center; | |
| 4444 | + gap: 0.5rem; | |
| 4445 | + min-height: var(--touch, 44px); | |
| 4446 | + padding: 0 1.1rem; | |
| 4447 | + border: 1.5px solid var(--ink); | |
| 4448 | + border-radius: var(--r-pill); | |
| 4449 | + background: var(--accent); | |
| 4450 | + color: var(--on-accent); | |
| 4451 | + font-weight: 600; | |
| 4452 | + box-shadow: 3px 3px 0 var(--ink); | |
| 4453 | + margin-bottom: 2.2rem; | |
| 4454 | +} | |
| 4455 | + | |
| 4456 | +.contact-hub:active { | |
| 4457 | + transform: translate(2px, 2px); | |
| 4458 | + box-shadow: 1px 1px 0 var(--ink); | |
| 4459 | +} | |
| 4460 | + | |
| 4461 | +@media (hover: hover) { | |
| 4462 | + .contact-hub:hover { | |
| 4463 | + text-decoration: none; | |
| 4464 | + background: var(--accent-deep); | |
| 4465 | + } | |
| 4466 | +} | |
| 4467 | + | |
| 4468 | +.contact-sites { | |
| 4469 | + list-style: none; | |
| 4470 | + margin: 0.8rem 0 0; | |
| 4471 | + padding: 0; | |
| 4472 | + display: grid; | |
| 4473 | + grid-template-columns: 1fr; | |
| 4474 | + gap: 0.5rem; | |
| 4475 | +} | |
| 4476 | + | |
| 4477 | +@media (min-width: 480px) { | |
| 4478 | + .contact-sites { | |
| 4479 | + grid-template-columns: repeat(2, 1fr); | |
| 4480 | + } | |
| 4481 | +} | |
| 4482 | + | |
| 4483 | +@media (min-width: 1024px) { | |
| 4484 | + .contact-sites { | |
| 4485 | + grid-template-columns: repeat(3, 1fr); | |
| 4486 | + } | |
| 4487 | +} | |
| 4488 | + | |
| 4489 | +.contact-sites a { | |
| 4490 | + display: flex; | |
| 4491 | + align-items: baseline; | |
| 4492 | + gap: 0.55rem; | |
| 4493 | + min-height: var(--touch, 44px); | |
| 4494 | + padding: 0.55rem 0.8rem; | |
| 4495 | + border: 1px solid var(--line); | |
| 4496 | + border-radius: var(--r-ctl); | |
| 4497 | + background: var(--surface); | |
| 4498 | + color: var(--ink); | |
| 4499 | +} | |
| 4500 | + | |
| 4501 | +@media (hover: hover) { | |
| 4502 | + .contact-sites a:hover { | |
| 4503 | + text-decoration: none; | |
| 4504 | + border-color: var(--line-strong); | |
| 4505 | + box-shadow: var(--shadow-off-mid); | |
| 4506 | + } | |
| 4507 | +} | |
| 4508 | + | |
| 4509 | +.contact-sites b { | |
| 4510 | + font-family: var(--font-display); | |
| 4511 | + font-size: 0.95rem; | |
| 4512 | + white-space: nowrap; | |
| 4513 | +} | |
| 4514 | + | |
| 4515 | +.contact-sites span { | |
| 4516 | + color: var(--muted); | |
| 4517 | + font-size: 0.82rem; | |
| 4518 | + min-width: 0; | |
| 4519 | +} | |
modified
frontend/tsconfig.app.json
+1 −0
@@ -10,6 +10,7 @@ | ||
| 10 | 10 | |
| 11 | 11 | /* Bundler mode */ |
| 12 | 12 | "moduleResolution": "bundler", |
| 13 | + "resolveJsonModule": true, | |
| 13 | 14 | "allowImportingTsExtensions": true, |
| 14 | 15 | "isolatedModules": true, |
| 15 | 16 | "moduleDetection": "force", |
| 16 | 17 | |