SPB Git forge

spb/hilmacorp

Public
1commits 1branches 0releases
29.2 MBsize
maindefault branch
20 days agolast push
TypeScript 93.3% JavaScript 4.4% CSS 2.3%

chore(retrait cluster): synchro copie du nœud M3U96a + README « URL live hors fonction » (2026-09-04)

Simon-Pierre Boucher committed 20 days ago (Sep 4, 2026)

46 changed files +5,101 −0

added .gitignore +15 −0
@@ -0,0 +1,15 @@
1 +# Author: Simon-Pierre Boucher
2 +# Contact: contact@spboucher.ai
3 +# Project: Hilmacorp.ai — Web Platform
4 +# File: .gitignore
5 +# Description: Files and directories excluded from version control
6 +
7 +/node_modules
8 +/.next
9 +/out
10 +.env
11 +.env.local
12 +.DS_Store
13 +*.tsbuildinfo
14 +next-env.d.ts
15 +/data
added CLAUDE.md +124 −0
@@ -0,0 +1,124 @@
1 +# CLAUDE.md — Plateforme Web Hilmacorp.ai
2 +
3 +## Objectif du projet
4 +
5 +Construire le site web officiel de **Hilmacorp.ai**, une entreprise d'intelligence artificielle fondée par **Simon-Pierre Boucher** et **Massamba Mbaye**. Le site doit être moderne, professionnel, rapide et bilingue (français / anglais), et présenter l'entreprise, ses services, ses fondateurs et sa vision.
6 +
7 +---
8 +
9 +## ⚠️ RÈGLE OBLIGATOIRE — En-tête de chaque fichier
10 +
11 +**Chaque fichier créé dans ce projet (code, config, style, composant, script, etc.) DOIT commencer par l'en-tête suivant**, adapté à la syntaxe de commentaire du langage :
12 +
13 +```
14 +/**
15 + * Author: Simon-Pierre Boucher
16 + * Contact: contact@spboucher.ai
17 + * Project: Hilmacorp.ai — Web Platform
18 + * File: <chemin/nom-du-fichier>
19 + * Description: <courte description du rôle du fichier>
20 + */
21 +```
22 +
23 +Exemples selon le type de fichier :
24 +
25 +- **JS / TS / TSX / CSS** : bloc `/** ... */`
26 +- **HTML** : `<!-- Author: Simon-Pierre Boucher | Contact: contact@spboucher.ai | ... -->`
27 +- **Python / Shell / YAML** : lignes commençant par `#`
28 +- **Markdown** : bloc de commentaire HTML `<!-- ... -->` en tête de fichier
29 +- **JSON** : exception — pas de commentaire possible; ajouter si pertinent un champ `"_author": "Simon-Pierre Boucher <contact@spboucher.ai>"` quand le schéma le permet (ex. `package.json` → champ `author`).
30 +
31 +**Ne jamais créer ou modifier un fichier sans vérifier la présence de cet en-tête.**
32 +
33 +---
34 +
35 +## Stack technique recommandé
36 +
37 +- **Framework** : Next.js 14+ (App Router) avec TypeScript
38 +- **Styles** : Tailwind CSS
39 +- **Composants** : composants React réutilisables, design system léger
40 +- **i18n** : support français / anglais (next-intl ou équivalent)
41 +- **Déploiement cible** : Vercel ou infrastructure privée (le code doit rester portable)
42 +- **SEO** : métadonnées complètes, Open Graph, sitemap, robots.txt
43 +- **Formulaire de contact** : API route avec validation, envoi vers `contact@spboucher.ai`
44 +
45 +---
46 +
47 +## Structure du site (pages)
48 +
49 +1. **Accueil (`/`)**
50 + - Hero : « Hilmacorp.ai builds intelligent systems for organizations that want to work, analyze, decide, and innovate differently. »
51 + - Présentation courte de l'entreprise
52 + - Aperçu des services principaux avec liens
53 + - Appel à l'action (contact / démonstration)
54 +
55 +2. **À propos (`/about`)**
56 + - Mission : rendre l'IA avancée utile, accessible et opérationnelle pour les organisations de toutes tailles
57 + - Approche (processus itératif en 8 étapes : identifier le problème → évaluer données/infrastructure → concevoir l'architecture → prototype → tests → mesure de la valeur → déploiement contrôlé → amélioration continue)
58 + - Vision de l'entreprise
59 +
60 +3. **Fondateurs (`/founders`)**
61 + - **Simon-Pierre Boucher** : recherche en IA, grands modèles de langage (LLM), agents IA, analyse de données, modélisation financière, développement logiciel, systèmes IA avancés, modèles open-weight, infrastructure locale, systèmes multimodaux, workflows agentiques.
62 + - **Massamba Mbaye** : leadership, développement stratégique, opérations d'affaires, partenariats, déploiement commercial de technologies innovantes, lien entre capacités techniques et besoins organisationnels.
63 +
64 +4. **Services (`/services`)** — une page index + sous-pages ou sections pour :
65 + - Agents IA et automatisation intelligente
66 + - Grands modèles de langage (approche indépendante des fournisseurs, routage multi-modèles)
67 + - IA privée et locale (déploiements open-weight, Apple Silicon, GPU, architectures hybrides)
68 + - RAG — Retrieval-Augmented Generation (citations, contrôles d'accès, journaux d'audit)
69 + - Analyse de données et recherche (finance, immobilier, veille concurrentielle, prévision…)
70 + - IA multimodale (texte, images, PDF, tableaux, audio, vidéo, données structurées)
71 + - Stratégie et consultation IA (évaluations d'opportunités, feuilles de route, POC, formation)
72 + - Développement logiciel sur mesure (plateformes, APIs, tableaux de bord, intégrations)
73 +
74 +5. **Sécurité et déploiement responsable (`/security`)**
75 + - Contrôles d'accès par rôles, authentification sécurisée, chiffrement, hébergement privé de modèles, workflows d'approbation humaine, monitoring, journalisation, validation des sorties.
76 +
77 +6. **Industries (`/industries`)**
78 + - Services financiers, immobilier, éducation, recherche académique, services professionnels, administration publique, technologie, consultation, assurance, juridique et conformité, médias, commerce de détail.
79 +
80 +7. **Contact (`/contact`)**
81 + - Formulaire (nom, organisation, courriel, message)
82 + - Courriel de contact affiché : **contact@spboucher.ai**
83 +
84 +---
85 +
86 +## Directives de design
87 +
88 +- Esthétique sobre, technologique et crédible : fond sombre ou clair épuré, typographie soignée, beaucoup d'espace blanc.
89 +- Éviter les templates génériques; le design doit paraître intentionnel et distinctif.
90 +- Palette suggérée : tons neutres profonds + un accent (bleu profond ou vert technologique).
91 +- Responsive obligatoire (mobile-first).
92 +- Accessibilité : contrastes AA minimum, navigation clavier, balises sémantiques.
93 +- Performance : images optimisées, chargement paresseux, score Lighthouse ≥ 90.
94 +
95 +---
96 +
97 +## Contenu
98 +
99 +- Tout le contenu textuel provient du document de présentation Hilmacorp.ai (mission, services, approche, vision). Reprendre ce contenu fidèlement, en versions française et anglaise.
100 +- Ton : professionnel, clair, orienté résultats. Pas de jargon marketing vide.
101 +
102 +---
103 +
104 +## Conventions de développement
105 +
106 +- TypeScript strict activé.
107 +- Composants dans `components/`, pages dans `app/`, contenus i18n dans `messages/` ou `locales/`.
108 +- Un composant = un fichier. Nommage PascalCase pour les composants.
109 +- Commits atomiques avec messages clairs.
110 +- Tests de base pour les API routes (formulaire de contact).
111 +- Aucune clé API ou secret dans le code — utiliser les variables d'environnement (`.env.example` fourni).
112 +
113 +---
114 +
115 +## Checklist avant livraison
116 +
117 +- [ ] En-tête **Simon-Pierre Boucher / contact@spboucher.ai** présent dans chaque fichier
118 +- [ ] Site bilingue FR/EN fonctionnel
119 +- [ ] Toutes les pages listées ci-dessus créées
120 +- [ ] Formulaire de contact fonctionnel avec validation
121 +- [ ] SEO et métadonnées configurés
122 +- [ ] Responsive vérifié (mobile, tablette, desktop)
123 +- [ ] Lighthouse ≥ 90 (performance, accessibilité, SEO)
124 +- [ ] `README.md` avec instructions d'installation et de déploiement
added README.md +72 −0
@@ -0,0 +1,72 @@
1 +> ⚠️ **URL live hors fonction pour le moment.** L'application a été retirée du cluster MacLustr le 2026-09-04 (processus arrêtés, copie du nœud supprimée). Ce dépôt spbgit est désormais la seule source de vérité du projet. Ancienne URL : https://www.hilmacorp.ai
2 +
3 +<!--
4 +Author: Simon-Pierre Boucher
5 +Contact: contact@spboucher.ai
6 +Project: Hilmacorp.ai — Web Platform
7 +File: README.md
8 +Description: Installation, development, testing, and deployment instructions
9 +-->
10 +
11 +# Hilmacorp.ai — Web Platform
12 +
13 +Official bilingual (FR/EN) corporate website of **Hilmacorp.ai**, an artificial-intelligence
14 +company founded by **Simon-Pierre Boucher** and **Massamba Mbaye**.
15 +
16 +## Stack
17 +
18 +- **Next.js 15** (App Router) + **TypeScript** (strict)
19 +- **Tailwind CSS v4**
20 +- Custom lightweight i18n (`/fr` and `/en` locale segments, dictionaries in `messages/`)
21 +- Contact form API route with server-side validation (`/api/contact`)
22 +- Full SEO: per-page metadata, Open Graph, `sitemap.xml`, `robots.txt`
23 +
24 +## Project layout
25 +
26 +```
27 +app/
28 + [locale]/ Pages (home, about, founders, services, security, industries, contact)
29 + api/contact/ Contact form API route
30 + robots.ts sitemap.ts SEO metadata routes
31 +components/ Reusable React components (PascalCase, one component per file)
32 +lib/ i18n loader + contact validation (pure, tested)
33 +messages/ fr.json / en.json content dictionaries
34 +tests/ node:test unit tests for the API validation
35 +```
36 +
37 +## Installation
38 +
39 +```bash
40 +npm install
41 +cp .env.example .env # then fill in values as needed
42 +```
43 +
44 +## Development
45 +
46 +```bash
47 +npm run dev # http://localhost:3000 → redirects to /fr or /en
48 +```
49 +
50 +## Tests
51 +
52 +```bash
53 +npm test # node --test (requires Node ≥ 22 for native TS type stripping)
54 +```
55 +
56 +## Production build & start
57 +
58 +```bash
59 +npm run build
60 +npm run start -- -p 8070
61 +```
62 +
63 +## Environment variables
64 +
65 +See `.env.example`. No secrets are committed. If `RESEND_API_KEY` is unset, contact
66 +messages are validated and appended to `data/contact-messages.jsonl` on the server.
67 +
68 +## Deployment
69 +
70 +The site is portable: Vercel, any Node host, or private infrastructure.
71 +Current production deployment: MacLustr node **M3U96a**, PM2 (`hilmacorp` + `hilmacorp-ngrok`),
72 +exposed at **https://www.hilmacorp.ai** via ngrok on port 8070.
added app/[locale]/[...rest]/page.tsx +13 −0
@@ -0,0 +1,13 @@
1 +/**
2 + * Author: Simon-Pierre Boucher
3 + * Contact: contact@spboucher.ai
4 + * Project: Hilmacorp.ai — Web Platform
5 + * File: app/[locale]/[...rest]/page.tsx
6 + * Description: Catch-all route delegating unknown paths to the locale-aware 404 page
7 + */
8 +
9 +import { notFound } from "next/navigation";
10 +
11 +export default function CatchAll() {
12 + notFound();
13 +}
added app/[locale]/about/page.tsx +104 −0
@@ -0,0 +1,104 @@
1 +/**
2 + * Author: Simon-Pierre Boucher
3 + * Contact: contact@spboucher.ai
4 + * Project: Hilmacorp.ai — Web Platform
5 + * File: app/[locale]/about/page.tsx
6 + * Description: About page — mission, iterative eight-step approach, principles, and company vision
7 + */
8 +
9 +import type { Metadata } from "next";
10 +import Container from "@/components/Container";
11 +import SectionHeading from "@/components/SectionHeading";
12 +import { getMessages, isLocale, type Locale } from "@/lib/i18n";
13 +
14 +export async function generateMetadata({
15 + params,
16 +}: {
17 + params: Promise<{ locale: string }>;
18 +}): Promise<Metadata> {
19 + const { locale } = await params;
20 + if (!isLocale(locale)) return {};
21 + const { meta } = getMessages(locale);
22 + return { title: { absolute: meta.about.title }, description: meta.about.description };
23 +}
24 +
25 +export default async function AboutPage({
26 + params,
27 +}: {
28 + params: Promise<{ locale: string }>;
29 +}) {
30 + const { locale } = await params;
31 + const { about } = getMessages(locale as Locale);
32 +
33 + return (
34 + <>
35 + <section className="relative overflow-hidden border-b border-line">
36 + <div className="grid-backdrop absolute inset-0" aria-hidden="true" />
37 + <Container className="relative py-20 sm:py-28">
38 + <SectionHeading as="h1" title={about.title} lead={about.lead} />
39 + </Container>
40 + </section>
41 +
42 + {/* Mission */}
43 + <section className="py-20 sm:py-24">
44 + <Container className="grid gap-10 lg:grid-cols-[1fr_2fr]">
45 + <h2 className="font-display text-2xl font-semibold tracking-tight text-accent">
46 + {about.missionTitle}
47 + </h2>
48 + <p className="text-lg leading-relaxed text-ink">{about.missionBody}</p>
49 + </Container>
50 + </section>
51 +
52 + {/* Approach — 8 steps */}
53 + <section className="border-t border-line bg-paper-2 py-20 sm:py-24">
54 + <Container>
55 + <SectionHeading title={about.approachTitle} lead={about.approachLead} />
56 + <ol className="mt-12 grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
57 + {about.steps.map((step, index) => (
58 + <li
59 + key={step.title}
60 + className="rounded-2xl border border-line bg-card p-6"
61 + >
62 + <p className="font-mono text-sm text-accent">
63 + {String(index + 1).padStart(2, "0")}
64 + </p>
65 + <h3 className="mt-3 text-base font-semibold text-ink">{step.title}</h3>
66 + <p className="mt-2 text-sm leading-relaxed text-ink-faint">{step.body}</p>
67 + </li>
68 + ))}
69 + </ol>
70 + </Container>
71 + </section>
72 +
73 + {/* Principles */}
74 + <section className="py-20 sm:py-24">
75 + <Container>
76 + <SectionHeading title={about.principlesTitle} lead={about.principlesLead} />
77 + <ul className="mt-12 grid gap-x-12 gap-y-10 sm:grid-cols-2">
78 + {about.principles.map((principle) => (
79 + <li key={principle.title} className="border-l-2 border-accent pl-6">
80 + <h3 className="font-display text-xl font-semibold text-ink">
81 + {principle.title}
82 + </h3>
83 + <p className="mt-2 leading-relaxed text-ink-soft">{principle.body}</p>
84 + </li>
85 + ))}
86 + </ul>
87 + </Container>
88 + </section>
89 +
90 + {/* Vision */}
91 + <section className="border-t border-line bg-ink py-20 sm:py-24">
92 + <Container className="grid gap-10 lg:grid-cols-[1fr_2fr]">
93 + <h2 className="font-display text-2xl font-semibold tracking-tight text-paper">
94 + {about.visionTitle}
95 + </h2>
96 + <div className="space-y-5">
97 + <p className="text-lg leading-relaxed text-paper/80">{about.visionBody1}</p>
98 + <p className="text-lg leading-relaxed text-paper/80">{about.visionBody2}</p>
99 + </div>
100 + </Container>
101 + </section>
102 + </>
103 + );
104 +}
added app/[locale]/apps/page.tsx +168 −0
@@ -0,0 +1,168 @@
1 +/**
2 + * Author: Simon-Pierre Boucher
3 + * Contact: contact@spboucher.ai
4 + * Project: Hilmacorp.ai — Web Platform
5 + * File: app/[locale]/apps/page.tsx
6 + * Description: Apps page — web platforms (AI Risk Index, LLM Index) and the Zyquo family
7 + * of native macOS applications with direct DMG downloads
8 + */
9 +
10 +import type { Metadata } from "next";
11 +import Container from "@/components/Container";
12 +import SectionHeading from "@/components/SectionHeading";
13 +import { getMessages, isLocale, type Locale } from "@/lib/i18n";
14 +
15 +export async function generateMetadata({
16 + params,
17 +}: {
18 + params: Promise<{ locale: string }>;
19 +}): Promise<Metadata> {
20 + const { locale } = await params;
21 + if (!isLocale(locale)) return {};
22 + const { meta } = getMessages(locale);
23 + return { title: { absolute: meta.apps.title }, description: meta.apps.description };
24 +}
25 +
26 +function CheckIcon() {
27 + return (
28 + <svg
29 + width="16"
30 + height="16"
31 + viewBox="0 0 16 16"
32 + fill="none"
33 + aria-hidden="true"
34 + className="mt-0.5 shrink-0"
35 + >
36 + <path
37 + d="M3 8.5l3.2 3L13 4.5"
38 + stroke="#0b6b4a"
39 + strokeWidth="1.8"
40 + strokeLinecap="round"
41 + strokeLinejoin="round"
42 + />
43 + </svg>
44 + );
45 +}
46 +
47 +export default async function AppsPage({
48 + params,
49 +}: {
50 + params: Promise<{ locale: string }>;
51 +}) {
52 + const { locale } = await params;
53 + const { apps } = getMessages(locale as Locale);
54 +
55 + return (
56 + <>
57 + <section className="relative overflow-hidden border-b border-line">
58 + <div className="grid-backdrop absolute inset-0" aria-hidden="true" />
59 + <Container className="relative py-20 sm:py-28">
60 + <SectionHeading as="h1" title={apps.title} lead={apps.lead} />
61 + </Container>
62 + </section>
63 +
64 + {/* Web platforms */}
65 + <section className="py-16 sm:py-20">
66 + <Container>
67 + <SectionHeading title={apps.webTitle} lead={apps.webLead} />
68 + <div className="mt-10 grid gap-6 lg:grid-cols-2">
69 + {apps.web.map((app) => (
70 + <article
71 + key={app.id}
72 + className="flex flex-col rounded-3xl border border-line bg-card p-8 shadow-sm sm:p-10"
73 + >
74 + <p className="font-mono text-xs uppercase tracking-[0.2em] text-ink-faint">
75 + {app.urlLabel}
76 + </p>
77 + <h3 className="mt-3 font-display text-2xl font-semibold tracking-tight text-ink">
78 + {app.name}
79 + </h3>
80 + <p className="mt-2 font-medium text-accent">{app.tagline}</p>
81 + <p className="mt-4 leading-relaxed text-ink-soft">{app.description}</p>
82 + <ul className="mt-6 grid gap-2.5">
83 + {app.points.map((point) => (
84 + <li key={point} className="flex items-start gap-2.5 text-sm text-ink">
85 + <CheckIcon />
86 + {point}
87 + </li>
88 + ))}
89 + </ul>
90 + <div className="mt-8 flex-1" />
91 + <a
92 + href={app.url}
93 + target="_blank"
94 + rel="noopener noreferrer"
95 + className="inline-flex w-fit items-center gap-2 rounded-full bg-ink px-6 py-2.5 text-sm font-medium text-paper transition-colors hover:bg-accent-strong"
96 + >
97 + {apps.visit}
98 + <svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
99 + <path
100 + d="M4 10L10 4M10 4H5.5M10 4v4.5"
101 + stroke="currentColor"
102 + strokeWidth="1.6"
103 + strokeLinecap="round"
104 + strokeLinejoin="round"
105 + />
106 + </svg>
107 + </a>
108 + </article>
109 + ))}
110 + </div>
111 + </Container>
112 + </section>
113 +
114 + {/* macOS apps */}
115 + <section className="border-t border-line bg-paper-2 py-16 sm:py-20">
116 + <Container>
117 + <SectionHeading title={apps.macTitle} lead={apps.macLead} />
118 + <div className="mt-10 grid gap-6 lg:grid-cols-2">
119 + {apps.mac.map((app) => (
120 + <article
121 + key={app.id}
122 + className="flex flex-col rounded-3xl border border-line bg-card p-8 shadow-sm sm:p-10"
123 + >
124 + <p className="font-mono text-xs uppercase tracking-[0.2em] text-ink-faint">
125 + {app.requires}
126 + </p>
127 + <h3 className="mt-3 font-display text-2xl font-semibold tracking-tight text-ink">
128 + {app.name}
129 + </h3>
130 + <p className="mt-2 font-medium text-accent">{app.tagline}</p>
131 + <p className="mt-4 leading-relaxed text-ink-soft">{app.description}</p>
132 + <ul className="mt-6 grid gap-2.5">
133 + {app.points.map((point) => (
134 + <li key={point} className="flex items-start gap-2.5 text-sm text-ink">
135 + <CheckIcon />
136 + {point}
137 + </li>
138 + ))}
139 + </ul>
140 + <div className="mt-8 flex-1" />
141 + <div className="flex flex-wrap items-center gap-4">
142 + <a
143 + href={`/downloads/${app.file}`}
144 + download
145 + className="inline-flex items-center gap-2 rounded-full bg-ink px-6 py-2.5 text-sm font-medium text-paper transition-colors hover:bg-accent-strong"
146 + >
147 + <svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
148 + <path
149 + d="M7 1.5v8m0 0L3.8 6.3M7 9.5l3.2-3.2M2 12.5h10"
150 + stroke="currentColor"
151 + strokeWidth="1.6"
152 + strokeLinecap="round"
153 + strokeLinejoin="round"
154 + />
155 + </svg>
156 + {apps.download}
157 + </a>
158 + <span className="font-mono text-xs text-ink-faint">{app.size}</span>
159 + </div>
160 + </article>
161 + ))}
162 + </div>
163 + <p className="mt-10 text-sm text-ink-faint">{apps.macNote}</p>
164 + </Container>
165 + </section>
166 + </>
167 + );
168 +}
added app/[locale]/contact/page.tsx +78 −0
@@ -0,0 +1,78 @@
1 +/**
2 + * Author: Simon-Pierre Boucher
3 + * Contact: contact@spboucher.ai
4 + * Project: Hilmacorp.ai — Web Platform
5 + * File: app/[locale]/contact/page.tsx
6 + * Description: Contact page — practical information cards and validated contact form
7 + */
8 +
9 +import type { Metadata } from "next";
10 +import Container from "@/components/Container";
11 +import SectionHeading from "@/components/SectionHeading";
12 +import ContactForm from "@/components/ContactForm";
13 +import { getMessages, isLocale, type Locale } from "@/lib/i18n";
14 +
15 +export async function generateMetadata({
16 + params,
17 +}: {
18 + params: Promise<{ locale: string }>;
19 +}): Promise<Metadata> {
20 + const { locale } = await params;
21 + if (!isLocale(locale)) return {};
22 + const { meta } = getMessages(locale);
23 + return { title: { absolute: meta.contact.title }, description: meta.contact.description };
24 +}
25 +
26 +export default async function ContactPage({
27 + params,
28 +}: {
29 + params: Promise<{ locale: string }>;
30 +}) {
31 + const { locale } = await params;
32 + const { contact } = getMessages(locale as Locale);
33 +
34 + const infoCards = [
35 + {
36 + title: contact.infoEmailTitle,
37 + body: (
38 + <a
39 + href="mailto:contact@spboucher.ai"
40 + className="font-medium text-accent hover:text-accent-strong"
41 + >
42 + contact@spboucher.ai
43 + </a>
44 + ),
45 + },
46 + { title: contact.infoResponseTitle, body: contact.infoResponse },
47 + { title: contact.infoLanguagesTitle, body: contact.infoLanguages },
48 + ];
49 +
50 + return (
51 + <>
52 + <section className="relative overflow-hidden border-b border-line">
53 + <div className="grid-backdrop absolute inset-0" aria-hidden="true" />
54 + <Container className="relative py-20 sm:py-28">
55 + <SectionHeading as="h1" title={contact.title} lead={contact.lead} />
56 + </Container>
57 + </section>
58 +
59 + <section className="py-16 sm:py-20">
60 + <Container className="grid gap-12 lg:grid-cols-[1fr_2fr]">
61 + <div className="flex flex-col gap-4">
62 + {infoCards.map((card) => (
63 + <div key={card.title} className="rounded-2xl border border-line bg-card p-6">
64 + <h2 className="font-mono text-xs uppercase tracking-[0.25em] text-ink-faint">
65 + {card.title}
66 + </h2>
67 + <p className="mt-2.5 text-sm leading-relaxed text-ink-soft">{card.body}</p>
68 + </div>
69 + ))}
70 + </div>
71 + <div className="rounded-3xl border border-line bg-card p-8 shadow-sm sm:p-10">
72 + <ContactForm strings={contact.form} />
73 + </div>
74 + </Container>
75 + </section>
76 + </>
77 + );
78 +}
added app/[locale]/founders/page.tsx +104 −0
@@ -0,0 +1,104 @@
1 +/**
2 + * Author: Simon-Pierre Boucher
3 + * Contact: contact@spboucher.ai
4 + * Project: Hilmacorp.ai — Web Platform
5 + * File: app/[locale]/founders/page.tsx
6 + * Description: Founders page — profiles of Simon-Pierre Boucher and Massamba Mbaye
7 + */
8 +
9 +import type { Metadata } from "next";
10 +import Container from "@/components/Container";
11 +import SectionHeading from "@/components/SectionHeading";
12 +import { getMessages, isLocale, type Locale } from "@/lib/i18n";
13 +
14 +export async function generateMetadata({
15 + params,
16 +}: {
17 + params: Promise<{ locale: string }>;
18 +}): Promise<Metadata> {
19 + const { locale } = await params;
20 + if (!isLocale(locale)) return {};
21 + const { meta } = getMessages(locale);
22 + return { title: { absolute: meta.founders.title }, description: meta.founders.description };
23 +}
24 +
25 +function FounderCard({
26 + name,
27 + role,
28 + bio,
29 + tags,
30 + initials,
31 +}: {
32 + name: string;
33 + role: string;
34 + bio: string;
35 + tags: string[];
36 + initials: string;
37 +}) {
38 + return (
39 + <article className="rounded-3xl border border-line bg-card p-8 shadow-sm sm:p-10">
40 + <div className="flex items-center gap-5">
41 + <span
42 + aria-hidden="true"
43 + className="flex h-16 w-16 shrink-0 items-center justify-center rounded-2xl bg-ink font-mono text-lg font-semibold text-paper"
44 + >
45 + {initials}
46 + </span>
47 + <div>
48 + <h2 className="font-display text-2xl font-semibold text-ink">{name}</h2>
49 + <p className="mt-1 text-sm font-medium text-accent">{role}</p>
50 + </div>
51 + </div>
52 + <p className="mt-6 leading-relaxed text-ink-soft">{bio}</p>
53 + <ul className="mt-6 flex flex-wrap gap-2">
54 + {tags.map((tag) => (
55 + <li
56 + key={tag}
57 + className="rounded-full bg-accent-soft px-3 py-1 text-xs font-medium text-accent-strong"
58 + >
59 + {tag}
60 + </li>
61 + ))}
62 + </ul>
63 + </article>
64 + );
65 +}
66 +
67 +export default async function FoundersPage({
68 + params,
69 +}: {
70 + params: Promise<{ locale: string }>;
71 +}) {
72 + const { locale } = await params;
73 + const { founders } = getMessages(locale as Locale);
74 +
75 + return (
76 + <>
77 + <section className="relative overflow-hidden border-b border-line">
78 + <div className="grid-backdrop absolute inset-0" aria-hidden="true" />
79 + <Container className="relative py-20 sm:py-28">
80 + <SectionHeading as="h1" title={founders.title} lead={founders.lead} />
81 + </Container>
82 + </section>
83 +
84 + <section className="py-20 sm:py-24">
85 + <Container className="grid gap-8 lg:grid-cols-2">
86 + <FounderCard
87 + name={founders.spb.name}
88 + role={founders.spb.role}
89 + bio={founders.spb.bio}
90 + tags={founders.spb.tags}
91 + initials="SPB"
92 + />
93 + <FounderCard
94 + name={founders.mm.name}
95 + role={founders.mm.role}
96 + bio={founders.mm.bio}
97 + tags={founders.mm.tags}
98 + initials="MM"
99 + />
100 + </Container>
101 + </section>
102 + </>
103 + );
104 +}
added app/[locale]/industries/page.tsx +61 −0
@@ -0,0 +1,61 @@
1 +/**
2 + * Author: Simon-Pierre Boucher
3 + * Contact: contact@spboucher.ai
4 + * Project: Hilmacorp.ai — Web Platform
5 + * File: app/[locale]/industries/page.tsx
6 + * Description: Industries page — the twelve sectors served by Hilmacorp.ai
7 + */
8 +
9 +import type { Metadata } from "next";
10 +import Container from "@/components/Container";
11 +import SectionHeading from "@/components/SectionHeading";
12 +import { getMessages, isLocale, type Locale } from "@/lib/i18n";
13 +
14 +export async function generateMetadata({
15 + params,
16 +}: {
17 + params: Promise<{ locale: string }>;
18 +}): Promise<Metadata> {
19 + const { locale } = await params;
20 + if (!isLocale(locale)) return {};
21 + const { meta } = getMessages(locale);
22 + return { title: { absolute: meta.industries.title }, description: meta.industries.description };
23 +}
24 +
25 +export default async function IndustriesPage({
26 + params,
27 +}: {
28 + params: Promise<{ locale: string }>;
29 +}) {
30 + const { locale } = await params;
31 + const { industries } = getMessages(locale as Locale);
32 +
33 + return (
34 + <>
35 + <section className="relative overflow-hidden border-b border-line">
36 + <div className="grid-backdrop absolute inset-0" aria-hidden="true" />
37 + <Container className="relative py-20 sm:py-28">
38 + <SectionHeading as="h1" title={industries.title} lead={industries.lead} />
39 + </Container>
40 + </section>
41 +
42 + <section className="py-16 sm:py-20">
43 + <Container>
44 + <ul className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
45 + {industries.items.map((industry) => (
46 + <li
47 + key={industry.title}
48 + className="rounded-2xl border border-line bg-card p-7 shadow-sm transition-colors hover:border-accent"
49 + >
50 + <h2 className="font-display text-lg font-semibold text-ink">
51 + {industry.title}
52 + </h2>
53 + <p className="mt-2 text-sm leading-relaxed text-ink-faint">{industry.body}</p>
54 + </li>
55 + ))}
56 + </ul>
57 + </Container>
58 + </section>
59 + </>
60 + );
61 +}
added app/[locale]/layout.tsx +84 −0
@@ -0,0 +1,84 @@
1 +/**
2 + * Author: Simon-Pierre Boucher
3 + * Contact: contact@spboucher.ai
4 + * Project: Hilmacorp.ai — Web Platform
5 + * File: app/[locale]/layout.tsx
6 + * Description: Locale-scoped root layout — HTML shell, fonts, global metadata, header and footer
7 + */
8 +
9 +import type { Metadata } from "next";
10 +import { Inter, Fraunces } from "next/font/google";
11 +import { notFound } from "next/navigation";
12 +import Header from "@/components/Header";
13 +import Footer from "@/components/Footer";
14 +import CookieConsent from "@/components/CookieConsent";
15 +import { getMessages, isLocale, locales, type Locale } from "@/lib/i18n";
16 +import "@/app/globals.css";
17 +
18 +const inter = Inter({ subsets: ["latin"], variable: "--font-inter", display: "swap" });
19 +const fraunces = Fraunces({ subsets: ["latin"], variable: "--font-fraunces", display: "swap" });
20 +
21 +const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? "https://www.hilmacorp.ai";
22 +
23 +export function generateStaticParams() {
24 + return locales.map((locale) => ({ locale }));
25 +}
26 +
27 +export async function generateMetadata({
28 + params,
29 +}: {
30 + params: Promise<{ locale: string }>;
31 +}): Promise<Metadata> {
32 + const { locale } = await params;
33 + if (!isLocale(locale)) return {};
34 + const messages = getMessages(locale);
35 + return {
36 + metadataBase: new URL(SITE_URL),
37 + title: {
38 + default: messages.meta.home.title,
39 + template: `%s — ${messages.meta.siteName}`,
40 + },
41 + description: messages.meta.home.description,
42 + alternates: {
43 + languages: { fr: "/fr", en: "/en" },
44 + },
45 + openGraph: {
46 + type: "website",
47 + siteName: messages.meta.siteName,
48 + locale: locale === "fr" ? "fr_CA" : "en_US",
49 + url: `${SITE_URL}/${locale}`,
50 + title: messages.meta.home.title,
51 + description: messages.meta.home.description,
52 + },
53 + twitter: {
54 + card: "summary",
55 + title: messages.meta.home.title,
56 + description: messages.meta.home.description,
57 + },
58 + robots: { index: true, follow: true },
59 + };
60 +}
61 +
62 +export default async function LocaleLayout({
63 + children,
64 + params,
65 +}: {
66 + children: React.ReactNode;
67 + params: Promise<{ locale: string }>;
68 +}) {
69 + const { locale } = await params;
70 + if (!isLocale(locale)) notFound();
71 + const typedLocale = locale as Locale;
72 + const messages = getMessages(typedLocale);
73 +
74 + return (
75 + <html lang={typedLocale} className={`${inter.variable} ${fraunces.variable}`}>
76 + <body className="flex min-h-screen flex-col font-sans">
77 + <Header locale={typedLocale} nav={messages.nav} />
78 + <main className="flex-1">{children}</main>
79 + <Footer locale={typedLocale} messages={messages} />
80 + <CookieConsent locale={typedLocale} strings={messages.cookies} />
81 + </body>
82 + </html>
83 + );
84 +}
added app/[locale]/not-found.tsx +38 −0
@@ -0,0 +1,38 @@
1 +/**
2 + * Author: Simon-Pierre Boucher
3 + * Contact: contact@spboucher.ai
4 + * Project: Hilmacorp.ai — Web Platform
5 + * File: app/[locale]/not-found.tsx
6 + * Description: Locale-aware 404 page (light theme)
7 + */
8 +
9 +import Link from "next/link";
10 +import Container from "@/components/Container";
11 +
12 +export default function NotFound() {
13 + return (
14 + <Container className="py-32 text-center">
15 + <p className="font-mono text-sm uppercase tracking-[0.3em] text-accent">404</p>
16 + <h1 className="mt-4 font-display text-3xl font-semibold text-ink">
17 + Page not found / Page introuvable
18 + </h1>
19 + <p className="mt-4 text-ink-soft">
20 + The page you are looking for does not exist. / La page que vous cherchez n&apos;existe pas.
21 + </p>
22 + <div className="mt-8 flex justify-center gap-4">
23 + <Link
24 + href="/en"
25 + className="rounded-full border border-line-strong px-5 py-2 text-sm text-ink hover:border-accent hover:text-accent"
26 + >
27 + English home
28 + </Link>
29 + <Link
30 + href="/fr"
31 + className="rounded-full border border-line-strong px-5 py-2 text-sm text-ink hover:border-accent hover:text-accent"
32 + >
33 + Accueil français
34 + </Link>
35 + </div>
36 + </Container>
37 + );
38 +}
added app/[locale]/page.tsx +203 −0
@@ -0,0 +1,203 @@
1 +/**
2 + * Author: Simon-Pierre Boucher
3 + * Contact: contact@spboucher.ai
4 + * Project: Hilmacorp.ai — Web Platform
5 + * File: app/[locale]/page.tsx
6 + * Description: Home page — hero, introduction, commitments, services overview, method strip, call to action
7 + */
8 +
9 +import type { Metadata } from "next";
10 +import Link from "next/link";
11 +import Container from "@/components/Container";
12 +import SectionHeading from "@/components/SectionHeading";
13 +import { getMessages, isLocale, type Locale } from "@/lib/i18n";
14 +
15 +export async function generateMetadata({
16 + params,
17 +}: {
18 + params: Promise<{ locale: string }>;
19 +}): Promise<Metadata> {
20 + const { locale } = await params;
21 + if (!isLocale(locale)) return {};
22 + const { meta } = getMessages(locale);
23 + return { title: { absolute: meta.home.title }, description: meta.home.description };
24 +}
25 +
26 +export default async function HomePage({
27 + params,
28 +}: {
29 + params: Promise<{ locale: string }>;
30 +}) {
31 + const { locale } = await params;
32 + const typedLocale = locale as Locale;
33 + const messages = getMessages(typedLocale);
34 + const { home, services } = messages;
35 +
36 + return (
37 + <>
38 + {/* Hero */}
39 + <section className="relative overflow-hidden border-b border-line">
40 + <div className="grid-backdrop absolute inset-0" aria-hidden="true" />
41 + <Container className="relative py-24 sm:py-36">
42 + <p className="mb-5 font-mono text-xs uppercase tracking-[0.3em] text-accent">
43 + {home.heroKicker}
44 + </p>
45 + <h1 className="max-w-4xl font-display text-4xl font-semibold leading-[1.08] tracking-tight text-ink sm:text-6xl">
46 + {home.heroTitle}
47 + </h1>
48 + <p className="mt-8 max-w-2xl text-lg leading-relaxed text-ink-soft">
49 + {home.heroLead}
50 + </p>
51 + <div className="mt-10 flex flex-wrap gap-4">
52 + <Link
53 + href={`/${typedLocale}/contact`}
54 + className="rounded-full bg-ink px-7 py-3 text-sm font-medium text-paper transition-colors hover:bg-accent-strong"
55 + >
56 + {home.heroCtaPrimary}
57 + </Link>
58 + <Link
59 + href={`/${typedLocale}/services`}
60 + className="rounded-full border border-line-strong px-7 py-3 text-sm font-medium text-ink transition-colors hover:border-accent hover:text-accent"
61 + >
62 + {home.heroCtaSecondary}
63 + </Link>
64 + </div>
65 + </Container>
66 + </section>
67 +
68 + {/* Stats band */}
69 + <section className="border-b border-line bg-paper-2">
70 + <Container>
71 + <dl className="grid grid-cols-1 divide-y divide-line sm:grid-cols-3 sm:divide-x sm:divide-y-0">
72 + {[
73 + { value: "8", label: home.statsProjects },
74 + { value: "2", label: home.statsLanguages },
75 + { value: "8", label: home.statsSteps },
76 + ].map((stat) => (
77 + <div key={stat.label} className="px-2 py-9 text-center">
78 + <dd className="font-display text-4xl font-semibold text-accent">
79 + {stat.value}
80 + </dd>
81 + <dt className="mt-1 text-sm text-ink-faint">{stat.label}</dt>
82 + </div>
83 + ))}
84 + </dl>
85 + </Container>
86 + </section>
87 +
88 + {/* Introduction */}
89 + <section className="py-20 sm:py-28">
90 + <Container>
91 + <SectionHeading title={home.introTitle} />
92 + <div className="mt-8 grid gap-8 lg:grid-cols-2">
93 + <p className="leading-relaxed text-ink-soft">{home.introBody1}</p>
94 + <p className="leading-relaxed text-ink-soft">{home.introBody2}</p>
95 + </div>
96 + <div className="mt-10 flex flex-wrap gap-6">
97 + <Link
98 + href={`/${typedLocale}/about`}
99 + className="text-sm font-medium text-accent hover:text-accent-strong"
100 + >
101 + {home.introLinkAbout} →
102 + </Link>
103 + <Link
104 + href={`/${typedLocale}/founders`}
105 + className="text-sm font-medium text-accent hover:text-accent-strong"
106 + >
107 + {home.introLinkFounders} →
108 + </Link>
109 + </div>
110 + </Container>
111 + </section>
112 +
113 + {/* Commitments */}
114 + <section className="border-t border-line bg-paper-2 py-20 sm:py-28">
115 + <Container>
116 + <SectionHeading title={home.whyTitle} lead={home.whyLead} />
117 + <ul className="mt-12 grid gap-4 sm:grid-cols-2">
118 + {home.why.map((item, index) => (
119 + <li
120 + key={item.title}
121 + className="rounded-2xl border border-line bg-card p-8"
122 + >
123 + <p className="font-mono text-sm text-accent">
124 + {String(index + 1).padStart(2, "0")}
125 + </p>
126 + <h3 className="mt-3 font-display text-xl font-semibold text-ink">
127 + {item.title}
128 + </h3>
129 + <p className="mt-3 leading-relaxed text-ink-soft">{item.body}</p>
130 + </li>
131 + ))}
132 + </ul>
133 + </Container>
134 + </section>
135 +
136 + {/* Services overview */}
137 + <section className="py-20 sm:py-28">
138 + <Container>
139 + <SectionHeading title={home.servicesTitle} lead={home.servicesLead} />
140 + <ul className="mt-12 grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
141 + {services.items.map((service, index) => (
142 + <li key={service.id}>
143 + <Link
144 + href={`/${typedLocale}/services#${service.id}`}
145 + className="group block h-full rounded-2xl border border-line bg-card p-6 transition-colors hover:border-accent"
146 + >
147 + <p className="font-mono text-xs text-ink-faint">
148 + {String(index + 1).padStart(2, "0")}
149 + </p>
150 + <h3 className="mt-3 text-base font-semibold text-ink group-hover:text-accent">
151 + {service.title}
152 + </h3>
153 + </Link>
154 + </li>
155 + ))}
156 + </ul>
157 + <div className="mt-10">
158 + <Link
159 + href={`/${typedLocale}/services`}
160 + className="text-sm font-medium text-accent hover:text-accent-strong"
161 + >
162 + {home.servicesCtaAll} →
163 + </Link>
164 + </div>
165 + </Container>
166 + </section>
167 +
168 + {/* Method strip */}
169 + <section className="border-t border-line bg-ink py-16 sm:py-20">
170 + <Container className="flex flex-col items-start gap-6 sm:flex-row sm:items-center sm:justify-between">
171 + <div className="max-w-2xl">
172 + <h2 className="font-display text-2xl font-semibold tracking-tight text-paper sm:text-3xl">
173 + {home.processTitle}
174 + </h2>
175 + <p className="mt-3 leading-relaxed text-paper/70">{home.processBody}</p>
176 + </div>
177 + <Link
178 + href={`/${typedLocale}/about`}
179 + className="shrink-0 rounded-full border border-paper/30 px-6 py-2.5 text-sm font-medium text-paper transition-colors hover:border-paper hover:bg-paper hover:text-ink"
180 + >
181 + {home.processLink}
182 + </Link>
183 + </Container>
184 + </section>
185 +
186 + {/* Call to action */}
187 + <section className="py-20 sm:py-28">
188 + <Container className="text-center">
189 + <h2 className="font-display text-3xl font-semibold tracking-tight text-ink sm:text-4xl">
190 + {home.ctaTitle}
191 + </h2>
192 + <p className="mx-auto mt-5 max-w-2xl leading-relaxed text-ink-soft">{home.ctaBody}</p>
193 + <Link
194 + href={`/${typedLocale}/contact`}
195 + className="mt-9 inline-block rounded-full bg-ink px-8 py-3.5 text-sm font-medium text-paper transition-colors hover:bg-accent-strong"
196 + >
197 + {home.ctaButton}
198 + </Link>
199 + </Container>
200 + </section>
201 + </>
202 + );
203 +}
added app/[locale]/privacy/page.tsx +77 −0
@@ -0,0 +1,77 @@
1 +/**
2 + * Author: Simon-Pierre Boucher
3 + * Contact: contact@spboucher.ai
4 + * Project: Hilmacorp.ai — Web Platform
5 + * File: app/[locale]/privacy/page.tsx
6 + * Description: Privacy Policy page — data practices plus the cookie inventory and preferences access
7 + */
8 +
9 +import type { Metadata } from "next";
10 +import LegalPage from "@/components/LegalPage";
11 +import ManageCookiesButton from "@/components/ManageCookiesButton";
12 +import { getMessages, isLocale, type Locale } from "@/lib/i18n";
13 +
14 +export async function generateMetadata({
15 + params,
16 +}: {
17 + params: Promise<{ locale: string }>;
18 +}): Promise<Metadata> {
19 + const { locale } = await params;
20 + if (!isLocale(locale)) return {};
21 + const { meta } = getMessages(locale);
22 + return { title: { absolute: meta.privacy.title }, description: meta.privacy.description };
23 +}
24 +
25 +export default async function PrivacyPage({
26 + params,
27 +}: {
28 + params: Promise<{ locale: string }>;
29 +}) {
30 + const { locale } = await params;
31 + const { privacy } = getMessages(locale as Locale);
32 + const cookies = privacy.cookiesSection;
33 +
34 + return (
35 + <LegalPage
36 + title={privacy.title}
37 + lead={privacy.lead}
38 + lastUpdated={privacy.lastUpdated}
39 + sections={privacy.sections}
40 + >
41 + <section>
42 + <h2 className="font-display text-xl font-semibold text-ink">{cookies.title}</h2>
43 + <p className="mt-3 leading-relaxed text-ink-soft">{cookies.intro}</p>
44 + <div className="mt-6 overflow-x-auto rounded-2xl border border-line">
45 + <table className="w-full border-collapse bg-card text-left text-sm">
46 + <thead>
47 + <tr className="border-b border-line bg-paper-2">
48 + <th scope="col" className="px-5 py-3 font-semibold text-ink">
49 + {cookies.tableName}
50 + </th>
51 + <th scope="col" className="px-5 py-3 font-semibold text-ink">
52 + {cookies.tablePurpose}
53 + </th>
54 + <th scope="col" className="px-5 py-3 font-semibold text-ink">
55 + {cookies.tableDuration}
56 + </th>
57 + </tr>
58 + </thead>
59 + <tbody>
60 + {cookies.rows.map((row) => (
61 + <tr key={row.name} className="border-b border-line last:border-b-0">
62 + <td className="px-5 py-3 font-mono text-xs text-ink">{row.name}</td>
63 + <td className="px-5 py-3 text-ink-soft">{row.purpose}</td>
64 + <td className="px-5 py-3 text-ink-soft">{row.duration}</td>
65 + </tr>
66 + ))}
67 + </tbody>
68 + </table>
69 + </div>
70 + <ManageCookiesButton
71 + label={cookies.manage}
72 + className="mt-6 rounded-full border border-line-strong px-6 py-2.5 text-sm font-medium text-ink transition-colors hover:border-accent hover:text-accent"
73 + />
74 + </section>
75 + </LegalPage>
76 + );
77 +}
added app/[locale]/security/page.tsx +79 −0
@@ -0,0 +1,79 @@
1 +/**
2 + * Author: Simon-Pierre Boucher
3 + * Contact: contact@spboucher.ai
4 + * Project: Hilmacorp.ai — Web Platform
5 + * File: app/[locale]/security/page.tsx
6 + * Description: Security & responsible deployment page — governance controls and practices
7 + */
8 +
9 +import type { Metadata } from "next";
10 +import Container from "@/components/Container";
11 +import SectionHeading from "@/components/SectionHeading";
12 +import { getMessages, isLocale, type Locale } from "@/lib/i18n";
13 +
14 +export async function generateMetadata({
15 + params,
16 +}: {
17 + params: Promise<{ locale: string }>;
18 +}): Promise<Metadata> {
19 + const { locale } = await params;
20 + if (!isLocale(locale)) return {};
21 + const { meta } = getMessages(locale);
22 + return { title: { absolute: meta.security.title }, description: meta.security.description };
23 +}
24 +
25 +export default async function SecurityPage({
26 + params,
27 +}: {
28 + params: Promise<{ locale: string }>;
29 +}) {
30 + const { locale } = await params;
31 + const { security } = getMessages(locale as Locale);
32 +
33 + return (
34 + <>
35 + <section className="relative overflow-hidden border-b border-line">
36 + <div className="grid-backdrop absolute inset-0" aria-hidden="true" />
37 + <Container className="relative py-20 sm:py-28">
38 + <SectionHeading as="h1" title={security.title} lead={security.lead} />
39 + </Container>
40 + </section>
41 +
42 + <section className="py-16 sm:py-20">
43 + <Container>
44 + <ul className="grid gap-4 sm:grid-cols-2">
45 + {security.items.map((item) => (
46 + <li
47 + key={item.title}
48 + className="rounded-2xl border border-line bg-card p-7 shadow-sm"
49 + >
50 + <div className="flex items-start gap-4">
51 + <span
52 + aria-hidden="true"
53 + className="mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-accent-soft"
54 + >
55 + <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
56 + <path
57 + d="M8 1.5l5 2v4c0 3.2-2.1 5.6-5 7-2.9-1.4-5-3.8-5-7v-4l5-2z"
58 + stroke="#0b6b4a"
59 + strokeWidth="1.5"
60 + strokeLinejoin="round"
61 + />
62 + </svg>
63 + </span>
64 + <div>
65 + <h2 className="text-base font-semibold text-ink">{item.title}</h2>
66 + <p className="mt-2 text-sm leading-relaxed text-ink-faint">{item.body}</p>
67 + </div>
68 + </div>
69 + </li>
70 + ))}
71 + </ul>
72 + <p className="mx-auto mt-14 max-w-3xl border-l-2 border-accent pl-6 font-display text-xl leading-relaxed text-ink">
73 + {security.closing}
74 + </p>
75 + </Container>
76 + </section>
77 + </>
78 + );
79 +}
added app/[locale]/services/page.tsx +119 −0
@@ -0,0 +1,119 @@
1 +/**
2 + * Author: Simon-Pierre Boucher
3 + * Contact: contact@spboucher.ai
4 + * Project: Hilmacorp.ai — Web Platform
5 + * File: app/[locale]/services/page.tsx
6 + * Description: Services page — the eight practice areas as anchored sections, plus engagement model
7 + */
8 +
9 +import type { Metadata } from "next";
10 +import Link from "next/link";
11 +import Container from "@/components/Container";
12 +import SectionHeading from "@/components/SectionHeading";
13 +import { getMessages, isLocale, type Locale } from "@/lib/i18n";
14 +
15 +export async function generateMetadata({
16 + params,
17 +}: {
18 + params: Promise<{ locale: string }>;
19 +}): Promise<Metadata> {
20 + const { locale } = await params;
21 + if (!isLocale(locale)) return {};
22 + const { meta } = getMessages(locale);
23 + return { title: { absolute: meta.services.title }, description: meta.services.description };
24 +}
25 +
26 +export default async function ServicesPage({
27 + params,
28 +}: {
29 + params: Promise<{ locale: string }>;
30 +}) {
31 + const { locale } = await params;
32 + const typedLocale = locale as Locale;
33 + const { services, nav } = getMessages(typedLocale);
34 +
35 + return (
36 + <>
37 + <section className="relative overflow-hidden border-b border-line">
38 + <div className="grid-backdrop absolute inset-0" aria-hidden="true" />
39 + <Container className="relative py-20 sm:py-28">
40 + <SectionHeading as="h1" title={services.title} lead={services.lead} />
41 + <nav aria-label="Services index" className="mt-10 flex flex-wrap gap-2">
42 + {services.items.map((service) => (
43 + <a
44 + key={service.id}
45 + href={`#${service.id}`}
46 + className="rounded-full border border-line-strong bg-card px-4 py-1.5 text-sm text-ink-soft transition-colors hover:border-accent hover:text-accent"
47 + >
48 + {service.title}
49 + </a>
50 + ))}
51 + </nav>
52 + </Container>
53 + </section>
54 +
55 + <section className="py-16 sm:py-20">
56 + <Container>
57 + <div className="grid gap-6 lg:grid-cols-2">
58 + {services.items.map((service, index) => (
59 + <article
60 + key={service.id}
61 + id={service.id}
62 + className="scroll-mt-24 rounded-3xl border border-line bg-card p-8 shadow-sm sm:p-10"
63 + >
64 + <p className="font-mono text-sm text-accent">
65 + {String(index + 1).padStart(2, "0")}
66 + </p>
67 + <h2 className="mt-3 font-display text-2xl font-semibold tracking-tight text-ink">
68 + {service.title}
69 + </h2>
70 + <p className="mt-4 leading-relaxed text-ink-soft">{service.description}</p>
71 + <ul className="mt-6 grid gap-2.5">
72 + {service.points.map((point) => (
73 + <li key={point} className="flex items-start gap-2.5 text-sm text-ink">
74 + <svg
75 + width="16"
76 + height="16"
77 + viewBox="0 0 16 16"
78 + fill="none"
79 + aria-hidden="true"
80 + className="mt-0.5 shrink-0"
81 + >
82 + <path
83 + d="M3 8.5l3.2 3L13 4.5"
84 + stroke="#0b6b4a"
85 + strokeWidth="1.8"
86 + strokeLinecap="round"
87 + strokeLinejoin="round"
88 + />
89 + </svg>
90 + {point}
91 + </li>
92 + ))}
93 + </ul>
94 + </article>
95 + ))}
96 + </div>
97 + </Container>
98 + </section>
99 +
100 + {/* Engagement model */}
101 + <section className="border-t border-line bg-ink py-16 sm:py-20">
102 + <Container className="text-center">
103 + <h2 className="font-display text-2xl font-semibold tracking-tight text-paper sm:text-3xl">
104 + {services.engagementTitle}
105 + </h2>
106 + <p className="mx-auto mt-4 max-w-3xl leading-relaxed text-paper/70">
107 + {services.engagementBody}
108 + </p>
109 + <Link
110 + href={`/${typedLocale}/contact`}
111 + className="mt-8 inline-block rounded-full bg-paper px-8 py-3.5 text-sm font-medium text-ink transition-colors hover:bg-accent-soft"
112 + >
113 + {nav.cta}
114 + </Link>
115 + </Container>
116 + </section>
117 + </>
118 + );
119 +}
added app/[locale]/terms/page.tsx +40 −0
@@ -0,0 +1,40 @@
1 +/**
2 + * Author: Simon-Pierre Boucher
3 + * Contact: contact@spboucher.ai
4 + * Project: Hilmacorp.ai — Web Platform
5 + * File: app/[locale]/terms/page.tsx
6 + * Description: Terms of Service page (bilingual legal document)
7 + */
8 +
9 +import type { Metadata } from "next";
10 +import LegalPage from "@/components/LegalPage";
11 +import { getMessages, isLocale, type Locale } from "@/lib/i18n";
12 +
13 +export async function generateMetadata({
14 + params,
15 +}: {
16 + params: Promise<{ locale: string }>;
17 +}): Promise<Metadata> {
18 + const { locale } = await params;
19 + if (!isLocale(locale)) return {};
20 + const { meta } = getMessages(locale);
21 + return { title: { absolute: meta.terms.title }, description: meta.terms.description };
22 +}
23 +
24 +export default async function TermsPage({
25 + params,
26 +}: {
27 + params: Promise<{ locale: string }>;
28 +}) {
29 + const { locale } = await params;
30 + const { terms } = getMessages(locale as Locale);
31 +
32 + return (
33 + <LegalPage
34 + title={terms.title}
35 + lead={terms.lead}
36 + lastUpdated={terms.lastUpdated}
37 + sections={terms.sections}
38 + />
39 + );
40 +}
added app/api/contact/route.ts +77 −0
@@ -0,0 +1,77 @@
1 +/**
2 + * Author: Simon-Pierre Boucher
3 + * Contact: contact@spboucher.ai
4 + * Project: Hilmacorp.ai — Web Platform
5 + * File: app/api/contact/route.ts
6 + * Description: Contact form API route — validates input, then delivers to contact@spboucher.ai
7 + * via Resend when RESEND_API_KEY is set, otherwise persists to data/contact-messages.jsonl
8 + */
9 +
10 +import { NextResponse } from "next/server";
11 +import { appendFile, mkdir } from "node:fs/promises";
12 +import path from "node:path";
13 +import { validateContact } from "@/lib/validate";
14 +
15 +export const runtime = "nodejs";
16 +
17 +const CONTACT_TO = process.env.CONTACT_TO ?? "contact@spboucher.ai";
18 +
19 +async function deliverByEmail(data: {
20 + name: string;
21 + organization: string;
22 + email: string;
23 + message: string;
24 +}): Promise<boolean> {
25 + const apiKey = process.env.RESEND_API_KEY;
26 + if (!apiKey) return false;
27 + const response = await fetch("https://api.resend.com/emails", {
28 + method: "POST",
29 + headers: {
30 + Authorization: `Bearer ${apiKey}`,
31 + "Content-Type": "application/json",
32 + },
33 + body: JSON.stringify({
34 + from: process.env.CONTACT_FROM ?? "Hilmacorp.ai <onboarding@resend.dev>",
35 + to: [CONTACT_TO],
36 + reply_to: data.email,
37 + subject: `[hilmacorp.ai] Contact — ${data.name}`,
38 + text: `Name: ${data.name}\nOrganization: ${data.organization || "—"}\nEmail: ${data.email}\n\n${data.message}`,
39 + }),
40 + });
41 + return response.ok;
42 +}
43 +
44 +async function persistLocally(data: object): Promise<void> {
45 + const dir = path.join(process.cwd(), "data");
46 + await mkdir(dir, { recursive: true });
47 + const record = { receivedAt: new Date().toISOString(), to: CONTACT_TO, ...data };
48 + await appendFile(path.join(dir, "contact-messages.jsonl"), JSON.stringify(record) + "\n", "utf8");
49 +}
50 +
51 +export async function POST(request: Request) {
52 + let body: unknown;
53 + try {
54 + body = await request.json();
55 + } catch {
56 + return NextResponse.json({ ok: false, errors: { body: "invalid_json" } }, { status: 400 });
57 + }
58 +
59 + const result = validateContact(body);
60 + if (!result.ok || !result.data) {
61 + return NextResponse.json({ ok: false, errors: result.errors }, { status: 422 });
62 + }
63 +
64 + try {
65 + const emailed = await deliverByEmail(result.data);
66 + if (!emailed) await persistLocally(result.data);
67 + return NextResponse.json({ ok: true });
68 + } catch (error) {
69 + console.error("[contact] delivery failed:", error);
70 + try {
71 + await persistLocally(result.data);
72 + return NextResponse.json({ ok: true });
73 + } catch {
74 + return NextResponse.json({ ok: false, errors: { server: "delivery_failed" } }, { status: 500 });
75 + }
76 + }
77 +}
added app/globals.css +59 −0
@@ -0,0 +1,59 @@
1 +/**
2 + * Author: Simon-Pierre Boucher
3 + * Contact: contact@spboucher.ai
4 + * Project: Hilmacorp.ai — Web Platform
5 + * File: app/globals.css
6 + * Description: Global styles — Tailwind v4 entry point, light editorial design tokens, base rules
7 + */
8 +
9 +@import "tailwindcss";
10 +
11 +@theme {
12 + /* Light editorial palette: warm paper base, deep green-black ink, deep emerald accent */
13 + --color-paper: #faf9f6;
14 + --color-paper-2: #f2f0ea;
15 + --color-card: #ffffff;
16 + --color-line: #e7e3da;
17 + --color-line-strong: #d3cec1;
18 + --color-ink: #191f1c;
19 + --color-ink-soft: #414c46;
20 + --color-ink-faint: #6d7871;
21 + --color-accent: #0b6b4a;
22 + --color-accent-strong: #095c40;
23 + --color-accent-soft: #e6f2ec;
24 +
25 + --font-sans: var(--font-inter), ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
26 + --font-display: var(--font-fraunces), ui-serif, Georgia, serif;
27 + --font-mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, monospace;
28 +}
29 +
30 +html {
31 + scroll-behavior: smooth;
32 +}
33 +
34 +body {
35 + background-color: var(--color-paper);
36 + color: var(--color-ink);
37 + -webkit-font-smoothing: antialiased;
38 + text-rendering: optimizeLegibility;
39 +}
40 +
41 +::selection {
42 + background-color: var(--color-accent);
43 + color: var(--color-paper);
44 +}
45 +
46 +/* Subtle engineering-grid backdrop used by hero sections (light variant) */
47 +.grid-backdrop {
48 + background-image:
49 + linear-gradient(to right, rgb(25 31 28 / 0.045) 1px, transparent 1px),
50 + linear-gradient(to bottom, rgb(25 31 28 / 0.045) 1px, transparent 1px);
51 + background-size: 44px 44px;
52 + mask-image: radial-gradient(ellipse 90% 75% at 50% 0%, black 35%, transparent 100%);
53 +}
54 +
55 +/* Focus visibility for keyboard navigation (accessibility) */
56 +:focus-visible {
57 + outline: 2px solid var(--color-accent);
58 + outline-offset: 2px;
59 +}
added app/icon.svg +5 −0
@@ -0,0 +1,5 @@
1 +<!-- Author: Simon-Pierre Boucher | Contact: contact@spboucher.ai | Project: Hilmacorp.ai — Web Platform | File: app/icon.svg | Description: Site favicon (H monogram) -->
2 +<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">
3 + <rect x="2" y="2" width="60" height="60" rx="14" fill="#0b1117"/>
4 + <path d="M20 17v30M44 17v30M20 32h24" stroke="#12b981" stroke-width="6" stroke-linecap="round"/>
5 +</svg>
added app/robots.ts +18 −0
@@ -0,0 +1,18 @@
1 +/**
2 + * Author: Simon-Pierre Boucher
3 + * Contact: contact@spboucher.ai
4 + * Project: Hilmacorp.ai — Web Platform
5 + * File: app/robots.ts
6 + * Description: robots.txt metadata route
7 + */
8 +
9 +import type { MetadataRoute } from "next";
10 +
11 +const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? "https://www.hilmacorp.ai";
12 +
13 +export default function robots(): MetadataRoute.Robots {
14 + return {
15 + rules: { userAgent: "*", allow: "/" },
16 + sitemap: `${SITE_URL}/sitemap.xml`,
17 + };
18 +}
added app/sitemap.ts +28 −0
@@ -0,0 +1,28 @@
1 +/**
2 + * Author: Simon-Pierre Boucher
3 + * Contact: contact@spboucher.ai
4 + * Project: Hilmacorp.ai — Web Platform
5 + * File: app/sitemap.ts
6 + * Description: sitemap.xml metadata route covering all pages in both locales
7 + */
8 +
9 +import type { MetadataRoute } from "next";
10 +import { locales } from "@/lib/i18n";
11 +
12 +const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? "https://www.hilmacorp.ai";
13 +const PAGES = ["", "/about", "/founders", "/services", "/security", "/industries", "/apps", "/contact", "/terms", "/privacy"];
14 +
15 +export default function sitemap(): MetadataRoute.Sitemap {
16 + return locales.flatMap((locale) =>
17 + PAGES.map((page) => ({
18 + url: `${SITE_URL}/${locale}${page}`,
19 + changeFrequency: "monthly" as const,
20 + priority: page === "" ? 1 : 0.8,
21 + alternates: {
22 + languages: Object.fromEntries(
23 + locales.map((alt) => [alt, `${SITE_URL}/${alt}${page}`])
24 + ),
25 + },
26 + }))
27 + );
28 +}
added components/ContactForm.tsx +154 −0
@@ -0,0 +1,154 @@
1 +/**
2 + * Author: Simon-Pierre Boucher
3 + * Contact: contact@spboucher.ai
4 + * Project: Hilmacorp.ai — Web Platform
5 + * File: components/ContactForm.tsx
6 + * Description: Client-side contact form with inline validation, posting to /api/contact (light theme)
7 + */
8 +
9 +"use client";
10 +
11 +import { useState, type FormEvent } from "react";
12 +
13 +interface FormStrings {
14 + name: string;
15 + namePlaceholder: string;
16 + organization: string;
17 + organizationPlaceholder: string;
18 + email: string;
19 + emailPlaceholder: string;
20 + message: string;
21 + messagePlaceholder: string;
22 + submit: string;
23 + sending: string;
24 + success: string;
25 + error: string;
26 + errName: string;
27 + errEmail: string;
28 + errMessage: string;
29 +}
30 +
31 +type Status = "idle" | "sending" | "success" | "error";
32 +
33 +const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
34 +
35 +export default function ContactForm({ strings }: { strings: FormStrings }) {
36 + const [status, setStatus] = useState<Status>("idle");
37 + const [errors, setErrors] = useState<Record<string, string>>({});
38 +
39 + async function handleSubmit(event: FormEvent<HTMLFormElement>) {
40 + event.preventDefault();
41 + const form = event.currentTarget;
42 + const payload = {
43 + name: (form.elements.namedItem("name") as HTMLInputElement).value.trim(),
44 + organization: (form.elements.namedItem("organization") as HTMLInputElement).value.trim(),
45 + email: (form.elements.namedItem("email") as HTMLInputElement).value.trim(),
46 + message: (form.elements.namedItem("message") as HTMLTextAreaElement).value.trim(),
47 + };
48 +
49 + const nextErrors: Record<string, string> = {};
50 + if (payload.name.length < 2) nextErrors.name = strings.errName;
51 + if (!EMAIL_RE.test(payload.email)) nextErrors.email = strings.errEmail;
52 + if (payload.message.length < 10) nextErrors.message = strings.errMessage;
53 + setErrors(nextErrors);
54 + if (Object.keys(nextErrors).length > 0) return;
55 +
56 + setStatus("sending");
57 + try {
58 + const response = await fetch("/api/contact", {
59 + method: "POST",
60 + headers: { "Content-Type": "application/json" },
61 + body: JSON.stringify(payload),
62 + });
63 + if (!response.ok) throw new Error(`HTTP ${response.status}`);
64 + setStatus("success");
65 + form.reset();
66 + } catch {
67 + setStatus("error");
68 + }
69 + }
70 +
71 + const fieldClass =
72 + "w-full rounded-xl border border-line-strong bg-card px-4 py-3 text-sm text-ink placeholder:text-ink-faint focus:border-accent focus:outline-none";
73 +
74 + return (
75 + <form onSubmit={handleSubmit} noValidate className="flex flex-col gap-5">
76 + <div className="grid gap-5 sm:grid-cols-2">
77 + <div>
78 + <label htmlFor="name" className="mb-1.5 block text-sm font-medium text-ink">
79 + {strings.name}
80 + </label>
81 + <input
82 + id="name"
83 + name="name"
84 + type="text"
85 + autoComplete="name"
86 + required
87 + placeholder={strings.namePlaceholder}
88 + className={fieldClass}
89 + aria-invalid={Boolean(errors.name)}
90 + />
91 + {errors.name ? <p className="mt-1.5 text-xs text-red-700">{errors.name}</p> : null}
92 + </div>
93 + <div>
94 + <label htmlFor="organization" className="mb-1.5 block text-sm font-medium text-ink">
95 + {strings.organization}
96 + </label>
97 + <input
98 + id="organization"
99 + name="organization"
100 + type="text"
101 + autoComplete="organization"
102 + placeholder={strings.organizationPlaceholder}
103 + className={fieldClass}
104 + />
105 + </div>
106 + </div>
107 + <div>
108 + <label htmlFor="email" className="mb-1.5 block text-sm font-medium text-ink">
109 + {strings.email}
110 + </label>
111 + <input
112 + id="email"
113 + name="email"
114 + type="email"
115 + autoComplete="email"
116 + required
117 + placeholder={strings.emailPlaceholder}
118 + className={fieldClass}
119 + aria-invalid={Boolean(errors.email)}
120 + />
121 + {errors.email ? <p className="mt-1.5 text-xs text-red-700">{errors.email}</p> : null}
122 + </div>
123 + <div>
124 + <label htmlFor="message" className="mb-1.5 block text-sm font-medium text-ink">
125 + {strings.message}
126 + </label>
127 + <textarea
128 + id="message"
129 + name="message"
130 + rows={6}
131 + required
132 + placeholder={strings.messagePlaceholder}
133 + className={fieldClass}
134 + aria-invalid={Boolean(errors.message)}
135 + />
136 + {errors.message ? <p className="mt-1.5 text-xs text-red-700">{errors.message}</p> : null}
137 + </div>
138 +
139 + <div className="flex flex-col gap-4 sm:flex-row sm:items-center">
140 + <button
141 + type="submit"
142 + disabled={status === "sending"}
143 + className="rounded-full bg-ink px-7 py-3 text-sm font-medium text-paper transition-colors hover:bg-accent-strong disabled:opacity-60"
144 + >
145 + {status === "sending" ? strings.sending : strings.submit}
146 + </button>
147 + <p aria-live="polite" className="text-sm">
148 + {status === "success" ? <span className="text-accent-strong">{strings.success}</span> : null}
149 + {status === "error" ? <span className="text-red-700">{strings.error}</span> : null}
150 + </p>
151 + </div>
152 + </form>
153 + );
154 +}
added components/Container.tsx +23 −0
@@ -0,0 +1,23 @@
1 +/**
2 + * Author: Simon-Pierre Boucher
3 + * Contact: contact@spboucher.ai
4 + * Project: Hilmacorp.ai — Web Platform
5 + * File: components/Container.tsx
6 + * Description: Layout primitive — centered, max-width content container
7 + */
8 +
9 +import type { ReactNode } from "react";
10 +
11 +export default function Container({
12 + children,
13 + className = "",
14 +}: {
15 + children: ReactNode;
16 + className?: string;
17 +}) {
18 + return (
19 + <div className={`mx-auto w-full max-w-6xl px-5 sm:px-8 ${className}`}>
20 + {children}
21 + </div>
22 + );
23 +}
added components/CookieConsent.tsx +97 −0
@@ -0,0 +1,97 @@
1 +/**
2 + * Author: Simon-Pierre Boucher
3 + * Contact: contact@spboucher.ai
4 + * Project: Hilmacorp.ai — Web Platform
5 + * File: components/CookieConsent.tsx
6 + * Description: Cookie consent banner — records the visitor's choice in a first-party cookie
7 + * and can be reopened at any time via the "hilmacorp:cookie-preferences" event
8 + */
9 +
10 +"use client";
11 +
12 +import { useEffect, useState } from "react";
13 +import Link from "next/link";
14 +
15 +export const COOKIE_CONSENT_NAME = "hilmacorp_cookie_consent";
16 +export const COOKIE_PREFERENCES_EVENT = "hilmacorp:cookie-preferences";
17 +
18 +interface CookieStrings {
19 + title: string;
20 + body: string;
21 + acceptAll: string;
22 + essentialOnly: string;
23 + privacyLink: string;
24 + note: string;
25 +}
26 +
27 +function readConsent(): string | null {
28 + const match = document.cookie.match(
29 + new RegExp(`(?:^|; )${COOKIE_CONSENT_NAME}=([^;]*)`)
30 + );
31 + return match ? decodeURIComponent(match[1]) : null;
32 +}
33 +
34 +function writeConsent(value: "all" | "essential") {
35 + document.cookie = `${COOKIE_CONSENT_NAME}=${value};path=/;max-age=31536000;samesite=lax`;
36 +}
37 +
38 +export default function CookieConsent({
39 + locale,
40 + strings,
41 +}: {
42 + locale: "fr" | "en";
43 + strings: CookieStrings;
44 +}) {
45 + const [visible, setVisible] = useState(false);
46 +
47 + useEffect(() => {
48 + if (readConsent() === null) setVisible(true);
49 + const reopen = () => setVisible(true);
50 + window.addEventListener(COOKIE_PREFERENCES_EVENT, reopen);
51 + return () => window.removeEventListener(COOKIE_PREFERENCES_EVENT, reopen);
52 + }, []);
53 +
54 + if (!visible) return null;
55 +
56 + function choose(value: "all" | "essential") {
57 + writeConsent(value);
58 + setVisible(false);
59 + }
60 +
61 + return (
62 + <div
63 + role="dialog"
64 + aria-modal="false"
65 + aria-label={strings.title}
66 + className="fixed inset-x-0 bottom-0 z-[60] px-4 pb-4 sm:px-6 sm:pb-6"
67 + >
68 + <div className="mx-auto max-w-3xl rounded-2xl border border-line-strong bg-card p-6 shadow-xl sm:p-7">
69 + <h2 className="font-display text-lg font-semibold text-ink">{strings.title}</h2>
70 + <p className="mt-2 text-sm leading-relaxed text-ink-soft">{strings.body}</p>
71 + <p className="mt-2 text-xs text-ink-faint">{strings.note}</p>
72 + <div className="mt-5 flex flex-wrap items-center gap-3">
73 + <button
74 + type="button"
75 + onClick={() => choose("all")}
76 + className="rounded-full bg-ink px-6 py-2.5 text-sm font-medium text-paper transition-colors hover:bg-accent-strong"
77 + >
78 + {strings.acceptAll}
79 + </button>
80 + <button
81 + type="button"
82 + onClick={() => choose("essential")}
83 + className="rounded-full border border-line-strong px-6 py-2.5 text-sm font-medium text-ink transition-colors hover:border-accent hover:text-accent"
84 + >
85 + {strings.essentialOnly}
86 + </button>
87 + <Link
88 + href={`/${locale}/privacy`}
89 + className="text-sm font-medium text-accent hover:text-accent-strong"
90 + >
91 + {strings.privacyLink}
92 + </Link>
93 + </div>
94 + </div>
95 + </div>
96 + );
97 +}
added components/Footer.tsx +106 −0
@@ -0,0 +1,106 @@
1 +/**
2 + * Author: Simon-Pierre Boucher
3 + * Contact: contact@spboucher.ai
4 + * Project: Hilmacorp.ai — Web Platform
5 + * File: components/Footer.tsx
6 + * Description: Site footer with tagline, navigation, and contact information (light theme)
7 + */
8 +
9 +import Link from "next/link";
10 +import Logo from "@/components/Logo";
11 +import ManageCookiesButton from "@/components/ManageCookiesButton";
12 +import type { Locale, Messages } from "@/lib/i18n";
13 +
14 +export default function Footer({
15 + locale,
16 + messages,
17 +}: {
18 + locale: Locale;
19 + messages: Messages;
20 +}) {
21 + const { nav, footer } = messages;
22 + const links = [
23 + { href: `/${locale}/about`, label: nav.about },
24 + { href: `/${locale}/founders`, label: nav.founders },
25 + { href: `/${locale}/services`, label: nav.services },
26 + { href: `/${locale}/security`, label: nav.security },
27 + { href: `/${locale}/industries`, label: nav.industries },
28 + { href: `/${locale}/apps`, label: nav.apps },
29 + { href: `/${locale}/contact`, label: nav.contact },
30 + ];
31 +
32 + return (
33 + <footer className="border-t border-line bg-paper-2">
34 + <div className="mx-auto grid w-full max-w-6xl gap-10 px-5 py-16 sm:px-8 md:grid-cols-4">
35 + <div className="md:col-span-1">
36 + <Logo />
37 + <p className="mt-5 max-w-xs text-sm leading-relaxed text-ink-faint">
38 + {footer.tagline}
39 + </p>
40 + </div>
41 + <nav aria-label="Footer">
42 + <h2 className="font-mono text-xs uppercase tracking-[0.25em] text-ink-faint">
43 + {footer.navTitle}
44 + </h2>
45 + <ul className="mt-4 grid grid-cols-2 gap-2.5">
46 + {links.map((link) => (
47 + <li key={link.href}>
48 + <Link
49 + href={link.href}
50 + className="text-sm text-ink-soft transition-colors hover:text-accent"
51 + >
52 + {link.label}
53 + </Link>
54 + </li>
55 + ))}
56 + </ul>
57 + </nav>
58 + <div>
59 + <h2 className="font-mono text-xs uppercase tracking-[0.25em] text-ink-faint">
60 + {footer.legalTitle}
61 + </h2>
62 + <ul className="mt-4 grid gap-2.5">
63 + <li>
64 + <Link
65 + href={`/${locale}/terms`}
66 + className="text-sm text-ink-soft transition-colors hover:text-accent"
67 + >
68 + {footer.terms}
69 + </Link>
70 + </li>
71 + <li>
72 + <Link
73 + href={`/${locale}/privacy`}
74 + className="text-sm text-ink-soft transition-colors hover:text-accent"
75 + >
76 + {footer.privacy}
77 + </Link>
78 + </li>
79 + <li>
80 + <ManageCookiesButton
81 + label={footer.cookies}
82 + className="text-left text-sm text-ink-soft transition-colors hover:text-accent"
83 + />
84 + </li>
85 + </ul>
86 + </div>
87 + <div>
88 + <h2 className="font-mono text-xs uppercase tracking-[0.25em] text-ink-faint">
89 + {footer.contactTitle}
90 + </h2>
91 + <a
92 + href="mailto:contact@spboucher.ai"
93 + className="mt-4 inline-block text-sm font-medium text-accent hover:text-accent-strong"
94 + >
95 + contact@spboucher.ai
96 + </a>
97 + </div>
98 + </div>
99 + <div className="border-t border-line">
100 + <p className="mx-auto w-full max-w-6xl px-5 py-5 text-xs text-ink-faint sm:px-8">
101 + © 2026 Hilmacorp.ai — {footer.rights}
102 + </p>
103 + </div>
104 + </footer>
105 + );
106 +}
added components/Header.tsx +138 −0
@@ -0,0 +1,138 @@
1 +/**
2 + * Author: Simon-Pierre Boucher
3 + * Contact: contact@spboucher.ai
4 + * Project: Hilmacorp.ai — Web Platform
5 + * File: components/Header.tsx
6 + * Description: Sticky site header with desktop navigation, mobile menu, and locale switcher (light theme)
7 + */
8 +
9 +"use client";
10 +
11 +import { useState } from "react";
12 +import Link from "next/link";
13 +import { usePathname } from "next/navigation";
14 +import Logo from "@/components/Logo";
15 +import LocaleSwitcher from "@/components/LocaleSwitcher";
16 +
17 +interface NavStrings {
18 + home: string;
19 + about: string;
20 + founders: string;
21 + services: string;
22 + security: string;
23 + industries: string;
24 + apps: string;
25 + contact: string;
26 + cta: string;
27 + openMenu: string;
28 + closeMenu: string;
29 + switchLocale: string;
30 +}
31 +
32 +export default function Header({
33 + locale,
34 + nav,
35 +}: {
36 + locale: "fr" | "en";
37 + nav: NavStrings;
38 +}) {
39 + const [open, setOpen] = useState(false);
40 + const pathname = usePathname() ?? "";
41 +
42 + const links = [
43 + { href: `/${locale}/about`, label: nav.about },
44 + { href: `/${locale}/founders`, label: nav.founders },
45 + { href: `/${locale}/services`, label: nav.services },
46 + { href: `/${locale}/security`, label: nav.security },
47 + { href: `/${locale}/industries`, label: nav.industries },
48 + { href: `/${locale}/apps`, label: nav.apps },
49 + { href: `/${locale}/contact`, label: nav.contact },
50 + ];
51 +
52 + return (
53 + <header className="sticky top-0 z-50 border-b border-line bg-paper/90 backdrop-blur-md">
54 + <div className="mx-auto flex h-[68px] w-full max-w-6xl items-center justify-between px-5 sm:px-8">
55 + <Link href={`/${locale}`} aria-label={nav.home} onClick={() => setOpen(false)}>
56 + <Logo />
57 + </Link>
58 +
59 + <nav className="hidden items-center gap-1 lg:flex" aria-label="Main">
60 + {links.map((link) => {
61 + const active = pathname.startsWith(link.href);
62 + return (
63 + <Link
64 + key={link.href}
65 + href={link.href}
66 + className={`rounded-full px-3.5 py-1.5 text-sm transition-colors ${
67 + active
68 + ? "bg-accent-soft text-accent-strong"
69 + : "text-ink-soft hover:text-ink"
70 + }`}
71 + aria-current={active ? "page" : undefined}
72 + >
73 + {link.label}
74 + </Link>
75 + );
76 + })}
77 + </nav>
78 +
79 + <div className="hidden items-center gap-3 lg:flex">
80 + <LocaleSwitcher locale={locale} label={nav.switchLocale} />
81 + <Link
82 + href={`/${locale}/contact`}
83 + className="rounded-full bg-ink px-4.5 py-2 text-sm font-medium text-paper transition-colors hover:bg-accent-strong"
84 + >
85 + {nav.cta}
86 + </Link>
87 + </div>
88 +
89 + <div className="flex items-center gap-3 lg:hidden">
90 + <LocaleSwitcher locale={locale} label={nav.switchLocale} />
91 + <button
92 + type="button"
93 + onClick={() => setOpen(!open)}
94 + aria-expanded={open}
95 + aria-label={open ? nav.closeMenu : nav.openMenu}
96 + className="flex h-9 w-9 items-center justify-center rounded-lg border border-line-strong text-ink"
97 + >
98 + <svg width="18" height="18" viewBox="0 0 18 18" fill="none" aria-hidden="true">
99 + {open ? (
100 + <path d="M4 4l10 10M14 4L4 14" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" />
101 + ) : (
102 + <path d="M2 5h14M2 9h14M2 13h14" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" />
103 + )}
104 + </svg>
105 + </button>
106 + </div>
107 + </div>
108 +
109 + {open ? (
110 + <nav
111 + className="border-t border-line bg-paper px-5 pb-6 pt-3 lg:hidden"
112 + aria-label="Mobile"
113 + >
114 + <ul className="flex flex-col">
115 + {links.map((link) => (
116 + <li key={link.href}>
117 + <Link
118 + href={link.href}
119 + onClick={() => setOpen(false)}
120 + className="block rounded-lg px-2 py-3 text-base text-ink-soft hover:bg-paper-2 hover:text-ink"
121 + >
122 + {link.label}
123 + </Link>
124 + </li>
125 + ))}
126 + </ul>
127 + <Link
128 + href={`/${locale}/contact`}
129 + onClick={() => setOpen(false)}
130 + className="mt-3 block rounded-full bg-ink px-4 py-2.5 text-center text-sm font-medium text-paper"
131 + >
132 + {nav.cta}
133 + </Link>
134 + </nav>
135 + ) : null}
136 + </header>
137 + );
138 +}
added components/LegalPage.tsx +64 −0
@@ -0,0 +1,64 @@
1 +/**
2 + * Author: Simon-Pierre Boucher
3 + * Contact: contact@spboucher.ai
4 + * Project: Hilmacorp.ai — Web Platform
5 + * File: components/LegalPage.tsx
6 + * Description: Shared layout for legal documents (Terms of Service, Privacy Policy)
7 + */
8 +
9 +import type { ReactNode } from "react";
10 +import Container from "@/components/Container";
11 +import SectionHeading from "@/components/SectionHeading";
12 +
13 +export interface LegalSection {
14 + title: string;
15 + paragraphs: string[];
16 +}
17 +
18 +export default function LegalPage({
19 + title,
20 + lead,
21 + lastUpdated,
22 + sections,
23 + children,
24 +}: {
25 + title: string;
26 + lead: string;
27 + lastUpdated: string;
28 + sections: LegalSection[];
29 + children?: ReactNode;
30 +}) {
31 + return (
32 + <>
33 + <section className="relative overflow-hidden border-b border-line">
34 + <div className="grid-backdrop absolute inset-0" aria-hidden="true" />
35 + <Container className="relative py-20 sm:py-24">
36 + <SectionHeading as="h1" title={title} lead={lead} />
37 + <p className="mt-6 font-mono text-xs uppercase tracking-[0.2em] text-ink-faint">
38 + {lastUpdated}
39 + </p>
40 + </Container>
41 + </section>
42 +
43 + <section className="py-16 sm:py-20">
44 + <Container className="max-w-3xl">
45 + <div className="space-y-10">
46 + {sections.map((section) => (
47 + <section key={section.title}>
48 + <h2 className="font-display text-xl font-semibold text-ink">
49 + {section.title}
50 + </h2>
51 + {section.paragraphs.map((paragraph) => (
52 + <p key={paragraph.slice(0, 40)} className="mt-3 leading-relaxed text-ink-soft">
53 + {paragraph}
54 + </p>
55 + ))}
56 + </section>
57 + ))}
58 + {children}
59 + </div>
60 + </Container>
61 + </section>
62 + </>
63 + );
64 +}
added components/LocaleSwitcher.tsx +37 −0
@@ -0,0 +1,37 @@
1 +/**
2 + * Author: Simon-Pierre Boucher
3 + * Contact: contact@spboucher.ai
4 + * Project: Hilmacorp.ai — Web Platform
5 + * File: components/LocaleSwitcher.tsx
6 + * Description: Client component toggling between FR and EN, preserving the current path
7 + */
8 +
9 +"use client";
10 +
11 +import Link from "next/link";
12 +import { usePathname } from "next/navigation";
13 +
14 +export default function LocaleSwitcher({
15 + locale,
16 + label,
17 +}: {
18 + locale: "fr" | "en";
19 + label: string;
20 +}) {
21 + const pathname = usePathname() ?? `/${locale}`;
22 + const target = locale === "fr" ? "en" : "fr";
23 + const switched = pathname.replace(new RegExp(`^/${locale}(?=/|$)`), `/${target}`);
24 +
25 + return (
26 + <Link
27 + href={switched}
28 + onClick={() => {
29 + document.cookie = `NEXT_LOCALE=${target};path=/;max-age=31536000;samesite=lax`;
30 + }}
31 + className="rounded-full border border-line-strong px-3.5 py-1.5 font-mono text-xs uppercase tracking-widest text-ink-soft transition-colors hover:border-accent hover:text-accent"
32 + aria-label={label}
33 + >
34 + {label}
35 + </Link>
36 + );
37 +}
added components/Logo.tsx +28 −0
@@ -0,0 +1,28 @@
1 +/**
2 + * Author: Simon-Pierre Boucher
3 + * Contact: contact@spboucher.ai
4 + * Project: Hilmacorp.ai — Web Platform
5 + * File: components/Logo.tsx
6 + * Description: Hilmacorp.ai wordmark with geometric monogram (light theme)
7 + */
8 +
9 +export default function Logo({ className = "" }: { className?: string }) {
10 + return (
11 + <span className={`inline-flex items-center gap-2.5 ${className}`}>
12 + <svg
13 + width="28"
14 + height="28"
15 + viewBox="0 0 28 28"
16 + fill="none"
17 + aria-hidden="true"
18 + className="shrink-0"
19 + >
20 + <rect x="1" y="1" width="26" height="26" rx="7" fill="#191f1c" />
21 + <path d="M9 8v12M19 8v12M9 14h10" stroke="#2fbd8b" strokeWidth="2.4" strokeLinecap="round" />
22 + </svg>
23 + <span className="font-display text-xl font-semibold tracking-tight text-ink">
24 + Hilmacorp<span className="text-accent">.ai</span>
25 + </span>
26 + </span>
27 + );
28 +}
added components/ManageCookiesButton.tsx +29 −0
@@ -0,0 +1,29 @@
1 +/**
2 + * Author: Simon-Pierre Boucher
3 + * Contact: contact@spboucher.ai
4 + * Project: Hilmacorp.ai — Web Platform
5 + * File: components/ManageCookiesButton.tsx
6 + * Description: Button that reopens the cookie consent banner (dispatches the preferences event)
7 + */
8 +
9 +"use client";
10 +
11 +import { COOKIE_PREFERENCES_EVENT } from "@/components/CookieConsent";
12 +
13 +export default function ManageCookiesButton({
14 + label,
15 + className = "",
16 +}: {
17 + label: string;
18 + className?: string;
19 +}) {
20 + return (
21 + <button
22 + type="button"
23 + onClick={() => window.dispatchEvent(new Event(COOKIE_PREFERENCES_EVENT))}
24 + className={className}
25 + >
26 + {label}
27 + </button>
28 + );
29 +}
added components/SectionHeading.tsx +41 −0
@@ -0,0 +1,41 @@
1 +/**
2 + * Author: Simon-Pierre Boucher
3 + * Contact: contact@spboucher.ai
4 + * Project: Hilmacorp.ai — Web Platform
5 + * File: components/SectionHeading.tsx
6 + * Description: Reusable section heading — kicker line, serif display title, optional lead paragraph
7 + */
8 +
9 +export default function SectionHeading({
10 + kicker,
11 + title,
12 + lead,
13 + as: Tag = "h2",
14 +}: {
15 + kicker?: string;
16 + title: string;
17 + lead?: string;
18 + as?: "h1" | "h2";
19 +}) {
20 + return (
21 + <div className="max-w-3xl">
22 + {kicker ? (
23 + <p className="mb-4 font-mono text-xs uppercase tracking-[0.28em] text-accent">
24 + {kicker}
25 + </p>
26 + ) : null}
27 + <Tag
28 + className={
29 + Tag === "h1"
30 + ? "font-display text-4xl font-semibold leading-[1.08] tracking-tight text-ink sm:text-6xl"
31 + : "font-display text-3xl font-semibold leading-tight tracking-tight text-ink sm:text-4xl"
32 + }
33 + >
34 + {title}
35 + </Tag>
36 + {lead ? (
37 + <p className="mt-6 text-lg leading-relaxed text-ink-soft">{lead}</p>
38 + ) : null}
39 + </div>
40 + );
41 +}
added lib/i18n.ts +30 −0
@@ -0,0 +1,30 @@
1 +/**
2 + * Author: Simon-Pierre Boucher
3 + * Contact: contact@spboucher.ai
4 + * Project: Hilmacorp.ai — Web Platform
5 + * File: lib/i18n.ts
6 + * Description: Locale definitions and typed message dictionary loader (FR/EN)
7 + */
8 +
9 +import fr from "@/messages/fr.json";
10 +import en from "@/messages/en.json";
11 +
12 +export const locales = ["fr", "en"] as const;
13 +export type Locale = (typeof locales)[number];
14 +export const defaultLocale: Locale = "fr";
15 +
16 +export type Messages = typeof fr;
17 +
18 +const dictionaries: Record<Locale, Messages> = { fr, en };
19 +
20 +export function isLocale(value: string): value is Locale {
21 + return (locales as readonly string[]).includes(value);
22 +}
23 +
24 +export function getMessages(locale: Locale): Messages {
25 + return dictionaries[locale];
26 +}
27 +
28 +export function otherLocale(locale: Locale): Locale {
29 + return locale === "fr" ? "en" : "fr";
30 +}
added lib/validate.ts +47 −0
@@ -0,0 +1,47 @@
1 +/**
2 + * Author: Simon-Pierre Boucher
3 + * Contact: contact@spboucher.ai
4 + * Project: Hilmacorp.ai — Web Platform
5 + * File: lib/validate.ts
6 + * Description: Pure validation logic for the contact form (shared by the API route and tests)
7 + */
8 +
9 +export interface ContactPayload {
10 + name: string;
11 + organization: string;
12 + email: string;
13 + message: string;
14 +}
15 +
16 +export interface ValidationResult {
17 + ok: boolean;
18 + errors: Partial<Record<keyof ContactPayload, string>>;
19 + data?: ContactPayload;
20 +}
21 +
22 +const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
23 +const MAX_FIELD = 500;
24 +const MAX_MESSAGE = 5000;
25 +
26 +function clean(value: unknown, max: number): string {
27 + if (typeof value !== "string") return "";
28 + return value.trim().slice(0, max);
29 +}
30 +
31 +export function validateContact(input: unknown): ValidationResult {
32 + const raw = (input ?? {}) as Record<string, unknown>;
33 + const data: ContactPayload = {
34 + name: clean(raw.name, MAX_FIELD),
35 + organization: clean(raw.organization, MAX_FIELD),
36 + email: clean(raw.email, MAX_FIELD),
37 + message: clean(raw.message, MAX_MESSAGE),
38 + };
39 +
40 + const errors: ValidationResult["errors"] = {};
41 + if (data.name.length < 2) errors.name = "invalid_name";
42 + if (!EMAIL_RE.test(data.email)) errors.email = "invalid_email";
43 + if (data.message.length < 10) errors.message = "invalid_message";
44 +
45 + const ok = Object.keys(errors).length === 0;
46 + return ok ? { ok, errors, data } : { ok, errors };
47 +}
added messages/en.json +394 −0
@@ -0,0 +1,394 @@
1 +{
2 + "_author": "Simon-Pierre Boucher <contact@spboucher.ai>",
3 + "meta": {
4 + "siteName": "Hilmacorp.ai",
5 + "home": {
6 + "title": "Hilmacorp.ai — Intelligent systems for organizations",
7 + "description": "Hilmacorp.ai builds intelligent systems for organizations that want to work, analyze, decide, and innovate differently. AI agents, LLMs, private AI, RAG, data analysis and custom software."
8 + },
9 + "about": {
10 + "title": "About — Hilmacorp.ai",
11 + "description": "Our mission: make advanced AI useful, accessible, and operational for organizations of all sizes. Discover our iterative eight-step approach and our vision."
12 + },
13 + "founders": {
14 + "title": "Founders — Hilmacorp.ai",
15 + "description": "Hilmacorp.ai was founded by Simon-Pierre Boucher (AI research, LLMs, AI agents, advanced AI systems) and Massamba Mbaye (leadership, strategy, business operations)."
16 + },
17 + "services": {
18 + "title": "Services — Hilmacorp.ai",
19 + "description": "AI agents, large language models, private and local AI, RAG, data analysis and research, multimodal AI, AI strategy, and custom software development."
20 + },
21 + "security": {
22 + "title": "Security & responsible deployment — Hilmacorp.ai",
23 + "description": "Role-based access controls, secure authentication, encryption, private model hosting, human-approval workflows, monitoring, logging, and output validation."
24 + },
25 + "industries": {
26 + "title": "Industries — Hilmacorp.ai",
27 + "description": "We serve financial services, real estate, education, academic research, professional services, public administration, technology, consulting, insurance, legal, media, and retail."
28 + },
29 + "contact": {
30 + "title": "Contact — Hilmacorp.ai",
31 + "description": "Talk to Hilmacorp.ai about your AI projects. Reach us at contact@spboucher.ai or through our contact form."
32 + },
33 + "apps": {
34 + "title": "Apps — Hilmacorp.ai",
35 + "description": "Products developed by Hilmacorp.ai: the AI Risk Index and LLM Index web platforms, and the Zyquo family of native macOS AI applications (Agent, Cloud, Local, Router)."
36 + },
37 + "terms": {
38 + "title": "Terms of Service — Hilmacorp.ai",
39 + "description": "The terms governing the use of the Hilmacorp.ai website and its content."
40 + },
41 + "privacy": {
42 + "title": "Privacy Policy — Hilmacorp.ai",
43 + "description": "How Hilmacorp.ai collects, uses, and protects personal information, and how cookies are used on this site."
44 + }
45 + },
46 + "nav": {
47 + "home": "Home",
48 + "about": "About",
49 + "founders": "Founders",
50 + "services": "Services",
51 + "security": "Security",
52 + "industries": "Industries",
53 + "apps": "Apps",
54 + "contact": "Contact",
55 + "cta": "Start a conversation",
56 + "openMenu": "Open menu",
57 + "closeMenu": "Close menu",
58 + "switchLocale": "Français"
59 + },
60 + "home": {
61 + "heroKicker": "Applied artificial intelligence",
62 + "heroTitle": "Hilmacorp.ai builds intelligent systems for organizations that want to work, analyze, decide, and innovate differently.",
63 + "heroLead": "We design, build, and deploy AI that is useful on day one — from autonomous agents and language-model platforms to private infrastructure and decision-grade analytics.",
64 + "heroCtaPrimary": "Talk to us",
65 + "heroCtaSecondary": "Explore our services",
66 + "introTitle": "AI that becomes operational, not experimental",
67 + "introBody1": "Hilmacorp.ai is an artificial-intelligence company founded by Simon-Pierre Boucher and Massamba Mbaye. We help organizations turn advanced AI — large language models, agents, retrieval systems, multimodal pipelines — into dependable tools that fit their data, their infrastructure, and their constraints.",
68 + "introBody2": "Our work spans the full lifecycle: identifying the right problem, designing the architecture, prototyping quickly, measuring real value, and deploying under control with continuous improvement.",
69 + "introLinkAbout": "Our approach",
70 + "introLinkFounders": "Meet the founders",
71 + "servicesTitle": "What we build",
72 + "servicesLead": "Eight practice areas, one goal: intelligent systems that hold up in production.",
73 + "servicesCtaAll": "See all services",
74 + "whyTitle": "Why organizations choose Hilmacorp.ai",
75 + "whyLead": "Four commitments run through everything we deliver.",
76 + "why": [
77 + { "title": "Vendor-independent by principle", "body": "We answer to your outcomes, not to a model provider. Every architecture keeps you free to switch, mix, and negotiate." },
78 + { "title": "Private by design", "body": "From open-weight models on your own hardware to strict data boundaries, confidentiality is an architecture decision — not a promise." },
79 + { "title": "Measured value", "body": "Every deployment has a baseline and a metric. If the system does not move the number, we change the system." },
80 + { "title": "Production-grade engineering", "body": "Monitoring, logging, tests, and rollback paths. We build AI the way serious software is built." }
81 + ],
82 + "processTitle": "A method, not a gamble",
83 + "processBody": "Eight steps take a project from problem definition to continuous improvement — and every iteration is measured against the baseline it started from.",
84 + "processLink": "See the full approach",
85 + "statsProjects": "practice areas",
86 + "statsLanguages": "languages, one platform",
87 + "statsSteps": "step delivery method",
88 + "ctaTitle": "Ready to work differently?",
89 + "ctaBody": "Tell us about your organization and your goals. We will come back with a clear, honest read on where AI can create value — and where it cannot.",
90 + "ctaButton": "Contact Hilmacorp.ai"
91 + },
92 + "about": {
93 + "title": "About Hilmacorp.ai",
94 + "lead": "We exist to close the gap between what artificial intelligence can do and what organizations actually get from it.",
95 + "missionTitle": "Mission",
96 + "missionBody": "Make advanced AI useful, accessible, and operational for organizations of all sizes. Not demos, not slideware — systems that people rely on every day, built on the organization's own data and running within its own constraints.",
97 + "approachTitle": "Our approach",
98 + "approachLead": "Every engagement follows an iterative eight-step process. Each step produces something concrete, and each loop makes the system better.",
99 + "steps": [
100 + { "title": "Identify the problem", "body": "We start with the business problem, not the technology. What decision, workflow, or bottleneck should improve, and how will we know it did?" },
101 + { "title": "Assess data & infrastructure", "body": "We evaluate the data you have, its quality and access paths, and the infrastructure available — cloud, on-premises, or hybrid." },
102 + { "title": "Design the architecture", "body": "We design a system that fits: model choices, retrieval strategy, integration points, security boundaries, and operating costs." },
103 + { "title": "Prototype", "body": "We build a working prototype fast, on real data, so stakeholders judge substance instead of promises." },
104 + { "title": "Test", "body": "We test rigorously — accuracy, robustness, edge cases, latency, and failure modes — before anything reaches users." },
105 + { "title": "Measure value", "body": "We quantify impact against the baseline defined at step one: time saved, quality gained, decisions improved." },
106 + { "title": "Deploy under control", "body": "We roll out progressively with monitoring, human approval where it matters, and clear rollback paths." },
107 + { "title": "Improve continuously", "body": "Models, data, and needs evolve. We iterate on what is deployed and keep the system aligned with reality." }
108 + ],
109 + "principlesTitle": "Principles",
110 + "principlesLead": "The convictions that shape how we work, whatever the project.",
111 + "principles": [
112 + { "title": "Clarity before code", "body": "We would rather spend a week defining the problem than a quarter building the wrong solution." },
113 + { "title": "Honesty about limits", "body": "We tell you where AI will not help. Trust outlasts hype." },
114 + { "title": "Your data stays yours", "body": "Architectures default to your perimeter, your keys, your logs." },
115 + { "title": "Ship, measure, iterate", "body": "Working systems over slideware; measured impact over impressions." }
116 + ],
117 + "visionTitle": "Vision",
118 + "visionBody1": "We believe the organizations that thrive in the coming decade will be those that treat AI as core operational infrastructure — governed, measured, and owned — rather than as an external novelty.",
119 + "visionBody2": "Hilmacorp.ai wants to be the partner that gets them there: technically rigorous, vendor-independent, and accountable for outcomes, not just deliverables."
120 + },
121 + "founders": {
122 + "title": "Founders",
123 + "lead": "Hilmacorp.ai pairs deep technical AI expertise with strategic and operational leadership.",
124 + "spb": {
125 + "name": "Simon-Pierre Boucher",
126 + "role": "Co-founder — AI Research & Engineering",
127 + "bio": "Simon-Pierre leads the scientific and technical direction of Hilmacorp.ai. His work covers AI research, large language models, AI agents, data analysis, financial modeling, and software development. He designs advanced AI systems end to end — from open-weight models running on local infrastructure to multimodal systems and agentic workflows deployed in production.",
128 + "tags": ["AI research", "Large language models", "AI agents", "Data analysis", "Financial modeling", "Software development", "Open-weight models", "Local infrastructure", "Multimodal systems", "Agentic workflows"]
129 + },
130 + "mm": {
131 + "name": "Massamba Mbaye",
132 + "role": "Co-founder — Strategy & Business Operations",
133 + "bio": "Massamba leads strategy, growth, and operations. He brings leadership in strategic development, business operations, partnerships, and the commercial deployment of innovative technologies. His focus is the link between technical capability and organizational need — making sure what we build answers a real problem and lands successfully inside the organization.",
134 + "tags": ["Leadership", "Strategic development", "Business operations", "Partnerships", "Commercial deployment", "Technology adoption"]
135 + }
136 + },
137 + "services": {
138 + "title": "Services",
139 + "lead": "Eight practice areas covering the full spectrum of applied AI — each one delivered with the same iterative, measured method.",
140 + "items": [
141 + {
142 + "id": "agents",
143 + "title": "AI agents & intelligent automation",
144 + "description": "Autonomous and semi-autonomous agents that execute real work: multi-step workflows, tool use, decision support, and process automation with human oversight where it matters.",
145 + "points": ["Multi-step agentic workflows", "Tool-using and API-connected agents", "Human-in-the-loop controls", "Process automation with audit trails"]
146 + },
147 + {
148 + "id": "llm",
149 + "title": "Large language models",
150 + "description": "A vendor-independent approach to LLMs: we select, combine, and route across models based on task, cost, latency, and privacy — never locked to a single provider.",
151 + "points": ["Vendor-independent model strategy", "Multi-model routing", "Evaluation and benchmarking", "Cost and latency optimization"]
152 + },
153 + {
154 + "id": "private-ai",
155 + "title": "Private & local AI",
156 + "description": "Open-weight model deployments on infrastructure you control — Apple Silicon, GPU servers, or hybrid architectures — so sensitive data never leaves your perimeter.",
157 + "points": ["Open-weight model deployments", "Apple Silicon & GPU infrastructure", "Hybrid cloud/on-prem architectures", "Full data sovereignty"]
158 + },
159 + {
160 + "id": "rag",
161 + "title": "RAG — Retrieval-Augmented Generation",
162 + "description": "Ground language models in your own documents and data, with verifiable citations, per-user access controls, and audit logs suitable for regulated environments.",
163 + "points": ["Verifiable citations", "Per-user access controls", "Audit logging", "Document, database and API retrieval"]
164 + },
165 + {
166 + "id": "data",
167 + "title": "Data analysis & research",
168 + "description": "Decision-grade analysis and applied research: finance, real estate, competitive intelligence, forecasting, and custom quantitative studies.",
169 + "points": ["Financial and quantitative modeling", "Real-estate and market analysis", "Competitive intelligence", "Forecasting and scenario analysis"]
170 + },
171 + {
172 + "id": "multimodal",
173 + "title": "Multimodal AI",
174 + "description": "Systems that understand more than text: images, PDFs, tables, audio, video, and structured data — combined into unified pipelines.",
175 + "points": ["Document and PDF understanding", "Image and video analysis", "Audio transcription and analysis", "Structured-data integration"]
176 + },
177 + {
178 + "id": "strategy",
179 + "title": "AI strategy & consulting",
180 + "description": "Opportunity assessments, realistic roadmaps, proof-of-concept sprints, and team training — an honest read on where AI creates value for you and where it does not.",
181 + "points": ["Opportunity assessments", "AI roadmaps", "Proof-of-concept sprints", "Team training and enablement"]
182 + },
183 + {
184 + "id": "software",
185 + "title": "Custom software development",
186 + "description": "The platforms around the intelligence: web applications, APIs, dashboards, and integrations engineered to production standards.",
187 + "points": ["Web platforms and applications", "APIs and integrations", "Dashboards and internal tools", "Production-grade engineering"]
188 + }
189 + ],
190 + "engagementTitle": "How engagements work",
191 + "engagementBody": "Most engagements begin with a focused assessment or a proof-of-concept sprint measured in weeks, not quarters. From there, we scale what demonstrates value — with clear milestones, transparent costs, and your team involved at every step."
192 + },
193 + "security": {
194 + "title": "Security & responsible deployment",
195 + "lead": "AI systems only earn trust when they are governed. Security and responsibility are design inputs at Hilmacorp.ai, not afterthoughts.",
196 + "items": [
197 + { "title": "Role-based access controls", "body": "Fine-grained permissions ensure every user, agent, and service only reaches the data and actions it is entitled to." },
198 + { "title": "Secure authentication", "body": "Modern authentication standards protect every entry point to the systems we build." },
199 + { "title": "Encryption", "body": "Data is encrypted in transit and at rest, with key management appropriate to your compliance requirements." },
200 + { "title": "Private model hosting", "body": "When data cannot leave your perimeter, we host models privately — on your infrastructure or on dedicated hardware." },
201 + { "title": "Human-approval workflows", "body": "Consequential actions can require explicit human sign-off, keeping people in control of what matters." },
202 + { "title": "Monitoring", "body": "Live monitoring of usage, performance, drift, and anomalies keeps deployed systems observable and accountable." },
203 + { "title": "Logging & auditability", "body": "Complete, tamper-evident logs of inputs, outputs, and decisions support audits and incident response." },
204 + { "title": "Output validation", "body": "Automated checks and guardrails validate model outputs before they reach users or downstream systems." }
205 + ],
206 + "closing": "Every deployment ships with a clear operating model: who can do what, what is logged, what requires approval, and how the system is watched."
207 + },
208 + "industries": {
209 + "title": "Industries",
210 + "lead": "Our methods are general; our deployments are specific. We adapt to the data, constraints, and regulations of each sector.",
211 + "items": [
212 + { "title": "Financial services", "body": "Analysis, modeling, document intelligence, and decision support under strict compliance." },
213 + { "title": "Real estate", "body": "Market analysis, valuation support, and document automation across the property lifecycle." },
214 + { "title": "Education", "body": "Learning platforms, intelligent tutoring, and administrative automation for institutions." },
215 + { "title": "Academic research", "body": "Literature analysis, data pipelines, and research tooling that accelerate scholarly work." },
216 + { "title": "Professional services", "body": "Knowledge systems and automation that multiply the capacity of expert teams." },
217 + { "title": "Public administration", "body": "Citizen-facing and internal systems with the transparency and auditability the public sector requires." },
218 + { "title": "Technology", "body": "AI features, platforms, and infrastructure for product and engineering organizations." },
219 + { "title": "Consulting", "body": "Analytical horsepower and AI tooling that strengthen advisory engagements." },
220 + { "title": "Insurance", "body": "Document processing, risk analysis, and claims support with human oversight." },
221 + { "title": "Legal & compliance", "body": "Contract analysis, research assistance, and compliance monitoring with verifiable citations." },
222 + { "title": "Media", "body": "Content pipelines, archives intelligence, and multimodal analysis at scale." },
223 + { "title": "Retail", "body": "Demand forecasting, customer intelligence, and operational automation." }
224 + ]
225 + },
226 + "contact": {
227 + "title": "Contact",
228 + "lead": "Tell us about your organization and what you want to achieve. We answer every message.",
229 + "emailLabel": "Email us directly",
230 + "infoEmailTitle": "Email",
231 + "infoResponseTitle": "Response time",
232 + "infoResponse": "We reply to every message within two business days.",
233 + "infoLanguagesTitle": "Languages",
234 + "infoLanguages": "We work in French and English.",
235 + "form": {
236 + "name": "Name",
237 + "namePlaceholder": "Your full name",
238 + "organization": "Organization",
239 + "organizationPlaceholder": "Company, institution, or team",
240 + "email": "Email",
241 + "emailPlaceholder": "you@example.com",
242 + "message": "Message",
243 + "messagePlaceholder": "Describe your project, question, or challenge…",
244 + "submit": "Send message",
245 + "sending": "Sending…",
246 + "success": "Thank you — your message has been sent. We will get back to you shortly.",
247 + "error": "Something went wrong. Please try again or email us directly.",
248 + "errName": "Please enter your name.",
249 + "errEmail": "Please enter a valid email address.",
250 + "errMessage": "Please enter a message (at least 10 characters)."
251 + }
252 + },
253 + "apps": {
254 + "title": "Apps",
255 + "lead": "Products designed, built, and operated by Hilmacorp.ai — live web platforms and native macOS applications.",
256 + "webTitle": "Web platforms",
257 + "webLead": "Publicly accessible platforms, each with a transparent methodology and a public API.",
258 + "visit": "Visit the platform",
259 + "web": [
260 + {
261 + "id": "airiskindex",
262 + "name": "AI Risk Index",
263 + "url": "https://www.airiskindex.io",
264 + "urlLabel": "www.airiskindex.io",
265 + "tagline": "The most methodologically rigorous, fully transparent AI job-exposure index.",
266 + "description": "AI Risk Index measures how exposed over 1,000 U.S. occupations are to artificial intelligence — distinguishing substitution pressure from augmentation potential. Nearly 18,800 O*NET work tasks were rated across multiple dimensions (~225,000 ratings) by a cross-checked LLM rater panel, then aggregated into occupation-level scores weighted by real employment data.",
267 + "points": ["1,016 occupations covered, 923 scored, 194M workers (BLS OEWS)", "18,796 O*NET tasks rated — ~225k dimension ratings", "Substitution and augmentation scored separately, never blended", "Open methodology, public API, scores under CC BY 4.0"]
268 + },
269 + {
270 + "id": "llmindex",
271 + "name": "LLM Index",
272 + "url": "https://www.llmindex.io",
273 + "urlLabel": "www.llmindex.io",
274 + "tagline": "The discriminative, contamination-resistant LLM ranking — updated live.",
275 + "description": "LLM Index ranks 135 large language models across 12 capability domains using item-response theory (IRT 2PL) and Bradley–Terry duels, with a 95% confidence interval on every score. Fresh, generated test items resist training-data contamination, a cross-provider judge panel scores open-ended work, and cost and latency are shown as a Pareto frontier rather than folded into the score.",
276 + "points": ["135 models ranked across 12 domains, benchmark running live", "IRT 2PL scoring + Bradley–Terry duels, 95% CI on every score", "Contamination-resistant items, 3-judge cross-provider panel", "Public leaderboard API and a 19-page methodology white paper"]
277 + }
278 + ],
279 + "macTitle": "macOS apps — the Zyquo family",
280 + "macLead": "Native Swift applications, signed and notarized with an Apple Developer ID. No Electron, no wrapper — real Mac software.",
281 + "download": "Download the DMG",
282 + "macNote": "All apps are code-signed and notarized by Apple. Open the DMG and drag the app into Applications.",
283 + "mac": [
284 + {
285 + "id": "zyquo-agent",
286 + "name": "Zyquo Agent",
287 + "tagline": "The autonomous agent that actually operates your Mac.",
288 + "description": "An agent that plans, runs real commands, verifies its own work, and asks before anything risky. Connects to 12 AI providers and 170 models — 77 of them live-verified for tool calling.",
289 + "points": ["Plans, executes, and self-verifies multi-step tasks", "12 providers, 170 models (77 agent-verified)", "Safety-first: confirmation before risky actions"],
290 + "file": "ZyquoAgent.dmg",
291 + "size": "5.5 MB",
292 + "requires": "macOS 13+ · Universal (Apple Silicon + Intel)"
293 + },
294 + {
295 + "id": "zyquo-cloud",
296 + "name": "Zyquo Cloud",
297 + "tagline": "Your keys, every cloud model, one beautiful chat.",
298 + "description": "A truly native macOS AI chat client for 12 cloud providers, bring-your-own-key. Every provider integration is verified live against the real APIs — 202 of 202 checks passing.",
299 + "points": ["12 cloud providers, bring-your-own-key", "202/202 live API verifications", "100% native SwiftUI — no Electron"],
300 + "file": "ZyquoCloud.dmg",
301 + "size": "4.1 MB",
302 + "requires": "macOS 13+ · Universal (Apple Silicon + Intel)"
303 + },
304 + {
305 + "id": "zyquo-local",
306 + "name": "Zyquo Local",
307 + "tagline": "Every token stays on your Mac.",
308 + "description": "A native chat client that runs large language models 100% locally on Apple Silicon with MLX — no API keys, no cloud, no data ever leaving your machine.",
309 + "points": ["100% local inference on Apple Silicon (MLX)", "No API key, no account, no telemetry", "Download and manage open-weight models in-app"],
310 + "file": "ZyquoLocal.dmg",
311 + "size": "13 MB",
312 + "requires": "macOS 14+ · Apple Silicon"
313 + },
314 + {
315 + "id": "zyquo-router",
316 + "name": "Zyquo Router",
317 + "tagline": "One local endpoint. Every AI provider. Spec-exact.",
318 + "description": "Turns your Mac into a private LLM gateway: 170 models from 12 providers behind a single OpenAI-compatible API served locally by SwiftNIO. Point any SDK or tool at localhost and switch providers freely.",
319 + "points": ["Single OpenAI-compatible endpoint for 12 providers", "170 models, spec-exact request translation", "Native SwiftNIO server in the menu bar"],
320 + "file": "ZyquoRouter.dmg",
321 + "size": "8 MB",
322 + "requires": "macOS 13+ · Universal (Apple Silicon + Intel)"
323 + }
324 + ]
325 + },
326 + "footer": {
327 + "tagline": "Intelligent systems for organizations that want to work, analyze, decide, and innovate differently.",
328 + "navTitle": "Navigation",
329 + "contactTitle": "Contact",
330 + "legalTitle": "Legal",
331 + "terms": "Terms of Service",
332 + "privacy": "Privacy Policy",
333 + "cookies": "Cookie preferences",
334 + "rights": "All rights reserved."
335 + },
336 + "cookies": {
337 + "title": "Cookies on this site",
338 + "body": "We use strictly necessary cookies to remember your language and your consent choice. No advertising or third-party tracking cookies are set. If we introduce optional cookies (such as anonymous analytics) in the future, they will only be activated with your consent.",
339 + "acceptAll": "Accept all",
340 + "essentialOnly": "Essential only",
341 + "privacyLink": "Privacy Policy",
342 + "note": "You can change this choice at any time via “Cookie preferences” in the footer."
343 + },
344 + "terms": {
345 + "title": "Terms of Service",
346 + "lead": "These terms govern your use of the hilmacorp.ai website. By accessing the site, you agree to them.",
347 + "lastUpdated": "Last updated: August 6, 2026",
348 + "sections": [
349 + { "title": "1. Acceptance of the terms", "paragraphs": ["By accessing or using the hilmacorp.ai website (the “Site”), operated by Hilmacorp.ai (“we”, “us”), you agree to be bound by these Terms of Service. If you do not agree with any part of these terms, please do not use the Site."] },
350 + { "title": "2. Purpose of the Site", "paragraphs": ["The Site presents Hilmacorp.ai, its services, its founders, and its approach, and allows you to contact us. The content is provided for general information purposes only. It does not constitute an offer, a contract, or professional advice of any kind."] },
351 + { "title": "3. Acceptable use", "paragraphs": ["You agree to use the Site lawfully and not to: attempt to gain unauthorized access to the Site or its infrastructure; disrupt or overload the Site; use the contact form to send unlawful, abusive, or unsolicited commercial content; or copy or scrape the Site's content in an automated manner without our written permission."] },
352 + { "title": "4. Intellectual property", "paragraphs": ["Unless otherwise indicated, the Site and its content — texts, design, logos, and graphics — are the property of Hilmacorp.ai or its founders and are protected by applicable intellectual-property laws. You may consult and print pages for personal, non-commercial use; any other reproduction or distribution requires our prior written consent."] },
353 + { "title": "5. No warranty", "paragraphs": ["The Site is provided “as is” and “as available”. While we strive for accuracy, we make no warranty that the content is complete, accurate, or up to date, nor that the Site will be available without interruption or free of errors. Descriptions of services are indicative; the scope of any engagement is defined exclusively in a separate written agreement."] },
354 + { "title": "6. Limitation of liability", "paragraphs": ["To the maximum extent permitted by applicable law, Hilmacorp.ai shall not be liable for any indirect, incidental, or consequential damages arising from the use of, or the inability to use, the Site or its content. Nothing in these terms excludes liability that cannot be excluded under applicable law."] },
355 + { "title": "7. Third-party links", "paragraphs": ["The Site may contain links to third-party websites. We have no control over their content and accept no responsibility for them. Accessing them is at your own risk."] },
356 + { "title": "8. Changes to these terms", "paragraphs": ["We may update these Terms of Service from time to time. The date at the top of this page indicates the latest revision. Continued use of the Site after a change constitutes acceptance of the revised terms."] },
357 + { "title": "9. Governing law", "paragraphs": ["These terms are governed by the laws applicable in the Province of Québec and the federal laws of Canada applicable therein. Any dispute relating to the Site shall be submitted to the competent courts of Québec."] },
358 + { "title": "10. Contact", "paragraphs": ["For any question about these terms, write to us at contact@spboucher.ai."] }
359 + ]
360 + },
361 + "privacy": {
362 + "title": "Privacy Policy",
363 + "lead": "This policy explains what personal information we collect through this site, why, how it is protected, and the rights you have over it.",
364 + "lastUpdated": "Last updated: August 6, 2026",
365 + "sections": [
366 + { "title": "1. Who we are", "paragraphs": ["Hilmacorp.ai is an artificial-intelligence company founded by Simon-Pierre Boucher and Massamba Mbaye. For anything related to this policy, the person responsible for the protection of personal information can be reached at contact@spboucher.ai."] },
367 + { "title": "2. Information we collect", "paragraphs": ["Contact form: when you write to us, we collect your name, your email address, your organization (optional), and the content of your message.", "Technical data: like most websites, our hosting infrastructure records standard server logs (IP address, browser type, pages requested, timestamps) for security and operations. We do not use them to profile visitors."] },
368 + { "title": "3. How we use your information", "paragraphs": ["We use the information you send us solely to respond to your message and follow up on your request. We do not sell, rent, or trade personal information, and we do not use it for advertising."] },
369 + { "title": "4. Storage and retention", "paragraphs": ["Contact messages are stored on infrastructure controlled by Hilmacorp.ai and, when email delivery is enabled, transmitted to our mailbox via an email-delivery provider. We keep contact messages only as long as necessary to handle your request and for reasonable follow-up, after which they are deleted."] },
370 + { "title": "5. Sharing", "paragraphs": ["Personal information is shared with no one, except: technical service providers strictly necessary to operate the Site (hosting, email delivery), bound to confidentiality; or authorities when the law requires it."] },
371 + { "title": "6. Security", "paragraphs": ["Traffic to the Site is encrypted (HTTPS). Access to stored messages is limited to the founders. No method of transmission or storage is perfectly secure, but we apply reasonable safeguards proportionate to the sensitivity of the data."] },
372 + { "title": "7. Your rights", "paragraphs": ["Subject to applicable law — including Québec's Law 25 and Canada's PIPEDA — you may ask to access the personal information we hold about you, have it corrected, or have it deleted, and withdraw your consent to its use. Write to contact@spboucher.ai; we will respond within the time limits provided by law."] },
373 + { "title": "8. Changes to this policy", "paragraphs": ["We may update this policy from time to time. The date at the top of this page indicates the latest revision. Material changes will be reflected directly on this page."] },
374 + { "title": "9. Contact", "paragraphs": ["For any question or request concerning your personal information: contact@spboucher.ai."] }
375 + ],
376 + "cookiesSection": {
377 + "title": "Cookies",
378 + "intro": "This site uses only strictly necessary cookies. No advertising, analytics, or third-party tracking cookies are currently set. Should optional cookies be introduced, they would only be activated with your prior consent, which you can give or withdraw at any time.",
379 + "tableName": "Cookie",
380 + "tablePurpose": "Purpose",
381 + "tableDuration": "Duration",
382 + "rows": [
383 + { "name": "NEXT_LOCALE", "purpose": "Remembers your language preference (FR/EN).", "duration": "12 months" },
384 + { "name": "hilmacorp_cookie_consent", "purpose": "Records your cookie consent choice.", "duration": "12 months" }
385 + ],
386 + "manage": "Open cookie preferences"
387 + }
388 + },
389 + "notFound": {
390 + "title": "Page not found",
391 + "body": "The page you are looking for does not exist or has moved.",
392 + "back": "Back to home"
393 + }
394 +}
added messages/fr.json +394 −0
@@ -0,0 +1,394 @@
1 +{
2 + "_author": "Simon-Pierre Boucher <contact@spboucher.ai>",
3 + "meta": {
4 + "siteName": "Hilmacorp.ai",
5 + "home": {
6 + "title": "Hilmacorp.ai — Systèmes intelligents pour les organisations",
7 + "description": "Hilmacorp.ai conçoit des systèmes intelligents pour les organisations qui veulent travailler, analyser, décider et innover autrement. Agents IA, LLM, IA privée, RAG, analyse de données et logiciels sur mesure."
8 + },
9 + "about": {
10 + "title": "À propos — Hilmacorp.ai",
11 + "description": "Notre mission : rendre l'IA avancée utile, accessible et opérationnelle pour les organisations de toutes tailles. Découvrez notre approche itérative en huit étapes et notre vision."
12 + },
13 + "founders": {
14 + "title": "Fondateurs — Hilmacorp.ai",
15 + "description": "Hilmacorp.ai a été fondée par Simon-Pierre Boucher (recherche en IA, LLM, agents IA, systèmes IA avancés) et Massamba Mbaye (leadership, stratégie, opérations d'affaires)."
16 + },
17 + "services": {
18 + "title": "Services — Hilmacorp.ai",
19 + "description": "Agents IA, grands modèles de langage, IA privée et locale, RAG, analyse de données et recherche, IA multimodale, stratégie IA et développement logiciel sur mesure."
20 + },
21 + "security": {
22 + "title": "Sécurité et déploiement responsable — Hilmacorp.ai",
23 + "description": "Contrôles d'accès par rôles, authentification sécurisée, chiffrement, hébergement privé de modèles, workflows d'approbation humaine, monitoring, journalisation et validation des sorties."
24 + },
25 + "industries": {
26 + "title": "Industries — Hilmacorp.ai",
27 + "description": "Nous servons les services financiers, l'immobilier, l'éducation, la recherche académique, les services professionnels, l'administration publique, la technologie, la consultation, l'assurance, le juridique, les médias et le commerce de détail."
28 + },
29 + "contact": {
30 + "title": "Contact — Hilmacorp.ai",
31 + "description": "Parlez à Hilmacorp.ai de vos projets d'IA. Écrivez-nous à contact@spboucher.ai ou via notre formulaire de contact."
32 + },
33 + "apps": {
34 + "title": "Applications — Hilmacorp.ai",
35 + "description": "Les produits développés par Hilmacorp.ai : les plateformes web AI Risk Index et LLM Index, et la famille Zyquo d'applications IA natives pour macOS (Agent, Cloud, Local, Router)."
36 + },
37 + "terms": {
38 + "title": "Conditions d'utilisation — Hilmacorp.ai",
39 + "description": "Les conditions qui régissent l'utilisation du site web Hilmacorp.ai et de son contenu."
40 + },
41 + "privacy": {
42 + "title": "Politique de confidentialité — Hilmacorp.ai",
43 + "description": "Comment Hilmacorp.ai recueille, utilise et protège les renseignements personnels, et comment les témoins (cookies) sont utilisés sur ce site."
44 + }
45 + },
46 + "nav": {
47 + "home": "Accueil",
48 + "about": "À propos",
49 + "founders": "Fondateurs",
50 + "services": "Services",
51 + "security": "Sécurité",
52 + "industries": "Industries",
53 + "apps": "Applications",
54 + "contact": "Contact",
55 + "cta": "Démarrer une conversation",
56 + "openMenu": "Ouvrir le menu",
57 + "closeMenu": "Fermer le menu",
58 + "switchLocale": "English"
59 + },
60 + "home": {
61 + "heroKicker": "Intelligence artificielle appliquée",
62 + "heroTitle": "Hilmacorp.ai conçoit des systèmes intelligents pour les organisations qui veulent travailler, analyser, décider et innover autrement.",
63 + "heroLead": "Nous concevons, construisons et déployons une IA utile dès le premier jour — des agents autonomes et plateformes de modèles de langage jusqu'à l'infrastructure privée et l'analyse décisionnelle.",
64 + "heroCtaPrimary": "Parlez-nous",
65 + "heroCtaSecondary": "Explorer nos services",
66 + "introTitle": "Une IA qui devient opérationnelle, pas expérimentale",
67 + "introBody1": "Hilmacorp.ai est une entreprise d'intelligence artificielle fondée par Simon-Pierre Boucher et Massamba Mbaye. Nous aidons les organisations à transformer l'IA avancée — grands modèles de langage, agents, systèmes de récupération, pipelines multimodaux — en outils fiables, adaptés à leurs données, à leur infrastructure et à leurs contraintes.",
68 + "introBody2": "Notre travail couvre tout le cycle de vie : identifier le bon problème, concevoir l'architecture, prototyper rapidement, mesurer la valeur réelle et déployer sous contrôle avec une amélioration continue.",
69 + "introLinkAbout": "Notre approche",
70 + "introLinkFounders": "Rencontrer les fondateurs",
71 + "servicesTitle": "Ce que nous construisons",
72 + "servicesLead": "Huit domaines de pratique, un seul objectif : des systèmes intelligents qui tiennent en production.",
73 + "servicesCtaAll": "Voir tous les services",
74 + "whyTitle": "Pourquoi les organisations choisissent Hilmacorp.ai",
75 + "whyLead": "Quatre engagements traversent tout ce que nous livrons.",
76 + "why": [
77 + { "title": "Indépendants des fournisseurs, par principe", "body": "Nous répondons de vos résultats, pas d'un fournisseur de modèles. Chaque architecture vous laisse libre de changer, de combiner et de négocier." },
78 + { "title": "Privé par conception", "body": "Des modèles open-weight sur votre propre matériel jusqu'aux frontières de données strictes, la confidentialité est une décision d'architecture — pas une promesse." },
79 + { "title": "Valeur mesurée", "body": "Chaque déploiement a une base de référence et une métrique. Si le système ne fait pas bouger le chiffre, nous changeons le système." },
80 + { "title": "Ingénierie de qualité production", "body": "Monitoring, journalisation, tests et voies de retour arrière. Nous construisons l'IA comme on construit du logiciel sérieux." }
81 + ],
82 + "processTitle": "Une méthode, pas un pari",
83 + "processBody": "Huit étapes mènent un projet de la définition du problème à l'amélioration continue — et chaque itération est mesurée par rapport à sa base de départ.",
84 + "processLink": "Voir l'approche complète",
85 + "statsProjects": "domaines de pratique",
86 + "statsLanguages": "langues, une plateforme",
87 + "statsSteps": "étapes de livraison",
88 + "ctaTitle": "Prêts à travailler autrement ?",
89 + "ctaBody": "Parlez-nous de votre organisation et de vos objectifs. Nous reviendrons avec une lecture claire et honnête des endroits où l'IA peut créer de la valeur — et de ceux où elle ne le peut pas.",
90 + "ctaButton": "Contacter Hilmacorp.ai"
91 + },
92 + "about": {
93 + "title": "À propos de Hilmacorp.ai",
94 + "lead": "Nous existons pour combler l'écart entre ce que l'intelligence artificielle peut faire et ce que les organisations en retirent réellement.",
95 + "missionTitle": "Mission",
96 + "missionBody": "Rendre l'IA avancée utile, accessible et opérationnelle pour les organisations de toutes tailles. Pas des démos, pas des présentations — des systèmes sur lesquels les gens s'appuient chaque jour, construits sur les données de l'organisation et fonctionnant dans ses propres contraintes.",
97 + "approachTitle": "Notre approche",
98 + "approachLead": "Chaque mandat suit un processus itératif en huit étapes. Chaque étape produit du concret, et chaque itération améliore le système.",
99 + "steps": [
100 + { "title": "Identifier le problème", "body": "Nous partons du problème d'affaires, pas de la technologie. Quelle décision, quel flux de travail ou quel goulot d'étranglement doit s'améliorer, et comment le saurons-nous ?" },
101 + { "title": "Évaluer données et infrastructure", "body": "Nous évaluons les données disponibles, leur qualité et leurs chemins d'accès, ainsi que l'infrastructure en place — infonuagique, sur site ou hybride." },
102 + { "title": "Concevoir l'architecture", "body": "Nous concevons un système adapté : choix de modèles, stratégie de récupération, points d'intégration, frontières de sécurité et coûts d'exploitation." },
103 + { "title": "Prototyper", "body": "Nous construisons rapidement un prototype fonctionnel, sur des données réelles, pour que les parties prenantes jugent sur pièces plutôt que sur promesses." },
104 + { "title": "Tester", "body": "Nous testons rigoureusement — exactitude, robustesse, cas limites, latence et modes de défaillance — avant que quoi que ce soit n'atteigne les utilisateurs." },
105 + { "title": "Mesurer la valeur", "body": "Nous quantifions l'impact par rapport à la base définie à l'étape un : temps gagné, qualité accrue, décisions améliorées." },
106 + { "title": "Déployer sous contrôle", "body": "Nous déployons progressivement, avec monitoring, approbation humaine là où c'est nécessaire et des voies de retour arrière claires." },
107 + { "title": "Améliorer en continu", "body": "Les modèles, les données et les besoins évoluent. Nous itérons sur ce qui est déployé et gardons le système aligné sur la réalité." }
108 + ],
109 + "principlesTitle": "Principes",
110 + "principlesLead": "Les convictions qui façonnent notre travail, quel que soit le projet.",
111 + "principles": [
112 + { "title": "La clarté avant le code", "body": "Nous préférons passer une semaine à définir le problème plutôt qu'un trimestre à construire la mauvaise solution." },
113 + { "title": "L'honnêteté sur les limites", "body": "Nous vous disons où l'IA n'aidera pas. La confiance dure plus longtemps que l'engouement." },
114 + { "title": "Vos données restent les vôtres", "body": "Les architectures privilégient par défaut votre périmètre, vos clés, vos journaux." },
115 + { "title": "Livrer, mesurer, itérer", "body": "Des systèmes qui fonctionnent plutôt que des présentations ; un impact mesuré plutôt que des impressions." }
116 + ],
117 + "visionTitle": "Vision",
118 + "visionBody1": "Nous croyons que les organisations qui prospéreront dans la prochaine décennie seront celles qui traitent l'IA comme une infrastructure opérationnelle de base — gouvernée, mesurée et maîtrisée — plutôt que comme une nouveauté externe.",
119 + "visionBody2": "Hilmacorp.ai veut être le partenaire qui les y amène : rigoureux techniquement, indépendant des fournisseurs et responsable des résultats, pas seulement des livrables."
120 + },
121 + "founders": {
122 + "title": "Fondateurs",
123 + "lead": "Hilmacorp.ai réunit une expertise technique de pointe en IA et un leadership stratégique et opérationnel.",
124 + "spb": {
125 + "name": "Simon-Pierre Boucher",
126 + "role": "Cofondateur — Recherche et ingénierie en IA",
127 + "bio": "Simon-Pierre dirige l'orientation scientifique et technique de Hilmacorp.ai. Ses travaux couvrent la recherche en IA, les grands modèles de langage, les agents IA, l'analyse de données, la modélisation financière et le développement logiciel. Il conçoit des systèmes IA avancés de bout en bout — des modèles open-weight sur infrastructure locale jusqu'aux systèmes multimodaux et aux workflows agentiques déployés en production.",
128 + "tags": ["Recherche en IA", "Grands modèles de langage", "Agents IA", "Analyse de données", "Modélisation financière", "Développement logiciel", "Modèles open-weight", "Infrastructure locale", "Systèmes multimodaux", "Workflows agentiques"]
129 + },
130 + "mm": {
131 + "name": "Massamba Mbaye",
132 + "role": "Cofondateur — Stratégie et opérations d'affaires",
133 + "bio": "Massamba dirige la stratégie, la croissance et les opérations. Il apporte un leadership en développement stratégique, en opérations d'affaires, en partenariats et en déploiement commercial de technologies innovantes. Son travail porte sur le lien entre capacités techniques et besoins organisationnels — s'assurer que ce que nous construisons répond à un vrai problème et s'implante avec succès dans l'organisation.",
134 + "tags": ["Leadership", "Développement stratégique", "Opérations d'affaires", "Partenariats", "Déploiement commercial", "Adoption technologique"]
135 + }
136 + },
137 + "services": {
138 + "title": "Services",
139 + "lead": "Huit domaines de pratique couvrant tout le spectre de l'IA appliquée — chacun livré avec la même méthode itérative et mesurée.",
140 + "items": [
141 + {
142 + "id": "agents",
143 + "title": "Agents IA et automatisation intelligente",
144 + "description": "Des agents autonomes et semi-autonomes qui exécutent un vrai travail : flux multi-étapes, utilisation d'outils, aide à la décision et automatisation de processus avec supervision humaine là où c'est nécessaire.",
145 + "points": ["Workflows agentiques multi-étapes", "Agents connectés aux outils et aux API", "Contrôles avec humain dans la boucle", "Automatisation de processus avec pistes d'audit"]
146 + },
147 + {
148 + "id": "llm",
149 + "title": "Grands modèles de langage",
150 + "description": "Une approche indépendante des fournisseurs : nous sélectionnons, combinons et routons entre les modèles selon la tâche, le coût, la latence et la confidentialité — jamais captifs d'un seul fournisseur.",
151 + "points": ["Stratégie de modèles indépendante des fournisseurs", "Routage multi-modèles", "Évaluation et étalonnage", "Optimisation des coûts et de la latence"]
152 + },
153 + {
154 + "id": "private-ai",
155 + "title": "IA privée et locale",
156 + "description": "Des déploiements de modèles open-weight sur une infrastructure que vous contrôlez — Apple Silicon, serveurs GPU ou architectures hybrides — pour que les données sensibles ne quittent jamais votre périmètre.",
157 + "points": ["Déploiements de modèles open-weight", "Infrastructure Apple Silicon et GPU", "Architectures hybrides nuage/sur site", "Souveraineté complète des données"]
158 + },
159 + {
160 + "id": "rag",
161 + "title": "RAG — Génération augmentée par récupération",
162 + "description": "Ancrer les modèles de langage dans vos propres documents et données, avec des citations vérifiables, des contrôles d'accès par utilisateur et des journaux d'audit adaptés aux environnements réglementés.",
163 + "points": ["Citations vérifiables", "Contrôles d'accès par utilisateur", "Journaux d'audit", "Récupération depuis documents, bases de données et API"]
164 + },
165 + {
166 + "id": "data",
167 + "title": "Analyse de données et recherche",
168 + "description": "Analyses décisionnelles et recherche appliquée : finance, immobilier, veille concurrentielle, prévision et études quantitatives sur mesure.",
169 + "points": ["Modélisation financière et quantitative", "Analyse immobilière et de marché", "Veille concurrentielle", "Prévision et analyse de scénarios"]
170 + },
171 + {
172 + "id": "multimodal",
173 + "title": "IA multimodale",
174 + "description": "Des systèmes qui comprennent plus que le texte : images, PDF, tableaux, audio, vidéo et données structurées — combinés en pipelines unifiés.",
175 + "points": ["Compréhension de documents et de PDF", "Analyse d'images et de vidéo", "Transcription et analyse audio", "Intégration de données structurées"]
176 + },
177 + {
178 + "id": "strategy",
179 + "title": "Stratégie et consultation IA",
180 + "description": "Évaluations d'opportunités, feuilles de route réalistes, sprints de preuve de concept et formation des équipes — une lecture honnête des endroits où l'IA crée de la valeur pour vous, et de ceux où elle n'en crée pas.",
181 + "points": ["Évaluations d'opportunités", "Feuilles de route IA", "Sprints de preuve de concept", "Formation et habilitation des équipes"]
182 + },
183 + {
184 + "id": "software",
185 + "title": "Développement logiciel sur mesure",
186 + "description": "Les plateformes autour de l'intelligence : applications web, API, tableaux de bord et intégrations, conçus selon les standards de la production.",
187 + "points": ["Plateformes et applications web", "API et intégrations", "Tableaux de bord et outils internes", "Ingénierie de qualité production"]
188 + }
189 + ],
190 + "engagementTitle": "Comment se déroulent les mandats",
191 + "engagementBody": "La plupart des mandats commencent par une évaluation ciblée ou un sprint de preuve de concept qui se mesure en semaines, pas en trimestres. Ensuite, nous faisons croître ce qui démontre de la valeur — avec des jalons clairs, des coûts transparents et votre équipe impliquée à chaque étape."
192 + },
193 + "security": {
194 + "title": "Sécurité et déploiement responsable",
195 + "lead": "Un système d'IA ne mérite la confiance que s'il est gouverné. Chez Hilmacorp.ai, la sécurité et la responsabilité sont des intrants de conception, pas des ajouts après coup.",
196 + "items": [
197 + { "title": "Contrôles d'accès par rôles", "body": "Des permissions fines garantissent que chaque utilisateur, agent et service n'accède qu'aux données et aux actions qui lui reviennent." },
198 + { "title": "Authentification sécurisée", "body": "Des standards d'authentification modernes protègent chaque point d'entrée des systèmes que nous construisons." },
199 + { "title": "Chiffrement", "body": "Les données sont chiffrées en transit et au repos, avec une gestion de clés adaptée à vos exigences de conformité." },
200 + { "title": "Hébergement privé de modèles", "body": "Quand les données ne peuvent pas quitter votre périmètre, nous hébergeons les modèles en privé — sur votre infrastructure ou sur du matériel dédié." },
201 + { "title": "Workflows d'approbation humaine", "body": "Les actions à conséquence peuvent exiger une validation humaine explicite, gardant les personnes aux commandes de ce qui compte." },
202 + { "title": "Monitoring", "body": "Une surveillance en continu de l'usage, de la performance, de la dérive et des anomalies garde les systèmes déployés observables et responsables." },
203 + { "title": "Journalisation et auditabilité", "body": "Des journaux complets et inviolables des entrées, sorties et décisions soutiennent les audits et la réponse aux incidents." },
204 + { "title": "Validation des sorties", "body": "Des vérifications automatisées et des garde-fous valident les sorties des modèles avant qu'elles n'atteignent les utilisateurs ou les systèmes en aval." }
205 + ],
206 + "closing": "Chaque déploiement s'accompagne d'un modèle d'exploitation clair : qui peut faire quoi, ce qui est journalisé, ce qui exige une approbation et comment le système est surveillé."
207 + },
208 + "industries": {
209 + "title": "Industries",
210 + "lead": "Nos méthodes sont générales ; nos déploiements sont spécifiques. Nous nous adaptons aux données, contraintes et réglementations de chaque secteur.",
211 + "items": [
212 + { "title": "Services financiers", "body": "Analyse, modélisation, intelligence documentaire et aide à la décision sous conformité stricte." },
213 + { "title": "Immobilier", "body": "Analyse de marché, soutien à l'évaluation et automatisation documentaire sur tout le cycle immobilier." },
214 + { "title": "Éducation", "body": "Plateformes d'apprentissage, tutorat intelligent et automatisation administrative pour les établissements." },
215 + { "title": "Recherche académique", "body": "Analyse de littérature, pipelines de données et outillage de recherche qui accélèrent le travail savant." },
216 + { "title": "Services professionnels", "body": "Systèmes de connaissance et automatisation qui multiplient la capacité des équipes d'experts." },
217 + { "title": "Administration publique", "body": "Systèmes internes et destinés aux citoyens, avec la transparence et l'auditabilité qu'exige le secteur public." },
218 + { "title": "Technologie", "body": "Fonctionnalités IA, plateformes et infrastructure pour les organisations produit et ingénierie." },
219 + { "title": "Consultation", "body": "Puissance analytique et outillage IA qui renforcent les mandats-conseils." },
220 + { "title": "Assurance", "body": "Traitement documentaire, analyse de risque et soutien aux réclamations avec supervision humaine." },
221 + { "title": "Juridique et conformité", "body": "Analyse de contrats, aide à la recherche et surveillance de conformité avec citations vérifiables." },
222 + { "title": "Médias", "body": "Pipelines de contenu, intelligence d'archives et analyse multimodale à grande échelle." },
223 + { "title": "Commerce de détail", "body": "Prévision de la demande, intelligence client et automatisation opérationnelle." }
224 + ]
225 + },
226 + "contact": {
227 + "title": "Contact",
228 + "lead": "Parlez-nous de votre organisation et de ce que vous voulez accomplir. Nous répondons à chaque message.",
229 + "emailLabel": "Écrivez-nous directement",
230 + "infoEmailTitle": "Courriel",
231 + "infoResponseTitle": "Délai de réponse",
232 + "infoResponse": "Nous répondons à chaque message en moins de deux jours ouvrables.",
233 + "infoLanguagesTitle": "Langues",
234 + "infoLanguages": "Nous travaillons en français et en anglais.",
235 + "form": {
236 + "name": "Nom",
237 + "namePlaceholder": "Votre nom complet",
238 + "organization": "Organisation",
239 + "organizationPlaceholder": "Entreprise, institution ou équipe",
240 + "email": "Courriel",
241 + "emailPlaceholder": "vous@exemple.com",
242 + "message": "Message",
243 + "messagePlaceholder": "Décrivez votre projet, votre question ou votre défi…",
244 + "submit": "Envoyer le message",
245 + "sending": "Envoi…",
246 + "success": "Merci — votre message a été envoyé. Nous vous répondrons sous peu.",
247 + "error": "Une erreur est survenue. Réessayez ou écrivez-nous directement.",
248 + "errName": "Veuillez entrer votre nom.",
249 + "errEmail": "Veuillez entrer une adresse courriel valide.",
250 + "errMessage": "Veuillez entrer un message (au moins 10 caractères)."
251 + }
252 + },
253 + "apps": {
254 + "title": "Applications",
255 + "lead": "Des produits conçus, construits et exploités par Hilmacorp.ai — plateformes web en ligne et applications macOS natives.",
256 + "webTitle": "Plateformes web",
257 + "webLead": "Des plateformes accessibles publiquement, chacune avec une méthodologie transparente et une API publique.",
258 + "visit": "Visiter la plateforme",
259 + "web": [
260 + {
261 + "id": "airiskindex",
262 + "name": "AI Risk Index",
263 + "url": "https://www.airiskindex.io",
264 + "urlLabel": "www.airiskindex.io",
265 + "tagline": "L'indice d'exposition des emplois à l'IA le plus rigoureux et le plus transparent.",
266 + "description": "AI Risk Index mesure l'exposition de plus de 1 000 professions américaines à l'intelligence artificielle — en distinguant la pression de substitution du potentiel d'augmentation. Près de 18 800 tâches de travail O*NET ont été évaluées sur plusieurs dimensions (~225 000 évaluations) par un panel d'évaluateurs LLM contre-vérifié, puis agrégées en scores par profession, pondérés par des données d'emploi réelles.",
267 + "points": ["1 016 professions couvertes, 923 notées, 194 M de travailleurs (BLS OEWS)", "18 796 tâches O*NET évaluées — ~225 k évaluations dimensionnelles", "Substitution et augmentation notées séparément, jamais fusionnées", "Méthodologie ouverte, API publique, scores sous licence CC BY 4.0"]
268 + },
269 + {
270 + "id": "llmindex",
271 + "name": "LLM Index",
272 + "url": "https://www.llmindex.io",
273 + "urlLabel": "www.llmindex.io",
274 + "tagline": "Le classement de LLM discriminant et résistant à la contamination — mis à jour en direct.",
275 + "description": "LLM Index classe 135 grands modèles de langage dans 12 domaines de capacités à l'aide de la théorie de réponse aux items (IRT 2PL) et de duels Bradley–Terry, avec un intervalle de confiance à 95 % sur chaque score. Des items de test générés et inédits résistent à la contamination des données d'entraînement, un panel de juges inter-fournisseurs note les réponses ouvertes, et coût et latence sont présentés en frontière de Pareto plutôt que fondus dans le score.",
276 + "points": ["135 modèles classés dans 12 domaines, benchmark en direct", "Scores IRT 2PL + duels Bradley–Terry, IC 95 % sur chaque score", "Items résistants à la contamination, panel de 3 juges inter-fournisseurs", "API publique du classement et livre blanc méthodologique de 19 pages"]
277 + }
278 + ],
279 + "macTitle": "Applications macOS — la famille Zyquo",
280 + "macLead": "Des applications Swift natives, signées et notariées avec un Developer ID Apple. Pas d'Electron, pas d'enrobage web — du vrai logiciel Mac.",
281 + "download": "Télécharger le DMG",
282 + "macNote": "Toutes les applications sont signées et notariées par Apple. Ouvrez le DMG et glissez l'application dans Applications.",
283 + "mac": [
284 + {
285 + "id": "zyquo-agent",
286 + "name": "Zyquo Agent",
287 + "tagline": "L'agent autonome qui opère réellement votre Mac.",
288 + "description": "Un agent qui planifie, exécute de vraies commandes, vérifie son propre travail et demande avant toute action risquée. Connecté à 12 fournisseurs d'IA et 170 modèles — dont 77 vérifiés en direct pour l'appel d'outils.",
289 + "points": ["Planifie, exécute et auto-vérifie des tâches multi-étapes", "12 fournisseurs, 170 modèles (77 vérifiés en mode agent)", "Sécurité d'abord : confirmation avant les actions risquées"],
290 + "file": "ZyquoAgent.dmg",
291 + "size": "5,5 Mo",
292 + "requires": "macOS 13+ · Universel (Apple Silicon + Intel)"
293 + },
294 + {
295 + "id": "zyquo-cloud",
296 + "name": "Zyquo Cloud",
297 + "tagline": "Vos clés, tous les modèles infonuagiques, une seule conversation.",
298 + "description": "Un client de clavardage IA réellement natif pour macOS, couvrant 12 fournisseurs infonuagiques avec vos propres clés. Chaque intégration est vérifiée en direct contre les vraies API — 202 vérifications sur 202 réussies.",
299 + "points": ["12 fournisseurs infonuagiques, avec vos propres clés", "202/202 vérifications d'API en direct", "SwiftUI 100 % natif — pas d'Electron"],
300 + "file": "ZyquoCloud.dmg",
301 + "size": "4,1 Mo",
302 + "requires": "macOS 13+ · Universel (Apple Silicon + Intel)"
303 + },
304 + {
305 + "id": "zyquo-local",
306 + "name": "Zyquo Local",
307 + "tagline": "Chaque token reste sur votre Mac.",
308 + "description": "Un client de clavardage natif qui exécute de grands modèles de langage 100 % localement sur Apple Silicon avec MLX — sans clé API, sans nuage, sans qu'aucune donnée ne quitte votre machine.",
309 + "points": ["Inférence 100 % locale sur Apple Silicon (MLX)", "Aucune clé API, aucun compte, aucune télémétrie", "Téléchargement et gestion des modèles open-weight dans l'app"],
310 + "file": "ZyquoLocal.dmg",
311 + "size": "13 Mo",
312 + "requires": "macOS 14+ · Apple Silicon"
313 + },
314 + {
315 + "id": "zyquo-router",
316 + "name": "Zyquo Router",
317 + "tagline": "Un point d'accès local. Tous les fournisseurs d'IA. Conforme aux spécifications.",
318 + "description": "Transforme votre Mac en passerelle LLM privée : 170 modèles de 12 fournisseurs derrière une seule API compatible OpenAI, servie localement par SwiftNIO. Pointez n'importe quel SDK ou outil vers localhost et changez de fournisseur librement.",
319 + "points": ["Un seul point d'accès compatible OpenAI pour 12 fournisseurs", "170 modèles, traduction des requêtes conforme aux spécifications", "Serveur SwiftNIO natif dans la barre de menus"],
320 + "file": "ZyquoRouter.dmg",
321 + "size": "8 Mo",
322 + "requires": "macOS 13+ · Universel (Apple Silicon + Intel)"
323 + }
324 + ]
325 + },
326 + "footer": {
327 + "tagline": "Des systèmes intelligents pour les organisations qui veulent travailler, analyser, décider et innover autrement.",
328 + "navTitle": "Navigation",
329 + "contactTitle": "Contact",
330 + "legalTitle": "Mentions légales",
331 + "terms": "Conditions d'utilisation",
332 + "privacy": "Politique de confidentialité",
333 + "cookies": "Préférences de cookies",
334 + "rights": "Tous droits réservés."
335 + },
336 + "cookies": {
337 + "title": "Témoins (cookies) sur ce site",
338 + "body": "Nous utilisons uniquement des témoins strictement nécessaires, pour mémoriser votre langue et votre choix de consentement. Aucun témoin publicitaire ou de suivi tiers n'est déposé. Si des témoins optionnels (comme des statistiques anonymes) étaient introduits à l'avenir, ils ne seraient activés qu'avec votre consentement.",
339 + "acceptAll": "Tout accepter",
340 + "essentialOnly": "Essentiels seulement",
341 + "privacyLink": "Politique de confidentialité",
342 + "note": "Vous pouvez modifier ce choix en tout temps via « Préférences de cookies » au bas de page."
343 + },
344 + "terms": {
345 + "title": "Conditions d'utilisation",
346 + "lead": "Ces conditions régissent votre utilisation du site hilmacorp.ai. En accédant au site, vous les acceptez.",
347 + "lastUpdated": "Dernière mise à jour : 6 août 2026",
348 + "sections": [
349 + { "title": "1. Acceptation des conditions", "paragraphs": ["En accédant au site hilmacorp.ai (le « Site »), exploité par Hilmacorp.ai (« nous »), vous acceptez d'être lié par les présentes conditions d'utilisation. Si vous êtes en désaccord avec une partie de ces conditions, veuillez ne pas utiliser le Site."] },
350 + { "title": "2. Objet du Site", "paragraphs": ["Le Site présente Hilmacorp.ai, ses services, ses fondateurs et son approche, et vous permet de nous contacter. Le contenu est fourni à titre informatif seulement. Il ne constitue ni une offre, ni un contrat, ni un conseil professionnel de quelque nature que ce soit."] },
351 + { "title": "3. Utilisation acceptable", "paragraphs": ["Vous vous engagez à utiliser le Site de façon licite et à ne pas : tenter d'accéder sans autorisation au Site ou à son infrastructure ; perturber ou surcharger le Site ; utiliser le formulaire de contact pour transmettre du contenu illicite, abusif ou commercial non sollicité ; ni copier ou moissonner le contenu du Site de manière automatisée sans notre permission écrite."] },
352 + { "title": "4. Propriété intellectuelle", "paragraphs": ["Sauf indication contraire, le Site et son contenu — textes, design, logos et éléments graphiques — sont la propriété de Hilmacorp.ai ou de ses fondateurs et sont protégés par les lois applicables en matière de propriété intellectuelle. Vous pouvez consulter et imprimer des pages pour un usage personnel et non commercial ; toute autre reproduction ou diffusion exige notre consentement écrit préalable."] },
353 + { "title": "5. Absence de garantie", "paragraphs": ["Le Site est fourni « tel quel » et « selon la disponibilité ». Bien que nous visions l'exactitude, nous ne garantissons pas que le contenu soit complet, exact ou à jour, ni que le Site soit accessible sans interruption ou exempt d'erreurs. Les descriptions de services sont indicatives ; la portée de tout mandat est définie exclusivement dans une entente écrite distincte."] },
354 + { "title": "6. Limitation de responsabilité", "paragraphs": ["Dans la mesure maximale permise par la loi applicable, Hilmacorp.ai ne saurait être tenue responsable des dommages indirects, accessoires ou consécutifs découlant de l'utilisation du Site ou de l'impossibilité de l'utiliser. Rien dans les présentes n'exclut une responsabilité qui ne peut être exclue en vertu de la loi applicable."] },
355 + { "title": "7. Liens vers des tiers", "paragraphs": ["Le Site peut contenir des liens vers des sites web de tiers. Nous n'exerçons aucun contrôle sur leur contenu et n'assumons aucune responsabilité à leur égard. Vous y accédez à vos propres risques."] },
356 + { "title": "8. Modification des conditions", "paragraphs": ["Nous pouvons mettre à jour ces conditions d'utilisation de temps à autre. La date en haut de cette page indique la dernière révision. L'utilisation continue du Site après une modification vaut acceptation des conditions révisées."] },
357 + { "title": "9. Droit applicable", "paragraphs": ["Les présentes conditions sont régies par les lois applicables dans la province de Québec et les lois fédérales du Canada qui s'y appliquent. Tout litige relatif au Site sera soumis aux tribunaux compétents du Québec."] },
358 + { "title": "10. Contact", "paragraphs": ["Pour toute question au sujet de ces conditions, écrivez-nous à contact@spboucher.ai."] }
359 + ]
360 + },
361 + "privacy": {
362 + "title": "Politique de confidentialité",
363 + "lead": "Cette politique explique quels renseignements personnels nous recueillons via ce site, pourquoi, comment ils sont protégés et quels droits vous détenez à leur égard.",
364 + "lastUpdated": "Dernière mise à jour : 6 août 2026",
365 + "sections": [
366 + { "title": "1. Qui nous sommes", "paragraphs": ["Hilmacorp.ai est une entreprise d'intelligence artificielle fondée par Simon-Pierre Boucher et Massamba Mbaye. Pour toute question relative à la présente politique, la personne responsable de la protection des renseignements personnels peut être jointe à contact@spboucher.ai."] },
367 + { "title": "2. Renseignements recueillis", "paragraphs": ["Formulaire de contact : lorsque vous nous écrivez, nous recueillons votre nom, votre adresse courriel, votre organisation (facultatif) et le contenu de votre message.", "Données techniques : comme la plupart des sites web, notre infrastructure d'hébergement enregistre des journaux serveur standards (adresse IP, type de navigateur, pages demandées, horodatages) à des fins de sécurité et d'exploitation. Nous ne les utilisons pas pour profiler les visiteurs."] },
368 + { "title": "3. Utilisation des renseignements", "paragraphs": ["Nous utilisons les renseignements que vous nous transmettez uniquement pour répondre à votre message et assurer le suivi de votre demande. Nous ne vendons, ne louons ni n'échangeons de renseignements personnels, et nous ne les utilisons pas à des fins publicitaires."] },
369 + { "title": "4. Conservation", "paragraphs": ["Les messages de contact sont conservés sur une infrastructure contrôlée par Hilmacorp.ai et, lorsque l'envoi par courriel est activé, transmis à notre boîte via un fournisseur d'acheminement de courriels. Nous conservons les messages uniquement le temps nécessaire au traitement de votre demande et à un suivi raisonnable, après quoi ils sont supprimés."] },
370 + { "title": "5. Communication à des tiers", "paragraphs": ["Les renseignements personnels ne sont communiqués à personne, sauf : aux fournisseurs techniques strictement nécessaires à l'exploitation du Site (hébergement, acheminement de courriels), tenus à la confidentialité ; ou aux autorités lorsque la loi l'exige."] },
371 + { "title": "6. Sécurité", "paragraphs": ["Le trafic vers le Site est chiffré (HTTPS). L'accès aux messages conservés est limité aux fondateurs. Aucune méthode de transmission ou de stockage n'est parfaitement sûre, mais nous appliquons des mesures de protection raisonnables et proportionnées à la sensibilité des données."] },
372 + { "title": "7. Vos droits", "paragraphs": ["Sous réserve des lois applicables — notamment la Loi 25 du Québec et la LPRPDE du Canada — vous pouvez demander l'accès aux renseignements personnels que nous détenons à votre sujet, leur rectification ou leur suppression, et retirer votre consentement à leur utilisation. Écrivez à contact@spboucher.ai ; nous répondrons dans les délais prévus par la loi."] },
373 + { "title": "8. Modification de la politique", "paragraphs": ["Nous pouvons mettre à jour cette politique de temps à autre. La date en haut de cette page indique la dernière révision. Les changements importants seront reflétés directement sur cette page."] },
374 + { "title": "9. Contact", "paragraphs": ["Pour toute question ou demande concernant vos renseignements personnels : contact@spboucher.ai."] }
375 + ],
376 + "cookiesSection": {
377 + "title": "Témoins (cookies)",
378 + "intro": "Ce site n'utilise que des témoins strictement nécessaires. Aucun témoin publicitaire, statistique ou de suivi tiers n'est actuellement déposé. Si des témoins optionnels étaient introduits, ils ne seraient activés qu'avec votre consentement préalable, que vous pouvez donner ou retirer en tout temps.",
379 + "tableName": "Témoin",
380 + "tablePurpose": "Finalité",
381 + "tableDuration": "Durée",
382 + "rows": [
383 + { "name": "NEXT_LOCALE", "purpose": "Mémorise votre préférence de langue (FR/EN).", "duration": "12 mois" },
384 + { "name": "hilmacorp_cookie_consent", "purpose": "Enregistre votre choix de consentement aux témoins.", "duration": "12 mois" }
385 + ],
386 + "manage": "Ouvrir les préférences de cookies"
387 + }
388 + },
389 + "notFound": {
390 + "title": "Page introuvable",
391 + "body": "La page que vous cherchez n'existe pas ou a été déplacée.",
392 + "back": "Retour à l'accueil"
393 + }
394 +}
added middleware.ts +41 −0
@@ -0,0 +1,41 @@
1 +/**
2 + * Author: Simon-Pierre Boucher
3 + * Contact: contact@spboucher.ai
4 + * Project: Hilmacorp.ai — Web Platform
5 + * File: middleware.ts
6 + * Description: Locale-prefix middleware — redirects unprefixed paths to /fr or /en
7 + * based on the NEXT_LOCALE cookie or the Accept-Language header
8 + */
9 +
10 +import { NextRequest, NextResponse } from "next/server";
11 +
12 +const LOCALES = ["fr", "en"];
13 +const DEFAULT_LOCALE = "fr";
14 +
15 +function detectLocale(request: NextRequest): string {
16 + const cookie = request.cookies.get("NEXT_LOCALE")?.value;
17 + if (cookie && LOCALES.includes(cookie)) return cookie;
18 + const header = request.headers.get("accept-language") ?? "";
19 + for (const part of header.split(",")) {
20 + const code = part.split(";")[0].trim().slice(0, 2).toLowerCase();
21 + if (LOCALES.includes(code)) return code;
22 + }
23 + return DEFAULT_LOCALE;
24 +}
25 +
26 +export function middleware(request: NextRequest) {
27 + const { pathname } = request.nextUrl;
28 + const hasLocale = LOCALES.some(
29 + (locale) => pathname === `/${locale}` || pathname.startsWith(`/${locale}/`)
30 + );
31 + if (hasLocale) return NextResponse.next();
32 +
33 + const locale = detectLocale(request);
34 + const url = request.nextUrl.clone();
35 + url.pathname = `/${locale}${pathname === "/" ? "" : pathname}`;
36 + return NextResponse.redirect(url);
37 +}
38 +
39 +export const config = {
40 + matcher: ["/((?!api|_next|favicon.ico|icon.svg|robots.txt|sitemap.xml|.*\\..*).*)"],
41 +};
added next.config.mjs +15 −0
@@ -0,0 +1,15 @@
1 +/**
2 + * Author: Simon-Pierre Boucher
3 + * Contact: contact@spboucher.ai
4 + * Project: Hilmacorp.ai — Web Platform
5 + * File: next.config.mjs
6 + * Description: Next.js configuration (strict mode, portable deployment)
7 + */
8 +
9 +/** @type {import('next').NextConfig} */
10 +const nextConfig = {
11 + reactStrictMode: true,
12 + poweredByHeader: false,
13 +};
14 +
15 +export default nextConfig;
added package-lock.json +1735 −0
@@ -0,0 +1,1735 @@
1 +{
2 + "name": "hilmacorp-web",
3 + "version": "1.0.0",
4 + "lockfileVersion": 3,
5 + "requires": true,
6 + "packages": {
7 + "": {
8 + "name": "hilmacorp-web",
9 + "version": "1.0.0",
10 + "dependencies": {
11 + "next": "^15.4.5",
12 + "react": "^19.1.0",
13 + "react-dom": "^19.1.0"
14 + },
15 + "devDependencies": {
16 + "@tailwindcss/postcss": "^4.1.11",
17 + "@types/node": "^24.0.0",
18 + "@types/react": "^19.1.8",
19 + "@types/react-dom": "^19.1.6",
20 + "tailwindcss": "^4.1.11",
21 + "typescript": "^5.8.3"
22 + }
23 + },
24 + "node_modules/@alloc/quick-lru": {
25 + "version": "5.2.0",
26 + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
27 + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
28 + "dev": true,
29 + "license": "MIT",
30 + "engines": {
31 + "node": ">=10"
32 + },
33 + "funding": {
34 + "url": "https://github.com/sponsors/sindresorhus"
35 + }
36 + },
37 + "node_modules/@emnapi/runtime": {
38 + "version": "1.11.3",
39 + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz",
40 + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==",
41 + "license": "MIT",
42 + "optional": true,
43 + "dependencies": {
44 + "tslib": "^2.4.0"
45 + }
46 + },
47 + "node_modules/@img/colour": {
48 + "version": "1.1.0",
49 + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
50 + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
51 + "license": "MIT",
52 + "optional": true,
53 + "engines": {
54 + "node": ">=18"
55 + }
56 + },
57 + "node_modules/@img/sharp-darwin-arm64": {
58 + "version": "0.34.5",
59 + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
60 + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
61 + "cpu": [
62 + "arm64"
63 + ],
64 + "license": "Apache-2.0",
65 + "optional": true,
66 + "os": [
67 + "darwin"
68 + ],
69 + "engines": {
70 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
71 + },
72 + "funding": {
73 + "url": "https://opencollective.com/libvips"
74 + },
75 + "optionalDependencies": {
76 + "@img/sharp-libvips-darwin-arm64": "1.2.4"
77 + }
78 + },
79 + "node_modules/@img/sharp-darwin-x64": {
80 + "version": "0.34.5",
81 + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
82 + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
83 + "cpu": [
84 + "x64"
85 + ],
86 + "license": "Apache-2.0",
87 + "optional": true,
88 + "os": [
89 + "darwin"
90 + ],
91 + "engines": {
92 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
93 + },
94 + "funding": {
95 + "url": "https://opencollective.com/libvips"
96 + },
97 + "optionalDependencies": {
98 + "@img/sharp-libvips-darwin-x64": "1.2.4"
99 + }
100 + },
101 + "node_modules/@img/sharp-libvips-darwin-arm64": {
102 + "version": "1.2.4",
103 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
104 + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
105 + "cpu": [
106 + "arm64"
107 + ],
108 + "license": "LGPL-3.0-or-later",
109 + "optional": true,
110 + "os": [
111 + "darwin"
112 + ],
113 + "funding": {
114 + "url": "https://opencollective.com/libvips"
115 + }
116 + },
117 + "node_modules/@img/sharp-libvips-darwin-x64": {
118 + "version": "1.2.4",
119 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
120 + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
121 + "cpu": [
122 + "x64"
123 + ],
124 + "license": "LGPL-3.0-or-later",
125 + "optional": true,
126 + "os": [
127 + "darwin"
128 + ],
129 + "funding": {
130 + "url": "https://opencollective.com/libvips"
131 + }
132 + },
133 + "node_modules/@img/sharp-libvips-linux-arm": {
134 + "version": "1.2.4",
135 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
136 + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
137 + "cpu": [
138 + "arm"
139 + ],
140 + "libc": [
141 + "glibc"
142 + ],
143 + "license": "LGPL-3.0-or-later",
144 + "optional": true,
145 + "os": [
146 + "linux"
147 + ],
148 + "funding": {
149 + "url": "https://opencollective.com/libvips"
150 + }
151 + },
152 + "node_modules/@img/sharp-libvips-linux-arm64": {
153 + "version": "1.2.4",
154 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
155 + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
156 + "cpu": [
157 + "arm64"
158 + ],
159 + "libc": [
160 + "glibc"
161 + ],
162 + "license": "LGPL-3.0-or-later",
163 + "optional": true,
164 + "os": [
165 + "linux"
166 + ],
167 + "funding": {
168 + "url": "https://opencollective.com/libvips"
169 + }
170 + },
171 + "node_modules/@img/sharp-libvips-linux-ppc64": {
172 + "version": "1.2.4",
173 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
174 + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
175 + "cpu": [
176 + "ppc64"
177 + ],
178 + "libc": [
179 + "glibc"
180 + ],
181 + "license": "LGPL-3.0-or-later",
182 + "optional": true,
183 + "os": [
184 + "linux"
185 + ],
186 + "funding": {
187 + "url": "https://opencollective.com/libvips"
188 + }
189 + },
190 + "node_modules/@img/sharp-libvips-linux-riscv64": {
191 + "version": "1.2.4",
192 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
193 + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
194 + "cpu": [
195 + "riscv64"
196 + ],
197 + "libc": [
198 + "glibc"
199 + ],
200 + "license": "LGPL-3.0-or-later",
201 + "optional": true,
202 + "os": [
203 + "linux"
204 + ],
205 + "funding": {
206 + "url": "https://opencollective.com/libvips"
207 + }
208 + },
209 + "node_modules/@img/sharp-libvips-linux-s390x": {
210 + "version": "1.2.4",
211 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
212 + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
213 + "cpu": [
214 + "s390x"
215 + ],
216 + "libc": [
217 + "glibc"
218 + ],
219 + "license": "LGPL-3.0-or-later",
220 + "optional": true,
221 + "os": [
222 + "linux"
223 + ],
224 + "funding": {
225 + "url": "https://opencollective.com/libvips"
226 + }
227 + },
228 + "node_modules/@img/sharp-libvips-linux-x64": {
229 + "version": "1.2.4",
230 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
231 + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
232 + "cpu": [
233 + "x64"
234 + ],
235 + "libc": [
236 + "glibc"
237 + ],
238 + "license": "LGPL-3.0-or-later",
239 + "optional": true,
240 + "os": [
241 + "linux"
242 + ],
243 + "funding": {
244 + "url": "https://opencollective.com/libvips"
245 + }
246 + },
247 + "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
248 + "version": "1.2.4",
249 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
250 + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
251 + "cpu": [
252 + "arm64"
253 + ],
254 + "libc": [
255 + "musl"
256 + ],
257 + "license": "LGPL-3.0-or-later",
258 + "optional": true,
259 + "os": [
260 + "linux"
261 + ],
262 + "funding": {
263 + "url": "https://opencollective.com/libvips"
264 + }
265 + },
266 + "node_modules/@img/sharp-libvips-linuxmusl-x64": {
267 + "version": "1.2.4",
268 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
269 + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
270 + "cpu": [
271 + "x64"
272 + ],
273 + "libc": [
274 + "musl"
275 + ],
276 + "license": "LGPL-3.0-or-later",
277 + "optional": true,
278 + "os": [
279 + "linux"
280 + ],
281 + "funding": {
282 + "url": "https://opencollective.com/libvips"
283 + }
284 + },
285 + "node_modules/@img/sharp-linux-arm": {
286 + "version": "0.34.5",
287 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
288 + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
289 + "cpu": [
290 + "arm"
291 + ],
292 + "libc": [
293 + "glibc"
294 + ],
295 + "license": "Apache-2.0",
296 + "optional": true,
297 + "os": [
298 + "linux"
299 + ],
300 + "engines": {
301 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
302 + },
303 + "funding": {
304 + "url": "https://opencollective.com/libvips"
305 + },
306 + "optionalDependencies": {
307 + "@img/sharp-libvips-linux-arm": "1.2.4"
308 + }
309 + },
310 + "node_modules/@img/sharp-linux-arm64": {
311 + "version": "0.34.5",
312 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
313 + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
314 + "cpu": [
315 + "arm64"
316 + ],
317 + "libc": [
318 + "glibc"
319 + ],
320 + "license": "Apache-2.0",
321 + "optional": true,
322 + "os": [
323 + "linux"
324 + ],
325 + "engines": {
326 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
327 + },
328 + "funding": {
329 + "url": "https://opencollective.com/libvips"
330 + },
331 + "optionalDependencies": {
332 + "@img/sharp-libvips-linux-arm64": "1.2.4"
333 + }
334 + },
335 + "node_modules/@img/sharp-linux-ppc64": {
336 + "version": "0.34.5",
337 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
338 + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
339 + "cpu": [
340 + "ppc64"
341 + ],
342 + "libc": [
343 + "glibc"
344 + ],
345 + "license": "Apache-2.0",
346 + "optional": true,
347 + "os": [
348 + "linux"
349 + ],
350 + "engines": {
351 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
352 + },
353 + "funding": {
354 + "url": "https://opencollective.com/libvips"
355 + },
356 + "optionalDependencies": {
357 + "@img/sharp-libvips-linux-ppc64": "1.2.4"
358 + }
359 + },
360 + "node_modules/@img/sharp-linux-riscv64": {
361 + "version": "0.34.5",
362 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
363 + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
364 + "cpu": [
365 + "riscv64"
366 + ],
367 + "libc": [
368 + "glibc"
369 + ],
370 + "license": "Apache-2.0",
371 + "optional": true,
372 + "os": [
373 + "linux"
374 + ],
375 + "engines": {
376 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
377 + },
378 + "funding": {
379 + "url": "https://opencollective.com/libvips"
380 + },
381 + "optionalDependencies": {
382 + "@img/sharp-libvips-linux-riscv64": "1.2.4"
383 + }
384 + },
385 + "node_modules/@img/sharp-linux-s390x": {
386 + "version": "0.34.5",
387 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
388 + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
389 + "cpu": [
390 + "s390x"
391 + ],
392 + "libc": [
393 + "glibc"
394 + ],
395 + "license": "Apache-2.0",
396 + "optional": true,
397 + "os": [
398 + "linux"
399 + ],
400 + "engines": {
401 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
402 + },
403 + "funding": {
404 + "url": "https://opencollective.com/libvips"
405 + },
406 + "optionalDependencies": {
407 + "@img/sharp-libvips-linux-s390x": "1.2.4"
408 + }
409 + },
410 + "node_modules/@img/sharp-linux-x64": {
411 + "version": "0.34.5",
412 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
413 + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
414 + "cpu": [
415 + "x64"
416 + ],
417 + "libc": [
418 + "glibc"
419 + ],
420 + "license": "Apache-2.0",
421 + "optional": true,
422 + "os": [
423 + "linux"
424 + ],
425 + "engines": {
426 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
427 + },
428 + "funding": {
429 + "url": "https://opencollective.com/libvips"
430 + },
431 + "optionalDependencies": {
432 + "@img/sharp-libvips-linux-x64": "1.2.4"
433 + }
434 + },
435 + "node_modules/@img/sharp-linuxmusl-arm64": {
436 + "version": "0.34.5",
437 + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
438 + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
439 + "cpu": [
440 + "arm64"
441 + ],
442 + "libc": [
443 + "musl"
444 + ],
445 + "license": "Apache-2.0",
446 + "optional": true,
447 + "os": [
448 + "linux"
449 + ],
450 + "engines": {
451 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
452 + },
453 + "funding": {
454 + "url": "https://opencollective.com/libvips"
455 + },
456 + "optionalDependencies": {
457 + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
458 + }
459 + },
460 + "node_modules/@img/sharp-linuxmusl-x64": {
461 + "version": "0.34.5",
462 + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
463 + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
464 + "cpu": [
465 + "x64"
466 + ],
467 + "libc": [
468 + "musl"
469 + ],
470 + "license": "Apache-2.0",
471 + "optional": true,
472 + "os": [
473 + "linux"
474 + ],
475 + "engines": {
476 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
477 + },
478 + "funding": {
479 + "url": "https://opencollective.com/libvips"
480 + },
481 + "optionalDependencies": {
482 + "@img/sharp-libvips-linuxmusl-x64": "1.2.4"
483 + }
484 + },
485 + "node_modules/@img/sharp-wasm32": {
486 + "version": "0.34.5",
487 + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
488 + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
489 + "cpu": [
490 + "wasm32"
491 + ],
492 + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
493 + "optional": true,
494 + "dependencies": {
495 + "@emnapi/runtime": "^1.7.0"
496 + },
497 + "engines": {
498 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
499 + },
500 + "funding": {
501 + "url": "https://opencollective.com/libvips"
502 + }
503 + },
504 + "node_modules/@img/sharp-win32-arm64": {
505 + "version": "0.34.5",
506 + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
507 + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
508 + "cpu": [
509 + "arm64"
510 + ],
511 + "license": "Apache-2.0 AND LGPL-3.0-or-later",
512 + "optional": true,
513 + "os": [
514 + "win32"
515 + ],
516 + "engines": {
517 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
518 + },
519 + "funding": {
520 + "url": "https://opencollective.com/libvips"
521 + }
522 + },
523 + "node_modules/@img/sharp-win32-ia32": {
524 + "version": "0.34.5",
525 + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
526 + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
527 + "cpu": [
528 + "ia32"
529 + ],
530 + "license": "Apache-2.0 AND LGPL-3.0-or-later",
531 + "optional": true,
532 + "os": [
533 + "win32"
534 + ],
535 + "engines": {
536 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
537 + },
538 + "funding": {
539 + "url": "https://opencollective.com/libvips"
540 + }
541 + },
542 + "node_modules/@img/sharp-win32-x64": {
543 + "version": "0.34.5",
544 + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
545 + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
546 + "cpu": [
547 + "x64"
548 + ],
549 + "license": "Apache-2.0 AND LGPL-3.0-or-later",
550 + "optional": true,
551 + "os": [
552 + "win32"
553 + ],
554 + "engines": {
555 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
556 + },
557 + "funding": {
558 + "url": "https://opencollective.com/libvips"
559 + }
560 + },
561 + "node_modules/@jridgewell/gen-mapping": {
562 + "version": "0.3.13",
563 + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
564 + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
565 + "dev": true,
566 + "license": "MIT",
567 + "dependencies": {
568 + "@jridgewell/sourcemap-codec": "^1.5.0",
569 + "@jridgewell/trace-mapping": "^0.3.24"
570 + }
571 + },
572 + "node_modules/@jridgewell/remapping": {
573 + "version": "2.3.5",
574 + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
575 + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
576 + "dev": true,
577 + "license": "MIT",
578 + "dependencies": {
579 + "@jridgewell/gen-mapping": "^0.3.5",
580 + "@jridgewell/trace-mapping": "^0.3.24"
581 + }
582 + },
583 + "node_modules/@jridgewell/resolve-uri": {
584 + "version": "3.1.2",
585 + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
586 + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
587 + "dev": true,
588 + "license": "MIT",
589 + "engines": {
590 + "node": ">=6.0.0"
591 + }
592 + },
593 + "node_modules/@jridgewell/sourcemap-codec": {
594 + "version": "1.5.5",
595 + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
596 + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
597 + "dev": true,
598 + "license": "MIT"
599 + },
600 + "node_modules/@jridgewell/trace-mapping": {
601 + "version": "0.3.31",
602 + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
603 + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
604 + "dev": true,
605 + "license": "MIT",
606 + "dependencies": {
607 + "@jridgewell/resolve-uri": "^3.1.0",
608 + "@jridgewell/sourcemap-codec": "^1.4.14"
609 + }
610 + },
611 + "node_modules/@next/env": {
612 + "version": "15.5.22",
613 + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.22.tgz",
614 + "integrity": "sha512-O5BlKb3KtsHkvO0gjjV66PuJnAgCtIEIzwkt50HRAHsQkU1t77eksIXSZV84/WMtZJjWrnDUPKHVRi0D62nSAA==",
615 + "license": "MIT"
616 + },
617 + "node_modules/@next/swc-darwin-arm64": {
618 + "version": "15.5.22",
619 + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.22.tgz",
620 + "integrity": "sha512-/VISwtffSg8+fVvBbXdglsvruCsdbBC4dG25iU6xascKVqfQKsj/OtjGnOEkIS7pX5GB9e9/r5QprpicsGL3gw==",
621 + "cpu": [
622 + "arm64"
623 + ],
624 + "license": "MIT",
625 + "optional": true,
626 + "os": [
627 + "darwin"
628 + ],
629 + "engines": {
630 + "node": ">= 10"
631 + }
632 + },
633 + "node_modules/@next/swc-darwin-x64": {
634 + "version": "15.5.22",
635 + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.22.tgz",
636 + "integrity": "sha512-NiA9ve8hbiuhG/Q17a2mZDRVxMTtg3rTOgjLnDaLlE+AEPAQlkkuKrfePEbeOrgYmX0U2KGX4EVEn09hXU5GlQ==",
637 + "cpu": [
638 + "x64"
639 + ],
640 + "license": "MIT",
641 + "optional": true,
642 + "os": [
643 + "darwin"
644 + ],
645 + "engines": {
646 + "node": ">= 10"
647 + }
648 + },
649 + "node_modules/@next/swc-linux-arm64-gnu": {
650 + "version": "15.5.22",
651 + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.22.tgz",
652 + "integrity": "sha512-vAPa9vltW+UW/KWtjXeSUFgV3wb1x9d/BeyC6WFI6eBpL0D2f70oGwtOp6193mNW3qusrpgBzMQferPf+Zh8Dw==",
653 + "cpu": [
654 + "arm64"
655 + ],
656 + "libc": [
657 + "glibc"
658 + ],
659 + "license": "MIT",
660 + "optional": true,
661 + "os": [
662 + "linux"
663 + ],
664 + "engines": {
665 + "node": ">= 10"
666 + }
667 + },
668 + "node_modules/@next/swc-linux-arm64-musl": {
669 + "version": "15.5.22",
670 + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.22.tgz",
671 + "integrity": "sha512-iknK80pWlNDnkdSr13bd8mMuG3Z2oTxODwsZHvuMY7caMk77+rBLdHVWsy8v2EVa3ZojJ/+wJX5fnq8va6Gv8A==",
672 + "cpu": [
673 + "arm64"
674 + ],
675 + "libc": [
676 + "musl"
677 + ],
678 + "license": "MIT",
679 + "optional": true,
680 + "os": [
681 + "linux"
682 + ],
683 + "engines": {
684 + "node": ">= 10"
685 + }
686 + },
687 + "node_modules/@next/swc-linux-x64-gnu": {
688 + "version": "15.5.22",
689 + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.22.tgz",
690 + "integrity": "sha512-penuEdkwU2OOAiS+n4LE8T/VIoCfAI01QcLZTJ2xc3+l4Q22L/DzURocmI2LU1b+8BMQoLAP1Sze3uYAZT05Bg==",
691 + "cpu": [
692 + "x64"
693 + ],
694 + "libc": [
695 + "glibc"
696 + ],
697 + "license": "MIT",
698 + "optional": true,
699 + "os": [
700 + "linux"
701 + ],
702 + "engines": {
703 + "node": ">= 10"
704 + }
705 + },
706 + "node_modules/@next/swc-linux-x64-musl": {
707 + "version": "15.5.22",
708 + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.22.tgz",
709 + "integrity": "sha512-ZM0BKJm3FZ+guG6WT6PcyOLtp6paZ5tngcJC/uUKvLW4Y0TQnnVi1+UGdo8Q6Yxp5gaS82pmC1rD/oFlhkWB3g==",
710 + "cpu": [
711 + "x64"
712 + ],
713 + "libc": [
714 + "musl"
715 + ],
716 + "license": "MIT",
717 + "optional": true,
718 + "os": [
719 + "linux"
720 + ],
721 + "engines": {
722 + "node": ">= 10"
723 + }
724 + },
725 + "node_modules/@next/swc-win32-arm64-msvc": {
726 + "version": "15.5.22",
727 + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.22.tgz",
728 + "integrity": "sha512-rY/YaumrZaS0//94BnHLF5VSRp0GFUO4GvXNuoCBb0cGSci96yO+p1JaNL2aq9YZAYv9cuZRziV02x5IQH/wjg==",
729 + "cpu": [
730 + "arm64"
731 + ],
732 + "license": "MIT",
733 + "optional": true,
734 + "os": [
735 + "win32"
736 + ],
737 + "engines": {
738 + "node": ">= 10"
739 + }
740 + },
741 + "node_modules/@next/swc-win32-x64-msvc": {
742 + "version": "15.5.22",
743 + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.22.tgz",
744 + "integrity": "sha512-s5IA4cyrbR2XK/5NWcu5dp8CfPBiKME+UhvNperia7uQybEgg5+LIhGMiY37WQE4rcI4owsDcU4IVUjLoTuDkA==",
745 + "cpu": [
746 + "x64"
747 + ],
748 + "license": "MIT",
749 + "optional": true,
750 + "os": [
751 + "win32"
752 + ],
753 + "engines": {
754 + "node": ">= 10"
755 + }
756 + },
757 + "node_modules/@swc/helpers": {
758 + "version": "0.5.15",
759 + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
760 + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
761 + "license": "Apache-2.0",
762 + "dependencies": {
763 + "tslib": "^2.8.0"
764 + }
765 + },
766 + "node_modules/@tailwindcss/node": {
767 + "version": "4.3.3",
768 + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz",
769 + "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==",
770 + "dev": true,
771 + "license": "MIT",
772 + "dependencies": {
773 + "@jridgewell/remapping": "^2.3.5",
774 + "enhanced-resolve": "^5.24.1",
775 + "jiti": "^2.7.0",
776 + "lightningcss": "1.32.0",
777 + "magic-string": "^0.30.21",
778 + "source-map-js": "^1.2.1",
779 + "tailwindcss": "4.3.3"
780 + }
781 + },
782 + "node_modules/@tailwindcss/oxide": {
783 + "version": "4.3.3",
784 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz",
785 + "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==",
786 + "dev": true,
787 + "license": "MIT",
788 + "engines": {
789 + "node": ">= 20"
790 + },
791 + "optionalDependencies": {
792 + "@tailwindcss/oxide-android-arm64": "4.3.3",
793 + "@tailwindcss/oxide-darwin-arm64": "4.3.3",
794 + "@tailwindcss/oxide-darwin-x64": "4.3.3",
795 + "@tailwindcss/oxide-freebsd-x64": "4.3.3",
796 + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3",
797 + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3",
798 + "@tailwindcss/oxide-linux-arm64-musl": "4.3.3",
799 + "@tailwindcss/oxide-linux-x64-gnu": "4.3.3",
800 + "@tailwindcss/oxide-linux-x64-musl": "4.3.3",
801 + "@tailwindcss/oxide-wasm32-wasi": "4.3.3",
802 + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3",
803 + "@tailwindcss/oxide-win32-x64-msvc": "4.3.3"
804 + }
805 + },
806 + "node_modules/@tailwindcss/oxide-android-arm64": {
807 + "version": "4.3.3",
808 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz",
809 + "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==",
810 + "cpu": [
811 + "arm64"
812 + ],
813 + "dev": true,
814 + "license": "MIT",
815 + "optional": true,
816 + "os": [
817 + "android"
818 + ],
819 + "engines": {
820 + "node": ">= 20"
821 + }
822 + },
823 + "node_modules/@tailwindcss/oxide-darwin-arm64": {
824 + "version": "4.3.3",
825 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz",
826 + "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==",
827 + "cpu": [
828 + "arm64"
829 + ],
830 + "dev": true,
831 + "license": "MIT",
832 + "optional": true,
833 + "os": [
834 + "darwin"
835 + ],
836 + "engines": {
837 + "node": ">= 20"
838 + }
839 + },
840 + "node_modules/@tailwindcss/oxide-darwin-x64": {
841 + "version": "4.3.3",
842 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz",
843 + "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==",
844 + "cpu": [
845 + "x64"
846 + ],
847 + "dev": true,
848 + "license": "MIT",
849 + "optional": true,
850 + "os": [
851 + "darwin"
852 + ],
853 + "engines": {
854 + "node": ">= 20"
855 + }
856 + },
857 + "node_modules/@tailwindcss/oxide-freebsd-x64": {
858 + "version": "4.3.3",
859 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz",
860 + "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==",
861 + "cpu": [
862 + "x64"
863 + ],
864 + "dev": true,
865 + "license": "MIT",
866 + "optional": true,
867 + "os": [
868 + "freebsd"
869 + ],
870 + "engines": {
871 + "node": ">= 20"
872 + }
873 + },
874 + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
875 + "version": "4.3.3",
876 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz",
877 + "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==",
878 + "cpu": [
879 + "arm"
880 + ],
881 + "dev": true,
882 + "license": "MIT",
883 + "optional": true,
884 + "os": [
885 + "linux"
886 + ],
887 + "engines": {
888 + "node": ">= 20"
889 + }
890 + },
891 + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
892 + "version": "4.3.3",
893 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz",
894 + "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==",
895 + "cpu": [
896 + "arm64"
897 + ],
898 + "dev": true,
899 + "libc": [
900 + "glibc"
901 + ],
902 + "license": "MIT",
903 + "optional": true,
904 + "os": [
905 + "linux"
906 + ],
907 + "engines": {
908 + "node": ">= 20"
909 + }
910 + },
911 + "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
912 + "version": "4.3.3",
913 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz",
914 + "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==",
915 + "cpu": [
916 + "arm64"
917 + ],
918 + "dev": true,
919 + "libc": [
920 + "musl"
921 + ],
922 + "license": "MIT",
923 + "optional": true,
924 + "os": [
925 + "linux"
926 + ],
927 + "engines": {
928 + "node": ">= 20"
929 + }
930 + },
931 + "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
932 + "version": "4.3.3",
933 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz",
934 + "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==",
935 + "cpu": [
936 + "x64"
937 + ],
938 + "dev": true,
939 + "libc": [
940 + "glibc"
941 + ],
942 + "license": "MIT",
943 + "optional": true,
944 + "os": [
945 + "linux"
946 + ],
947 + "engines": {
948 + "node": ">= 20"
949 + }
950 + },
951 + "node_modules/@tailwindcss/oxide-linux-x64-musl": {
952 + "version": "4.3.3",
953 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz",
954 + "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==",
955 + "cpu": [
956 + "x64"
957 + ],
958 + "dev": true,
959 + "libc": [
960 + "musl"
961 + ],
962 + "license": "MIT",
963 + "optional": true,
964 + "os": [
965 + "linux"
966 + ],
967 + "engines": {
968 + "node": ">= 20"
969 + }
970 + },
971 + "node_modules/@tailwindcss/oxide-wasm32-wasi": {
972 + "version": "4.3.3",
973 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz",
974 + "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==",
975 + "bundleDependencies": [
976 + "@napi-rs/wasm-runtime",
977 + "@emnapi/core",
978 + "@emnapi/runtime",
979 + "@tybys/wasm-util",
980 + "@emnapi/wasi-threads",
981 + "tslib"
982 + ],
983 + "cpu": [
984 + "wasm32"
985 + ],
986 + "dev": true,
987 + "license": "MIT",
988 + "optional": true,
989 + "dependencies": {
990 + "@emnapi/core": "^1.11.1",
991 + "@emnapi/runtime": "^1.11.1",
992 + "@emnapi/wasi-threads": "^1.2.2",
993 + "@napi-rs/wasm-runtime": "^1.1.4",
994 + "@tybys/wasm-util": "^0.10.2",
995 + "tslib": "^2.8.1"
996 + },
997 + "engines": {
998 + "node": ">=14.0.0"
999 + }
1000 + },
1001 + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
1002 + "version": "4.3.3",
1003 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz",
1004 + "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==",
1005 + "cpu": [
1006 + "arm64"
1007 + ],
1008 + "dev": true,
1009 + "license": "MIT",
1010 + "optional": true,
1011 + "os": [
1012 + "win32"
1013 + ],
1014 + "engines": {
1015 + "node": ">= 20"
1016 + }
1017 + },
1018 + "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
1019 + "version": "4.3.3",
1020 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz",
1021 + "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==",
1022 + "cpu": [
1023 + "x64"
1024 + ],
1025 + "dev": true,
1026 + "license": "MIT",
1027 + "optional": true,
1028 + "os": [
1029 + "win32"
1030 + ],
1031 + "engines": {
1032 + "node": ">= 20"
1033 + }
1034 + },
1035 + "node_modules/@tailwindcss/postcss": {
1036 + "version": "4.3.3",
1037 + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.3.tgz",
1038 + "integrity": "sha512-JTSZZGQi1AyKirbLN3azmjVzef92tcX7h+iSqPdaeStyFpGpDlKvvpxeOE8njhbUanbRwr3z8DyzhICWnMtQeg==",
1039 + "dev": true,
1040 + "license": "MIT",
1041 + "dependencies": {
1042 + "@alloc/quick-lru": "^5.2.0",
1043 + "@tailwindcss/node": "4.3.3",
1044 + "@tailwindcss/oxide": "4.3.3",
1045 + "postcss": "^8.5.16",
1046 + "tailwindcss": "4.3.3"
1047 + }
1048 + },
1049 + "node_modules/@types/node": {
1050 + "version": "24.13.3",
1051 + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz",
1052 + "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==",
1053 + "dev": true,
1054 + "license": "MIT",
1055 + "dependencies": {
1056 + "undici-types": "~7.18.0"
1057 + }
1058 + },
1059 + "node_modules/@types/react": {
1060 + "version": "19.2.18",
1061 + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz",
1062 + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==",
1063 + "dev": true,
1064 + "license": "MIT",
1065 + "dependencies": {
1066 + "csstype": "^3.2.2"
1067 + }
1068 + },
1069 + "node_modules/@types/react-dom": {
1070 + "version": "19.2.4",
1071 + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz",
1072 + "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==",
1073 + "dev": true,
1074 + "license": "MIT",
1075 + "peerDependencies": {
1076 + "@types/react": "^19.2.0"
1077 + }
1078 + },
1079 + "node_modules/caniuse-lite": {
1080 + "version": "1.0.30001807",
1081 + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001807.tgz",
1082 + "integrity": "sha512-daRXJ9EB/rdRgu7kV+TTl1YUKtlsMWblPl2sLnpg9DZae16QCegol6A1SmCE31Lm9mXC1sRWGt/krouH+/dl7Q==",
1083 + "funding": [
1084 + {
1085 + "type": "opencollective",
1086 + "url": "https://opencollective.com/browserslist"
1087 + },
1088 + {
1089 + "type": "tidelift",
1090 + "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
1091 + },
1092 + {
1093 + "type": "github",
1094 + "url": "https://github.com/sponsors/ai"
1095 + }
1096 + ],
1097 + "license": "CC-BY-4.0"
1098 + },
1099 + "node_modules/client-only": {
1100 + "version": "0.0.1",
1101 + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
1102 + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
1103 + "license": "MIT"
1104 + },
1105 + "node_modules/csstype": {
1106 + "version": "3.2.3",
1107 + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
1108 + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
1109 + "dev": true,
1110 + "license": "MIT"
1111 + },
1112 + "node_modules/detect-libc": {
1113 + "version": "2.1.2",
1114 + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
1115 + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
1116 + "devOptional": true,
1117 + "license": "Apache-2.0",
1118 + "engines": {
1119 + "node": ">=8"
1120 + }
1121 + },
1122 + "node_modules/enhanced-resolve": {
1123 + "version": "5.24.5",
1124 + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz",
1125 + "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==",
1126 + "dev": true,
1127 + "license": "MIT",
1128 + "dependencies": {
1129 + "graceful-fs": "^4.2.4",
1130 + "tapable": "^2.3.3"
1131 + },
1132 + "engines": {
1133 + "node": ">=10.13.0"
1134 + }
1135 + },
1136 + "node_modules/graceful-fs": {
1137 + "version": "4.2.11",
1138 + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
1139 + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
1140 + "dev": true,
1141 + "license": "ISC"
1142 + },
1143 + "node_modules/jiti": {
1144 + "version": "2.7.0",
1145 + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
1146 + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==",
1147 + "dev": true,
1148 + "license": "MIT",
1149 + "bin": {
1150 + "jiti": "lib/jiti-cli.mjs"
1151 + }
1152 + },
1153 + "node_modules/lightningcss": {
1154 + "version": "1.32.0",
1155 + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
1156 + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
1157 + "dev": true,
1158 + "license": "MPL-2.0",
1159 + "dependencies": {
1160 + "detect-libc": "^2.0.3"
1161 + },
1162 + "engines": {
1163 + "node": ">= 12.0.0"
1164 + },
1165 + "funding": {
1166 + "type": "opencollective",
1167 + "url": "https://opencollective.com/parcel"
1168 + },
1169 + "optionalDependencies": {
1170 + "lightningcss-android-arm64": "1.32.0",
1171 + "lightningcss-darwin-arm64": "1.32.0",
1172 + "lightningcss-darwin-x64": "1.32.0",
1173 + "lightningcss-freebsd-x64": "1.32.0",
1174 + "lightningcss-linux-arm-gnueabihf": "1.32.0",
1175 + "lightningcss-linux-arm64-gnu": "1.32.0",
1176 + "lightningcss-linux-arm64-musl": "1.32.0",
1177 + "lightningcss-linux-x64-gnu": "1.32.0",
1178 + "lightningcss-linux-x64-musl": "1.32.0",
1179 + "lightningcss-win32-arm64-msvc": "1.32.0",
1180 + "lightningcss-win32-x64-msvc": "1.32.0"
1181 + }
1182 + },
1183 + "node_modules/lightningcss-android-arm64": {
1184 + "version": "1.32.0",
1185 + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
1186 + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
1187 + "cpu": [
1188 + "arm64"
1189 + ],
1190 + "dev": true,
1191 + "license": "MPL-2.0",
1192 + "optional": true,
1193 + "os": [
1194 + "android"
1195 + ],
1196 + "engines": {
1197 + "node": ">= 12.0.0"
1198 + },
1199 + "funding": {
1200 + "type": "opencollective",
1201 + "url": "https://opencollective.com/parcel"
1202 + }
1203 + },
1204 + "node_modules/lightningcss-darwin-arm64": {
1205 + "version": "1.32.0",
1206 + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
1207 + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
1208 + "cpu": [
1209 + "arm64"
1210 + ],
1211 + "dev": true,
1212 + "license": "MPL-2.0",
1213 + "optional": true,
1214 + "os": [
1215 + "darwin"
1216 + ],
1217 + "engines": {
1218 + "node": ">= 12.0.0"
1219 + },
1220 + "funding": {
1221 + "type": "opencollective",
1222 + "url": "https://opencollective.com/parcel"
1223 + }
1224 + },
1225 + "node_modules/lightningcss-darwin-x64": {
1226 + "version": "1.32.0",
1227 + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
1228 + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
1229 + "cpu": [
1230 + "x64"
1231 + ],
1232 + "dev": true,
1233 + "license": "MPL-2.0",
1234 + "optional": true,
1235 + "os": [
1236 + "darwin"
1237 + ],
1238 + "engines": {
1239 + "node": ">= 12.0.0"
1240 + },
1241 + "funding": {
1242 + "type": "opencollective",
1243 + "url": "https://opencollective.com/parcel"
1244 + }
1245 + },
1246 + "node_modules/lightningcss-freebsd-x64": {
1247 + "version": "1.32.0",
1248 + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
1249 + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
1250 + "cpu": [
1251 + "x64"
1252 + ],
1253 + "dev": true,
1254 + "license": "MPL-2.0",
1255 + "optional": true,
1256 + "os": [
1257 + "freebsd"
1258 + ],
1259 + "engines": {
1260 + "node": ">= 12.0.0"
1261 + },
1262 + "funding": {
1263 + "type": "opencollective",
1264 + "url": "https://opencollective.com/parcel"
1265 + }
1266 + },
1267 + "node_modules/lightningcss-linux-arm-gnueabihf": {
1268 + "version": "1.32.0",
1269 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
1270 + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
1271 + "cpu": [
1272 + "arm"
1273 + ],
1274 + "dev": true,
1275 + "license": "MPL-2.0",
1276 + "optional": true,
1277 + "os": [
1278 + "linux"
1279 + ],
1280 + "engines": {
1281 + "node": ">= 12.0.0"
1282 + },
1283 + "funding": {
1284 + "type": "opencollective",
1285 + "url": "https://opencollective.com/parcel"
1286 + }
1287 + },
1288 + "node_modules/lightningcss-linux-arm64-gnu": {
1289 + "version": "1.32.0",
1290 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
1291 + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
1292 + "cpu": [
1293 + "arm64"
1294 + ],
1295 + "dev": true,
1296 + "libc": [
1297 + "glibc"
1298 + ],
1299 + "license": "MPL-2.0",
1300 + "optional": true,
1301 + "os": [
1302 + "linux"
1303 + ],
1304 + "engines": {
1305 + "node": ">= 12.0.0"
1306 + },
1307 + "funding": {
1308 + "type": "opencollective",
1309 + "url": "https://opencollective.com/parcel"
1310 + }
1311 + },
1312 + "node_modules/lightningcss-linux-arm64-musl": {
1313 + "version": "1.32.0",
1314 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
1315 + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
1316 + "cpu": [
1317 + "arm64"
1318 + ],
1319 + "dev": true,
1320 + "libc": [
1321 + "musl"
1322 + ],
1323 + "license": "MPL-2.0",
1324 + "optional": true,
1325 + "os": [
1326 + "linux"
1327 + ],
1328 + "engines": {
1329 + "node": ">= 12.0.0"
1330 + },
1331 + "funding": {
1332 + "type": "opencollective",
1333 + "url": "https://opencollective.com/parcel"
1334 + }
1335 + },
1336 + "node_modules/lightningcss-linux-x64-gnu": {
1337 + "version": "1.32.0",
1338 + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
1339 + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
1340 + "cpu": [
1341 + "x64"
1342 + ],
1343 + "dev": true,
1344 + "libc": [
1345 + "glibc"
1346 + ],
1347 + "license": "MPL-2.0",
1348 + "optional": true,
1349 + "os": [
1350 + "linux"
1351 + ],
1352 + "engines": {
1353 + "node": ">= 12.0.0"
1354 + },
1355 + "funding": {
1356 + "type": "opencollective",
1357 + "url": "https://opencollective.com/parcel"
1358 + }
1359 + },
1360 + "node_modules/lightningcss-linux-x64-musl": {
1361 + "version": "1.32.0",
1362 + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
1363 + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
1364 + "cpu": [
1365 + "x64"
1366 + ],
1367 + "dev": true,
1368 + "libc": [
1369 + "musl"
1370 + ],
1371 + "license": "MPL-2.0",
1372 + "optional": true,
1373 + "os": [
1374 + "linux"
1375 + ],
1376 + "engines": {
1377 + "node": ">= 12.0.0"
1378 + },
1379 + "funding": {
1380 + "type": "opencollective",
1381 + "url": "https://opencollective.com/parcel"
1382 + }
1383 + },
1384 + "node_modules/lightningcss-win32-arm64-msvc": {
1385 + "version": "1.32.0",
1386 + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
1387 + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
1388 + "cpu": [
1389 + "arm64"
1390 + ],
1391 + "dev": true,
1392 + "license": "MPL-2.0",
1393 + "optional": true,
1394 + "os": [
1395 + "win32"
1396 + ],
1397 + "engines": {
1398 + "node": ">= 12.0.0"
1399 + },
1400 + "funding": {
1401 + "type": "opencollective",
1402 + "url": "https://opencollective.com/parcel"
1403 + }
1404 + },
1405 + "node_modules/lightningcss-win32-x64-msvc": {
1406 + "version": "1.32.0",
1407 + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
1408 + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
1409 + "cpu": [
1410 + "x64"
1411 + ],
1412 + "dev": true,
1413 + "license": "MPL-2.0",
1414 + "optional": true,
1415 + "os": [
1416 + "win32"
1417 + ],
1418 + "engines": {
1419 + "node": ">= 12.0.0"
1420 + },
1421 + "funding": {
1422 + "type": "opencollective",
1423 + "url": "https://opencollective.com/parcel"
1424 + }
1425 + },
1426 + "node_modules/magic-string": {
1427 + "version": "0.30.21",
1428 + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
1429 + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
1430 + "dev": true,
1431 + "license": "MIT",
1432 + "dependencies": {
1433 + "@jridgewell/sourcemap-codec": "^1.5.5"
1434 + }
1435 + },
1436 + "node_modules/nanoid": {
1437 + "version": "3.3.17",
1438 + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.17.tgz",
1439 + "integrity": "sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==",
1440 + "funding": [
1441 + {
1442 + "type": "github",
1443 + "url": "https://github.com/sponsors/ai"
1444 + }
1445 + ],
1446 + "license": "MIT",
1447 + "bin": {
1448 + "nanoid": "bin/nanoid.cjs"
1449 + },
1450 + "engines": {
1451 + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
1452 + }
1453 + },
1454 + "node_modules/next": {
1455 + "version": "15.5.22",
1456 + "resolved": "https://registry.npmjs.org/next/-/next-15.5.22.tgz",
1457 + "integrity": "sha512-mrtal1sRxO4YrlDS98sDuIvGZivKbFix8w7oAL9ZynfOgc3cADQOQgvwtMooc18Qr8bKzvQAcHwHZ0mbJ7zcfQ==",
1458 + "license": "MIT",
1459 + "dependencies": {
1460 + "@next/env": "15.5.22",
1461 + "@swc/helpers": "0.5.15",
1462 + "caniuse-lite": "^1.0.30001579",
1463 + "postcss": "8.4.31",
1464 + "styled-jsx": "5.1.6"
1465 + },
1466 + "bin": {
1467 + "next": "dist/bin/next"
1468 + },
1469 + "engines": {
1470 + "node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
1471 + },
1472 + "optionalDependencies": {
1473 + "@next/swc-darwin-arm64": "15.5.22",
1474 + "@next/swc-darwin-x64": "15.5.22",
1475 + "@next/swc-linux-arm64-gnu": "15.5.22",
1476 + "@next/swc-linux-arm64-musl": "15.5.22",
1477 + "@next/swc-linux-x64-gnu": "15.5.22",
1478 + "@next/swc-linux-x64-musl": "15.5.22",
1479 + "@next/swc-win32-arm64-msvc": "15.5.22",
1480 + "@next/swc-win32-x64-msvc": "15.5.22",
1481 + "sharp": "^0.34.3"
1482 + },
1483 + "peerDependencies": {
1484 + "@opentelemetry/api": "^1.1.0",
1485 + "@playwright/test": "^1.51.1",
1486 + "babel-plugin-react-compiler": "*",
1487 + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
1488 + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
1489 + "sass": "^1.3.0"
1490 + },
1491 + "peerDependenciesMeta": {
1492 + "@opentelemetry/api": {
1493 + "optional": true
1494 + },
1495 + "@playwright/test": {
1496 + "optional": true
1497 + },
1498 + "babel-plugin-react-compiler": {
1499 + "optional": true
1500 + },
1501 + "sass": {
1502 + "optional": true
1503 + }
1504 + }
1505 + },
1506 + "node_modules/next/node_modules/postcss": {
1507 + "version": "8.4.31",
1508 + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
1509 + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
1510 + "funding": [
1511 + {
1512 + "type": "opencollective",
1513 + "url": "https://opencollective.com/postcss/"
1514 + },
1515 + {
1516 + "type": "tidelift",
1517 + "url": "https://tidelift.com/funding/github/npm/postcss"
1518 + },
1519 + {
1520 + "type": "github",
1521 + "url": "https://github.com/sponsors/ai"
1522 + }
1523 + ],
1524 + "license": "MIT",
1525 + "dependencies": {
1526 + "nanoid": "^3.3.6",
1527 + "picocolors": "^1.0.0",
1528 + "source-map-js": "^1.0.2"
1529 + },
1530 + "engines": {
1531 + "node": "^10 || ^12 || >=14"
1532 + }
1533 + },
1534 + "node_modules/picocolors": {
1535 + "version": "1.1.1",
1536 + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
1537 + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
1538 + "license": "ISC"
1539 + },
1540 + "node_modules/postcss": {
1541 + "version": "8.5.26",
1542 + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz",
1543 + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==",
1544 + "dev": true,
1545 + "funding": [
1546 + {
1547 + "type": "opencollective",
1548 + "url": "https://opencollective.com/postcss/"
1549 + },
1550 + {
1551 + "type": "tidelift",
1552 + "url": "https://tidelift.com/funding/github/npm/postcss"
1553 + },
1554 + {
1555 + "type": "github",
1556 + "url": "https://github.com/sponsors/ai"
1557 + }
1558 + ],
1559 + "license": "MIT",
1560 + "dependencies": {
1561 + "nanoid": "^3.3.17",
1562 + "picocolors": "^1.1.1",
1563 + "source-map-js": "^1.2.1"
1564 + },
1565 + "engines": {
1566 + "node": "^10 || ^12 || >=14"
1567 + }
1568 + },
1569 + "node_modules/react": {
1570 + "version": "19.2.8",
1571 + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz",
1572 + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==",
1573 + "license": "MIT",
1574 + "engines": {
1575 + "node": ">=0.10.0"
1576 + }
1577 + },
1578 + "node_modules/react-dom": {
1579 + "version": "19.2.8",
1580 + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz",
1581 + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==",
1582 + "license": "MIT",
1583 + "dependencies": {
1584 + "scheduler": "^0.27.0"
1585 + },
1586 + "peerDependencies": {
1587 + "react": "^19.2.8"
1588 + }
1589 + },
1590 + "node_modules/scheduler": {
1591 + "version": "0.27.0",
1592 + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
1593 + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
1594 + "license": "MIT"
1595 + },
1596 + "node_modules/semver": {
1597 + "version": "7.8.5",
1598 + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
1599 + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
1600 + "license": "ISC",
1601 + "optional": true,
1602 + "bin": {
1603 + "semver": "bin/semver.js"
1604 + },
1605 + "engines": {
1606 + "node": ">=10"
1607 + }
1608 + },
1609 + "node_modules/sharp": {
1610 + "version": "0.34.5",
1611 + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
1612 + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
1613 + "hasInstallScript": true,
1614 + "license": "Apache-2.0",
1615 + "optional": true,
1616 + "dependencies": {
1617 + "@img/colour": "^1.0.0",
1618 + "detect-libc": "^2.1.2",
1619 + "semver": "^7.7.3"
1620 + },
1621 + "engines": {
1622 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
1623 + },
1624 + "funding": {
1625 + "url": "https://opencollective.com/libvips"
1626 + },
1627 + "optionalDependencies": {
1628 + "@img/sharp-darwin-arm64": "0.34.5",
1629 + "@img/sharp-darwin-x64": "0.34.5",
1630 + "@img/sharp-libvips-darwin-arm64": "1.2.4",
1631 + "@img/sharp-libvips-darwin-x64": "1.2.4",
1632 + "@img/sharp-libvips-linux-arm": "1.2.4",
1633 + "@img/sharp-libvips-linux-arm64": "1.2.4",
1634 + "@img/sharp-libvips-linux-ppc64": "1.2.4",
1635 + "@img/sharp-libvips-linux-riscv64": "1.2.4",
1636 + "@img/sharp-libvips-linux-s390x": "1.2.4",
1637 + "@img/sharp-libvips-linux-x64": "1.2.4",
1638 + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
1639 + "@img/sharp-libvips-linuxmusl-x64": "1.2.4",
1640 + "@img/sharp-linux-arm": "0.34.5",
1641 + "@img/sharp-linux-arm64": "0.34.5",
1642 + "@img/sharp-linux-ppc64": "0.34.5",
1643 + "@img/sharp-linux-riscv64": "0.34.5",
1644 + "@img/sharp-linux-s390x": "0.34.5",
1645 + "@img/sharp-linux-x64": "0.34.5",
1646 + "@img/sharp-linuxmusl-arm64": "0.34.5",
1647 + "@img/sharp-linuxmusl-x64": "0.34.5",
1648 + "@img/sharp-wasm32": "0.34.5",
1649 + "@img/sharp-win32-arm64": "0.34.5",
1650 + "@img/sharp-win32-ia32": "0.34.5",
1651 + "@img/sharp-win32-x64": "0.34.5"
1652 + }
1653 + },
1654 + "node_modules/source-map-js": {
1655 + "version": "1.2.1",
1656 + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
1657 + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
1658 + "license": "BSD-3-Clause",
1659 + "engines": {
1660 + "node": ">=0.10.0"
1661 + }
1662 + },
1663 + "node_modules/styled-jsx": {
1664 + "version": "5.1.6",
1665 + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
1666 + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==",
1667 + "license": "MIT",
1668 + "dependencies": {
1669 + "client-only": "0.0.1"
1670 + },
1671 + "engines": {
1672 + "node": ">= 12.0.0"
1673 + },
1674 + "peerDependencies": {
1675 + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0"
1676 + },
1677 + "peerDependenciesMeta": {
1678 + "@babel/core": {
1679 + "optional": true
1680 + },
1681 + "babel-plugin-macros": {
1682 + "optional": true
1683 + }
1684 + }
1685 + },
1686 + "node_modules/tailwindcss": {
1687 + "version": "4.3.3",
1688 + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz",
1689 + "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==",
1690 + "dev": true,
1691 + "license": "MIT"
1692 + },
1693 + "node_modules/tapable": {
1694 + "version": "2.3.3",
1695 + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",
1696 + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==",
1697 + "dev": true,
1698 + "license": "MIT",
1699 + "engines": {
1700 + "node": ">=6"
1701 + },
1702 + "funding": {
1703 + "type": "opencollective",
1704 + "url": "https://opencollective.com/webpack"
1705 + }
1706 + },
1707 + "node_modules/tslib": {
1708 + "version": "2.8.1",
1709 + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
1710 + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
1711 + "license": "0BSD"
1712 + },
1713 + "node_modules/typescript": {
1714 + "version": "5.9.3",
1715 + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
1716 + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
1717 + "dev": true,
1718 + "license": "Apache-2.0",
1719 + "bin": {
1720 + "tsc": "bin/tsc",
1721 + "tsserver": "bin/tsserver"
1722 + },
1723 + "engines": {
1724 + "node": ">=14.17"
1725 + }
1726 + },
1727 + "node_modules/undici-types": {
1728 + "version": "7.18.2",
1729 + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
1730 + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
1731 + "dev": true,
1732 + "license": "MIT"
1733 + }
1734 + }
1735 +}
added package.json +27 −0
@@ -0,0 +1,27 @@
1 +{
2 + "name": "hilmacorp-web",
3 + "version": "1.0.0",
4 + "private": true,
5 + "author": "Simon-Pierre Boucher <contact@spboucher.ai>",
6 + "description": "Hilmacorp.ai — Web Platform (bilingual FR/EN corporate site)",
7 + "scripts": {
8 + "dev": "next dev",
9 + "build": "next build",
10 + "start": "next start",
11 + "lint": "next lint",
12 + "test": "node --test tests/*.test.mjs"
13 + },
14 + "dependencies": {
15 + "next": "^15.4.5",
16 + "react": "^19.1.0",
17 + "react-dom": "^19.1.0"
18 + },
19 + "devDependencies": {
20 + "@tailwindcss/postcss": "^4.1.11",
21 + "@types/node": "^24.0.0",
22 + "@types/react": "^19.1.8",
23 + "@types/react-dom": "^19.1.6",
24 + "tailwindcss": "^4.1.11",
25 + "typescript": "^5.8.3"
26 + }
27 +}
added postcss.config.mjs +15 −0
@@ -0,0 +1,15 @@
1 +/**
2 + * Author: Simon-Pierre Boucher
3 + * Contact: contact@spboucher.ai
4 + * Project: Hilmacorp.ai — Web Platform
5 + * File: postcss.config.mjs
6 + * Description: PostCSS configuration wiring Tailwind CSS v4
7 + */
8 +
9 +const config = {
10 + plugins: {
11 + "@tailwindcss/postcss": {},
12 + },
13 +};
14 +
15 +export default config;
added public/downloads/ZyquoAgent.dmg +0 −0

Binary file not shown.

added public/downloads/ZyquoCloud.dmg +0 −0

Binary file not shown.

added public/downloads/ZyquoLocal.dmg +0 −0

Binary file not shown.

added public/downloads/ZyquoRouter.dmg +0 −0

Binary file not shown.

added tests/contact-validation.test.mjs +94 −0
@@ -0,0 +1,94 @@
1 +/**
2 + * Author: Simon-Pierre Boucher
3 + * Contact: contact@spboucher.ai
4 + * Project: Hilmacorp.ai — Web Platform
5 + * File: tests/contact-validation.test.mjs
6 + * Description: Unit tests for the contact form validation used by the /api/contact route
7 + */
8 +
9 +import { test } from "node:test";
10 +import assert from "node:assert/strict";
11 +import { validateContact } from "../lib/validate.ts";
12 +
13 +test("accepts a valid payload", () => {
14 + const result = validateContact({
15 + name: "Jane Doe",
16 + organization: "Acme Inc.",
17 + email: "jane@acme.com",
18 + message: "We would like to discuss an AI project.",
19 + });
20 + assert.equal(result.ok, true);
21 + assert.deepEqual(result.errors, {});
22 + assert.equal(result.data?.name, "Jane Doe");
23 +});
24 +
25 +test("organization is optional", () => {
26 + const result = validateContact({
27 + name: "Jane Doe",
28 + organization: "",
29 + email: "jane@acme.com",
30 + message: "A perfectly valid message.",
31 + });
32 + assert.equal(result.ok, true);
33 +});
34 +
35 +test("rejects a missing name", () => {
36 + const result = validateContact({
37 + name: "",
38 + email: "jane@acme.com",
39 + message: "A perfectly valid message.",
40 + });
41 + assert.equal(result.ok, false);
42 + assert.equal(result.errors.name, "invalid_name");
43 +});
44 +
45 +test("rejects an invalid email", () => {
46 + for (const email of ["", "not-an-email", "a@b", "a b@c.com"]) {
47 + const result = validateContact({
48 + name: "Jane Doe",
49 + email,
50 + message: "A perfectly valid message.",
51 + });
52 + assert.equal(result.ok, false, `expected rejection for email: "${email}"`);
53 + assert.equal(result.errors.email, "invalid_email");
54 + }
55 +});
56 +
57 +test("rejects a message that is too short", () => {
58 + const result = validateContact({
59 + name: "Jane Doe",
60 + email: "jane@acme.com",
61 + message: "hi",
62 + });
63 + assert.equal(result.ok, false);
64 + assert.equal(result.errors.message, "invalid_message");
65 +});
66 +
67 +test("trims whitespace and handles non-string input safely", () => {
68 + const result = validateContact({
69 + name: " Jane Doe ",
70 + organization: 42,
71 + email: " jane@acme.com ",
72 + message: " A perfectly valid message. ",
73 + });
74 + assert.equal(result.ok, true);
75 + assert.equal(result.data?.name, "Jane Doe");
76 + assert.equal(result.data?.organization, "");
77 +});
78 +
79 +test("handles null and garbage payloads without throwing", () => {
80 + for (const payload of [null, undefined, "string", 12, []]) {
81 + const result = validateContact(payload);
82 + assert.equal(result.ok, false);
83 + }
84 +});
85 +
86 +test("caps oversized fields instead of rejecting them", () => {
87 + const result = validateContact({
88 + name: "Jane Doe",
89 + email: "jane@acme.com",
90 + message: "x".repeat(10000),
91 + });
92 + assert.equal(result.ok, true);
93 + assert.equal(result.data?.message.length, 5000);
94 +});
added tsconfig.json +26 −0
@@ -0,0 +1,26 @@
1 +{
2 + // Author: Simon-Pierre Boucher
3 + // Contact: contact@spboucher.ai
4 + // Project: Hilmacorp.ai — Web Platform
5 + // File: tsconfig.json
6 + // Description: TypeScript strict configuration for the Next.js App Router project
7 + "compilerOptions": {
8 + "target": "ES2022",
9 + "lib": ["dom", "dom.iterable", "esnext"],
10 + "allowJs": true,
11 + "skipLibCheck": true,
12 + "strict": true,
13 + "noEmit": true,
14 + "esModuleInterop": true,
15 + "module": "esnext",
16 + "moduleResolution": "bundler",
17 + "resolveJsonModule": true,
18 + "isolatedModules": true,
19 + "jsx": "preserve",
20 + "incremental": true,
21 + "plugins": [{ "name": "next" }],
22 + "paths": { "@/*": ["./*"] }
23 + },
24 + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
25 + "exclude": ["node_modules"]
26 +}
27