SPB Git forge

spb/job-ka

Public
229commits 1branches 0releases
38.1 MBsize
maindefault branch
2 h agolast push
HTML 82.1% Python 14.6% TypeScript 1.9% CSS 1% JavaScript 0.5%

feat(doc): page documentation /doc (guide + captures) + lien footer + PDF téléchargeable

Simon-Pierre Boucher committed 1 mo ago (Aug 24, 2026) parent a8b1668

8 changed files +193 −1

added frontend/public/doc/img/etape1.png +0 −0

Binary file not shown.

added frontend/public/doc/img/etape2.png +0 −0

Binary file not shown.

added frontend/public/doc/img/etape3.png +0 −0

Binary file not shown.

added frontend/public/doc/img/etape4.png +0 −0

Binary file not shown.

added frontend/public/doc/index.html +189 −0
@@ -0,0 +1,189 @@
1 +<!DOCTYPE html>
2 +<!-- Page /doc de Job·Ka — générée depuis le gabarit commun Groupe KA (2026-08-24). -->
3 +<html lang="fr">
4 +<head>
5 +<meta charset="utf-8">
6 +<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
7 +<title>Documentation — Job·Ka</title>
8 +<meta name="description" content="Comment fonctionne Job·Ka : guide pas à pas, données, architecture.">
9 +<style>
10 + :root{ --ink:#101014; --paper:#faf9f6; --accent:#0c8599; --muted:#6b6b70; --line:#e6e4de; }
11 + *{box-sizing:border-box} html,body{margin:0;padding:0;background:var(--paper);color:var(--ink);
12 + font:16px/1.65 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;-webkit-text-size-adjust:100%}
13 + .container{max-width:960px;margin:0 auto;padding:0 20px}
14 + header.doc{background:var(--ink);color:var(--paper);padding:20px 0}
15 + header.doc .container{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}
16 + header.doc a{color:var(--paper);text-decoration:none}
17 + .wordmark{font-weight:800;font-size:20px;letter-spacing:.02em}
18 + .wordmark .ka{color:var(--accent)}
19 + .btn-pdf{display:inline-block;background:var(--accent);color:#fff;font-weight:700;
20 + padding:10px 18px;border-radius:999px;text-decoration:none;font-size:15px}
21 + .hero{padding:48px 0 8px}
22 + .hero h1{font-size:clamp(28px,5vw,44px);line-height:1.1;margin:0 0 12px}
23 + .hero h1 mark{background:var(--accent);color:#fff;padding:2px 8px}
24 + .hero p.lead{font-size:18px;color:var(--muted);max-width:640px}
25 + .kicker{font-size:12px;letter-spacing:.14em;text-transform:uppercase;color:var(--muted);
26 + border-left:3px solid var(--accent);padding-left:10px;margin:40px 0 8px;font-weight:700}
27 + section{padding:8px 0 16px}
28 + h2{font-size:26px;margin:6px 0 12px}
29 + .step{display:grid;grid-template-columns:56px 1fr;gap:16px;margin:26px 0;align-items:start}
30 + .step .num{width:44px;height:44px;border-radius:50%;background:var(--ink);color:var(--accent);
31 + display:flex;align-items:center;justify-content:center;font-weight:800;font-size:18px}
32 + .step h3{margin:6px 0 6px;font-size:19px}
33 + .step p{margin:0 0 12px;color:#3a3a40}
34 + .shot{border:1px solid var(--line);border-radius:12px;overflow:hidden;box-shadow:0 8px 30px rgba(16,16,20,.08)}
35 + .shot img{display:block;width:100%;height:auto}
36 + .cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:14px;margin:16px 0}
37 + .card{border:1px solid var(--line);border-radius:12px;padding:16px;background:#fff}
38 + .card b{display:block;margin-bottom:6px}
39 + table{border-collapse:collapse;width:100%;font-size:15px}
40 + th,td{border:1px solid var(--line);padding:8px 10px;text-align:left;vertical-align:top}
41 + th{background:var(--ink);color:var(--paper)}
42 + footer.doc{margin-top:56px;background:var(--ink);color:var(--paper);padding:28px 0;font-size:14px}
43 + footer.doc a{color:var(--accent);text-decoration:none}
44 + @media print{
45 + header.doc .btn-pdf{display:none}
46 + .shot{box-shadow:none;break-inside:avoid}
47 + .step{break-inside:avoid}
48 + a{color:inherit;text-decoration:none}
49 + }
50 + @media(max-width:640px){ .step{grid-template-columns:40px 1fr} .step .num{width:34px;height:34px;font-size:15px} }
51 +</style>
52 +</head>
53 +<body>
54 +<header class="doc">
55 + <div class="container">
56 + <a class="wordmark" href="/">Job<span class="ka">·Ka</span></a>
57 + <nav style="display:flex;gap:14px;align-items:center">
58 + <a href="/">← Retour au site</a>
59 + <a class="btn-pdf" href="./job-ka-documentation.pdf" download>Télécharger le PDF</a>
60 + </nav>
61 + </div>
62 +</header>
63 +
64 +<div class="container">
65 + <div class="hero">
66 + <p class="kicker">Documentation · mise à jour 2026-08-24</p>
67 + <h1>Comment fonctionne <mark>Job·Ka</mark></h1>
68 + <p class="lead">Tous les emplois des employeurs québécois, collectés directement sur leurs pages carrières — un seul endroit, zéro boîte noire.</p>
69 + </div>
70 +
71 + <section>
72 + <p class="kicker">Vue d'ensemble</p>
73 + <h2>À quoi sert le site</h2>
74 + <p>Les grands sites d'emploi mélangent annonces payantes, doublons repostés et offres mortes. L'offre originale —
75 + celle de la page carrière de l'employeur — est la seule source fiable. Job·Ka se branche <b>directement sur
76 + les systèmes de recrutement (ATS)</b> des employeurs québécois, complétés par trois portails publics
77 + (Guichet-Emplois, Jobillico, Espresso-Jobs) — l'offre directe gagne toujours. Chaque offre est traçable à sa
78 + source et la candidature se fait chez l'employeur. Pour les chercheurs d'emploi québécois qui veulent
79 + l'inventaire réel, à jour et transparent des postes affichés dans la province.</p>
80 + <div class="cards">
81 + <div class="card"><b>21 000+ offres actives</b>De plus de 1 600 employeurs (≈960 sources), dont ~19 800
82 + offres directes collectées sur les pages carrières.</div>
83 + <div class="card"><b>968 connecteurs employeurs directs</b>Sur 17 plateformes ATS : Workday, Lever,
84 + Greenhouse, SmartRecruiters, BambooHR, Taleo, iCIMS, SuccessFactors…</div>
85 + <div class="card"><b>~6 900 offres avec salaire affiché</b>Salaires normalisés en $/h et $/an, jamais
86 + inventés — un signe monétaire ou une unité explicite est exigé.</div>
87 + <div class="card"><b>Resynchronisation jour et nuit</b>Un watcher horaire recollecte toutes les sources,
88 + détecte les changements et désactive les offres disparues.</div>
89 + </div>
90 + </section>
91 +
92 + <section>
93 + <p class="kicker">Guide pas à pas</p>
94 + <h2>Utiliser le site en 4 étapes</h2>
95 +
96 + <div class="step">
97 + <div class="num">1</div>
98 + <div>
99 + <h3>Arriver sur l'accueil</h3>
100 + <p>L'accueil présente la barre de recherche et les filtres : ville, région, catégorie (16 catégories : TI,
101 + Santé, Finance, Ingénierie…), mode de travail (dont télétravail), type d'emploi, langue et la case
102 + « Salaire affiché ». Les offres les plus récentes défilent dessous.</p>
103 + <div class="shot"><img src="./img/etape1.png" alt="Étape 1 — accueil de Job·Ka"></div>
104 + </div>
105 + </div>
106 +
107 + <div class="step">
108 + <div class="num">2</div>
109 + <div>
110 + <h3>Chercher et filtrer les offres</h3>
111 + <p>Tapez un métier ou un mot-clé (ici « machiniste ») et combinez les filtres : la liste se met à jour avec
112 + le nombre d'offres trouvées. Chaque carte montre l'employeur, la ville, le salaire quand il est affiché,
113 + et le badge « Offre directe » quand l'offre vient de la page carrière de l'employeur.</p>
114 + <div class="shot"><img src="./img/etape2.png" alt="Étape 2 — recherche filtrée d'offres"></div>
115 + </div>
116 + </div>
117 +
118 + <div class="step">
119 + <div class="num">3</div>
120 + <div>
121 + <h3>Lire la fiche et postuler chez l'employeur</h3>
122 + <p>La fiche donne la description complète, le salaire normalisé ($/h et l'équivalent $/an), le type d'emploi
123 + et la plateforme d'origine. Le bouton « Postuler chez … » mène directement au site de l'employeur :
124 + Job·Ka n'est pas un intermédiaire, la candidature se fait chez l'employeur.</p>
125 + <div class="shot"><img src="./img/etape3.png" alt="Étape 3 — fiche d'offre avec bouton Postuler chez l'employeur"></div>
126 + </div>
127 + </div>
128 +
129 + <div class="step">
130 + <div class="num">4</div>
131 + <div>
132 + <h3>Explorer par la carte (et plus)</h3>
133 + <p>L'onglet « Carte » situe les offres géocodées partout au Québec — zoomez sur votre coin de pays et ouvrez
134 + les offres d'un secteur. À explorer aussi : « Stats » (tableau de bord + rapports PDF), « Employeurs »
135 + (les sources), « Publier » (dépôt direct d'offres par les employeurs) et les favoris « Mon univers Ka ».</p>
136 + <div class="shot"><img src="./img/etape4.png" alt="Étape 4 — carte des offres"></div>
137 + </div>
138 + </div>
139 + </section>
140 +
141 + <section>
142 + <p class="kicker">Sous le capot</p>
143 + <h2>D'où viennent les données</h2>
144 + <p>Job·Ka interroge <b>968 connecteurs employeurs directs</b> répartis sur <b>17 plateformes ATS</b> (Workday,
145 + Lever, Greenhouse, SmartRecruiters, BambooHR, Breezy, Recruitee, Ashby, Workable, Taleo, Njoyn, UltiPro,
146 + iCIMS, SuccessFactors, ADP WFN, Digital Recruiters, Workland), des sites carrières maison et
147 + <b>3 portails publics</b> (Guichet-Emplois, Jobillico, Espresso-Jobs). Chaque offre est normalisée vers un
148 + schéma commun, puis passe par un <b>diff engine anti-dérive</b> : comparaison par empreinte de contenu à
149 + chaque synchronisation, une offre absente 2 syncs est désactivée (jamais supprimée), et si une source chute
150 + anormalement, les retraits sont suspendus le temps de vérifier. Les <b>salaires ne sont jamais inventés</b>
151 + (unité explicite exigée, bornes de plausibilité, conversion $/h ↔ $/an), le <b>géocodage</b> passe par
152 + Nominatim avec repli sur les Adresses Québec officielles (MERN), et la <b>déduplication inter-sources</b>
153 + privilégie l'offre directe : en cas de doublon, la page carrière de l'employeur devient la fiche canonique,
154 + les copies portail sont masquées. La collecte est polie : throttling par source et User-Agent identifiable
155 + <code>JobKaBot/1.0</code>. Le tout est resynchronisé jour et nuit, toutes les heures.</p>
156 + </section>
157 +
158 + <section>
159 + <p class="kicker">Questions fréquentes</p>
160 + <h2>FAQ</h2>
161 + <div class="cards">
162 + <div class="card"><b>Est-ce que je postule sur Job·Ka ?</b>
163 + Non — le bouton « Postuler » mène directement au site de l'employeur (sa page carrière ou son ATS). Job·Ka
164 + ne collecte aucune donnée personnelle de candidat.</div>
165 + <div class="card"><b>Que signifie le badge « Offre directe » ?</b>
166 + Que l'offre a été collectée directement sur la page carrière de l'employeur — la source la plus fiable. En
167 + cas de doublon avec un portail, c'est l'offre directe qui est affichée.</div>
168 + <div class="card"><b>Les salaires affichés sont-ils fiables ?</b>
169 + Oui : un salaire n'est retenu que si la source affiche un montant avec unité explicite. Il est ensuite
170 + normalisé en $/h et $/an pour comparer les offres — jamais estimé ni inventé.</div>
171 + <div class="card"><b>À quelle fréquence les offres sont-elles mises à jour ?</b>
172 + Toutes les heures, jour et nuit. Une offre retirée par l'employeur disparaît de la liste après
173 + 2 synchronisations consécutives d'absence.</div>
174 + <div class="card"><b>Je suis employeur : comment publier mes offres ?</b>
175 + Via la page « Publier une offre » (/employeurs) — dépôt direct avec modération manuelle, gratuit. Si vous
176 + utilisez un ATS répandu, votre page carrière peut aussi être branchée comme connecteur.</div>
177 + </div>
178 + </section>
179 +</div>
180 +
181 +<footer class="doc">
182 + <div class="container">
183 + <p><b>Job·Ka</b> — un service <a href="https://www.groupe-ka.com">Groupe KA</a>.
184 + Écosystème : les 13 plateformes sont liées au pied de chaque site.</p>
185 + <p>© 2026 Groupe KA — Simon-Pierre Boucher · <a href="mailto:contact@spboucher.ai">contact@spboucher.ai</a></p>
186 + </div>
187 +</footer>
188 +</body>
189 +</html>
added frontend/public/doc/job-ka-documentation.pdf +0 −0

Binary file not shown.

modified frontend/src/App.tsx +1 −1
@@ -198,7 +198,7 @@ export default function App() {
198 198 <NavLink to="/contact">Contact</NavLink>
199 199 </div>
200 200 </div>
201 − <KaFooter siteId="job-ka" />
201 + <KaFooter siteId="job-ka" localLegal={[{ label: "Documentation", href: "/doc/" }]} />
202 202 </FavProvider>
203 203 );
204 204 }
modified jobka/web.py +3 −0
@@ -595,6 +595,9 @@ if FRONTEND_DIST.exists():
595 595 target = FRONTEND_DIST / full_path
596 596 if full_path and target.is_file():
597 597 return FileResponse(target)
598 + if full_path and target.is_dir() and (target / "index.html").is_file():
599 + # sous-répertoire statique avec index (ex. /doc/) — servi tel quel
600 + return FileResponse(target / "index.html")
598 601 known = (full_path == "" or full_path in _CLIENT_ROUTES
599 602 or any(full_path.startswith(p) for p in _CLIENT_PREFIXES))
600 603 return FileResponse(FRONTEND_DIST / "index.html",
601 604