SPB Git forge

spb/immbot-ai

Public
1commits 1branches 0releases
1.5 MBsize
maindefault branch
20 days agolast push
TypeScript 98.3% CSS 0.9% Shell 0.7%
24.6 KB · 422 lines tsx
Raw Blame History
1// Page d'accueil — plateforme pédagogique de cours (IMM1003 · IMM1033, UQO).2// Ton institutionnel et transparent : ce n'est pas un produit commercial.3import Image from "next/image";4import Link from "next/link";5import { redirect } from "next/navigation";6import { DemoButton } from "@/components/demo-button";7import {8  ArrowRight, BadgeCheck, BookOpen, BookOpenCheck, BrainCircuit, FileSearch,9  GraduationCap, Layers, LineChart, Lock, MessageSquareText, Network,10  Presentation, ShieldCheck, Timer,11} from "lucide-react";12import { ImmbotLogo, ImmbotMark, UqoLogo } from "@/components/logo";13import { ThemeToggle } from "@/components/theme-toggle";14import { currentUser } from "@/lib/auth/session.ts";15import { get } from "@/lib/db/index.ts";1617export default async function HomePage() {18  const user = await currentUser();19  if (user) redirect("/chat");2021  const chunks = get<{ n: number }>("SELECT COUNT(*) as n FROM chunks WHERE space LIKE 'official-%'")?.n ?? 2000;22  const questions = get<{ n: number }>("SELECT COUNT(*) as n FROM quiz_questions")?.n ?? 129;23  const cards = get<{ n: number }>("SELECT COUNT(*) as n FROM flashcards")?.n ?? 140;24  const exams = get<{ n: number }>("SELECT COUNT(*) as n FROM mock_exams")?.n ?? 6;2526  return (27    <div className="min-h-dvh bg-app">28      {/* Filet institutionnel */}29      <div className="h-1 bg-brand-700" aria-hidden>30        <div className="h-full w-24 bg-gold-500 ml-4 sm:ml-8" />31      </div>3233      <header className="sticky top-0 z-40 bg-card/95 backdrop-blur border-b border-app">34        <div className="max-w-6xl mx-auto px-4 sm:px-6 h-15 sm:h-16 flex items-center justify-between gap-3">35          <div className="flex items-center gap-3 sm:gap-4 min-w-0">36            <ImmbotLogo size={28} />37            <span className="hidden sm:block w-px h-7" style={{ background: "var(--border)" }} aria-hidden />38            <UqoLogo height={30} className="hidden sm:block dark:invert" />39          </div>40          <nav className="flex items-center gap-1.5 sm:gap-2 shrink-0">41            <ThemeToggle className="hidden sm:block" />42            <Link href="/connexion" className="px-3 sm:px-4 py-2 text-[13.5px] sm:text-sm font-semibold text-fg hover:bg-surface-2 dark:hover:bg-brand-900/40 rounded-lg transition-colors">43              Connexion44            </Link>45            <Link href="/inscription" className="px-3 sm:px-4 py-2 text-[13.5px] sm:text-sm font-semibold bg-brand-700 hover:bg-brand-800 text-white rounded-lg transition-colors">46              Créer un compte47            </Link>48          </nav>49        </div>50        {/* Bandeau de divulgation */}51        <div className="bg-brand-700 text-white">52          <p className="max-w-6xl mx-auto px-4 sm:px-6 py-1.5 text-[11.5px] sm:text-[12.5px] font-medium text-center sm:text-left">53            Plateforme pédagogique réservée aux personnes inscrites à <strong>IMM1003</strong> et{" "}54            <strong>IMM1033</strong> — Automne 2026 · Fournie par le professeur Simon-Pierre Boucher, UQO55          </p>56        </div>57      </header>5859      <main>60        {/* ================= Héro ================= */}61        <section className="relative overflow-hidden">62          {/* Quadrillage discret */}63          <div64            aria-hidden65            className="absolute inset-0 opacity-[0.5] dark:opacity-[0.12]"66            style={{67              backgroundImage:68                "linear-gradient(var(--border) 1px, transparent 1px), linear-gradient(90deg, var(--border) 1px, transparent 1px)",69              backgroundSize: "44px 44px",70              maskImage: "radial-gradient(ellipse 90% 65% at 50% 0%, black 35%, transparent 78%)",71              WebkitMaskImage: "radial-gradient(ellipse 90% 65% at 50% 0%, black 35%, transparent 78%)",72            }}73          />74          <div className="relative max-w-6xl mx-auto px-4 sm:px-6 pt-10 sm:pt-16 pb-12 sm:pb-16 grid lg:grid-cols-2 gap-10 lg:gap-12 items-center">75            <div>76              <p className="inline-flex items-center gap-2 text-[12px] sm:text-[13px] font-semibold text-brand-700 dark:text-brand-300 bg-brand-100 dark:bg-brand-900/50 border border-brand-200 dark:border-brand-800 px-3.5 py-1.5 rounded-full animate-fade-up">77                <GraduationCap size={14} /> L'outil d'étude officiel de vos cours78              </p>79              <h1 className="mt-5 text-[2.1rem] leading-[1.1] sm:text-[3.4rem] font-bold tracking-tight text-fg animate-fade-up [animation-delay:60ms] text-balance">80                Étudiez IMM1003 et IMM1033 avec un assistant qui connaît{" "}81                <span className="relative whitespace-nowrap">82                  <span className="text-brand-600 dark:text-brand-300">vos diapositives</span>83                  <svg className="absolute -bottom-1.5 left-0 w-full" height="6" viewBox="0 0 200 6" preserveAspectRatio="none" aria-hidden>84                    <path d="M0 4 Q 50 0.5 100 3 T 200 2.5" stroke="#C6A300" strokeWidth="2.5" fill="none" strokeLinecap="round" />85                  </svg>86                </span>87                .88              </h1>89              <p className="mt-5 text-[15px] sm:text-[17px] text-muted leading-relaxed animate-fade-up [animation-delay:120ms] text-pretty max-w-xl">90                Mise en place par votre professeur, cette plateforme est construite sur le{" "}91                <strong className="text-fg font-semibold">matériel officiel des deux cours</strong> — chaque92                réponse cite la séance et la diapositive exactes, que vous pouvez vérifier d'un clic.93              </p>94              <ul className="mt-5 space-y-2 animate-fade-up [animation-delay:160ms]">95                {[96                  "Fondé uniquement sur le contenu du cours (mode par défaut)",97                  "Vos conversations et votre progression restent privées",98                  "Gratuit pour les personnes inscrites — aucun objectif commercial",99                ].map((t) => (100                  <li key={t} className="flex items-start gap-2.5 text-[13.5px] sm:text-[14.5px] text-fg">101                    <BadgeCheck size={17} className="text-brand-600 dark:text-brand-300 shrink-0 mt-0.5" />102                    {t}103                  </li>104                ))}105              </ul>106              <div className="mt-7 flex flex-col sm:flex-row sm:items-center gap-2.5 animate-fade-up [animation-delay:200ms]">107                <Link href="/inscription" className="inline-flex items-center justify-center gap-2 px-6 py-3 bg-brand-700 hover:bg-brand-800 text-white font-semibold rounded-xl shadow-md shadow-brand-700/20 transition-colors">108                  Créer mon compte étudiant <ArrowRight size={16} />109                </Link>110                <Link href="/connexion" className="inline-flex items-center justify-center px-6 py-3 bg-card border border-app hover:border-brand-400 text-fg font-semibold rounded-xl transition-colors">111                  J'ai déjà un compte112                </Link>113              </div>114              <div className="mt-3 animate-fade-up [animation-delay:240ms]">115                <DemoButton />116                <p className="text-[11.5px] text-muted mt-1.5">117                  Compte d'essai partagé avec accès aux deux cours — vos vraies données commencent avec votre compte personnel.118                </p>119              </div>120            </div>121122            {/* Aperçu du chat */}123            <div className="animate-fade-up [animation-delay:240ms]">124              <div className="bg-card border border-app rounded-xl shadow-xl overflow-hidden">125                <div className="px-4 py-2.5 border-b border-app flex items-center gap-2.5 bg-surface-1 dark:bg-brand-950/60">126                  <ImmbotMark size={20} />127                  <span className="text-[12.5px] font-semibold text-fg">IMM1033 · Cours interactif</span>128                  <span className="ml-auto text-[11px] text-muted font-medium">Mode par défaut : matériel officiel</span>129                </div>130                <div className="p-4 sm:p-5 space-y-3.5 text-left">131                  <div className="flex justify-end">132                    <div className="bg-brand-700 text-white rounded-2xl rounded-br-md px-4 py-2.5 max-w-[88%] text-[13px] sm:text-[13.5px]">133                      Que dit la séance 10 sur la dépréciation incurable de longue durée ?134                    </div>135                  </div>136                  <div className="flex flex-wrap gap-1.5">137                    <span className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-lg border border-app bg-surface-1 dark:bg-brand-950/40 text-[11.5px] font-medium text-muted">138                      <BookOpen size={11} /> Parcourt le plan de la séance 10139                    </span>140                    <span className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-lg border border-app bg-surface-1 dark:bg-brand-950/40 text-[11.5px] font-medium text-muted">141                      <Presentation size={11} /> Lit les diapositives 23–27142                    </span>143                  </div>144                  <div className="flex gap-2.5">145                    <ImmbotMark size={22} className="shrink-0 mt-1" />146                    <div className="bg-surface-1 dark:bg-brand-900/40 border border-app rounded-2xl rounded-bl-md px-4 py-3 text-[13px] sm:text-[13.5px] text-fg leading-relaxed">147                      La dépréciation physique incurable de longue durée touche les composantes qui durent148                      aussi longtemps que le bâtiment <span className="citation-chip">S1</span>. On l'estime149                      par la méthode <strong>âge-vie</strong> appliquée à la base résiduelle — après avoir150                      retiré le curable et le court terme pour éviter le{" "}151                      <strong>double comptage</strong> <span className="citation-chip">S3</span>.152                      <span className="block mt-2.5 pt-2 border-t border-app text-[11px] text-muted">153                        Sources : Séance 10 — Diapositives 23 et 25 · vérifiables d'un clic154                      </span>155                    </div>156                  </div>157                </div>158              </div>159              <p className="text-center text-[11.5px] text-muted mt-2">160                Réponse réelle du mode « Cours interactif » : l'assistant consulte les diapositives sous vos yeux.161              </p>162            </div>163          </div>164        </section>165166        {/* ================= Matériel indexé ================= */}167        <section className="border-y border-app bg-card">168          <div className="max-w-6xl mx-auto px-4 sm:px-6 py-8 sm:py-10">169            <p className="text-center text-[12px] font-bold uppercase tracking-widest text-muted">170              Le matériel de vos deux cours, indexé à la diapositive près171            </p>172            <div className="mt-5 grid grid-cols-2 sm:grid-cols-4 gap-px rounded-xl overflow-hidden border border-app" style={{ background: "var(--border)" }}>173              {[174                [chunks.toLocaleString("fr-CA"), "extraits du matériel officiel"],175                ["28", "séances consultables en ligne"],176                [`${questions} + ${cards}`, "questions et cartes mémoire"],177                [String(exams), "examens blancs corrigés"],178              ].map(([n, label]) => (179                <div key={label} className="bg-card px-4 py-4 sm:py-5 text-center">180                  <p className="text-xl sm:text-2xl font-bold text-brand-700 dark:text-brand-300 tracking-tight">{n}</p>181                  <p className="text-[11.5px] sm:text-[12.5px] text-muted mt-0.5">{label}</p>182                </div>183              ))}184            </div>185          </div>186        </section>187188        {/* ================= Trois façons d'étudier ================= */}189        <section className="max-w-6xl mx-auto px-4 sm:px-6 py-14 sm:py-20">190          <h2 className="text-xl sm:text-3xl font-bold tracking-tight text-center text-fg">Trois façons d'étudier</h2>191          <p className="text-center text-[13.5px] sm:text-base text-muted mt-2.5 max-w-xl mx-auto">192            Chaque module applique un principe validé par la science de l'apprentissage — pas de gadgets.193          </p>194          <div className="mt-9 sm:mt-12 grid md:grid-cols-3 gap-4 sm:gap-5">195            {PILLARS.map((p, i) => (196              <div key={p.title} className="relative bg-card border border-app rounded-xl p-5 sm:p-6 overflow-hidden">197                <div className="absolute inset-x-0 top-0 h-1" style={{ background: i === 1 ? "#C6A300" : "var(--color-brand-700, #003E7E)" }} />198                <div className="flex items-center gap-3">199                  <div className="w-10 h-10 rounded-lg bg-brand-700 text-white flex items-center justify-center shrink-0">200                    <p.icon size={19} />201                  </div>202                  <div>203                    <p className="text-[11px] font-bold uppercase tracking-widest text-muted">{p.eyebrow}</p>204                    <h3 className="font-bold text-[16px] text-fg tracking-tight">{p.title}</h3>205                  </div>206                </div>207                <ul className="mt-4 space-y-2.5">208                  {p.items.map((it) => (209                    <li key={it.label} className="flex items-start gap-2.5">210                      <it.icon size={15} className="text-brand-600 dark:text-brand-300 shrink-0 mt-0.5" />211                      <span className="text-[13px] text-fg leading-snug">212                        <strong className="font-semibold">{it.label}</strong>213                        <span className="text-muted"> — {it.text}</span>214                      </span>215                    </li>216                  ))}217                </ul>218              </div>219            ))}220          </div>221        </section>222223        {/* ================= La plateforme en images ================= */}224        <section className="border-t border-app bg-card">225          <div className="max-w-6xl mx-auto px-4 sm:px-6 py-14 sm:py-20">226            <h2 className="text-xl sm:text-3xl font-bold tracking-tight text-center text-fg">La plateforme en images</h2>227            <p className="text-center text-[13.5px] sm:text-base text-muted mt-2.5 max-w-xl mx-auto">228              Captures réelles de l'application — exactement ce que vous verrez une fois connecté·e.229            </p>230            <div className="mt-9 grid md:grid-cols-2 gap-5 sm:gap-7">231              {SCREENS.map((s) => (232                <figure key={s.src} className="group">233                  <div className="rounded-xl border border-app bg-app shadow-lg overflow-hidden transition-transform duration-300 group-hover:-translate-y-0.5 group-hover:shadow-xl">234                    <div className="flex items-center gap-1.5 px-3.5 py-2 bg-surface-2 dark:bg-brand-950/60 border-b border-app">235                      <span className="w-2.5 h-2.5 rounded-full bg-red-400/80" aria-hidden />236                      <span className="w-2.5 h-2.5 rounded-full bg-amber-400/80" aria-hidden />237                      <span className="w-2.5 h-2.5 rounded-full bg-emerald-400/80" aria-hidden />238                      <span className="ml-2 text-[11px] font-medium text-muted truncate">{s.url}</span>239                    </div>240                    <Image241                      src={s.src}242                      alt={s.alt}243                      width={1280}244                      height={800}245                      className="w-full h-auto"246                      sizes="(max-width: 768px) 100vw, 560px"247                    />248                  </div>249                  <figcaption className="mt-2.5 px-1">250                    <p className="text-[13.5px] font-semibold text-fg">{s.title}</p>251                    <p className="text-[12.5px] text-muted mt-0.5">{s.caption}</p>252                  </figcaption>253                </figure>254              ))}255            </div>256          </div>257        </section>258259        {/* ================= Les deux cours ================= */}260        <section className="border-t border-app bg-card">261          <div className="max-w-6xl mx-auto px-4 sm:px-6 py-14 sm:py-18">262            <div className="grid md:grid-cols-2 gap-4">263              <div className="relative border border-app rounded-xl p-5 sm:p-7 bg-app overflow-hidden">264                <div className="absolute inset-x-0 top-0 h-1 bg-brand-700" />265                <p className="text-[12px] font-bold text-brand-700 dark:text-brand-300 tracking-widest">IMM1003 · AUTOMNE 2026</p>266                <h3 className="text-lg sm:text-xl font-bold tracking-tight text-fg mt-1.5">Éléments d'évaluation immobilière</h3>267                <p className="text-[13px] sm:text-sm text-muted mt-2.5 leading-relaxed">268                  Cadre professionnel OEAQ, principes économiques, types de valeur, marché québécois et les269                  trois méthodes reconnues — jusqu'au rapport d'évaluation conforme.270                </p>271              </div>272              <div className="relative border border-app rounded-xl p-5 sm:p-7 bg-app overflow-hidden">273                <div className="absolute inset-x-0 top-0 h-1 bg-gold-500" />274                <p className="text-[12px] font-bold text-gold-600 dark:text-gold-400 tracking-widest">IMM1033 · AUTOMNE 2026</p>275                <h3 className="text-lg sm:text-xl font-bold tracking-tight text-fg mt-1.5">Méthodes du coût en évaluation immobilière</h3>276                <p className="text-[13px] sm:text-sm text-muted mt-2.5 leading-relaxed">277                  Évaluation du terrain, coûts directs et indirects, profit de l'entrepreneur et la mesure278                  rigoureuse des trois formes de dépréciation — jusqu'au cas intégrateur complet.279                </p>280              </div>281            </div>282            <p className="text-center text-[12.5px] text-muted mt-5">283              Professeur : <strong className="text-fg font-semibold">Simon-Pierre Boucher</strong> — Département284              des sciences administratives, Université du Québec en Outaouais285            </p>286          </div>287        </section>288289        {/* ================= Un outil pédagogique, pas un raccourci ================= */}290        <section className="max-w-4xl mx-auto px-4 sm:px-6 py-14 sm:py-20">291          <h2 className="text-xl sm:text-3xl font-bold tracking-tight text-center text-fg">292            Un outil pédagogique — pas un raccourci293          </h2>294          <div className="mt-8 grid sm:grid-cols-3 gap-4">295            {[296              {297                icon: ShieldCheck,298                title: "Transparent",299                text: "L'origine de chaque information est identifiée : matériel officiel (cité par diapositive), vos documents, Web ou connaissances du modèle. Quand le cours ne couvre pas une question, l'assistant le dit.",300              },301              {302                icon: BookOpenCheck,303                title: "Honnête avec vos travaux",304                text: "Pour un atelier noté, l'assistant vous demande d'abord votre tentative et guide par indices — il est là pour vous préparer à l'examen, pas pour faire le travail à votre place.",305              },306              {307                icon: Lock,308                title: "Privé",309                text: "Vos conversations et votre progression individuelle ne sont jamais montrées au professeur — il ne voit que des statistiques agrégées et anonymisées. Aucune donnée n'est vendue ni utilisée à d'autres fins.",310              },311            ].map((c) => (312              <div key={c.title} className="text-center px-2">313                <c.icon className="mx-auto text-brand-600 dark:text-brand-300" size={26} />314                <h3 className="font-bold text-[15px] text-fg mt-3 tracking-tight">{c.title}</h3>315                <p className="text-[12.5px] text-muted mt-2 leading-relaxed">{c.text}</p>316              </div>317            ))}318          </div>319          <div className="mt-10 text-center">320            <Link href="/inscription" className="inline-flex items-center gap-2 px-6 py-3 bg-brand-700 hover:bg-brand-800 text-white font-semibold rounded-xl transition-colors">321              Accéder à la plateforme <ArrowRight size={16} />322            </Link>323            <p className="text-[12px] text-muted mt-3">324              Réservé aux personnes inscrites aux cours. L'assistant peut se tromper : vérifiez les sources citées.325            </p>326          </div>327        </section>328      </main>329330      <footer className="border-t border-app bg-card">331        <div className="max-w-6xl mx-auto px-4 sm:px-6 py-8 flex flex-col sm:flex-row items-center sm:items-start justify-between gap-5">332          <div className="flex flex-col items-center sm:items-start gap-3 max-w-md text-center sm:text-left">333            <div className="flex items-center gap-4">334              <ImmbotLogo size={24} />335              <span className="w-px h-6" style={{ background: "var(--border)" }} aria-hidden />336              <UqoLogo height={26} className="dark:invert" />337            </div>338            <p className="text-[11.5px] text-muted leading-relaxed">339              Immbot AI est un outil pédagogique fourni gratuitement aux étudiantes et étudiants340              d'IMM1003 et IMM1033 par le professeur Simon-Pierre Boucher (UQO). Il ne remplace ni les341              séances, ni le matériel officiel, ni le plan de cours.342            </p>343          </div>344          <div className="text-[11.5px] text-muted text-center sm:text-right space-y-1 shrink-0">345            <p>IMM1003 · IMM1033 — Automne 2026</p>346            <p>Université du Québec en Outaouais</p>347            <p>simon-pierre.boucher@uqo.ca</p>348            <p className="pt-1.5 space-x-3">349              <Link href="/conditions" className="font-semibold text-fg hover:underline">Conditions d'utilisation</Link>350              <Link href="/confidentialite" className="font-semibold text-fg hover:underline">Confidentialité</Link>351            </p>352          </div>353        </div>354      </footer>355    </div>356  );357}358359const SCREENS = [360  {361    src: "/screens/chat.png",362    url: "immbot.ai/chat",363    alt: "Le chat d'Immbot AI répond avec des citations vérifiables et montre ses consultations du cours",364    title: "Le chat qui montre ses sources",365    caption: "L'assistant consulte le plan et lit les diapositives sous vos yeux, puis cite chaque affirmation — cliquez S1 pour voir l'extrait original.",366  },367  {368    src: "/screens/diapositives.png",369    url: "immbot.ai/apprendre/imm1033/diapositives",370    alt: "La visionneuse de diapositives avec sommaire et encadrés colorés",371    title: "Les 28 séances consultables en ligne",372    caption: "Navigation au clavier, recherche dans la séance, définitions et formules en encadrés, PDF original téléchargeable.",373  },374  {375    src: "/screens/progression.png",376    url: "immbot.ai/apprendre/imm1003",377    alt: "Le tableau de progression avec maîtrise estimée, série de jours et statistiques",378    title: "Votre progression, honnêtement",379    caption: "Maîtrise estimée par notion, série de jours d'étude, cartes dues et recommandations justifiées — visible par vous seul·e.",380  },381  {382    src: "/screens/concepts.png",383    url: "immbot.ai/apprendre/imm1003/concepts",384    alt: "La carte interactive des concepts du cours, colorée selon la maîtrise",385    title: "La carte des concepts du cours",386    caption: "Les notions semaine par semaine avec leurs préalables, colorées selon votre maîtrise — un clic pour réviser, quizzer ou en discuter.",387  },388] as const;389390const PILLARS = [391  {392    eyebrow: "Comprendre",393    title: "Poser des questions au cours",394    icon: MessageSquareText,395    items: [396      { icon: FileSearch, label: "Citations exactes", text: "chaque réponse renvoie à la séance et à la diapositive, vérifiables d'un clic" },397      { icon: BrainCircuit, label: "Cours interactif", text: "l'assistant consulte le plan et lit les diapositives sous vos yeux" },398      { icon: Presentation, label: "Diapositives en ligne", text: "les 28 séances consultables avec recherche, sommaire et PDF original" },399    ],400  },401  {402    eyebrow: "Pratiquer",403    title: "Se tester régulièrement",404    icon: BookOpenCheck,405    items: [406      { icon: Layers, label: "Cartes mémoire", text: "répétition espacée pour retenir formules et taxonomies durablement" },407      { icon: BookOpenCheck, label: "Quiz adaptatifs", text: "la difficulté suit vos réponses et cible vos faiblesses" },408      { icon: Timer, label: "Examens blancs", text: "intra et final simulés, chronométrés, corrigés par compétence" },409    ],410  },411  {412    eyebrow: "Progresser",413    title: "Savoir où vous en êtes",414    icon: LineChart,415    items: [416      { icon: Network, label: "Carte des concepts", text: "les notions du cours et leurs préalables, colorées selon votre maîtrise" },417      { icon: LineChart, label: "Progression honnête", text: "une estimation par notion, fondée sur la difficulté et la récence" },418      { icon: GraduationCap, label: "Plan d'étude", text: "généré depuis votre date d'examen et vos disponibilités, il s'adapte" },419    ],420  },421] as const;422