TypeScript 90.1%
JavaScript 3.2%
Python 3.2%
CSS 1.8%
HTML 1.8%
1// UQO Éval — page d'accueil (landing du laboratoire pédagogique).2"use client";3import { useState } from "react";4import Link from "next/link";5import SearchBox from "@/components/SearchBox";6import EstimateForm from "@/components/EstimateForm";7import { locNum, MarketPulseChart } from "@/components/MetricViz";8import { useLang } from "@/components/LangContext";910/** Indice de marché d'un grand type — série + variations (calculé côté serveur). */11export interface PulseItem {12 type: "unifamilial" | "condo" | "plex";13 series: { month: string; idx: number }[];14 pct12m: number | null;15 since2021: number | null;16}1718/** Vente réelle récente appariée à une unité d'évaluation. */19export interface RecentSaleItem {20 id: string;21 date: string;22 amount: number;23 street: string | null;24 city: string | null;25 propertyType: string | null;26 idProvinc: string | null;27 vsRolePct: number | null;28}2930const PULSE_LABELS: Record<PulseItem["type"], { fr: string; en: string }> = {31 unifamilial: { fr: "Unifamiliale", en: "Single-family" },32 condo: { fr: "Condo", en: "Condo" },33 plex: { fr: "Plex", en: "Plex" },34};35/* attribution FIXE des couleurs de série (jamais recyclée) */36const PULSE_COLORS: Record<PulseItem["type"], string> = {37 unifamilial: "var(--chart-1)",38 condo: "var(--chart-2)",39 plex: "var(--chart-3)",40};4142/** Variation signée — l'encre porte le positif, le rouge danger le négatif. */43function Pct({ v, digits = 1 }: { v: number | null; digits?: number }) {44 if (v == null) return <span className="text-ink-3">—</span>;45 return (46 <span className={v >= 0 ? "text-ink" : "text-[var(--danger)]"}>47 {v >= 0 ? "+" : "−"}48 {Math.abs(v).toFixed(digits)} %49 </span>50 );51}5253/** En-tête de section éditorial : filet fort, numéro mono rouge, titre display. */54function SectionHead({55 num,56 kicker,57 title,58 lede,59}: {60 num: string;61 kicker: string;62 title: string;63 lede?: string;64}) {65 return (66 <header className="sec">67 <div className="flex flex-wrap items-baseline gap-x-4 gap-y-1">68 <span className="sec-num">69 {num} — {kicker}70 </span>71 </div>72 <h2 className="vp-display mt-2 max-w-3xl text-[clamp(24px,3.6vw,36px)] font-bold uppercase leading-[1.02] tracking-[-0.03em]">73 {title}74 </h2>75 {lede && <p className="mt-3 max-w-2xl text-[14.5px] leading-relaxed text-ink-2">{lede}</p>}76 </header>77 );78}7980const PERIODS = [81 { key: "12", months: 12, fr: "12 mois", en: "12 mo" },82 { key: "24", months: 24, fr: "24 mois", en: "24 mo" },83 { key: "all", months: null as number | null, fr: "Depuis 2021", en: "Since 2021" },84] as const;8586export default function HomeClient({87 pulse,88 recent,89 salesLast30d,90}: {91 pulse: PulseItem[];92 recent: RecentSaleItem[];93 salesLast30d: number;94}) {95 const { lang, t } = useLang();96 const fr = lang === "fr";97 const [period, setPeriod] = useState<(typeof PERIODS)[number]["key"]>("24");98 const money = (v: number) =>99 new Intl.NumberFormat(fr ? "fr-CA" : "en-CA", {100 style: "currency",101 currency: "CAD",102 maximumFractionDigits: 0,103 }).format(v);104 const dateFmt = (d: string) =>105 new Date(`${d}T12:00:00`).toLocaleDateString(fr ? "fr-CA" : "en-CA", {106 day: "numeric",107 month: "short",108 });109110 const months = PERIODS.find((p) => p.key === period)?.months ?? 24;111 const chartData = pulse.map((p) => ({112 key: p.type,113 label: PULSE_LABELS[p.type][lang],114 color: PULSE_COLORS[p.type],115 series: p.series,116 }));117118 const proofs: [string, string][] = [119 ["3 747 008", fr ? "propriétés estimées" : "estimated properties"],120 ["745 119", fr ? "ventes réelles 2021-2026" : "real sales 2021-2026"],121 [locNum(salesLast30d, lang, { maxFrac: 0 }), fr ? "ventes captées / 30 jours" : "sales captured / 30 days"],122 ["1 100", fr ? "municipalités couvertes" : "municipalities covered"],123 ];124125 return (126 <div className="pb-6">127 {/* ================= HÉRO — terminal encre pleine largeur ================= */}128 <section className="bleed vp-dark">129 <div className="mx-auto max-w-6xl px-4 pb-10 pt-12 sm:px-6 sm:pb-14 sm:pt-16">130 <div className="flex items-end gap-3.5">131 {/* logo officiel UQO — version blanche sur encre */}132 {/* eslint-disable-next-line @next/next/no-img-element */}133 <img src="/uqo/uqo-blanc.svg" alt="UQO" className="h-[34px] w-auto" />134 <span className="vp-display text-[26px] font-bold leading-none tracking-[-0.04em] text-[var(--accent-bright)]">135 Éval136 </span>137 </div>138 <span className="kicker mt-5 !text-[var(--accent-bright)] [&::before]:!bg-[var(--accent-bright)]">139 {fr140 ? "Laboratoire pédagogique d'évaluation immobilière — Québec"141 : "Real-estate valuation teaching laboratory — Québec"}142 </span>143 <h1 className="vp-display mt-4 text-[clamp(36px,7vw,84px)] font-bold uppercase leading-[0.95] tracking-[-0.035em]">144 {fr ? (145 <>146 Comprendre la <span className="outline-txt">valeur</span>.147 <br />148 <span className="text-[var(--accent-bright)]">Pas juste l'obtenir.</span>149 </>150 ) : (151 <>152 Understand the <span className="outline-txt">value</span>.153 <br />154 <span className="text-[var(--accent-bright)]">Not just get it.</span>155 </>156 )}157 </h1>158 <p className="mt-5 max-w-xl text-[15.5px] leading-relaxed text-[rgba(233,237,238,0.72)]">159 {t("sub")}160 </p>161162 {/* -------- le CTA central : analyser une propriété -------- */}163 <div className="mt-9 max-w-3xl">164 <p className="vp-mono mb-1 text-[10.5px] font-medium uppercase tracking-[0.16em] text-[rgba(233,237,238,0.6)]">165 {fr ? "Analyser une propriété — entrez une adresse au Québec" : "Analyze a property — enter a Québec address"}166 </p>167 <SearchBox variant="hero" />168 <p className="vp-mono mt-2.5 text-[10px] uppercase tracking-[0.06em] text-[rgba(233,237,238,0.4)]">169 {t("dataNote")}170 </p>171 </div>172173 {/* -------- preuves de profondeur de données, intégrées -------- */}174 <div className="mt-10 grid grid-cols-2 gap-x-6 gap-y-5 border-t border-[rgba(233,237,238,0.16)] pt-6 sm:grid-cols-4">175 {proofs.map(([num, label], i) => (176 <div key={label}>177 <p className="vp-display flex items-baseline gap-2 text-[clamp(20px,2.6vw,28px)] font-bold leading-none tracking-[-0.02em] text-[var(--paper-on-ink)]">178 {num}179 {i === 2 && (180 <span181 aria-hidden="true"182 className="inline-block h-[7px] w-[7px] rounded-full bg-[var(--accent-bright)] [animation:pulse_2.4s_ease_infinite]"183 />184 )}185 </p>186 <p className="vp-mono mt-1.5 text-[9.5px] uppercase tracking-[0.12em] text-[rgba(233,237,238,0.5)]">187 {label}188 </p>189 </div>190 ))}191 </div>192 </div>193 </section>194195 {/* ============ le principe, en une ligne (pas de boîtes) ============ */}196 <section className="border-b border-[var(--line)] py-6">197 <div className="grid gap-x-8 gap-y-4 sm:grid-cols-3">198 {(fr199 ? [200 ["01", "Analysez une adresse", "3,7 M d'unités d'évaluation, recherche tolérante aux fautes."],201 ["02", "Voyez le calcul en entier", "Modèle hédonique (690 k ventes) + comparables réels ajustés en dollars."],202 ["03", "Apprenez les concepts", "P10-P90, indice A-D, mode étudiant — chaque notion expliquée."],203 ]204 : [205 ["01", "Analyze an address", "3.7M assessment units, typo-tolerant search."],206 ["02", "See the full computation", "Hedonic model (690k sales) + real comparables adjusted in dollars."],207 ["03", "Learn the concepts", "P10-P90, A-D index, student mode — every notion explained."],208 ]209 ).map(([num, title, body]) => (210 <div key={num} className="flex items-start gap-3">211 <span className="vp-mono pt-[3px] text-[11px] font-bold text-accent">{num}</span>212 <p className="text-[13px] leading-snug text-ink-2">213 <span className="vp-display block text-[14px] font-bold uppercase tracking-[-0.01em] text-ink">214 {title}215 </span>216 {body}217 </p>218 </div>219 ))}220 </div>221 </section>222223 {/* ================= ATELIERS — à vendre + modélisation hédonique ================= */}224 <section className="mt-8 grid gap-4 lg:grid-cols-3">225 <Link href="/a-vendre" className="vp-card vp-card-hover flex flex-col border-l-4 border-l-[var(--accent)] p-5">226 <span className="kicker">{fr ? "Atelier d'évaluation" : "Valuation workshop"}</span>227 <h2 className="vp-display mt-2 text-[20px] font-bold uppercase tracking-[-0.02em]">{fr ? "Évaluer une propriété à vendre" : "Value a property for sale"}</h2>228 <p className="mt-2 flex-1 text-[13.5px] leading-relaxed text-ink-2">229 {fr230 ? "Plus de 70 000 propriétés réellement à vendre au Québec, déjà mesurées par le moteur. Choisissez la vôtre, sélectionnez vos comparables, ajustez-les en dollars et confrontez votre valeur à la mesure UQO Éval et au prix demandé."231 : "Over 70,000 properties actually for sale in Québec, already measured by the engine. Pick yours, select your comparables, adjust them in dollars and confront your value with the UQO Éval measure and the asking price."}232 </p>233 <span className="vp-mono mt-3 text-[11px] font-bold uppercase tracking-[0.08em] text-accent">{fr ? "Ouvrir l'atelier" : "Open the workshop"} →</span>234 </Link>235 <Link href="/hedonique" className="vp-card vp-card-hover flex flex-col border-l-4 border-l-ink p-5">236 <span className="kicker">{fr ? "Atelier d'économétrie" : "Econometrics workshop"}</span>237 <h2 className="vp-display mt-2 text-[20px] font-bold uppercase tracking-[-0.02em]">{fr ? "Estimer votre modèle hédonique" : "Estimate your hedonic model"}</h2>238 <p className="mt-2 flex-1 text-[13.5px] leading-relaxed text-ink-2">239 {fr240 ? "750 000 ventes réelles ou 70 000 annonces : choisissez vos variables, vos effets fixes de temps et de lieu, votre méthode (MCO, robuste, quantile, ridge, LASSO, spatial) et lisez le rapport complet — indice à qualité constante, primes de quartier, diagnostics, code R / Python / Stata."241 : "750,000 real sales or 70,000 listings: choose your variables, time and location fixed effects, method (OLS, robust, quantile, ridge, LASSO, spatial) and read the full report — constant-quality index, neighbourhood premiums, diagnostics, R / Python / Stata code."}242 </p>243 <span className="vp-mono mt-3 text-[11px] font-bold uppercase tracking-[0.08em] text-accent">{fr ? "Ouvrir l'atelier" : "Open the workshop"} →</span>244 </Link>245 <Link href="/cout" className="vp-card vp-card-hover flex flex-col border-l-4 border-l-[var(--accent-bright)] p-5">246 <span className="kicker">{fr ? "Atelier de la méthode du coût" : "Cost-approach workshop"}</span>247 <h2 className="vp-display mt-2 text-[20px] font-bold uppercase tracking-[-0.02em]">{fr ? "Combien coûterait ce bâtiment à reconstruire ?" : "What would this building cost to rebuild?"}</h2>248 <p className="mt-2 flex-1 text-[13.5px] leading-relaxed text-ink-2">249 {fr250 ? "Coût de remplacement à neuf décomposé par assemblages — matériaux sourcés, heures de métier aux taux APCHQ/CCQ, équipement, facteurs régionaux —, puis dépréciation physique, fonctionnelle et externe et valeur du terrain. Chaque hypothèse et chaque source sont visibles (IMM1033)."251 : "Replacement cost new broken down by assemblies — sourced materials, trade hours at APCHQ/CCQ rates, equipment, regional factors —, then physical, functional and external depreciation and land value. Every assumption and every source is visible (IMM1033)."}252 </p>253 <span className="vp-mono mt-3 text-[11px] font-bold uppercase tracking-[0.08em] text-accent">{fr ? "Explorer les coûts" : "Explore costs"} →</span>254 </Link>255 </section>256257 {/* ================= MARCHÉ — data visualization éditoriale ================= */}258 <section className="mt-12">259 <SectionHead260 num="01"261 kicker={fr ? "Indice UQO Éval" : "UQO Éval index"}262 title={fr ? "Le pouls du marché québécois" : "The pulse of the Québec market"}263 lede={264 fr265 ? "L'indice mensuel calculé sur les ventes réelles — celui-là même qui ajuste les comparables de chaque estimation. Trois grands types, une même échelle : base 100 au début de la période."266 : "The monthly index built from real sales — the same one adjusting every estimate's comparables. Three major types, one scale: base 100 at the period start."267 }268 />269270 {/* contrôles : période (une rangée, au-dessus du graphique) */}271 <div className="mt-5 flex flex-wrap items-end justify-between gap-x-6 gap-y-2 border-b border-[var(--line)]">272 <div className="flex gap-5">273 {PERIODS.map((p) => (274 <button275 key={p.key}276 type="button"277 className="vp-tab"278 aria-pressed={period === p.key}279 onClick={() => setPeriod(p.key)}280 >281 {p[lang]}282 </button>283 ))}284 </div>285 {/* légende — l'identité ne repose jamais sur la couleur seule */}286 <div className="flex flex-wrap gap-x-4 gap-y-1 pb-2">287 {chartData.map((s) => (288 <span key={s.key} className="vp-mono flex items-center gap-1.5 text-[10px] uppercase tracking-[0.08em] text-ink-2">289 <span className="inline-block h-[2px] w-[14px]" style={{ background: s.color }} />290 {s.label}291 </span>292 ))}293 </div>294 </div>295296 <div className="mt-4">297 <MarketPulseChart data={chartData} lang={lang} months={months} />298 </div>299300 {/* lecture par type : variations en colonnes de données */}301 <div className="mt-5 grid gap-x-8 gap-y-3 border-t border-[var(--line)] pt-4 sm:grid-cols-3">302 {pulse.map((p) => (303 <div key={p.type} className="flex items-baseline justify-between gap-4 sm:block">304 <p className="vp-mono flex items-center gap-1.5 text-[10.5px] font-bold uppercase tracking-[0.08em] text-ink">305 <span className="inline-block h-[2px] w-[12px]" style={{ background: PULSE_COLORS[p.type] }} />306 {PULSE_LABELS[p.type][lang]}307 </p>308 <p className="vp-mono mt-1 flex gap-4 text-[11px] text-ink-2">309 <span>310 12 {fr ? "mois" : "mo"} 311 <b className="vp-display text-[14px]">312 <Pct v={p.pct12m} />313 </b>314 </span>315 <span>316 2021→ 317 <b className="vp-display text-[14px]">318 <Pct v={p.since2021} digits={0} />319 </b>320 </span>321 </p>322 </div>323 ))}324 </div>325 </section>326327 {/* ================= VENTES RÉELLES — flux prix-d'abord ================= */}328 {recent.length > 0 && (329 <section id="ventes" className="mt-14 scroll-mt-20">330 <SectionHead331 num="02"332 kicker={fr ? "Flux en continu" : "Live feed"}333 title={fr ? "Dernières ventes réelles captées" : "Latest real sales captured"}334 lede={335 fr336 ? "Transactions publiées, appariées automatiquement à leur unité d'évaluation. Chaque vente mène à l'estimation complète de la propriété."337 : "Published transactions, automatically matched to their assessment unit. Each sale leads to the property's full estimate."338 }339 />340 <div className="mt-5">341 {recent.map((s) => {342 const inner = (343 <div className="flex flex-wrap items-baseline gap-x-4 gap-y-1 sm:flex-nowrap">344 {/* le prix d'abord — la donnée mène */}345 <p className="vp-display w-full text-[22px] font-bold leading-none tracking-[-0.02em] sm:w-[168px] sm:shrink-0 sm:text-[20px]">346 {money(s.amount)}347 </p>348 <div className="min-w-0 flex-1">349 <p className="truncate text-[14.5px] font-semibold leading-snug">350 {s.street}351 {s.city ? `, ${s.city}` : ""}352 </p>353 <p className="vp-mono mt-0.5 text-[10.5px] uppercase tracking-[0.05em] text-ink-3">354 {dateFmt(s.date)}355 {s.propertyType && s.propertyType !== "indéterminé" ? ` · ${s.propertyType}` : ""}356 {s.vsRolePct != null && Math.abs(s.vsRolePct) < 400 && (357 <>358 {" · "}359 <span className={s.vsRolePct >= 0 ? "text-ink-2" : "text-[var(--danger)]"}>360 {s.vsRolePct >= 0 ? "+" : "−"}361 {Math.abs(s.vsRolePct).toFixed(0)} % {fr ? "vs rôle" : "vs roll"}362 </span>363 </>364 )}365 </p>366 </div>367 {s.idProvinc && (368 <p className="vp-mono shrink-0 text-[10.5px] font-bold uppercase tracking-[0.08em] text-accent">369 {fr ? "Voir l'estimation" : "See the estimate"} →370 </p>371 )}372 </div>373 );374 return s.idProvinc ? (375 <Link key={s.id} href={`/estimation/${encodeURIComponent(s.idProvinc)}`} className="feed-row">376 {inner}377 </Link>378 ) : (379 <div key={s.id} className="feed-row">380 {inner}381 </div>382 );383 })}384 </div>385 </section>386 )}387388 {/* ================= LE CALCUL, MONTRÉ — promesse centrale ================= */}389 <section className="mt-14">390 <SectionHead391 num="03"392 kicker={fr ? "Sans boîte noire" : "No black box"}393 title={fr ? "Voici exactement comment le prix est calculé" : "Here is exactly how the price is computed"}394 />395 <div className="mt-6 grid gap-10 lg:grid-cols-[1.2fr_1fr]">396 <ol className="tl">397 {(fr398 ? [399 ["Propriété identifiée", "3,7 M d'unités d'évaluation géoréférencées (MAMH)."],400 ["Rôle d'évaluation récupéré", "Valeurs terrain + bâtiment, six millésimes 2021-2026."],401 ["Ventes comparables sélectionnées", "Rayon adaptatif, même famille de type, aire ±20 %."],402 ["Caractéristiques ajustées", "Marché, superficie, âge — chaque ajustement affiché en dollars."],403 ["Estimation calculée", "65 % modèle hédonique + 35 % comparables pondérés."],404 ["Intervalle de confiance produit", "Fourchette P10-P90 et indice A-D, toujours affichés."],405 ]406 : [407 ["Property identified", "3.7M georeferenced assessment units (MAMH)."],408 ["Assessment roll retrieved", "Land + building values, six vintages 2021-2026."],409 ["Comparable sales selected", "Adaptive radius, same type family, area ±20%."],410 ["Characteristics adjusted", "Market, area, age — every adjustment shown in dollars."],411 ["Estimate computed", "65% hedonic model + 35% weighted comparables."],412 ["Confidence interval produced", "P10-P90 range and A-D index, always shown."],413 ]414 ).map(([title, body], i, arr) => (415 <li key={title} className="tl-step" data-accent={i === arr.length - 1 ? "true" : undefined}>416 <p className="vp-display flex items-baseline gap-2.5 text-[15px] font-bold uppercase tracking-[-0.01em]">417 <span className="vp-mono text-[10.5px] font-bold text-accent">0{i + 1}</span>418 {title}419 </p>420 <p className="mt-1 text-[13px] leading-snug text-ink-2">{body}</p>421 </li>422 ))}423 </ol>424 <div>425 {(lang === "fr"426 ? [427 ["Le calcul, montré", "Chaque ajustement de chaque comparable est affiché en dollars. Pas de score magique."],428 ["Fourchette honnête", "Un prix unique est un mensonge statistique. On donne P10-P90 et un indice de confiance A-D."],429 ["Données réelles", "Rôles fonciers officiels (MAMH) et ventes publiées — locales et récentes en priorité."],430 ]431 : [432 ["The math, shown", "Every adjustment on every comparable is displayed in dollars. No magic score."],433 ["Honest range", "A single price is a statistical lie. We give P10-P90 and an A-D confidence index."],434 ["Real data", "Official assessment rolls (MAMH) and published sales — local and recent first."],435 ]436 ).map(([title, body]) => (437 <div key={title} className="border-t border-[var(--line)] py-4 first:border-t-0 first:pt-0">438 <h3 className="vp-display text-[15px] font-bold uppercase tracking-[-0.01em]">{title}</h3>439 <p className="mt-1.5 text-[13px] leading-snug text-ink-2">{body}</p>440 </div>441 ))}442 <p className="vp-mono mt-2 border-t border-[var(--line)] pt-4 text-[11px] uppercase tracking-[0.06em]">443 <Link href="/methodologie" className="border-b border-accent pb-px text-accent-deep hover:text-accent">444 {fr ? "Méthodologie complète — niveau rapport technique" : "Full methodology — technical-report depth"} →445 </Link>446 </p>447 </div>448 </div>449 </section>450451 {/* ================= FONCTIONNALITÉS — le laboratoire ================= */}452 <section className="mt-14">453 <SectionHead454 num="04"455 kicker={fr ? "Le laboratoire" : "The laboratory"}456 title={fr ? "Explorer les mécanismes de l'évaluation" : "Explore the mechanics of valuation"}457 lede={458 fr459 ? "UQO Éval sert à explorer les mécanismes de l'évaluation immobilière à travers des données réelles et des modèles quantitatifs — chaque résultat est décomposé, expliqué et vérifiable."460 : "UQO Éval explores the mechanics of real-estate valuation through real data and quantitative models — every result is decomposed, explained and verifiable."461 }462 />463 <div className="mt-6 grid gap-x-8 gap-y-6 sm:grid-cols-2 lg:grid-cols-3">464 {(fr465 ? [466 ["Estimation immobilière", "Valeur marchande, intervalle P10-P90 et indice de confiance A-D pour 3,7 M de propriétés.", "/"],467 ["Comparables expliqués", "Carte et tableau des ventes retenues : distance, date, prix/m², ajustements en dollars, poids.", "/"],468 ["Analyse géographique", "Carte 3D des valeurs estimées, ventes du secteur, position dans la municipalité.", "/carte"],469 ["Modèles quantitatifs", "Modèle hédonique LightGBM (18 variables), régression quantile, indice de marché mensuel.", "/methodologie"],470 ["Décomposition de la valeur", "Terrain vs bâtiment, poids modèle vs comparables, historique 2021-2026 millésime par millésime.", "/"],471 ["Méthode du coût", "Reconstituez le coût de remplacement d'un bâtiment, composante par composante, puis appliquez la dépréciation. Explorer les coûts →", "/cout"],472 ["Outils pédagogiques", "Mode étudiant, méthodologie de niveau rapport technique, assistante IA qui explique chaque calcul.", "/assistant"],473 ]474 : [475 ["Property estimation", "Market value, P10-P90 interval and A-D confidence index for 3.7M properties.", "/"],476 ["Comparables, explained", "Map and table of selected sales: distance, date, $/m², dollar adjustments, weights.", "/"],477 ["Geographic analysis", "3D map of estimated values, sector sales, position within the municipality.", "/carte"],478 ["Quantitative models", "LightGBM hedonic model (18 variables), quantile regression, monthly market index.", "/methodologie"],479 ["Value decomposition", "Land vs building, model vs comparables weights, 2021-2026 history vintage by vintage.", "/"],480 ["Cost approach", "Rebuild a building's replacement cost component by component, then apply depreciation. Explore costs →", "/cout"],481 ["Teaching tools", "Student mode, technical-report-depth methodology, an AI assistant that explains every computation.", "/assistant"],482 ]483 ).map(([title, body, href]) => (484 <Link485 key={title}486 href={href}487 className="group border-t border-[var(--line)] pt-4"488 >489 <h3 className="vp-display text-[15px] font-bold uppercase tracking-[-0.01em] group-hover:text-accent-deep">490 {title}491 </h3>492 <p className="mt-1.5 text-[13px] leading-snug text-ink-2">{body}</p>493 </Link>494 ))}495 </div>496 {/* disclaimer pédagogique — visible dès l'accueil */}497 <p className="vp-mono mt-8 border-l-2 border-accent pl-4 text-[11px] uppercase leading-relaxed tracking-[0.05em] text-ink-2">498 {fr499 ? "Plateforme à vocation pédagogique, universitaire et expérimentale — cours IMM1003 et IMM1033, UQO. Les estimations ne constituent pas une évaluation immobilière professionnelle certifiée (OEAQ)."500 : "Educational, academic and experimental platform — courses IMM1003 and IMM1033, UQO. Estimates are not a certified professional appraisal (OEAQ)."}501 </p>502 </section>503504 {/* ================= PLAN B — estimation sans adresse ================= */}505 <section className="mt-14">506 <SectionHead507 num="05"508 kicker={fr ? "Plan B" : "Plan B"}509 title={t("manualTitle")}510 />511 <div className="mt-6 max-w-3xl">512 <EstimateForm />513 </div>514 </section>515 </div>516 );517}518