SPB Git

spb/spboucher.ai Public

spboucher.ai — personal website of Simon-Pierre Boucher.

TypeScript 93.4% HTML 5.5% CSS 1%

Initial release: spboucher.ai personal website

Multi-page academic + developer site (Next.js 16 App Router, Tailwind CSS v4,
shadcn/ui-style components, Framer Motion): home, research, teaching, Zyquo
macOS apps portfolio, CV, and a health endpoint. Dark mode, responsive,
accessible. Deployed on node m2u64 (PM2, port 3100) behind ngrok at
https://www.spboucher.ai.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Simon-Pierre Boucher committed 10 days ago (Jul 31, 2026)

Showing 33 changed files with +3,881 and −0

added .gitignore +31 −0
@@ -0,0 +1,31 @@
1 +# .gitignore
2 +# spboucher.ai Web
3 +# Author: Simon-Pierre Boucher
4 +# Mail: contact@spboucher.ai
5 +
6 +# dependencies
7 +/node_modules
8 +
9 +# next.js
10 +/.next/
11 +/out/
12 +
13 +# production
14 +/build
15 +
16 +# misc
17 +.DS_Store
18 +*.pem
19 +
20 +# debug
21 +npm-debug.log*
22 +
23 +# env files
24 +.env*
25 +
26 +# typescript
27 +*.tsbuildinfo
28 +next-env.d.ts
29 +
30 +# local tooling settings
31 +.claude/settings.local.json
added CLAUDE.md +191 −0
@@ -0,0 +1,191 @@
1 +<!--
2 + CLAUDE.md
3 + spboucher.ai — Personal Website
4 + Author: Simon-Pierre Boucher
5 + Mail: contact@spboucher.ai
6 +-->
7 +
8 +# CLAUDE.md — Personal Website (www.spboucher.ai)
9 +
10 +This file provides guidance to Claude Code when working on this repository.
11 +
12 +## Project Overview
13 +
14 +Personal academic and developer website for **Simon-Pierre Boucher**, Professor in the Department of Administrative Sciences at Université du Québec en Outaouais (UQO). The site presents his academic profile (research, publications, teaching) and a portfolio of macOS applications he develops (the Zyquo suite).
15 +
16 +- **Domain:** www.spboucher.ai
17 +- **Hosting:** Served locally on node `m2u64` and exposed publicly via **ngrok**
18 +- **Stack:** React / Next.js, Tailwind CSS, shadcn/ui, Framer Motion
19 +- **Language of the site:** English
20 +
21 +## Mandatory File Header Convention
22 +
23 +**Every source file created or modified in this project (`.tsx`, `.ts`, `.jsx`, `.js`, `.css`, `.md`, config files, etc.) MUST begin with a header comment in this exact format:**
24 +
25 +```
26 +<!--
27 + FILENAME.ext
28 + spboucher.ai Web
29 + Author: Simon-Pierre Boucher
30 + Mail: contact@spboucher.ai
31 +-->
32 +```
33 +
34 +Adapt the comment syntax to the file type:
35 +
36 +- **HTML / Markdown / MDX:** `<!-- ... -->`
37 +- **TS / TSX / JS / JSX / CSS:**
38 + ```ts
39 + /*
40 + FILENAME.tsx
41 + spboucher.ai Web
42 + Author: Simon-Pierre Boucher
43 + Mail: contact@spboucher.ai
44 + */
45 + ```
46 +- **Shell / YAML / Python:** `#` prefixed lines with the same four fields.
47 +
48 +Never omit this header. When editing an existing file that lacks it, add it.
49 +
50 +## Dev & Deployment
51 +
52 +```bash
53 +npm install # install dependencies
54 +npm run dev # local dev server (default: http://localhost:3000)
55 +npm run build # production build
56 +npm run start # serve production build
57 +```
58 +
59 +### ngrok exposure (node m2u64)
60 +
61 +The site runs on node `m2u64` and is exposed with ngrok bound to the custom domain:
62 +
63 +```bash
64 +ngrok http --domain=www.spboucher.ai 3000
65 +```
66 +
67 +- Keep the production build (`npm run build && npm run start`) behind ngrok, not the dev server.
68 +- If the tunnel drops, restart the ngrok command above on m2u64.
69 +
70 +## Site Structure
71 +
72 +```
73 +/ Home (hero, bio, highlights)
74 +/research Publications, working papers, presentations
75 +/teaching Courses taught
76 +/apps Zyquo macOS app portfolio
77 +/cv CV summary + contact
78 +```
79 +
80 +## Homepage Content (use this copy)
81 +
82 +### Hero
83 +
84 +- **Name:** Simon-Pierre Boucher
85 +- **Title:** Professor — Department of Administrative Sciences
86 +- **Institution:** Université du Québec en Outaouais (UQO), Gatineau — Pavillon Alexandre-Taché
87 +- **Tagline suggestion:** "Financial econometrics researcher and macOS/AI developer."
88 +- **Contact:**
89 + - Academic: simon-pierre.boucher@uqo.ca
90 + - Personal: contact@spboucher.ai
91 + - GitHub: https://github.com/spboucher-ai
92 +- **Languages:** Bilingual — French and English
93 +
94 +### About / Bio
95 +
96 +Simon-Pierre Boucher is a Professor in the Department of Administrative Sciences at Université du Québec en Outaouais (UQO). His research focuses on financial econometrics, commodity markets, monetary policy announcements, high-frequency finance, volatility modelling, textual analysis, and financialization. He is completing a Ph.D. in Business Administration (Finance and Insurance) at Université Laval under the supervision of Prof. Marie-Hélène Gagnon and Prof. Gabriel Power. Alongside his academic work, he builds native macOS applications focused on AI and local LLMs (the Zyquo suite).
97 +
98 +### Education
99 +
100 +- **Ph.D. in Business Administration (Finance and Insurance)** — Université Laval, 2020–Present
101 +- **M.Sc. in Business Administration (Finance)** — Université Laval, 2017–2019 (Thesis: *Impact of Commuting Times on Residential Property Values: Evidence from the Province of Québec*)
102 +- **B.B.A. in Business Administration (Finance)** — Université Laval, 2013–2017
103 +
104 +### Research Highlights
105 +
106 +**Publication:**
107 +- Boucher, S.-P., Gagnon, M.-H., & Power, G. J. (2025). *Speculative Trading in Energy Markets: Evidence from Macroeconomic Surprises.* The Energy Journal.
108 +
109 +**Working papers:**
110 +1. *Has Financialization Changed the Impact of Macro Announcements on U.S. Commodity Markets?* — SSRN Working Paper, May 2022 — https://ssrn.com/abstract=400929
111 +2. *Modelling Volatility Dynamics Between Commodity ETFs and Their Net Asset Value using BVAR and HAR Models* — January 2023
112 +3. *Returns and Volatility Around FOMC Announcements: A High-Frequency Analysis of Policy Tone and Novelty* — November 2023
113 +
114 +**Conference presentations:** CRREP Research Day (2022, 2023); Canadian Economics Association 61st & 62nd Annual Conferences (2022, 2023); 7th Winter Workshop on Commodity Markets, Mont-Tremblant (2024).
115 +
116 +### Teaching (Université Laval — Lecturer, 2021–Present)
117 +
118 +| Code | Course | Level | Terms |
119 +|------|--------|-------|-------|
120 +| GSF-3100 | Capital Markets | Undergraduate | F2021, W2021, F2022, W2023 |
121 +| GSF-6053 | Financial Econometrics I | Graduate | W2022, W2025 |
122 +| GSF-1500 | Financial Management | Undergraduate | S2022 |
123 +| GSF-6028 | Financial Theory | Graduate | W2024 |
124 +
125 +Teaching Assistant at Université Laval (2018–2021): GSF-2101 Portfolio Management, GSF-2102 Corporate Finance, GSF-6008 Corporate Finance, GSF-6025 Financial Strategies & Policies I, GSF-6028 Financial Theory.
126 +
127 +### Technical Skills (for a skills section)
128 +
129 +- **Languages & frameworks:** Python, TypeScript/JavaScript, R, MATLAB, SAS/STATA, SQL, Julia, C++, Swift/SwiftUI, LaTeX, React/Next.js, Node.js, FastAPI, Flask, Streamlit, Tailwind CSS, shadcn/ui, Framer Motion
130 +- **Data & econometrics:** Pandas, NumPy, statsmodels, scikit-learn; VAR, BVAR, HAR, GARCH, DCC-GARCH; event studies, high-frequency analysis, realized volatility; FRED, FMP, EODHD, Yahoo Finance, World Bank, IMF, OECD
131 +- **AI, LLMs & agents:** Claude/Anthropic SDK, OpenAI API, OpenRouter, Vercel AI SDK, Ollama, llama.cpp, GGUF, Hugging Face; embeddings, RAG, vector databases; tool-augmented agents; LoRA, QLoRA, SFT fine-tuning
132 +
133 +## Apps Page — Zyquo macOS Suite (REQUIRED)
134 +
135 +The `/apps` page is a portfolio of six native macOS applications. **Each app card MUST display: the app icon, the name, a short description, and three buttons/links (GitHub Repo, Release, Download DMG).**
136 +
137 +App icons: store them in `/public/icons/` (e.g. `/public/icons/zyquo-cloud.png`). If an icon file is missing, fetch it from the corresponding GitHub repo (app icon asset) or use a styled placeholder with the app's emoji until the real icon is added — but the icon slot must always be present in the layout.
138 +
139 +### 1. ☁️ Zyquo Cloud — Multi-provider AI chat
140 +Native macOS chat client supporting multiple AI providers (Anthropic, OpenAI, and more) in a single unified interface.
141 +- Repo: https://github.com/spboucher-ai/zyquo-cloud
142 +- Release: https://github.com/spboucher-ai/zyquo-cloud/releases/tag/v1.0.0
143 +- DMG: https://github.com/spboucher-ai/zyquo-cloud/releases/latest/download/ZyquoCloud.dmg
144 +
145 +### 2. 💻 Zyquo Local — 100% local LLMs (MLX)
146 +Run large language models entirely on-device with Apple MLX. Private, offline, no API keys required.
147 +- Repo: https://github.com/spboucher-ai/zyquo-local
148 +- Release: https://github.com/spboucher-ai/zyquo-local/releases/tag/v1.0.0
149 +- DMG: https://github.com/spboucher-ai/zyquo-local/releases/latest/download/ZyquoLocal.dmg
150 +
151 +### 3. 🤖 Zyquo Agent — Autonomous agent for the Mac
152 +An autonomous AI agent that can act on your Mac: tool calling, task automation, and multi-step workflows.
153 +- Repo: https://github.com/spboucher-ai/zyquo-agent
154 +- Release: https://github.com/spboucher-ai/zyquo-agent/releases/tag/v1.0.0
155 +- DMG: https://github.com/spboucher-ai/zyquo-agent/releases/latest/download/ZyquoAgent.dmg
156 +
157 +### 4. 🌍 Zyquo Atlas — AI-native web browser
158 +A web browser built around AI from the ground up: page understanding, summarization, and assisted browsing.
159 +- Repo: https://github.com/spboucher-ai/zyquo-atlas
160 +- Release: https://github.com/spboucher-ai/zyquo-atlas/releases/tag/v1.0.0
161 +- DMG: https://github.com/spboucher-ai/zyquo-atlas/releases/latest/download/ZyquoAtlas.dmg
162 +
163 +### 5. ⚒️ Zyquo MLX — On-device model forge
164 +Fine-tune and quantize models directly on Apple Silicon: LoRA/QLoRA fine-tuning, quantization, and model management with MLX.
165 +- Repo: https://github.com/spboucher-ai/zyquo-mlx
166 +- Release: https://github.com/spboucher-ai/zyquo-mlx/releases/tag/v1.0.0
167 +- DMG: https://github.com/spboucher-ai/zyquo-mlx/releases/latest/download/ZyquoMLX.dmg
168 +
169 +### 6. 🔀 Zyquo Router — Local LLM gateway
170 +A local gateway that routes requests across LLM providers and local models through a single unified endpoint.
171 +- Repo: https://github.com/spboucher-ai/zyquo-router
172 +- Release: https://github.com/spboucher-ai/zyquo-router/releases/tag/v1.0.0
173 +- DMG: https://github.com/spboucher-ai/zyquo-router/releases/latest/download/ZyquoRouter.dmg
174 +
175 +## Design Guidelines
176 +
177 +- Clean, professional academic aesthetic with a modern developer edge.
178 +- Dark mode support.
179 +- Use Tailwind CSS + shadcn/ui components; Framer Motion for subtle animations only.
180 +- Fully responsive (mobile-first).
181 +- App cards on `/apps` in a responsive grid (1 col mobile, 2 cols tablet, 3 cols desktop).
182 +- Semantic HTML, accessible (alt text on all app icons).
183 +
184 +## Rules for Claude
185 +
186 +1. **Always add the file header comment** (see "Mandatory File Header Convention") at the top of every file you create or edit.
187 +2. The entire site content is in **English**.
188 +3. Never remove or alter the app download links (Repo / Release / DMG) — they must remain exactly as listed above.
189 +4. Every Zyquo app card must include its icon and description.
190 +5. Keep the ngrok/m2u64 deployment instructions in the README up to date if ports or commands change.
191 +6. Prefer server components (Next.js App Router) where possible; client components only when interactivity requires it.
added README.md +171 −0
@@ -0,0 +1,171 @@
1 +<!--
2 + README.md
3 + spboucher.ai Web
4 + Author: Simon-Pierre Boucher
5 + Mail: contact@spboucher.ai
6 +-->
7 +
8 +<div align="center">
9 +
10 +# 🎓 spboucher.ai
11 +
12 +### Personal website of **Simon-Pierre Boucher**
13 +
14 +*Financial econometrics researcher and macOS/AI developer.*
15 +
16 +Professor — Department of Administrative Sciences, Université du Québec en Outaouais (UQO)
17 +
18 +<br/>
19 +
20 +[![Website](https://img.shields.io/website?url=https%3A%2F%2Fwww.spboucher.ai&up_message=online&down_message=offline&label=www.spboucher.ai&style=for-the-badge)](https://www.spboucher.ai)
21 +[![Next.js](https://img.shields.io/badge/Next.js-16-000000?style=for-the-badge&logo=nextdotjs&logoColor=white)](https://nextjs.org)
22 +[![React](https://img.shields.io/badge/React-19-61DAFB?style=for-the-badge&logo=react&logoColor=black)](https://react.dev)
23 +[![TypeScript](https://img.shields.io/badge/TypeScript-5-3178C6?style=for-the-badge&logo=typescript&logoColor=white)](https://www.typescriptlang.org)
24 +
25 +[![Tailwind CSS](https://img.shields.io/badge/Tailwind_CSS-4-06B6D4?style=flat-square&logo=tailwindcss&logoColor=white)](https://tailwindcss.com)
26 +[![shadcn/ui](https://img.shields.io/badge/shadcn%2Fui-components-18181B?style=flat-square)](https://ui.shadcn.com)
27 +[![Framer Motion](https://img.shields.io/badge/Framer_Motion-12-0055FF?style=flat-square&logo=framer&logoColor=white)](https://www.framer.com/motion/)
28 +[![PM2](https://img.shields.io/badge/PM2-managed-2B037A?style=flat-square&logo=pm2&logoColor=white)](https://pm2.keymetrics.io)
29 +[![ngrok](https://img.shields.io/badge/ngrok-tunnel-1F1E37?style=flat-square&logo=ngrok&logoColor=white)](https://ngrok.com)
30 +[![Made in Québec](https://img.shields.io/badge/Made_in-Québec_🇨🇦-1E40AF?style=flat-square)](https://uqo.ca)
31 +
32 +<br/>
33 +
34 +**[🏠 Home](https://www.spboucher.ai)** ·
35 +**[🔬 Research](https://www.spboucher.ai/research)** ·
36 +**[📚 Teaching](https://www.spboucher.ai/teaching)** ·
37 +**[🖥️ Apps](https://www.spboucher.ai/apps)** ·
38 +**[📄 CV](https://www.spboucher.ai/cv)**
39 +
40 +</div>
41 +
42 +---
43 +
44 +## 👋 About
45 +
46 +This repository powers **[www.spboucher.ai](https://www.spboucher.ai)** — the personal academic and developer website of Simon-Pierre Boucher. It presents his academic profile (research in financial econometrics, commodity markets, monetary policy announcements, high-frequency finance, volatility modelling, textual analysis, and financialization) alongside the **Zyquo suite**: six native macOS applications focused on AI and local LLMs.
47 +
48 +## ✨ Features
49 +
50 +-**Next.js 16 App Router** — server components everywhere, static prerendering on every page
51 +- 🎨 **Tailwind CSS v4 + shadcn/ui-style components** — clean academic aesthetic with a modern developer edge
52 +- 🌗 **Dark mode** — system-aware with a manual toggle, no flash on load
53 +- 🎬 **Framer Motion** — subtle scroll reveals, with `prefers-reduced-motion` and no-JS fallbacks
54 +- 📱 **Fully responsive** — mobile-first, 1/2/3-column app grid
55 +-**Accessible** — semantic HTML, alt text on all app icons, keyboard-friendly nav
56 +- 💚 **Health endpoint**`GET /api/health` for uptime monitoring
57 +
58 +## 🗺️ Site Map
59 +
60 +| Route | Content |
61 +|-------|---------|
62 +| `/` | Hero, bio, education, research highlight, technical skills |
63 +| `/research` | Publications, working papers, conference presentations |
64 +| `/teaching` | Courses taught (lecturer & teaching assistant, Université Laval) |
65 +| `/apps` | Zyquo macOS app portfolio |
66 +| `/cv` | CV summary + contact |
67 +| `/api/health` | JSON health check |
68 +
69 +## 🖥️ The Zyquo macOS Suite
70 +
71 +Six native macOS applications for AI and local LLMs — Swift/SwiftUI, built for Apple Silicon.
72 +
73 +| | App | Description | Links |
74 +|---|-----|-------------|-------|
75 +| <img src="public/icons/zyquo-cloud.png" width="48" alt="Zyquo Cloud icon"/> | **☁️ Zyquo Cloud** | Multi-provider AI chat — Anthropic, OpenAI, and more in one unified interface | [Repo](https://github.com/spboucher-ai/zyquo-cloud) · [Release](https://github.com/spboucher-ai/zyquo-cloud/releases/tag/v1.0.0) · [DMG](https://github.com/spboucher-ai/zyquo-cloud/releases/latest/download/ZyquoCloud.dmg) |
76 +| <img src="public/icons/zyquo-local.png" width="48" alt="Zyquo Local icon"/> | **💻 Zyquo Local** | 100% local LLMs with Apple MLX — private, offline, no API keys | [Repo](https://github.com/spboucher-ai/zyquo-local) · [Release](https://github.com/spboucher-ai/zyquo-local/releases/tag/v1.0.0) · [DMG](https://github.com/spboucher-ai/zyquo-local/releases/latest/download/ZyquoLocal.dmg) |
77 +| <img src="public/icons/zyquo-agent.png" width="48" alt="Zyquo Agent icon"/> | **🤖 Zyquo Agent** | Autonomous AI agent for the Mac — tool calling, automation, multi-step workflows | [Repo](https://github.com/spboucher-ai/zyquo-agent) · [Release](https://github.com/spboucher-ai/zyquo-agent/releases/tag/v1.0.0) · [DMG](https://github.com/spboucher-ai/zyquo-agent/releases/latest/download/ZyquoAgent.dmg) |
78 +| <img src="public/icons/zyquo-atlas.png" width="48" alt="Zyquo Atlas icon"/> | **🌍 Zyquo Atlas** | AI-native web browser — page understanding, summarization, assisted browsing | [Repo](https://github.com/spboucher-ai/zyquo-atlas) · [Release](https://github.com/spboucher-ai/zyquo-atlas/releases/tag/v1.0.0) · [DMG](https://github.com/spboucher-ai/zyquo-atlas/releases/latest/download/ZyquoAtlas.dmg) |
79 +| <img src="public/icons/zyquo-mlx.png" width="48" alt="Zyquo MLX icon"/> | **⚒️ Zyquo MLX** | On-device model forge — LoRA/QLoRA fine-tuning, quantization, model management | [Repo](https://github.com/spboucher-ai/zyquo-mlx) · [Release](https://github.com/spboucher-ai/zyquo-mlx/releases/tag/v1.0.0) · [DMG](https://github.com/spboucher-ai/zyquo-mlx/releases/latest/download/ZyquoMLX.dmg) |
80 +| <img src="public/icons/zyquo-router.png" width="48" alt="Zyquo Router icon"/> | **🔀 Zyquo Router** | Local LLM gateway — one unified endpoint across providers and local models | [Repo](https://github.com/spboucher-ai/zyquo-router) · [Release](https://github.com/spboucher-ai/zyquo-router/releases/tag/v1.0.0) · [DMG](https://github.com/spboucher-ai/zyquo-router/releases/latest/download/ZyquoRouter.dmg) |
81 +
82 +## 🚀 Quick Start
83 +
84 +```bash
85 +git clone https://github.com/spboucher-ai/spboucher.ai.git
86 +cd spboucher.ai
87 +
88 +npm install # install dependencies
89 +npm run dev # local dev server → http://localhost:3000
90 +npm run build # production build
91 +npm run start # serve production build
92 +npm run typecheck # TypeScript check
93 +```
94 +
95 +## 🌐 Production Deployment (node m2u64)
96 +
97 +The site runs on cluster node `m2u64` and is exposed publicly with **ngrok** bound to the custom domain. On m2u64 the app listens on **port 3100** (port 3000 is already in use on that node); both the app and the tunnel run under **PM2** for auto-restart.
98 +
99 +```mermaid
100 +flowchart LR
101 + A["🌍 Visitor"] --> B["🔒 ngrok edge<br/>www.spboucher.ai"]
102 + B --> C["🖥️ m2u64 : 3100<br/>next start (PM2: spboucher)"]
103 + C --> D["💚 /api/health"]
104 +```
105 +
106 +```bash
107 +# on m2u64
108 +npm ci && npm run build
109 +pm2 start scripts/start-production.sh --name spboucher --interpreter /bin/zsh
110 +pm2 start "ngrok http --url=www.spboucher.ai 3100" --name spboucher-ngrok
111 +pm2 save
112 +```
113 +
114 +- Always serve the production build (`npm run build && npm run start`) behind ngrok — never the dev server.
115 +- If the tunnel drops, restart it: `pm2 restart spboucher-ngrok` (or run `ngrok http --url=www.spboucher.ai 3100` manually).
116 +- Health check: `GET /api/health``{"status":"ok","service":"spboucher-ai"}`.
117 +
118 +## 📁 Project Structure
119 +
120 +```
121 +app/
122 +├── layout.tsx Root layout (theme init, header, footer)
123 +├── page.tsx Home — hero, bio, education, highlight, skills
124 +├── research/page.tsx Publications, working papers, presentations
125 +├── teaching/page.tsx Courses taught
126 +├── apps/page.tsx Zyquo macOS app portfolio
127 +├── cv/page.tsx CV summary + contact
128 +├── api/health/route.ts Health check endpoint
129 +└── globals.css Tailwind v4 theme tokens (light/dark)
130 +
131 +components/
132 +├── site-header.tsx Sticky nav + mobile menu
133 +├── site-footer.tsx Footer
134 +├── theme-toggle.tsx Dark mode toggle
135 +├── reveal.tsx Framer Motion scroll reveal
136 +├── app-card.tsx Zyquo app card (icon, description, 3 links)
137 +└── ui/ shadcn/ui-style primitives (button, card, badge)
138 +
139 +lib/
140 +├── apps.ts Zyquo suite data (single source of truth)
141 +└── utils.ts cn() class merger
142 +
143 +public/icons/ Zyquo app icons (fetched from the app repos)
144 +scripts/ Production start script (PM2)
145 +```
146 +
147 +## 📐 Conventions
148 +
149 +- 📝 Every source file starts with the mandatory project header comment (see `CLAUDE.md`)
150 +- 🇬🇧 All site content is in English
151 +- 🔗 App download links on `/apps` (Repo / Release / DMG) must remain exactly as specified in `CLAUDE.md`
152 +- 🧩 Server components by default; client components only where interactivity requires it
153 +
154 +## 📫 Contact
155 +
156 +| | |
157 +|---|---|
158 +| 🎓 Academic | [simon-pierre.boucher@uqo.ca](mailto:simon-pierre.boucher@uqo.ca) |
159 +| ✉️ Personal | [contact@spboucher.ai](mailto:contact@spboucher.ai) |
160 +| 🐙 GitHub | [@spboucher-ai](https://github.com/spboucher-ai) |
161 +| 📍 Office | UQO, Gatineau — Pavillon Alexandre-Taché |
162 +
163 +---
164 +
165 +<div align="center">
166 +
167 +**© Simon-Pierre Boucher** — Université du Québec en Outaouais (UQO)
168 +
169 +*Built with Next.js · Tailwind CSS · shadcn/ui · Framer Motion — served from a Mac, tunneled to the world.* 🚇
170 +
171 +</div>
added app/api/health/route.ts +12 −0
@@ -0,0 +1,12 @@
1 +/*
2 + route.ts
3 + spboucher.ai Web
4 + Author: Simon-Pierre Boucher
5 + Mail: contact@spboucher.ai
6 +*/
7 +
8 +import { NextResponse } from "next/server";
9 +
10 +export function GET() {
11 + return NextResponse.json({ status: "ok", service: "spboucher-ai" });
12 +}
added app/apps/page.tsx +42 −0
@@ -0,0 +1,42 @@
1 +/*
2 + page.tsx
3 + spboucher.ai Web
4 + Author: Simon-Pierre Boucher
5 + Mail: contact@spboucher.ai
6 +*/
7 +
8 +import type { Metadata } from "next";
9 +
10 +import { AppCard } from "@/components/app-card";
11 +import { Reveal } from "@/components/reveal";
12 +import { zyquoApps } from "@/lib/apps";
13 +
14 +export const metadata: Metadata = {
15 + title: "Apps — Zyquo macOS Suite",
16 + description:
17 + "The Zyquo suite: six native macOS applications for AI and local LLMs — multi-provider chat, on-device MLX models, autonomous agents, an AI-native browser, model fine-tuning, and LLM routing.",
18 +};
19 +
20 +export default function AppsPage() {
21 + return (
22 + <div className="mx-auto max-w-5xl px-4 py-16 sm:px-6">
23 + <Reveal>
24 + <h1 className="text-3xl font-bold tracking-tight sm:text-4xl">
25 + Zyquo macOS Suite
26 + </h1>
27 + <p className="mt-3 max-w-2xl text-muted-foreground">
28 + Six native macOS applications focused on AI and local LLMs — built
29 + with Swift/SwiftUI for Apple Silicon.
30 + </p>
31 + </Reveal>
32 +
33 + <div className="mt-10 grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
34 + {zyquoApps.map((app, i) => (
35 + <Reveal key={app.slug} delay={i * 0.06} className="h-full">
36 + <AppCard app={app} />
37 + </Reveal>
38 + ))}
39 + </div>
40 + </div>
41 + );
42 +}
added app/cv/page.tsx +177 −0
@@ -0,0 +1,177 @@
1 +/*
2 + page.tsx
3 + spboucher.ai Web
4 + Author: Simon-Pierre Boucher
5 + Mail: contact@spboucher.ai
6 +*/
7 +
8 +import type { Metadata } from "next";
9 +import { Github, Mail, MapPin } from "lucide-react";
10 +
11 +import { Badge } from "@/components/ui/badge";
12 +import {
13 + Card,
14 + CardContent,
15 + CardDescription,
16 + CardHeader,
17 + CardTitle,
18 +} from "@/components/ui/card";
19 +import { Reveal } from "@/components/reveal";
20 +
21 +export const metadata: Metadata = {
22 + title: "CV",
23 + description:
24 + "CV summary and contact information for Simon-Pierre Boucher — Professor, Department of Administrative Sciences, Université du Québec en Outaouais (UQO).",
25 +};
26 +
27 +const positions = [
28 + {
29 + role: "Professor — Department of Administrative Sciences",
30 + org: "Université du Québec en Outaouais (UQO), Gatineau",
31 + period: "Present",
32 + },
33 + {
34 + role: "Lecturer",
35 + org: "Université Laval",
36 + period: "2021–Present",
37 + },
38 + {
39 + role: "Teaching Assistant",
40 + org: "Université Laval",
41 + period: "2018–2021",
42 + },
43 +];
44 +
45 +const education = [
46 + {
47 + degree: "Ph.D. in Business Administration (Finance and Insurance)",
48 + school: "Université Laval",
49 + period: "2020–Present",
50 + },
51 + {
52 + degree: "M.Sc. in Business Administration (Finance)",
53 + school: "Université Laval",
54 + period: "2017–2019",
55 + },
56 + {
57 + degree: "B.B.A. in Business Administration (Finance)",
58 + school: "Université Laval",
59 + period: "2013–2017",
60 + },
61 +];
62 +
63 +export default function CvPage() {
64 + return (
65 + <div className="mx-auto max-w-5xl px-4 py-16 sm:px-6">
66 + <Reveal>
67 + <h1 className="text-3xl font-bold tracking-tight sm:text-4xl">CV</h1>
68 + <p className="mt-3 max-w-2xl text-muted-foreground">
69 + Professor in the Department of Administrative Sciences at Université
70 + du Québec en Outaouais (UQO). Research in financial econometrics,
71 + commodity markets, monetary policy announcements, high-frequency
72 + finance, volatility modelling, textual analysis, and
73 + financialization.
74 + </p>
75 + </Reveal>
76 +
77 + {/* Contact */}
78 + <section className="mt-12">
79 + <Reveal>
80 + <h2 className="text-xl font-semibold tracking-tight">Contact</h2>
81 + <Card className="mt-4">
82 + <CardContent className="grid gap-3 p-6 text-sm sm:grid-cols-2">
83 + <a
84 + href="mailto:simon-pierre.boucher@uqo.ca"
85 + className="inline-flex items-center gap-2 text-muted-foreground transition-colors hover:text-foreground"
86 + >
87 + <Mail className="h-4 w-4 text-primary" aria-hidden="true" />
88 + simon-pierre.boucher@uqo.ca (academic)
89 + </a>
90 + <a
91 + href="mailto:contact@spboucher.ai"
92 + className="inline-flex items-center gap-2 text-muted-foreground transition-colors hover:text-foreground"
93 + >
94 + <Mail className="h-4 w-4 text-primary" aria-hidden="true" />
95 + contact@spboucher.ai (personal)
96 + </a>
97 + <a
98 + href="https://github.com/spboucher-ai"
99 + target="_blank"
100 + rel="noopener noreferrer"
101 + className="inline-flex items-center gap-2 text-muted-foreground transition-colors hover:text-foreground"
102 + >
103 + <Github className="h-4 w-4 text-primary" aria-hidden="true" />
104 + github.com/spboucher-ai
105 + </a>
106 + <span className="inline-flex items-center gap-2 text-muted-foreground">
107 + <MapPin className="h-4 w-4 text-primary" aria-hidden="true" />
108 + UQO, Gatineau — Pavillon Alexandre-Taché
109 + </span>
110 + </CardContent>
111 + </Card>
112 + </Reveal>
113 + </section>
114 +
115 + {/* Positions */}
116 + <section className="mt-12">
117 + <Reveal>
118 + <h2 className="text-xl font-semibold tracking-tight">Positions</h2>
119 + </Reveal>
120 + <div className="mt-4 space-y-4">
121 + {positions.map((p, i) => (
122 + <Reveal key={p.role} delay={i * 0.06}>
123 + <Card>
124 + <CardHeader className="flex-row items-start justify-between space-y-0">
125 + <div>
126 + <CardTitle className="text-base leading-snug">
127 + {p.role}
128 + </CardTitle>
129 + <CardDescription className="mt-1">{p.org}</CardDescription>
130 + </div>
131 + <Badge variant="outline">{p.period}</Badge>
132 + </CardHeader>
133 + </Card>
134 + </Reveal>
135 + ))}
136 + </div>
137 + </section>
138 +
139 + {/* Education */}
140 + <section className="mt-12">
141 + <Reveal>
142 + <h2 className="text-xl font-semibold tracking-tight">Education</h2>
143 + </Reveal>
144 + <div className="mt-4 space-y-4">
145 + {education.map((item, i) => (
146 + <Reveal key={item.degree} delay={i * 0.06}>
147 + <Card>
148 + <CardHeader className="flex-row items-start justify-between space-y-0">
149 + <div>
150 + <CardTitle className="text-base leading-snug">
151 + {item.degree}
152 + </CardTitle>
153 + <CardDescription className="mt-1">
154 + {item.school}
155 + </CardDescription>
156 + </div>
157 + <Badge variant="outline">{item.period}</Badge>
158 + </CardHeader>
159 + </Card>
160 + </Reveal>
161 + ))}
162 + </div>
163 + </section>
164 +
165 + {/* Languages */}
166 + <section className="mt-12">
167 + <Reveal>
168 + <h2 className="text-xl font-semibold tracking-tight">Languages</h2>
169 + <div className="mt-4 flex gap-2">
170 + <Badge variant="secondary">French — Bilingual</Badge>
171 + <Badge variant="secondary">English — Bilingual</Badge>
172 + </div>
173 + </Reveal>
174 + </section>
175 + </div>
176 + );
177 +}
added app/globals.css +77 −0
@@ -0,0 +1,77 @@
1 +/*
2 + globals.css
3 + spboucher.ai Web
4 + Author: Simon-Pierre Boucher
5 + Mail: contact@spboucher.ai
6 +*/
7 +
8 +@import "tailwindcss";
9 +
10 +@custom-variant dark (&:where(.dark, .dark *));
11 +
12 +:root {
13 + --background: oklch(0.985 0.002 247.839);
14 + --foreground: oklch(0.208 0.042 265.755);
15 + --card: oklch(1 0 0);
16 + --card-foreground: oklch(0.208 0.042 265.755);
17 + --muted: oklch(0.968 0.007 247.896);
18 + --muted-foreground: oklch(0.446 0.043 257.281);
19 + --primary: oklch(0.488 0.243 264.376);
20 + --primary-foreground: oklch(0.984 0.003 247.858);
21 + --secondary: oklch(0.929 0.013 255.508);
22 + --secondary-foreground: oklch(0.208 0.042 265.755);
23 + --accent: oklch(0.929 0.013 255.508);
24 + --accent-foreground: oklch(0.208 0.042 265.755);
25 + --border: oklch(0.869 0.022 252.894);
26 + --ring: oklch(0.488 0.243 264.376);
27 +}
28 +
29 +.dark {
30 + --background: oklch(0.179 0.025 265.755);
31 + --foreground: oklch(0.929 0.013 255.508);
32 + --card: oklch(0.218 0.028 265.755);
33 + --card-foreground: oklch(0.929 0.013 255.508);
34 + --muted: oklch(0.255 0.03 262.881);
35 + --muted-foreground: oklch(0.704 0.04 256.788);
36 + --primary: oklch(0.707 0.165 254.624);
37 + --primary-foreground: oklch(0.179 0.025 265.755);
38 + --secondary: oklch(0.279 0.041 260.031);
39 + --secondary-foreground: oklch(0.929 0.013 255.508);
40 + --accent: oklch(0.279 0.041 260.031);
41 + --accent-foreground: oklch(0.929 0.013 255.508);
42 + --border: oklch(0.323 0.038 262.881);
43 + --ring: oklch(0.707 0.165 254.624);
44 +}
45 +
46 +@theme inline {
47 + --color-background: var(--background);
48 + --color-foreground: var(--foreground);
49 + --color-card: var(--card);
50 + --color-card-foreground: var(--card-foreground);
51 + --color-muted: var(--muted);
52 + --color-muted-foreground: var(--muted-foreground);
53 + --color-primary: var(--primary);
54 + --color-primary-foreground: var(--primary-foreground);
55 + --color-secondary: var(--secondary);
56 + --color-secondary-foreground: var(--secondary-foreground);
57 + --color-accent: var(--accent);
58 + --color-accent-foreground: var(--accent-foreground);
59 + --color-border: var(--border);
60 + --color-ring: var(--ring);
61 + --font-sans: var(--font-inter), ui-sans-serif, system-ui, sans-serif;
62 +}
63 +
64 +body {
65 + background-color: var(--background);
66 + color: var(--foreground);
67 + font-family: var(--font-sans);
68 +}
69 +
70 +/* Framer Motion reveals bake opacity:0 into SSR inline styles; guarantee
71 + visibility when animations cannot run (reduced motion or JS disabled). */
72 +@media (prefers-reduced-motion: reduce) {
73 + [data-reveal] {
74 + opacity: 1 !important;
75 + transform: none !important;
76 + }
77 +}
added app/layout.tsx +71 −0
@@ -0,0 +1,71 @@
1 +/*
2 + layout.tsx
3 + spboucher.ai Web
4 + Author: Simon-Pierre Boucher
5 + Mail: contact@spboucher.ai
6 +*/
7 +
8 +import type { Metadata } from "next";
9 +import { Inter } from "next/font/google";
10 +
11 +import { SiteHeader } from "@/components/site-header";
12 +import { SiteFooter } from "@/components/site-footer";
13 +
14 +import "./globals.css";
15 +
16 +const inter = Inter({
17 + subsets: ["latin"],
18 + variable: "--font-inter",
19 +});
20 +
21 +export const metadata: Metadata = {
22 + metadataBase: new URL("https://www.spboucher.ai"),
23 + title: {
24 + default: "Simon-Pierre Boucher — Professor & macOS/AI Developer",
25 + template: "%s — Simon-Pierre Boucher",
26 + },
27 + description:
28 + "Simon-Pierre Boucher is a Professor in the Department of Administrative Sciences at Université du Québec en Outaouais (UQO). Financial econometrics researcher and macOS/AI developer.",
29 + openGraph: {
30 + title: "Simon-Pierre Boucher",
31 + description:
32 + "Financial econometrics researcher and macOS/AI developer. Professor at Université du Québec en Outaouais (UQO).",
33 + url: "https://www.spboucher.ai",
34 + siteName: "Simon-Pierre Boucher",
35 + locale: "en_US",
36 + type: "website",
37 + },
38 +};
39 +
40 +// Applies the saved (or system) theme before first paint to avoid a flash
41 +// of the wrong color scheme.
42 +const themeInitScript = `
43 +try {
44 + var t = localStorage.getItem("theme");
45 + if (t === "dark" || (!t && window.matchMedia("(prefers-color-scheme: dark)").matches)) {
46 + document.documentElement.classList.add("dark");
47 + }
48 +} catch (e) {}
49 +`;
50 +
51 +export default function RootLayout({
52 + children,
53 +}: Readonly<{
54 + children: React.ReactNode;
55 +}>) {
56 + return (
57 + <html lang="en" suppressHydrationWarning>
58 + <head>
59 + <script dangerouslySetInnerHTML={{ __html: themeInitScript }} />
60 + <noscript>
61 + <style>{`[data-reveal]{opacity:1!important;transform:none!important}`}</style>
62 + </noscript>
63 + </head>
64 + <body className={`${inter.variable} flex min-h-screen flex-col antialiased`}>
65 + <SiteHeader />
66 + <main className="flex-1">{children}</main>
67 + <SiteFooter />
68 + </body>
69 + </html>
70 + );
71 +}
added app/page.tsx +283 −0
@@ -0,0 +1,283 @@
1 +/*
2 + page.tsx
3 + spboucher.ai Web
4 + Author: Simon-Pierre Boucher
5 + Mail: contact@spboucher.ai
6 +*/
7 +
8 +import Link from "next/link";
9 +import { ArrowRight, Github, GraduationCap, Mail } from "lucide-react";
10 +
11 +import { Badge } from "@/components/ui/badge";
12 +import { buttonVariants } from "@/components/ui/button";
13 +import {
14 + Card,
15 + CardContent,
16 + CardDescription,
17 + CardHeader,
18 + CardTitle,
19 +} from "@/components/ui/card";
20 +import { Reveal } from "@/components/reveal";
21 +import { cn } from "@/lib/utils";
22 +
23 +const education = [
24 + {
25 + degree: "Ph.D. in Business Administration (Finance and Insurance)",
26 + school: "Université Laval",
27 + period: "2020–Present",
28 + },
29 + {
30 + degree: "M.Sc. in Business Administration (Finance)",
31 + school: "Université Laval",
32 + period: "2017–2019",
33 + note: "Thesis: Impact of Commuting Times on Residential Property Values: Evidence from the Province of Québec",
34 + },
35 + {
36 + degree: "B.B.A. in Business Administration (Finance)",
37 + school: "Université Laval",
38 + period: "2013–2017",
39 + },
40 +];
41 +
42 +const skillGroups = [
43 + {
44 + title: "Languages & frameworks",
45 + skills: [
46 + "Python",
47 + "TypeScript/JavaScript",
48 + "R",
49 + "MATLAB",
50 + "SAS/STATA",
51 + "SQL",
52 + "Julia",
53 + "C++",
54 + "Swift/SwiftUI",
55 + "LaTeX",
56 + "React/Next.js",
57 + "Node.js",
58 + "FastAPI",
59 + "Flask",
60 + "Streamlit",
61 + "Tailwind CSS",
62 + "shadcn/ui",
63 + "Framer Motion",
64 + ],
65 + },
66 + {
67 + title: "Data & econometrics",
68 + skills: [
69 + "Pandas",
70 + "NumPy",
71 + "statsmodels",
72 + "scikit-learn",
73 + "VAR",
74 + "BVAR",
75 + "HAR",
76 + "GARCH",
77 + "DCC-GARCH",
78 + "Event studies",
79 + "High-frequency analysis",
80 + "Realized volatility",
81 + "FRED",
82 + "FMP",
83 + "EODHD",
84 + "Yahoo Finance",
85 + "World Bank",
86 + "IMF",
87 + "OECD",
88 + ],
89 + },
90 + {
91 + title: "AI, LLMs & agents",
92 + skills: [
93 + "Claude/Anthropic SDK",
94 + "OpenAI API",
95 + "OpenRouter",
96 + "Vercel AI SDK",
97 + "Ollama",
98 + "llama.cpp",
99 + "GGUF",
100 + "Hugging Face",
101 + "Embeddings",
102 + "RAG",
103 + "Vector databases",
104 + "Tool-augmented agents",
105 + "LoRA",
106 + "QLoRA",
107 + "SFT fine-tuning",
108 + ],
109 + },
110 +];
111 +
112 +export default function HomePage() {
113 + return (
114 + <div className="mx-auto max-w-5xl px-4 sm:px-6">
115 + {/* Hero */}
116 + <section className="py-16 sm:py-24">
117 + <Reveal>
118 + <p className="text-sm font-medium text-primary">
119 + Professor — Department of Administrative Sciences
120 + </p>
121 + <h1 className="mt-3 text-4xl font-bold tracking-tight sm:text-5xl">
122 + Simon-Pierre Boucher
123 + </h1>
124 + <p className="mt-4 max-w-2xl text-lg text-muted-foreground">
125 + Financial econometrics researcher and macOS/AI developer.
126 + </p>
127 + <p className="mt-2 text-sm text-muted-foreground">
128 + Université du Québec en Outaouais (UQO), Gatineau — Pavillon
129 + Alexandre-Taché · Bilingual — French and English
130 + </p>
131 + <div className="mt-8 flex flex-wrap gap-3">
132 + <Link href="/research" className={cn(buttonVariants())}>
133 + View Research
134 + <ArrowRight aria-hidden="true" />
135 + </Link>
136 + <Link
137 + href="/apps"
138 + className={cn(buttonVariants({ variant: "secondary" }))}
139 + >
140 + Zyquo macOS Apps
141 + </Link>
142 + <a
143 + href="https://github.com/spboucher-ai"
144 + target="_blank"
145 + rel="noopener noreferrer"
146 + className={cn(buttonVariants({ variant: "outline" }))}
147 + >
148 + <Github aria-hidden="true" />
149 + GitHub
150 + </a>
151 + </div>
152 + <div className="mt-6 flex flex-col gap-1 text-sm text-muted-foreground sm:flex-row sm:gap-6">
153 + <a
154 + href="mailto:simon-pierre.boucher@uqo.ca"
155 + className="inline-flex items-center gap-2 transition-colors hover:text-foreground"
156 + >
157 + <Mail className="h-4 w-4" aria-hidden="true" />
158 + simon-pierre.boucher@uqo.ca
159 + </a>
160 + <a
161 + href="mailto:contact@spboucher.ai"
162 + className="inline-flex items-center gap-2 transition-colors hover:text-foreground"
163 + >
164 + <Mail className="h-4 w-4" aria-hidden="true" />
165 + contact@spboucher.ai
166 + </a>
167 + </div>
168 + </Reveal>
169 + </section>
170 +
171 + {/* About */}
172 + <section className="border-t border-border py-16">
173 + <Reveal>
174 + <h2 className="text-2xl font-semibold tracking-tight">About</h2>
175 + <p className="mt-4 max-w-3xl leading-relaxed text-muted-foreground">
176 + Simon-Pierre Boucher is a Professor in the Department of
177 + Administrative Sciences at Université du Québec en Outaouais (UQO).
178 + His research focuses on financial econometrics, commodity markets,
179 + monetary policy announcements, high-frequency finance, volatility
180 + modelling, textual analysis, and financialization. He is completing
181 + a Ph.D. in Business Administration (Finance and Insurance) at
182 + Université Laval under the supervision of Prof. Marie-Hélène Gagnon
183 + and Prof. Gabriel Power. Alongside his academic work, he builds
184 + native macOS applications focused on AI and local LLMs (the Zyquo
185 + suite).
186 + </p>
187 + </Reveal>
188 + </section>
189 +
190 + {/* Education */}
191 + <section className="border-t border-border py-16">
192 + <Reveal>
193 + <h2 className="text-2xl font-semibold tracking-tight">Education</h2>
194 + </Reveal>
195 + <div className="mt-6 grid gap-4 sm:grid-cols-3">
196 + {education.map((item, i) => (
197 + <Reveal key={item.degree} delay={i * 0.08}>
198 + <Card className="h-full">
199 + <CardHeader>
200 + <GraduationCap
201 + className="h-5 w-5 text-primary"
202 + aria-hidden="true"
203 + />
204 + <CardTitle className="text-base leading-snug">
205 + {item.degree}
206 + </CardTitle>
207 + <CardDescription>
208 + {item.school} · {item.period}
209 + </CardDescription>
210 + </CardHeader>
211 + {item.note && (
212 + <CardContent>
213 + <p className="text-sm italic text-muted-foreground">
214 + {item.note}
215 + </p>
216 + </CardContent>
217 + )}
218 + </Card>
219 + </Reveal>
220 + ))}
221 + </div>
222 + </section>
223 +
224 + {/* Research highlight */}
225 + <section className="border-t border-border py-16">
226 + <Reveal>
227 + <h2 className="text-2xl font-semibold tracking-tight">
228 + Research Highlight
229 + </h2>
230 + <Card className="mt-6">
231 + <CardHeader>
232 + <Badge className="w-fit">Publication · 2025</Badge>
233 + <CardTitle className="text-lg leading-snug">
234 + Speculative Trading in Energy Markets: Evidence from
235 + Macroeconomic Surprises
236 + </CardTitle>
237 + <CardDescription>
238 + Boucher, S.-P., Gagnon, M.-H., &amp; Power, G. J. —{" "}
239 + <em>The Energy Journal</em>
240 + </CardDescription>
241 + </CardHeader>
242 + <CardContent>
243 + <Link
244 + href="/research"
245 + className="inline-flex items-center gap-1 text-sm font-medium text-primary hover:underline"
246 + >
247 + All publications, working papers &amp; presentations
248 + <ArrowRight className="h-4 w-4" aria-hidden="true" />
249 + </Link>
250 + </CardContent>
251 + </Card>
252 + </Reveal>
253 + </section>
254 +
255 + {/* Skills */}
256 + <section className="border-t border-border py-16">
257 + <Reveal>
258 + <h2 className="text-2xl font-semibold tracking-tight">
259 + Technical Skills
260 + </h2>
261 + </Reveal>
262 + <div className="mt-6 grid gap-4 md:grid-cols-3">
263 + {skillGroups.map((group, i) => (
264 + <Reveal key={group.title} delay={i * 0.08}>
265 + <Card className="h-full">
266 + <CardHeader>
267 + <CardTitle className="text-base">{group.title}</CardTitle>
268 + </CardHeader>
269 + <CardContent className="flex flex-wrap gap-1.5">
270 + {group.skills.map((skill) => (
271 + <Badge key={skill} variant="secondary">
272 + {skill}
273 + </Badge>
274 + ))}
275 + </CardContent>
276 + </Card>
277 + </Reveal>
278 + ))}
279 + </div>
280 + </section>
281 + </div>
282 + );
283 +}
added app/research/page.tsx +156 −0
@@ -0,0 +1,156 @@
1 +/*
2 + page.tsx
3 + spboucher.ai Web
4 + Author: Simon-Pierre Boucher
5 + Mail: contact@spboucher.ai
6 +*/
7 +
8 +import type { Metadata } from "next";
9 +import { ExternalLink } from "lucide-react";
10 +
11 +import { Badge } from "@/components/ui/badge";
12 +import {
13 + Card,
14 + CardContent,
15 + CardDescription,
16 + CardHeader,
17 + CardTitle,
18 +} from "@/components/ui/card";
19 +import { Reveal } from "@/components/reveal";
20 +
21 +export const metadata: Metadata = {
22 + title: "Research",
23 + description:
24 + "Publications, working papers, and conference presentations by Simon-Pierre Boucher — financial econometrics, commodity markets, monetary policy, and high-frequency finance.",
25 +};
26 +
27 +const workingPapers = [
28 + {
29 + title:
30 + "Has Financialization Changed the Impact of Macro Announcements on U.S. Commodity Markets?",
31 + venue: "SSRN Working Paper",
32 + date: "May 2022",
33 + link: "https://ssrn.com/abstract=400929",
34 + },
35 + {
36 + title:
37 + "Modelling Volatility Dynamics Between Commodity ETFs and Their Net Asset Value using BVAR and HAR Models",
38 + venue: "Working Paper",
39 + date: "January 2023",
40 + },
41 + {
42 + title:
43 + "Returns and Volatility Around FOMC Announcements: A High-Frequency Analysis of Policy Tone and Novelty",
44 + venue: "Working Paper",
45 + date: "November 2023",
46 + },
47 +];
48 +
49 +const presentations = [
50 + { event: "CRREP Research Day", detail: "2022, 2023" },
51 + {
52 + event: "Canadian Economics Association — 61st & 62nd Annual Conferences",
53 + detail: "2022, 2023",
54 + },
55 + {
56 + event: "7th Winter Workshop on Commodity Markets",
57 + detail: "Mont-Tremblant, 2024",
58 + },
59 +];
60 +
61 +export default function ResearchPage() {
62 + return (
63 + <div className="mx-auto max-w-5xl px-4 py-16 sm:px-6">
64 + <Reveal>
65 + <h1 className="text-3xl font-bold tracking-tight sm:text-4xl">
66 + Research
67 + </h1>
68 + <p className="mt-3 max-w-2xl text-muted-foreground">
69 + Financial econometrics, commodity markets, monetary policy
70 + announcements, high-frequency finance, volatility modelling, textual
71 + analysis, and financialization.
72 + </p>
73 + </Reveal>
74 +
75 + {/* Publication */}
76 + <section className="mt-12">
77 + <Reveal>
78 + <h2 className="text-xl font-semibold tracking-tight">Publication</h2>
79 + <Card className="mt-4">
80 + <CardHeader>
81 + <Badge className="w-fit">The Energy Journal · 2025</Badge>
82 + <CardTitle className="text-lg leading-snug">
83 + Speculative Trading in Energy Markets: Evidence from
84 + Macroeconomic Surprises
85 + </CardTitle>
86 + <CardDescription>
87 + Boucher, S.-P., Gagnon, M.-H., &amp; Power, G. J. (2025)
88 + </CardDescription>
89 + </CardHeader>
90 + </Card>
91 + </Reveal>
92 + </section>
93 +
94 + {/* Working papers */}
95 + <section className="mt-12">
96 + <Reveal>
97 + <h2 className="text-xl font-semibold tracking-tight">
98 + Working Papers
99 + </h2>
100 + </Reveal>
101 + <div className="mt-4 space-y-4">
102 + {workingPapers.map((paper, i) => (
103 + <Reveal key={paper.title} delay={i * 0.08}>
104 + <Card>
105 + <CardHeader>
106 + <Badge variant="secondary" className="w-fit">
107 + {paper.venue} · {paper.date}
108 + </Badge>
109 + <CardTitle className="text-lg leading-snug">
110 + {paper.title}
111 + </CardTitle>
112 + </CardHeader>
113 + {paper.link && (
114 + <CardContent>
115 + <a
116 + href={paper.link}
117 + target="_blank"
118 + rel="noopener noreferrer"
119 + className="inline-flex items-center gap-1 text-sm font-medium text-primary hover:underline"
120 + >
121 + View on SSRN
122 + <ExternalLink className="h-4 w-4" aria-hidden="true" />
123 + </a>
124 + </CardContent>
125 + )}
126 + </Card>
127 + </Reveal>
128 + ))}
129 + </div>
130 + </section>
131 +
132 + {/* Presentations */}
133 + <section className="mt-12">
134 + <Reveal>
135 + <h2 className="text-xl font-semibold tracking-tight">
136 + Conference Presentations
137 + </h2>
138 + </Reveal>
139 + <div className="mt-4 grid gap-4 sm:grid-cols-3">
140 + {presentations.map((p, i) => (
141 + <Reveal key={p.event} delay={i * 0.08}>
142 + <Card className="h-full">
143 + <CardHeader>
144 + <CardTitle className="text-base leading-snug">
145 + {p.event}
146 + </CardTitle>
147 + <CardDescription>{p.detail}</CardDescription>
148 + </CardHeader>
149 + </Card>
150 + </Reveal>
151 + ))}
152 + </div>
153 + </section>
154 + </div>
155 + );
156 +}
added app/teaching/page.tsx +155 −0
@@ -0,0 +1,155 @@
1 +/*
2 + page.tsx
3 + spboucher.ai Web
4 + Author: Simon-Pierre Boucher
5 + Mail: contact@spboucher.ai
6 +*/
7 +
8 +import type { Metadata } from "next";
9 +
10 +import { Badge } from "@/components/ui/badge";
11 +import {
12 + Card,
13 + CardContent,
14 + CardHeader,
15 + CardTitle,
16 +} from "@/components/ui/card";
17 +import { Reveal } from "@/components/reveal";
18 +
19 +export const metadata: Metadata = {
20 + title: "Teaching",
21 + description:
22 + "Courses taught by Simon-Pierre Boucher at Université Laval — capital markets, financial econometrics, financial management, and financial theory.",
23 +};
24 +
25 +const courses = [
26 + {
27 + code: "GSF-3100",
28 + title: "Capital Markets",
29 + level: "Undergraduate",
30 + terms: "F2021, W2021, F2022, W2023",
31 + },
32 + {
33 + code: "GSF-6053",
34 + title: "Financial Econometrics I",
35 + level: "Graduate",
36 + terms: "W2022, W2025",
37 + },
38 + {
39 + code: "GSF-1500",
40 + title: "Financial Management",
41 + level: "Undergraduate",
42 + terms: "S2022",
43 + },
44 + {
45 + code: "GSF-6028",
46 + title: "Financial Theory",
47 + level: "Graduate",
48 + terms: "W2024",
49 + },
50 +];
51 +
52 +const taCourses = [
53 + { code: "GSF-2101", title: "Portfolio Management" },
54 + { code: "GSF-2102", title: "Corporate Finance" },
55 + { code: "GSF-6008", title: "Corporate Finance" },
56 + { code: "GSF-6025", title: "Financial Strategies & Policies I" },
57 + { code: "GSF-6028", title: "Financial Theory" },
58 +];
59 +
60 +export default function TeachingPage() {
61 + return (
62 + <div className="mx-auto max-w-5xl px-4 py-16 sm:px-6">
63 + <Reveal>
64 + <h1 className="text-3xl font-bold tracking-tight sm:text-4xl">
65 + Teaching
66 + </h1>
67 + <p className="mt-3 max-w-2xl text-muted-foreground">
68 + Lecturer at Université Laval (2021–Present) and Teaching Assistant
69 + (2018–2021).
70 + </p>
71 + </Reveal>
72 +
73 + {/* Lecturer */}
74 + <section className="mt-12">
75 + <Reveal>
76 + <h2 className="text-xl font-semibold tracking-tight">
77 + Lecturer — Université Laval (2021–Present)
78 + </h2>
79 + <Card className="mt-4 overflow-x-auto">
80 + <CardContent className="p-0">
81 + <table className="w-full text-sm">
82 + <thead>
83 + <tr className="border-b border-border text-left">
84 + <th scope="col" className="px-4 py-3 font-medium">
85 + Code
86 + </th>
87 + <th scope="col" className="px-4 py-3 font-medium">
88 + Course
89 + </th>
90 + <th scope="col" className="px-4 py-3 font-medium">
91 + Level
92 + </th>
93 + <th scope="col" className="px-4 py-3 font-medium">
94 + Terms
95 + </th>
96 + </tr>
97 + </thead>
98 + <tbody>
99 + {courses.map((course) => (
100 + <tr
101 + key={`${course.code}-${course.terms}`}
102 + className="border-b border-border last:border-0"
103 + >
104 + <td className="px-4 py-3 font-mono text-xs">
105 + {course.code}
106 + </td>
107 + <td className="px-4 py-3 font-medium">{course.title}</td>
108 + <td className="px-4 py-3">
109 + <Badge
110 + variant={
111 + course.level === "Graduate"
112 + ? "default"
113 + : "secondary"
114 + }
115 + >
116 + {course.level}
117 + </Badge>
118 + </td>
119 + <td className="px-4 py-3 text-muted-foreground">
120 + {course.terms}
121 + </td>
122 + </tr>
123 + ))}
124 + </tbody>
125 + </table>
126 + </CardContent>
127 + </Card>
128 + </Reveal>
129 + </section>
130 +
131 + {/* Teaching assistant */}
132 + <section className="mt-12">
133 + <Reveal>
134 + <h2 className="text-xl font-semibold tracking-tight">
135 + Teaching Assistant — Université Laval (2018–2021)
136 + </h2>
137 + </Reveal>
138 + <div className="mt-4 grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
139 + {taCourses.map((course, i) => (
140 + <Reveal key={`${course.code}-${course.title}`} delay={i * 0.06}>
141 + <Card className="h-full">
142 + <CardHeader>
143 + <Badge variant="outline" className="w-fit font-mono">
144 + {course.code}
145 + </Badge>
146 + <CardTitle className="text-base">{course.title}</CardTitle>
147 + </CardHeader>
148 + </Card>
149 + </Reveal>
150 + ))}
151 + </div>
152 + </section>
153 + </div>
154 + );
155 +}
added components/app-card.tsx +76 −0
@@ -0,0 +1,76 @@
1 +/*
2 + app-card.tsx
3 + spboucher.ai Web
4 + Author: Simon-Pierre Boucher
5 + Mail: contact@spboucher.ai
6 +*/
7 +
8 +import Image from "next/image";
9 +import { Download, Github, Tag } from "lucide-react";
10 +
11 +import type { ZyquoApp } from "@/lib/apps";
12 +import { buttonVariants } from "@/components/ui/button";
13 +import {
14 + Card,
15 + CardContent,
16 + CardDescription,
17 + CardFooter,
18 + CardHeader,
19 + CardTitle,
20 +} from "@/components/ui/card";
21 +import { cn } from "@/lib/utils";
22 +
23 +export function AppCard({ app }: { app: ZyquoApp }) {
24 + return (
25 + <Card className="flex h-full flex-col transition-shadow hover:shadow-md">
26 + <CardHeader className="flex-row items-center gap-4 space-y-0">
27 + <Image
28 + src={app.icon}
29 + alt={`${app.name} app icon`}
30 + width={64}
31 + height={64}
32 + className="h-16 w-16 shrink-0 rounded-2xl shadow-sm"
33 + />
34 + <div className="min-w-0">
35 + <CardTitle className="text-lg">
36 + <span aria-hidden="true" className="mr-1.5">
37 + {app.emoji}
38 + </span>
39 + {app.name}
40 + </CardTitle>
41 + <CardDescription className="mt-1">{app.tagline}</CardDescription>
42 + </div>
43 + </CardHeader>
44 + <CardContent className="flex-1">
45 + <p className="text-sm text-muted-foreground">{app.description}</p>
46 + </CardContent>
47 + <CardFooter className="flex flex-wrap gap-2">
48 + <a
49 + href={app.repo}
50 + target="_blank"
51 + rel="noopener noreferrer"
52 + className={cn(buttonVariants({ variant: "outline", size: "sm" }))}
53 + >
54 + <Github aria-hidden="true" />
55 + GitHub Repo
56 + </a>
57 + <a
58 + href={app.release}
59 + target="_blank"
60 + rel="noopener noreferrer"
61 + className={cn(buttonVariants({ variant: "outline", size: "sm" }))}
62 + >
63 + <Tag aria-hidden="true" />
64 + Release
65 + </a>
66 + <a
67 + href={app.dmg}
68 + className={cn(buttonVariants({ variant: "default", size: "sm" }))}
69 + >
70 + <Download aria-hidden="true" />
71 + Download DMG
72 + </a>
73 + </CardFooter>
74 + </Card>
75 + );
76 +}
added components/reveal.tsx +32 −0
@@ -0,0 +1,32 @@
1 +/*
2 + reveal.tsx
3 + spboucher.ai Web
4 + Author: Simon-Pierre Boucher
5 + Mail: contact@spboucher.ai
6 +*/
7 +
8 +"use client";
9 +
10 +import * as React from "react";
11 +import { motion } from "framer-motion";
12 +
13 +interface RevealProps {
14 + children: React.ReactNode;
15 + delay?: number;
16 + className?: string;
17 +}
18 +
19 +export function Reveal({ children, delay = 0, className }: RevealProps) {
20 + return (
21 + <motion.div
22 + data-reveal
23 + className={className}
24 + initial={{ opacity: 0, y: 16 }}
25 + whileInView={{ opacity: 1, y: 0 }}
26 + viewport={{ once: true, margin: "-40px" }}
27 + transition={{ duration: 0.5, delay, ease: "easeOut" }}
28 + >
29 + {children}
30 + </motion.div>
31 + );
32 +}
added components/site-footer.tsx +40 −0
@@ -0,0 +1,40 @@
1 +/*
2 + site-footer.tsx
3 + spboucher.ai Web
4 + Author: Simon-Pierre Boucher
5 + Mail: contact@spboucher.ai
6 +*/
7 +
8 +import Link from "next/link";
9 +
10 +export function SiteFooter() {
11 + return (
12 + <footer className="border-t border-border">
13 + <div className="mx-auto flex max-w-5xl flex-col items-center justify-between gap-4 px-4 py-8 text-sm text-muted-foreground sm:flex-row sm:px-6">
14 + <p>
15 + © {new Date().getFullYear()} Simon-Pierre Boucher — Université du
16 + Québec en Outaouais (UQO)
17 + </p>
18 + <div className="flex items-center gap-4">
19 + <a
20 + href="mailto:contact@spboucher.ai"
21 + className="transition-colors hover:text-foreground"
22 + >
23 + contact@spboucher.ai
24 + </a>
25 + <a
26 + href="https://github.com/spboucher-ai"
27 + target="_blank"
28 + rel="noopener noreferrer"
29 + className="transition-colors hover:text-foreground"
30 + >
31 + GitHub
32 + </a>
33 + <Link href="/cv" className="transition-colors hover:text-foreground">
34 + CV
35 + </Link>
36 + </div>
37 + </div>
38 + </footer>
39 + );
40 +}
added components/site-header.tsx +94 −0
@@ -0,0 +1,94 @@
1 +/*
2 + site-header.tsx
3 + spboucher.ai Web
4 + Author: Simon-Pierre Boucher
5 + Mail: contact@spboucher.ai
6 +*/
7 +
8 +"use client";
9 +
10 +import * as React from "react";
11 +import Link from "next/link";
12 +import { usePathname } from "next/navigation";
13 +import { Menu, X } from "lucide-react";
14 +
15 +import { cn } from "@/lib/utils";
16 +import { Button } from "@/components/ui/button";
17 +import { ThemeToggle } from "@/components/theme-toggle";
18 +
19 +const navItems = [
20 + { href: "/", label: "Home" },
21 + { href: "/research", label: "Research" },
22 + { href: "/teaching", label: "Teaching" },
23 + { href: "/apps", label: "Apps" },
24 + { href: "/cv", label: "CV" },
25 +];
26 +
27 +export function SiteHeader() {
28 + const pathname = usePathname();
29 + const [open, setOpen] = React.useState(false);
30 +
31 + return (
32 + <header className="sticky top-0 z-50 w-full border-b border-border bg-background/80 backdrop-blur">
33 + <div className="mx-auto flex h-14 max-w-5xl items-center justify-between px-4 sm:px-6">
34 + <Link href="/" className="font-semibold tracking-tight">
35 + Simon-Pierre Boucher
36 + </Link>
37 +
38 + <nav className="hidden items-center gap-1 md:flex" aria-label="Main">
39 + {navItems.map((item) => (
40 + <Link
41 + key={item.href}
42 + href={item.href}
43 + className={cn(
44 + "rounded-md px-3 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground",
45 + pathname === item.href
46 + ? "text-foreground"
47 + : "text-muted-foreground"
48 + )}
49 + >
50 + {item.label}
51 + </Link>
52 + ))}
53 + <ThemeToggle />
54 + </nav>
55 +
56 + <div className="flex items-center gap-1 md:hidden">
57 + <ThemeToggle />
58 + <Button
59 + variant="ghost"
60 + size="icon"
61 + aria-label={open ? "Close menu" : "Open menu"}
62 + aria-expanded={open}
63 + onClick={() => setOpen(!open)}
64 + >
65 + {open ? <X /> : <Menu />}
66 + </Button>
67 + </div>
68 + </div>
69 +
70 + {open && (
71 + <nav
72 + className="border-t border-border px-4 pb-4 pt-2 md:hidden"
73 + aria-label="Mobile"
74 + >
75 + {navItems.map((item) => (
76 + <Link
77 + key={item.href}
78 + href={item.href}
79 + onClick={() => setOpen(false)}
80 + className={cn(
81 + "block rounded-md px-3 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground",
82 + pathname === item.href
83 + ? "text-foreground"
84 + : "text-muted-foreground"
85 + )}
86 + >
87 + {item.label}
88 + </Link>
89 + ))}
90 + </nav>
91 + )}
92 + </header>
93 + );
94 +}
added components/theme-toggle.tsx +45 −0
@@ -0,0 +1,45 @@
1 +/*
2 + theme-toggle.tsx
3 + spboucher.ai Web
4 + Author: Simon-Pierre Boucher
5 + Mail: contact@spboucher.ai
6 +*/
7 +
8 +"use client";
9 +
10 +import * as React from "react";
11 +import { Moon, Sun } from "lucide-react";
12 +
13 +import { Button } from "@/components/ui/button";
14 +
15 +export function ThemeToggle() {
16 + const [mounted, setMounted] = React.useState(false);
17 + const [dark, setDark] = React.useState(false);
18 +
19 + React.useEffect(() => {
20 + setMounted(true);
21 + setDark(document.documentElement.classList.contains("dark"));
22 + }, []);
23 +
24 + function toggle() {
25 + const next = !dark;
26 + setDark(next);
27 + document.documentElement.classList.toggle("dark", next);
28 + try {
29 + localStorage.setItem("theme", next ? "dark" : "light");
30 + } catch {
31 + // localStorage unavailable (private mode); theme still applies for this page view
32 + }
33 + }
34 +
35 + return (
36 + <Button
37 + variant="ghost"
38 + size="icon"
39 + aria-label={dark ? "Switch to light mode" : "Switch to dark mode"}
40 + onClick={toggle}
41 + >
42 + {mounted && dark ? <Sun /> : <Moon />}
43 + </Button>
44 + );
45 +}
added components/ui/badge.tsx +39 −0
@@ -0,0 +1,39 @@
1 +/*
2 + badge.tsx
3 + spboucher.ai Web
4 + Author: Simon-Pierre Boucher
5 + Mail: contact@spboucher.ai
6 +*/
7 +
8 +import * as React from "react";
9 +import { cva, type VariantProps } from "class-variance-authority";
10 +
11 +import { cn } from "@/lib/utils";
12 +
13 +const badgeVariants = cva(
14 + "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-medium transition-colors",
15 + {
16 + variants: {
17 + variant: {
18 + default: "border-transparent bg-primary text-primary-foreground",
19 + secondary: "border-transparent bg-secondary text-secondary-foreground",
20 + outline: "border-border text-foreground",
21 + },
22 + },
23 + defaultVariants: {
24 + variant: "default",
25 + },
26 + }
27 +);
28 +
29 +export interface BadgeProps
30 + extends React.HTMLAttributes<HTMLDivElement>,
31 + VariantProps<typeof badgeVariants> {}
32 +
33 +function Badge({ className, variant, ...props }: BadgeProps) {
34 + return (
35 + <div className={cn(badgeVariants({ variant }), className)} {...props} />
36 + );
37 +}
38 +
39 +export { Badge, badgeVariants };
added components/ui/button.tsx +55 −0
@@ -0,0 +1,55 @@
1 +/*
2 + button.tsx
3 + spboucher.ai Web
4 + Author: Simon-Pierre Boucher
5 + Mail: contact@spboucher.ai
6 +*/
7 +
8 +import * as React from "react";
9 +import { cva, type VariantProps } from "class-variance-authority";
10 +
11 +import { cn } from "@/lib/utils";
12 +
13 +const buttonVariants = cva(
14 + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0",
15 + {
16 + variants: {
17 + variant: {
18 + default: "bg-primary text-primary-foreground hover:bg-primary/90",
19 + secondary:
20 + "bg-secondary text-secondary-foreground hover:bg-secondary/80",
21 + outline:
22 + "border border-border bg-transparent hover:bg-accent hover:text-accent-foreground",
23 + ghost: "hover:bg-accent hover:text-accent-foreground",
24 + link: "text-primary underline-offset-4 hover:underline",
25 + },
26 + size: {
27 + default: "h-9 px-4 py-2",
28 + sm: "h-8 rounded-md px-3 text-xs",
29 + lg: "h-10 rounded-md px-8",
30 + icon: "h-9 w-9",
31 + },
32 + },
33 + defaultVariants: {
34 + variant: "default",
35 + size: "default",
36 + },
37 + }
38 +);
39 +
40 +export interface ButtonProps
41 + extends React.ButtonHTMLAttributes<HTMLButtonElement>,
42 + VariantProps<typeof buttonVariants> {}
43 +
44 +const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
45 + ({ className, variant, size, ...props }, ref) => (
46 + <button
47 + className={cn(buttonVariants({ variant, size, className }))}
48 + ref={ref}
49 + {...props}
50 + />
51 + )
52 +);
53 +Button.displayName = "Button";
54 +
55 +export { Button, buttonVariants };
added components/ui/card.tsx +77 −0
@@ -0,0 +1,77 @@
1 +/*
2 + card.tsx
3 + spboucher.ai Web
4 + Author: Simon-Pierre Boucher
5 + Mail: contact@spboucher.ai
6 +*/
7 +
8 +import * as React from "react";
9 +
10 +import { cn } from "@/lib/utils";
11 +
12 +const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
13 + ({ className, ...props }, ref) => (
14 + <div
15 + ref={ref}
16 + className={cn(
17 + "rounded-xl border border-border bg-card text-card-foreground shadow-sm",
18 + className
19 + )}
20 + {...props}
21 + />
22 + )
23 +);
24 +Card.displayName = "Card";
25 +
26 +const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
27 + ({ className, ...props }, ref) => (
28 + <div
29 + ref={ref}
30 + className={cn("flex flex-col space-y-1.5 p-6", className)}
31 + {...props}
32 + />
33 + )
34 +);
35 +CardHeader.displayName = "CardHeader";
36 +
37 +const CardTitle = React.forwardRef<HTMLHeadingElement, React.HTMLAttributes<HTMLHeadingElement>>(
38 + ({ className, ...props }, ref) => (
39 + <h3
40 + ref={ref}
41 + className={cn("font-semibold leading-none tracking-tight", className)}
42 + {...props}
43 + />
44 + )
45 +);
46 +CardTitle.displayName = "CardTitle";
47 +
48 +const CardDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
49 + ({ className, ...props }, ref) => (
50 + <p
51 + ref={ref}
52 + className={cn("text-sm text-muted-foreground", className)}
53 + {...props}
54 + />
55 + )
56 +);
57 +CardDescription.displayName = "CardDescription";
58 +
59 +const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
60 + ({ className, ...props }, ref) => (
61 + <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
62 + )
63 +);
64 +CardContent.displayName = "CardContent";
65 +
66 +const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
67 + ({ className, ...props }, ref) => (
68 + <div
69 + ref={ref}
70 + className={cn("flex items-center p-6 pt-0", className)}
71 + {...props}
72 + />
73 + )
74 +);
75 +CardFooter.displayName = "CardFooter";
76 +
77 +export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter };
added lib/apps.ts +93 −0
@@ -0,0 +1,93 @@
1 +/*
2 + apps.ts
3 + spboucher.ai Web
4 + Author: Simon-Pierre Boucher
5 + Mail: contact@spboucher.ai
6 +*/
7 +
8 +export interface ZyquoApp {
9 + slug: string;
10 + name: string;
11 + emoji: string;
12 + tagline: string;
13 + description: string;
14 + icon: string;
15 + repo: string;
16 + release: string;
17 + dmg: string;
18 +}
19 +
20 +export const zyquoApps: ZyquoApp[] = [
21 + {
22 + slug: "zyquo-cloud",
23 + name: "Zyquo Cloud",
24 + emoji: "☁️",
25 + tagline: "Multi-provider AI chat",
26 + description:
27 + "Native macOS chat client supporting multiple AI providers (Anthropic, OpenAI, and more) in a single unified interface.",
28 + icon: "/icons/zyquo-cloud.png",
29 + repo: "https://github.com/spboucher-ai/zyquo-cloud",
30 + release: "https://github.com/spboucher-ai/zyquo-cloud/releases/tag/v1.0.0",
31 + dmg: "https://github.com/spboucher-ai/zyquo-cloud/releases/latest/download/ZyquoCloud.dmg",
32 + },
33 + {
34 + slug: "zyquo-local",
35 + name: "Zyquo Local",
36 + emoji: "💻",
37 + tagline: "100% local LLMs (MLX)",
38 + description:
39 + "Run large language models entirely on-device with Apple MLX. Private, offline, no API keys required.",
40 + icon: "/icons/zyquo-local.png",
41 + repo: "https://github.com/spboucher-ai/zyquo-local",
42 + release: "https://github.com/spboucher-ai/zyquo-local/releases/tag/v1.0.0",
43 + dmg: "https://github.com/spboucher-ai/zyquo-local/releases/latest/download/ZyquoLocal.dmg",
44 + },
45 + {
46 + slug: "zyquo-agent",
47 + name: "Zyquo Agent",
48 + emoji: "🤖",
49 + tagline: "Autonomous agent for the Mac",
50 + description:
51 + "An autonomous AI agent that can act on your Mac: tool calling, task automation, and multi-step workflows.",
52 + icon: "/icons/zyquo-agent.png",
53 + repo: "https://github.com/spboucher-ai/zyquo-agent",
54 + release: "https://github.com/spboucher-ai/zyquo-agent/releases/tag/v1.0.0",
55 + dmg: "https://github.com/spboucher-ai/zyquo-agent/releases/latest/download/ZyquoAgent.dmg",
56 + },
57 + {
58 + slug: "zyquo-atlas",
59 + name: "Zyquo Atlas",
60 + emoji: "🌍",
61 + tagline: "AI-native web browser",
62 + description:
63 + "A web browser built around AI from the ground up: page understanding, summarization, and assisted browsing.",
64 + icon: "/icons/zyquo-atlas.png",
65 + repo: "https://github.com/spboucher-ai/zyquo-atlas",
66 + release: "https://github.com/spboucher-ai/zyquo-atlas/releases/tag/v1.0.0",
67 + dmg: "https://github.com/spboucher-ai/zyquo-atlas/releases/latest/download/ZyquoAtlas.dmg",
68 + },
69 + {
70 + slug: "zyquo-mlx",
71 + name: "Zyquo MLX",
72 + emoji: "⚒️",
73 + tagline: "On-device model forge",
74 + description:
75 + "Fine-tune and quantize models directly on Apple Silicon: LoRA/QLoRA fine-tuning, quantization, and model management with MLX.",
76 + icon: "/icons/zyquo-mlx.png",
77 + repo: "https://github.com/spboucher-ai/zyquo-mlx",
78 + release: "https://github.com/spboucher-ai/zyquo-mlx/releases/tag/v1.0.0",
79 + dmg: "https://github.com/spboucher-ai/zyquo-mlx/releases/latest/download/ZyquoMLX.dmg",
80 + },
81 + {
82 + slug: "zyquo-router",
83 + name: "Zyquo Router",
84 + emoji: "🔀",
85 + tagline: "Local LLM gateway",
86 + description:
87 + "A local gateway that routes requests across LLM providers and local models through a single unified endpoint.",
88 + icon: "/icons/zyquo-router.png",
89 + repo: "https://github.com/spboucher-ai/zyquo-router",
90 + release: "https://github.com/spboucher-ai/zyquo-router/releases/tag/v1.0.0",
91 + dmg: "https://github.com/spboucher-ai/zyquo-router/releases/latest/download/ZyquoRouter.dmg",
92 + },
93 +];
added lib/utils.ts +13 −0
@@ -0,0 +1,13 @@
1 +/*
2 + utils.ts
3 + spboucher.ai Web
4 + Author: Simon-Pierre Boucher
5 + Mail: contact@spboucher.ai
6 +*/
7 +
8 +import { clsx, type ClassValue } from "clsx";
9 +import { twMerge } from "tailwind-merge";
10 +
11 +export function cn(...inputs: ClassValue[]) {
12 + return twMerge(clsx(inputs));
13 +}
added next.config.ts +14 −0
@@ -0,0 +1,14 @@
1 +/*
2 + next.config.ts
3 + spboucher.ai Web
4 + Author: Simon-Pierre Boucher
5 + Mail: contact@spboucher.ai
6 +*/
7 +
8 +import type { NextConfig } from "next";
9 +
10 +const nextConfig: NextConfig = {
11 + reactStrictMode: true,
12 +};
13 +
14 +export default nextConfig;
added package-lock.json +1835 −0
@@ -0,0 +1,1835 @@
1 +{
2 + "name": "spboucher-ai",
3 + "version": "1.0.0",
4 + "lockfileVersion": 3,
5 + "requires": true,
6 + "packages": {
7 + "": {
8 + "name": "spboucher-ai",
9 + "version": "1.0.0",
10 + "dependencies": {
11 + "class-variance-authority": "^0.7.1",
12 + "clsx": "^2.1.1",
13 + "framer-motion": "^12.0.0",
14 + "lucide-react": "^0.525.0",
15 + "next": "^16.0.0",
16 + "react": "^19.0.0",
17 + "react-dom": "^19.0.0",
18 + "tailwind-merge": "^3.0.0"
19 + },
20 + "devDependencies": {
21 + "@tailwindcss/postcss": "^4.0.0",
22 + "@types/node": "^24.0.0",
23 + "@types/react": "^19.0.0",
24 + "@types/react-dom": "^19.0.0",
25 + "tailwindcss": "^4.0.0",
26 + "typescript": "^5.8.0"
27 + }
28 + },
29 + "node_modules/@alloc/quick-lru": {
30 + "version": "5.2.0",
31 + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
32 + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
33 + "dev": true,
34 + "license": "MIT",
35 + "engines": {
36 + "node": ">=10"
37 + },
38 + "funding": {
39 + "url": "https://github.com/sponsors/sindresorhus"
40 + }
41 + },
42 + "node_modules/@emnapi/runtime": {
43 + "version": "1.11.3",
44 + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz",
45 + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==",
46 + "license": "MIT",
47 + "optional": true,
48 + "dependencies": {
49 + "tslib": "^2.4.0"
50 + }
51 + },
52 + "node_modules/@img/colour": {
53 + "version": "1.1.0",
54 + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
55 + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
56 + "license": "MIT",
57 + "optional": true,
58 + "engines": {
59 + "node": ">=18"
60 + }
61 + },
62 + "node_modules/@img/sharp-darwin-arm64": {
63 + "version": "0.34.5",
64 + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
65 + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
66 + "cpu": [
67 + "arm64"
68 + ],
69 + "license": "Apache-2.0",
70 + "optional": true,
71 + "os": [
72 + "darwin"
73 + ],
74 + "engines": {
75 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
76 + },
77 + "funding": {
78 + "url": "https://opencollective.com/libvips"
79 + },
80 + "optionalDependencies": {
81 + "@img/sharp-libvips-darwin-arm64": "1.2.4"
82 + }
83 + },
84 + "node_modules/@img/sharp-darwin-x64": {
85 + "version": "0.34.5",
86 + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
87 + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
88 + "cpu": [
89 + "x64"
90 + ],
91 + "license": "Apache-2.0",
92 + "optional": true,
93 + "os": [
94 + "darwin"
95 + ],
96 + "engines": {
97 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
98 + },
99 + "funding": {
100 + "url": "https://opencollective.com/libvips"
101 + },
102 + "optionalDependencies": {
103 + "@img/sharp-libvips-darwin-x64": "1.2.4"
104 + }
105 + },
106 + "node_modules/@img/sharp-libvips-darwin-arm64": {
107 + "version": "1.2.4",
108 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
109 + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
110 + "cpu": [
111 + "arm64"
112 + ],
113 + "license": "LGPL-3.0-or-later",
114 + "optional": true,
115 + "os": [
116 + "darwin"
117 + ],
118 + "funding": {
119 + "url": "https://opencollective.com/libvips"
120 + }
121 + },
122 + "node_modules/@img/sharp-libvips-darwin-x64": {
123 + "version": "1.2.4",
124 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
125 + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
126 + "cpu": [
127 + "x64"
128 + ],
129 + "license": "LGPL-3.0-or-later",
130 + "optional": true,
131 + "os": [
132 + "darwin"
133 + ],
134 + "funding": {
135 + "url": "https://opencollective.com/libvips"
136 + }
137 + },
138 + "node_modules/@img/sharp-libvips-linux-arm": {
139 + "version": "1.2.4",
140 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
141 + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
142 + "cpu": [
143 + "arm"
144 + ],
145 + "libc": [
146 + "glibc"
147 + ],
148 + "license": "LGPL-3.0-or-later",
149 + "optional": true,
150 + "os": [
151 + "linux"
152 + ],
153 + "funding": {
154 + "url": "https://opencollective.com/libvips"
155 + }
156 + },
157 + "node_modules/@img/sharp-libvips-linux-arm64": {
158 + "version": "1.2.4",
159 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
160 + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
161 + "cpu": [
162 + "arm64"
163 + ],
164 + "libc": [
165 + "glibc"
166 + ],
167 + "license": "LGPL-3.0-or-later",
168 + "optional": true,
169 + "os": [
170 + "linux"
171 + ],
172 + "funding": {
173 + "url": "https://opencollective.com/libvips"
174 + }
175 + },
176 + "node_modules/@img/sharp-libvips-linux-ppc64": {
177 + "version": "1.2.4",
178 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
179 + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
180 + "cpu": [
181 + "ppc64"
182 + ],
183 + "libc": [
184 + "glibc"
185 + ],
186 + "license": "LGPL-3.0-or-later",
187 + "optional": true,
188 + "os": [
189 + "linux"
190 + ],
191 + "funding": {
192 + "url": "https://opencollective.com/libvips"
193 + }
194 + },
195 + "node_modules/@img/sharp-libvips-linux-riscv64": {
196 + "version": "1.2.4",
197 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
198 + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
199 + "cpu": [
200 + "riscv64"
201 + ],
202 + "libc": [
203 + "glibc"
204 + ],
205 + "license": "LGPL-3.0-or-later",
206 + "optional": true,
207 + "os": [
208 + "linux"
209 + ],
210 + "funding": {
211 + "url": "https://opencollective.com/libvips"
212 + }
213 + },
214 + "node_modules/@img/sharp-libvips-linux-s390x": {
215 + "version": "1.2.4",
216 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
217 + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
218 + "cpu": [
219 + "s390x"
220 + ],
221 + "libc": [
222 + "glibc"
223 + ],
224 + "license": "LGPL-3.0-or-later",
225 + "optional": true,
226 + "os": [
227 + "linux"
228 + ],
229 + "funding": {
230 + "url": "https://opencollective.com/libvips"
231 + }
232 + },
233 + "node_modules/@img/sharp-libvips-linux-x64": {
234 + "version": "1.2.4",
235 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
236 + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
237 + "cpu": [
238 + "x64"
239 + ],
240 + "libc": [
241 + "glibc"
242 + ],
243 + "license": "LGPL-3.0-or-later",
244 + "optional": true,
245 + "os": [
246 + "linux"
247 + ],
248 + "funding": {
249 + "url": "https://opencollective.com/libvips"
250 + }
251 + },
252 + "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
253 + "version": "1.2.4",
254 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
255 + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
256 + "cpu": [
257 + "arm64"
258 + ],
259 + "libc": [
260 + "musl"
261 + ],
262 + "license": "LGPL-3.0-or-later",
263 + "optional": true,
264 + "os": [
265 + "linux"
266 + ],
267 + "funding": {
268 + "url": "https://opencollective.com/libvips"
269 + }
270 + },
271 + "node_modules/@img/sharp-libvips-linuxmusl-x64": {
272 + "version": "1.2.4",
273 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
274 + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
275 + "cpu": [
276 + "x64"
277 + ],
278 + "libc": [
279 + "musl"
280 + ],
281 + "license": "LGPL-3.0-or-later",
282 + "optional": true,
283 + "os": [
284 + "linux"
285 + ],
286 + "funding": {
287 + "url": "https://opencollective.com/libvips"
288 + }
289 + },
290 + "node_modules/@img/sharp-linux-arm": {
291 + "version": "0.34.5",
292 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
293 + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
294 + "cpu": [
295 + "arm"
296 + ],
297 + "libc": [
298 + "glibc"
299 + ],
300 + "license": "Apache-2.0",
301 + "optional": true,
302 + "os": [
303 + "linux"
304 + ],
305 + "engines": {
306 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
307 + },
308 + "funding": {
309 + "url": "https://opencollective.com/libvips"
310 + },
311 + "optionalDependencies": {
312 + "@img/sharp-libvips-linux-arm": "1.2.4"
313 + }
314 + },
315 + "node_modules/@img/sharp-linux-arm64": {
316 + "version": "0.34.5",
317 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
318 + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
319 + "cpu": [
320 + "arm64"
321 + ],
322 + "libc": [
323 + "glibc"
324 + ],
325 + "license": "Apache-2.0",
326 + "optional": true,
327 + "os": [
328 + "linux"
329 + ],
330 + "engines": {
331 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
332 + },
333 + "funding": {
334 + "url": "https://opencollective.com/libvips"
335 + },
336 + "optionalDependencies": {
337 + "@img/sharp-libvips-linux-arm64": "1.2.4"
338 + }
339 + },
340 + "node_modules/@img/sharp-linux-ppc64": {
341 + "version": "0.34.5",
342 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
343 + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
344 + "cpu": [
345 + "ppc64"
346 + ],
347 + "libc": [
348 + "glibc"
349 + ],
350 + "license": "Apache-2.0",
351 + "optional": true,
352 + "os": [
353 + "linux"
354 + ],
355 + "engines": {
356 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
357 + },
358 + "funding": {
359 + "url": "https://opencollective.com/libvips"
360 + },
361 + "optionalDependencies": {
362 + "@img/sharp-libvips-linux-ppc64": "1.2.4"
363 + }
364 + },
365 + "node_modules/@img/sharp-linux-riscv64": {
366 + "version": "0.34.5",
367 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
368 + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
369 + "cpu": [
370 + "riscv64"
371 + ],
372 + "libc": [
373 + "glibc"
374 + ],
375 + "license": "Apache-2.0",
376 + "optional": true,
377 + "os": [
378 + "linux"
379 + ],
380 + "engines": {
381 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
382 + },
383 + "funding": {
384 + "url": "https://opencollective.com/libvips"
385 + },
386 + "optionalDependencies": {
387 + "@img/sharp-libvips-linux-riscv64": "1.2.4"
388 + }
389 + },
390 + "node_modules/@img/sharp-linux-s390x": {
391 + "version": "0.34.5",
392 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
393 + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
394 + "cpu": [
395 + "s390x"
396 + ],
397 + "libc": [
398 + "glibc"
399 + ],
400 + "license": "Apache-2.0",
401 + "optional": true,
402 + "os": [
403 + "linux"
404 + ],
405 + "engines": {
406 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
407 + },
408 + "funding": {
409 + "url": "https://opencollective.com/libvips"
410 + },
411 + "optionalDependencies": {
412 + "@img/sharp-libvips-linux-s390x": "1.2.4"
413 + }
414 + },
415 + "node_modules/@img/sharp-linux-x64": {
416 + "version": "0.34.5",
417 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
418 + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
419 + "cpu": [
420 + "x64"
421 + ],
422 + "libc": [
423 + "glibc"
424 + ],
425 + "license": "Apache-2.0",
426 + "optional": true,
427 + "os": [
428 + "linux"
429 + ],
430 + "engines": {
431 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
432 + },
433 + "funding": {
434 + "url": "https://opencollective.com/libvips"
435 + },
436 + "optionalDependencies": {
437 + "@img/sharp-libvips-linux-x64": "1.2.4"
438 + }
439 + },
440 + "node_modules/@img/sharp-linuxmusl-arm64": {
441 + "version": "0.34.5",
442 + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
443 + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
444 + "cpu": [
445 + "arm64"
446 + ],
447 + "libc": [
448 + "musl"
449 + ],
450 + "license": "Apache-2.0",
451 + "optional": true,
452 + "os": [
453 + "linux"
454 + ],
455 + "engines": {
456 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
457 + },
458 + "funding": {
459 + "url": "https://opencollective.com/libvips"
460 + },
461 + "optionalDependencies": {
462 + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
463 + }
464 + },
465 + "node_modules/@img/sharp-linuxmusl-x64": {
466 + "version": "0.34.5",
467 + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
468 + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
469 + "cpu": [
470 + "x64"
471 + ],
472 + "libc": [
473 + "musl"
474 + ],
475 + "license": "Apache-2.0",
476 + "optional": true,
477 + "os": [
478 + "linux"
479 + ],
480 + "engines": {
481 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
482 + },
483 + "funding": {
484 + "url": "https://opencollective.com/libvips"
485 + },
486 + "optionalDependencies": {
487 + "@img/sharp-libvips-linuxmusl-x64": "1.2.4"
488 + }
489 + },
490 + "node_modules/@img/sharp-wasm32": {
491 + "version": "0.34.5",
492 + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
493 + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
494 + "cpu": [
495 + "wasm32"
496 + ],
497 + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
498 + "optional": true,
499 + "dependencies": {
500 + "@emnapi/runtime": "^1.7.0"
501 + },
502 + "engines": {
503 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
504 + },
505 + "funding": {
506 + "url": "https://opencollective.com/libvips"
507 + }
508 + },
509 + "node_modules/@img/sharp-win32-arm64": {
510 + "version": "0.34.5",
511 + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
512 + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
513 + "cpu": [
514 + "arm64"
515 + ],
516 + "license": "Apache-2.0 AND LGPL-3.0-or-later",
517 + "optional": true,
518 + "os": [
519 + "win32"
520 + ],
521 + "engines": {
522 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
523 + },
524 + "funding": {
525 + "url": "https://opencollective.com/libvips"
526 + }
527 + },
528 + "node_modules/@img/sharp-win32-ia32": {
529 + "version": "0.34.5",
530 + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
531 + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
532 + "cpu": [
533 + "ia32"
534 + ],
535 + "license": "Apache-2.0 AND LGPL-3.0-or-later",
536 + "optional": true,
537 + "os": [
538 + "win32"
539 + ],
540 + "engines": {
541 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
542 + },
543 + "funding": {
544 + "url": "https://opencollective.com/libvips"
545 + }
546 + },
547 + "node_modules/@img/sharp-win32-x64": {
548 + "version": "0.34.5",
549 + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
550 + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
551 + "cpu": [
552 + "x64"
553 + ],
554 + "license": "Apache-2.0 AND LGPL-3.0-or-later",
555 + "optional": true,
556 + "os": [
557 + "win32"
558 + ],
559 + "engines": {
560 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
561 + },
562 + "funding": {
563 + "url": "https://opencollective.com/libvips"
564 + }
565 + },
566 + "node_modules/@jridgewell/gen-mapping": {
567 + "version": "0.3.13",
568 + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
569 + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
570 + "dev": true,
571 + "license": "MIT",
572 + "dependencies": {
573 + "@jridgewell/sourcemap-codec": "^1.5.0",
574 + "@jridgewell/trace-mapping": "^0.3.24"
575 + }
576 + },
577 + "node_modules/@jridgewell/remapping": {
578 + "version": "2.3.5",
579 + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
580 + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
581 + "dev": true,
582 + "license": "MIT",
583 + "dependencies": {
584 + "@jridgewell/gen-mapping": "^0.3.5",
585 + "@jridgewell/trace-mapping": "^0.3.24"
586 + }
587 + },
588 + "node_modules/@jridgewell/resolve-uri": {
589 + "version": "3.1.2",
590 + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
591 + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
592 + "dev": true,
593 + "license": "MIT",
594 + "engines": {
595 + "node": ">=6.0.0"
596 + }
597 + },
598 + "node_modules/@jridgewell/sourcemap-codec": {
599 + "version": "1.5.5",
600 + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
601 + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
602 + "dev": true,
603 + "license": "MIT"
604 + },
605 + "node_modules/@jridgewell/trace-mapping": {
606 + "version": "0.3.31",
607 + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
608 + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
609 + "dev": true,
610 + "license": "MIT",
611 + "dependencies": {
612 + "@jridgewell/resolve-uri": "^3.1.0",
613 + "@jridgewell/sourcemap-codec": "^1.4.14"
614 + }
615 + },
616 + "node_modules/@next/env": {
617 + "version": "16.2.12",
618 + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.12.tgz",
619 + "integrity": "sha512-d0Z5Bc13Fa4nR8pFAKx2jay2yhJM16vlfHbTzYnUQAxlNb6B6lmn4hjt69lYNt4kRtyYP6gEM49lPRHNbIyneg==",
620 + "license": "MIT"
621 + },
622 + "node_modules/@next/swc-darwin-arm64": {
623 + "version": "16.2.12",
624 + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.12.tgz",
625 + "integrity": "sha512-0W1R0teHWJrqKX0FH20IzzIWAOuGtBxPGuObrxy1lE8hQvCFj49KE8a3WUg0D7sq6rn6zkM4c7YGUnhudBS6oA==",
626 + "cpu": [
627 + "arm64"
628 + ],
629 + "license": "MIT",
630 + "optional": true,
631 + "os": [
632 + "darwin"
633 + ],
634 + "engines": {
635 + "node": ">= 10"
636 + }
637 + },
638 + "node_modules/@next/swc-darwin-x64": {
639 + "version": "16.2.12",
640 + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.12.tgz",
641 + "integrity": "sha512-Hy5Ls099+aFUmOLmIgPfLqNi6iCwhL3uQCssz5rWk+5Nkc6TUKCE83DY5BbNylfm3+mfwcSFnLRfrZDJhVxdtw==",
642 + "cpu": [
643 + "x64"
644 + ],
645 + "license": "MIT",
646 + "optional": true,
647 + "os": [
648 + "darwin"
649 + ],
650 + "engines": {
651 + "node": ">= 10"
652 + }
653 + },
654 + "node_modules/@next/swc-linux-arm64-gnu": {
655 + "version": "16.2.12",
656 + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.12.tgz",
657 + "integrity": "sha512-+YqU2h1cQkHsGfvjAsrSmst8UIFBibBGm5x3Xgel8NLMiDQtNOM4sM2GOEMvG5YiOBNeN/Ykk8cQC2S0Xrqljg==",
658 + "cpu": [
659 + "arm64"
660 + ],
661 + "libc": [
662 + "glibc"
663 + ],
664 + "license": "MIT",
665 + "optional": true,
666 + "os": [
667 + "linux"
668 + ],
669 + "engines": {
670 + "node": ">= 10"
671 + }
672 + },
673 + "node_modules/@next/swc-linux-arm64-musl": {
674 + "version": "16.2.12",
675 + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.12.tgz",
676 + "integrity": "sha512-0qjhiYBaKAqF63LA1ZWAAnKTzFUguAaZiRa5etMLGGPj/B6uEVjtIZldIzFEp3wHlB0koK6aTzqPtSdplTCjoA==",
677 + "cpu": [
678 + "arm64"
679 + ],
680 + "libc": [
681 + "musl"
682 + ],
683 + "license": "MIT",
684 + "optional": true,
685 + "os": [
686 + "linux"
687 + ],
688 + "engines": {
689 + "node": ">= 10"
690 + }
691 + },
692 + "node_modules/@next/swc-linux-x64-gnu": {
693 + "version": "16.2.12",
694 + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.12.tgz",
695 + "integrity": "sha512-7A3q26W+h7gnA15uqBToNuDqBEFZZcqh0mW2mn4AJh/G5pdg2RVE3n4slzLEliASZFG3NmsbEzng/x2Sh09mBg==",
696 + "cpu": [
697 + "x64"
698 + ],
699 + "libc": [
700 + "glibc"
701 + ],
702 + "license": "MIT",
703 + "optional": true,
704 + "os": [
705 + "linux"
706 + ],
707 + "engines": {
708 + "node": ">= 10"
709 + }
710 + },
711 + "node_modules/@next/swc-linux-x64-musl": {
712 + "version": "16.2.12",
713 + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.12.tgz",
714 + "integrity": "sha512-qSjL/uppm+cbh21s72Ss8gkiOhQ4dExWHNGOWy6eZV7STj5WsKehgxT61beSsOj+YYQuTplL376lOCdMQU5T8w==",
715 + "cpu": [
716 + "x64"
717 + ],
718 + "libc": [
719 + "musl"
720 + ],
721 + "license": "MIT",
722 + "optional": true,
723 + "os": [
724 + "linux"
725 + ],
726 + "engines": {
727 + "node": ">= 10"
728 + }
729 + },
730 + "node_modules/@next/swc-win32-arm64-msvc": {
731 + "version": "16.2.12",
732 + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.12.tgz",
733 + "integrity": "sha512-X6hzsOUJac/e7AWSbn9gQ9nzHld1xWP5iyjHpYWvud8pufB679O1xg4JDyKr8Xd69Jvd+kM2Der6uftiZCmjYA==",
734 + "cpu": [
735 + "arm64"
736 + ],
737 + "license": "MIT",
738 + "optional": true,
739 + "os": [
740 + "win32"
741 + ],
742 + "engines": {
743 + "node": ">= 10"
744 + }
745 + },
746 + "node_modules/@next/swc-win32-x64-msvc": {
747 + "version": "16.2.12",
748 + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.12.tgz",
749 + "integrity": "sha512-F6fakeHuFTLOPt0bslQJdf+xtT+WIP9DVn/m4y1w1mRnVPyh3D/cNvzlRkxM444xfm+IvvYNSOrKiA2CDJ0Uxw==",
750 + "cpu": [
751 + "x64"
752 + ],
753 + "license": "MIT",
754 + "optional": true,
755 + "os": [
756 + "win32"
757 + ],
758 + "engines": {
759 + "node": ">= 10"
760 + }
761 + },
762 + "node_modules/@swc/helpers": {
763 + "version": "0.5.15",
764 + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
765 + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
766 + "license": "Apache-2.0",
767 + "dependencies": {
768 + "tslib": "^2.8.0"
769 + }
770 + },
771 + "node_modules/@tailwindcss/node": {
772 + "version": "4.3.3",
773 + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz",
774 + "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==",
775 + "dev": true,
776 + "license": "MIT",
777 + "dependencies": {
778 + "@jridgewell/remapping": "^2.3.5",
779 + "enhanced-resolve": "^5.24.1",
780 + "jiti": "^2.7.0",
781 + "lightningcss": "1.32.0",
782 + "magic-string": "^0.30.21",
783 + "source-map-js": "^1.2.1",
784 + "tailwindcss": "4.3.3"
785 + }
786 + },
787 + "node_modules/@tailwindcss/oxide": {
788 + "version": "4.3.3",
789 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz",
790 + "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==",
791 + "dev": true,
792 + "license": "MIT",
793 + "engines": {
794 + "node": ">= 20"
795 + },
796 + "optionalDependencies": {
797 + "@tailwindcss/oxide-android-arm64": "4.3.3",
798 + "@tailwindcss/oxide-darwin-arm64": "4.3.3",
799 + "@tailwindcss/oxide-darwin-x64": "4.3.3",
800 + "@tailwindcss/oxide-freebsd-x64": "4.3.3",
801 + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3",
802 + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3",
803 + "@tailwindcss/oxide-linux-arm64-musl": "4.3.3",
804 + "@tailwindcss/oxide-linux-x64-gnu": "4.3.3",
805 + "@tailwindcss/oxide-linux-x64-musl": "4.3.3",
806 + "@tailwindcss/oxide-wasm32-wasi": "4.3.3",
807 + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3",
808 + "@tailwindcss/oxide-win32-x64-msvc": "4.3.3"
809 + }
810 + },
811 + "node_modules/@tailwindcss/oxide-android-arm64": {
812 + "version": "4.3.3",
813 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz",
814 + "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==",
815 + "cpu": [
816 + "arm64"
817 + ],
818 + "dev": true,
819 + "license": "MIT",
820 + "optional": true,
821 + "os": [
822 + "android"
823 + ],
824 + "engines": {
825 + "node": ">= 20"
826 + }
827 + },
828 + "node_modules/@tailwindcss/oxide-darwin-arm64": {
829 + "version": "4.3.3",
830 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz",
831 + "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==",
832 + "cpu": [
833 + "arm64"
834 + ],
835 + "dev": true,
836 + "license": "MIT",
837 + "optional": true,
838 + "os": [
839 + "darwin"
840 + ],
841 + "engines": {
842 + "node": ">= 20"
843 + }
844 + },
845 + "node_modules/@tailwindcss/oxide-darwin-x64": {
846 + "version": "4.3.3",
847 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz",
848 + "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==",
849 + "cpu": [
850 + "x64"
851 + ],
852 + "dev": true,
853 + "license": "MIT",
854 + "optional": true,
855 + "os": [
856 + "darwin"
857 + ],
858 + "engines": {
859 + "node": ">= 20"
860 + }
861 + },
862 + "node_modules/@tailwindcss/oxide-freebsd-x64": {
863 + "version": "4.3.3",
864 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz",
865 + "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==",
866 + "cpu": [
867 + "x64"
868 + ],
869 + "dev": true,
870 + "license": "MIT",
871 + "optional": true,
872 + "os": [
873 + "freebsd"
874 + ],
875 + "engines": {
876 + "node": ">= 20"
877 + }
878 + },
879 + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
880 + "version": "4.3.3",
881 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz",
882 + "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==",
883 + "cpu": [
884 + "arm"
885 + ],
886 + "dev": true,
887 + "license": "MIT",
888 + "optional": true,
889 + "os": [
890 + "linux"
891 + ],
892 + "engines": {
893 + "node": ">= 20"
894 + }
895 + },
896 + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
897 + "version": "4.3.3",
898 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz",
899 + "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==",
900 + "cpu": [
901 + "arm64"
902 + ],
903 + "dev": true,
904 + "libc": [
905 + "glibc"
906 + ],
907 + "license": "MIT",
908 + "optional": true,
909 + "os": [
910 + "linux"
911 + ],
912 + "engines": {
913 + "node": ">= 20"
914 + }
915 + },
916 + "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
917 + "version": "4.3.3",
918 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz",
919 + "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==",
920 + "cpu": [
921 + "arm64"
922 + ],
923 + "dev": true,
924 + "libc": [
925 + "musl"
926 + ],
927 + "license": "MIT",
928 + "optional": true,
929 + "os": [
930 + "linux"
931 + ],
932 + "engines": {
933 + "node": ">= 20"
934 + }
935 + },
936 + "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
937 + "version": "4.3.3",
938 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz",
939 + "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==",
940 + "cpu": [
941 + "x64"
942 + ],
943 + "dev": true,
944 + "libc": [
945 + "glibc"
946 + ],
947 + "license": "MIT",
948 + "optional": true,
949 + "os": [
950 + "linux"
951 + ],
952 + "engines": {
953 + "node": ">= 20"
954 + }
955 + },
956 + "node_modules/@tailwindcss/oxide-linux-x64-musl": {
957 + "version": "4.3.3",
958 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz",
959 + "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==",
960 + "cpu": [
961 + "x64"
962 + ],
963 + "dev": true,
964 + "libc": [
965 + "musl"
966 + ],
967 + "license": "MIT",
968 + "optional": true,
969 + "os": [
970 + "linux"
971 + ],
972 + "engines": {
973 + "node": ">= 20"
974 + }
975 + },
976 + "node_modules/@tailwindcss/oxide-wasm32-wasi": {
977 + "version": "4.3.3",
978 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz",
979 + "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==",
980 + "bundleDependencies": [
981 + "@napi-rs/wasm-runtime",
982 + "@emnapi/core",
983 + "@emnapi/runtime",
984 + "@tybys/wasm-util",
985 + "@emnapi/wasi-threads",
986 + "tslib"
987 + ],
988 + "cpu": [
989 + "wasm32"
990 + ],
991 + "dev": true,
992 + "license": "MIT",
993 + "optional": true,
994 + "dependencies": {
995 + "@emnapi/core": "^1.11.1",
996 + "@emnapi/runtime": "^1.11.1",
997 + "@emnapi/wasi-threads": "^1.2.2",
998 + "@napi-rs/wasm-runtime": "^1.1.4",
999 + "@tybys/wasm-util": "^0.10.2",
1000 + "tslib": "^2.8.1"
1001 + },
1002 + "engines": {
1003 + "node": ">=14.0.0"
1004 + }
1005 + },
1006 + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
1007 + "version": "4.3.3",
1008 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz",
1009 + "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==",
1010 + "cpu": [
1011 + "arm64"
1012 + ],
1013 + "dev": true,
1014 + "license": "MIT",
1015 + "optional": true,
1016 + "os": [
1017 + "win32"
1018 + ],
1019 + "engines": {
1020 + "node": ">= 20"
1021 + }
1022 + },
1023 + "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
1024 + "version": "4.3.3",
1025 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz",
1026 + "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==",
1027 + "cpu": [
1028 + "x64"
1029 + ],
1030 + "dev": true,
1031 + "license": "MIT",
1032 + "optional": true,
1033 + "os": [
1034 + "win32"
1035 + ],
1036 + "engines": {
1037 + "node": ">= 20"
1038 + }
1039 + },
1040 + "node_modules/@tailwindcss/postcss": {
1041 + "version": "4.3.3",
1042 + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.3.tgz",
1043 + "integrity": "sha512-JTSZZGQi1AyKirbLN3azmjVzef92tcX7h+iSqPdaeStyFpGpDlKvvpxeOE8njhbUanbRwr3z8DyzhICWnMtQeg==",
1044 + "dev": true,
1045 + "license": "MIT",
1046 + "dependencies": {
1047 + "@alloc/quick-lru": "^5.2.0",
1048 + "@tailwindcss/node": "4.3.3",
1049 + "@tailwindcss/oxide": "4.3.3",
1050 + "postcss": "^8.5.16",
1051 + "tailwindcss": "4.3.3"
1052 + }
1053 + },
1054 + "node_modules/@types/node": {
1055 + "version": "24.13.3",
1056 + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz",
1057 + "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==",
1058 + "dev": true,
1059 + "license": "MIT",
1060 + "dependencies": {
1061 + "undici-types": "~7.18.0"
1062 + }
1063 + },
1064 + "node_modules/@types/react": {
1065 + "version": "19.2.18",
1066 + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz",
1067 + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==",
1068 + "dev": true,
1069 + "license": "MIT",
1070 + "dependencies": {
1071 + "csstype": "^3.2.2"
1072 + }
1073 + },
1074 + "node_modules/@types/react-dom": {
1075 + "version": "19.2.4",
1076 + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz",
1077 + "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==",
1078 + "dev": true,
1079 + "license": "MIT",
1080 + "peerDependencies": {
1081 + "@types/react": "^19.2.0"
1082 + }
1083 + },
1084 + "node_modules/baseline-browser-mapping": {
1085 + "version": "2.11.8",
1086 + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.8.tgz",
1087 + "integrity": "sha512-zAgkquC2WYF0PIc6XbNYkA2uuxxFavzgmX61R+dHDUa558V8Ejf8ozTZFR6QzM24RWu4kBcRkhJ5kpz77j9fnQ==",
1088 + "license": "Apache-2.0",
1089 + "bin": {
1090 + "baseline-browser-mapping": "dist/cli.cjs"
1091 + },
1092 + "engines": {
1093 + "node": ">=6.0.0"
1094 + }
1095 + },
1096 + "node_modules/caniuse-lite": {
1097 + "version": "1.0.30001806",
1098 + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz",
1099 + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==",
1100 + "funding": [
1101 + {
1102 + "type": "opencollective",
1103 + "url": "https://opencollective.com/browserslist"
1104 + },
1105 + {
1106 + "type": "tidelift",
1107 + "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
1108 + },
1109 + {
1110 + "type": "github",
1111 + "url": "https://github.com/sponsors/ai"
1112 + }
1113 + ],
1114 + "license": "CC-BY-4.0"
1115 + },
1116 + "node_modules/class-variance-authority": {
1117 + "version": "0.7.1",
1118 + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
1119 + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
1120 + "license": "Apache-2.0",
1121 + "dependencies": {
1122 + "clsx": "^2.1.1"
1123 + },
1124 + "funding": {
1125 + "url": "https://polar.sh/cva"
1126 + }
1127 + },
1128 + "node_modules/client-only": {
1129 + "version": "0.0.1",
1130 + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
1131 + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
1132 + "license": "MIT"
1133 + },
1134 + "node_modules/clsx": {
1135 + "version": "2.1.1",
1136 + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
1137 + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
1138 + "license": "MIT",
1139 + "engines": {
1140 + "node": ">=6"
1141 + }
1142 + },
1143 + "node_modules/csstype": {
1144 + "version": "3.2.3",
1145 + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
1146 + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
1147 + "dev": true,
1148 + "license": "MIT"
1149 + },
1150 + "node_modules/detect-libc": {
1151 + "version": "2.1.2",
1152 + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
1153 + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
1154 + "devOptional": true,
1155 + "license": "Apache-2.0",
1156 + "engines": {
1157 + "node": ">=8"
1158 + }
1159 + },
1160 + "node_modules/enhanced-resolve": {
1161 + "version": "5.24.5",
1162 + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz",
1163 + "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==",
1164 + "dev": true,
1165 + "license": "MIT",
1166 + "dependencies": {
1167 + "graceful-fs": "^4.2.4",
1168 + "tapable": "^2.3.3"
1169 + },
1170 + "engines": {
1171 + "node": ">=10.13.0"
1172 + }
1173 + },
1174 + "node_modules/framer-motion": {
1175 + "version": "12.43.0",
1176 + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.43.0.tgz",
1177 + "integrity": "sha512-1eaL3RvR/kAlbG7UYcpMptEyzPoENO0c6w7ZnB3/hh2vSAz/6uGAFn6fdoqTBguNstf3MsFhJHsD/0DHiclG+g==",
1178 + "license": "MIT",
1179 + "dependencies": {
1180 + "motion-dom": "^12.43.0",
1181 + "motion-utils": "^12.39.0",
1182 + "tslib": "^2.4.0"
1183 + },
1184 + "peerDependencies": {
1185 + "@emotion/is-prop-valid": "*",
1186 + "react": "^18.0.0 || ^19.0.0",
1187 + "react-dom": "^18.0.0 || ^19.0.0"
1188 + },
1189 + "peerDependenciesMeta": {
1190 + "@emotion/is-prop-valid": {
1191 + "optional": true
1192 + },
1193 + "react": {
1194 + "optional": true
1195 + },
1196 + "react-dom": {
1197 + "optional": true
1198 + }
1199 + }
1200 + },
1201 + "node_modules/graceful-fs": {
1202 + "version": "4.2.11",
1203 + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
1204 + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
1205 + "dev": true,
1206 + "license": "ISC"
1207 + },
1208 + "node_modules/jiti": {
1209 + "version": "2.7.0",
1210 + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
1211 + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==",
1212 + "dev": true,
1213 + "license": "MIT",
1214 + "bin": {
1215 + "jiti": "lib/jiti-cli.mjs"
1216 + }
1217 + },
1218 + "node_modules/lightningcss": {
1219 + "version": "1.32.0",
1220 + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
1221 + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
1222 + "dev": true,
1223 + "license": "MPL-2.0",
1224 + "dependencies": {
1225 + "detect-libc": "^2.0.3"
1226 + },
1227 + "engines": {
1228 + "node": ">= 12.0.0"
1229 + },
1230 + "funding": {
1231 + "type": "opencollective",
1232 + "url": "https://opencollective.com/parcel"
1233 + },
1234 + "optionalDependencies": {
1235 + "lightningcss-android-arm64": "1.32.0",
1236 + "lightningcss-darwin-arm64": "1.32.0",
1237 + "lightningcss-darwin-x64": "1.32.0",
1238 + "lightningcss-freebsd-x64": "1.32.0",
1239 + "lightningcss-linux-arm-gnueabihf": "1.32.0",
1240 + "lightningcss-linux-arm64-gnu": "1.32.0",
1241 + "lightningcss-linux-arm64-musl": "1.32.0",
1242 + "lightningcss-linux-x64-gnu": "1.32.0",
1243 + "lightningcss-linux-x64-musl": "1.32.0",
1244 + "lightningcss-win32-arm64-msvc": "1.32.0",
1245 + "lightningcss-win32-x64-msvc": "1.32.0"
1246 + }
1247 + },
1248 + "node_modules/lightningcss-android-arm64": {
1249 + "version": "1.32.0",
1250 + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
1251 + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
1252 + "cpu": [
1253 + "arm64"
1254 + ],
1255 + "dev": true,
1256 + "license": "MPL-2.0",
1257 + "optional": true,
1258 + "os": [
1259 + "android"
1260 + ],
1261 + "engines": {
1262 + "node": ">= 12.0.0"
1263 + },
1264 + "funding": {
1265 + "type": "opencollective",
1266 + "url": "https://opencollective.com/parcel"
1267 + }
1268 + },
1269 + "node_modules/lightningcss-darwin-arm64": {
1270 + "version": "1.32.0",
1271 + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
1272 + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
1273 + "cpu": [
1274 + "arm64"
1275 + ],
1276 + "dev": true,
1277 + "license": "MPL-2.0",
1278 + "optional": true,
1279 + "os": [
1280 + "darwin"
1281 + ],
1282 + "engines": {
1283 + "node": ">= 12.0.0"
1284 + },
1285 + "funding": {
1286 + "type": "opencollective",
1287 + "url": "https://opencollective.com/parcel"
1288 + }
1289 + },
1290 + "node_modules/lightningcss-darwin-x64": {
1291 + "version": "1.32.0",
1292 + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
1293 + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
1294 + "cpu": [
1295 + "x64"
1296 + ],
1297 + "dev": true,
1298 + "license": "MPL-2.0",
1299 + "optional": true,
1300 + "os": [
1301 + "darwin"
1302 + ],
1303 + "engines": {
1304 + "node": ">= 12.0.0"
1305 + },
1306 + "funding": {
1307 + "type": "opencollective",
1308 + "url": "https://opencollective.com/parcel"
1309 + }
1310 + },
1311 + "node_modules/lightningcss-freebsd-x64": {
1312 + "version": "1.32.0",
1313 + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
1314 + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
1315 + "cpu": [
1316 + "x64"
1317 + ],
1318 + "dev": true,
1319 + "license": "MPL-2.0",
1320 + "optional": true,
1321 + "os": [
1322 + "freebsd"
1323 + ],
1324 + "engines": {
1325 + "node": ">= 12.0.0"
1326 + },
1327 + "funding": {
1328 + "type": "opencollective",
1329 + "url": "https://opencollective.com/parcel"
1330 + }
1331 + },
1332 + "node_modules/lightningcss-linux-arm-gnueabihf": {
1333 + "version": "1.32.0",
1334 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
1335 + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
1336 + "cpu": [
1337 + "arm"
1338 + ],
1339 + "dev": true,
1340 + "license": "MPL-2.0",
1341 + "optional": true,
1342 + "os": [
1343 + "linux"
1344 + ],
1345 + "engines": {
1346 + "node": ">= 12.0.0"
1347 + },
1348 + "funding": {
1349 + "type": "opencollective",
1350 + "url": "https://opencollective.com/parcel"
1351 + }
1352 + },
1353 + "node_modules/lightningcss-linux-arm64-gnu": {
1354 + "version": "1.32.0",
1355 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
1356 + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
1357 + "cpu": [
1358 + "arm64"
1359 + ],
1360 + "dev": true,
1361 + "libc": [
1362 + "glibc"
1363 + ],
1364 + "license": "MPL-2.0",
1365 + "optional": true,
1366 + "os": [
1367 + "linux"
1368 + ],
1369 + "engines": {
1370 + "node": ">= 12.0.0"
1371 + },
1372 + "funding": {
1373 + "type": "opencollective",
1374 + "url": "https://opencollective.com/parcel"
1375 + }
1376 + },
1377 + "node_modules/lightningcss-linux-arm64-musl": {
1378 + "version": "1.32.0",
1379 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
1380 + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
1381 + "cpu": [
1382 + "arm64"
1383 + ],
1384 + "dev": true,
1385 + "libc": [
1386 + "musl"
1387 + ],
1388 + "license": "MPL-2.0",
1389 + "optional": true,
1390 + "os": [
1391 + "linux"
1392 + ],
1393 + "engines": {
1394 + "node": ">= 12.0.0"
1395 + },
1396 + "funding": {
1397 + "type": "opencollective",
1398 + "url": "https://opencollective.com/parcel"
1399 + }
1400 + },
1401 + "node_modules/lightningcss-linux-x64-gnu": {
1402 + "version": "1.32.0",
1403 + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
1404 + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
1405 + "cpu": [
1406 + "x64"
1407 + ],
1408 + "dev": true,
1409 + "libc": [
1410 + "glibc"
1411 + ],
1412 + "license": "MPL-2.0",
1413 + "optional": true,
1414 + "os": [
1415 + "linux"
1416 + ],
1417 + "engines": {
1418 + "node": ">= 12.0.0"
1419 + },
1420 + "funding": {
1421 + "type": "opencollective",
1422 + "url": "https://opencollective.com/parcel"
1423 + }
1424 + },
1425 + "node_modules/lightningcss-linux-x64-musl": {
1426 + "version": "1.32.0",
1427 + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
1428 + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
1429 + "cpu": [
1430 + "x64"
1431 + ],
1432 + "dev": true,
1433 + "libc": [
1434 + "musl"
1435 + ],
1436 + "license": "MPL-2.0",
1437 + "optional": true,
1438 + "os": [
1439 + "linux"
1440 + ],
1441 + "engines": {
1442 + "node": ">= 12.0.0"
1443 + },
1444 + "funding": {
1445 + "type": "opencollective",
1446 + "url": "https://opencollective.com/parcel"
1447 + }
1448 + },
1449 + "node_modules/lightningcss-win32-arm64-msvc": {
1450 + "version": "1.32.0",
1451 + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
1452 + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
1453 + "cpu": [
1454 + "arm64"
1455 + ],
1456 + "dev": true,
1457 + "license": "MPL-2.0",
1458 + "optional": true,
1459 + "os": [
1460 + "win32"
1461 + ],
1462 + "engines": {
1463 + "node": ">= 12.0.0"
1464 + },
1465 + "funding": {
1466 + "type": "opencollective",
1467 + "url": "https://opencollective.com/parcel"
1468 + }
1469 + },
1470 + "node_modules/lightningcss-win32-x64-msvc": {
1471 + "version": "1.32.0",
1472 + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
1473 + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
1474 + "cpu": [
1475 + "x64"
1476 + ],
1477 + "dev": true,
1478 + "license": "MPL-2.0",
1479 + "optional": true,
1480 + "os": [
1481 + "win32"
1482 + ],
1483 + "engines": {
1484 + "node": ">= 12.0.0"
1485 + },
1486 + "funding": {
1487 + "type": "opencollective",
1488 + "url": "https://opencollective.com/parcel"
1489 + }
1490 + },
1491 + "node_modules/lucide-react": {
1492 + "version": "0.525.0",
1493 + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.525.0.tgz",
1494 + "integrity": "sha512-Tm1txJ2OkymCGkvwoHt33Y2JpN5xucVq1slHcgE6Lk0WjDfjgKWor5CdVER8U6DvcfMwh4M8XxmpTiyzfmfDYQ==",
1495 + "license": "ISC",
1496 + "peerDependencies": {
1497 + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
1498 + }
1499 + },
1500 + "node_modules/magic-string": {
1501 + "version": "0.30.21",
1502 + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
1503 + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
1504 + "dev": true,
1505 + "license": "MIT",
1506 + "dependencies": {
1507 + "@jridgewell/sourcemap-codec": "^1.5.5"
1508 + }
1509 + },
1510 + "node_modules/motion-dom": {
1511 + "version": "12.43.0",
1512 + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.43.0.tgz",
1513 + "integrity": "sha512-azKON4d9S65PEoFUiQTMTgPheEmzf2QngdRc50AKfJp9Q9mmcBVw22c8eMq9k8kxOFHdL7+WZY7N/5F/lwiDag==",
1514 + "license": "MIT",
1515 + "dependencies": {
1516 + "motion-utils": "^12.39.0"
1517 + }
1518 + },
1519 + "node_modules/motion-utils": {
1520 + "version": "12.39.0",
1521 + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.39.0.tgz",
1522 + "integrity": "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==",
1523 + "license": "MIT"
1524 + },
1525 + "node_modules/nanoid": {
1526 + "version": "3.3.16",
1527 + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
1528 + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
1529 + "funding": [
1530 + {
1531 + "type": "github",
1532 + "url": "https://github.com/sponsors/ai"
1533 + }
1534 + ],
1535 + "license": "MIT",
1536 + "bin": {
1537 + "nanoid": "bin/nanoid.cjs"
1538 + },
1539 + "engines": {
1540 + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
1541 + }
1542 + },
1543 + "node_modules/next": {
1544 + "version": "16.2.12",
1545 + "resolved": "https://registry.npmjs.org/next/-/next-16.2.12.tgz",
1546 + "integrity": "sha512-iD59eYQWmbFcEbX7v/acG5DRym9iw1DdaPoD0WTA920naWsE25wShzJW4+UvAs8MK9EC2kBfIH6vtto1H1PHGw==",
1547 + "license": "MIT",
1548 + "dependencies": {
1549 + "@next/env": "16.2.12",
1550 + "@swc/helpers": "0.5.15",
1551 + "baseline-browser-mapping": "^2.9.19",
1552 + "caniuse-lite": "^1.0.30001579",
1553 + "postcss": "8.4.31",
1554 + "styled-jsx": "5.1.6"
1555 + },
1556 + "bin": {
1557 + "next": "dist/bin/next"
1558 + },
1559 + "engines": {
1560 + "node": ">=20.9.0"
1561 + },
1562 + "optionalDependencies": {
1563 + "@next/swc-darwin-arm64": "16.2.12",
1564 + "@next/swc-darwin-x64": "16.2.12",
1565 + "@next/swc-linux-arm64-gnu": "16.2.12",
1566 + "@next/swc-linux-arm64-musl": "16.2.12",
1567 + "@next/swc-linux-x64-gnu": "16.2.12",
1568 + "@next/swc-linux-x64-musl": "16.2.12",
1569 + "@next/swc-win32-arm64-msvc": "16.2.12",
1570 + "@next/swc-win32-x64-msvc": "16.2.12",
1571 + "sharp": "^0.34.5"
1572 + },
1573 + "peerDependencies": {
1574 + "@opentelemetry/api": "^1.1.0",
1575 + "@playwright/test": "^1.51.1",
1576 + "babel-plugin-react-compiler": "*",
1577 + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
1578 + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
1579 + "sass": "^1.3.0"
1580 + },
1581 + "peerDependenciesMeta": {
1582 + "@opentelemetry/api": {
1583 + "optional": true
1584 + },
1585 + "@playwright/test": {
1586 + "optional": true
1587 + },
1588 + "babel-plugin-react-compiler": {
1589 + "optional": true
1590 + },
1591 + "sass": {
1592 + "optional": true
1593 + }
1594 + }
1595 + },
1596 + "node_modules/next/node_modules/postcss": {
1597 + "version": "8.4.31",
1598 + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
1599 + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
1600 + "funding": [
1601 + {
1602 + "type": "opencollective",
1603 + "url": "https://opencollective.com/postcss/"
1604 + },
1605 + {
1606 + "type": "tidelift",
1607 + "url": "https://tidelift.com/funding/github/npm/postcss"
1608 + },
1609 + {
1610 + "type": "github",
1611 + "url": "https://github.com/sponsors/ai"
1612 + }
1613 + ],
1614 + "license": "MIT",
1615 + "dependencies": {
1616 + "nanoid": "^3.3.6",
1617 + "picocolors": "^1.0.0",
1618 + "source-map-js": "^1.0.2"
1619 + },
1620 + "engines": {
1621 + "node": "^10 || ^12 || >=14"
1622 + }
1623 + },
1624 + "node_modules/picocolors": {
1625 + "version": "1.1.1",
1626 + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
1627 + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
1628 + "license": "ISC"
1629 + },
1630 + "node_modules/postcss": {
1631 + "version": "8.5.25",
1632 + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz",
1633 + "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==",
1634 + "dev": true,
1635 + "funding": [
1636 + {
1637 + "type": "opencollective",
1638 + "url": "https://opencollective.com/postcss/"
1639 + },
1640 + {
1641 + "type": "tidelift",
1642 + "url": "https://tidelift.com/funding/github/npm/postcss"
1643 + },
1644 + {
1645 + "type": "github",
1646 + "url": "https://github.com/sponsors/ai"
1647 + }
1648 + ],
1649 + "license": "MIT",
1650 + "dependencies": {
1651 + "nanoid": "^3.3.16",
1652 + "picocolors": "^1.1.1",
1653 + "source-map-js": "^1.2.1"
1654 + },
1655 + "engines": {
1656 + "node": "^10 || ^12 || >=14"
1657 + }
1658 + },
1659 + "node_modules/react": {
1660 + "version": "19.2.8",
1661 + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz",
1662 + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==",
1663 + "license": "MIT",
1664 + "engines": {
1665 + "node": ">=0.10.0"
1666 + }
1667 + },
1668 + "node_modules/react-dom": {
1669 + "version": "19.2.8",
1670 + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz",
1671 + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==",
1672 + "license": "MIT",
1673 + "dependencies": {
1674 + "scheduler": "^0.27.0"
1675 + },
1676 + "peerDependencies": {
1677 + "react": "^19.2.8"
1678 + }
1679 + },
1680 + "node_modules/scheduler": {
1681 + "version": "0.27.0",
1682 + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
1683 + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
1684 + "license": "MIT"
1685 + },
1686 + "node_modules/semver": {
1687 + "version": "7.8.5",
1688 + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
1689 + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
1690 + "license": "ISC",
1691 + "optional": true,
1692 + "bin": {
1693 + "semver": "bin/semver.js"
1694 + },
1695 + "engines": {
1696 + "node": ">=10"
1697 + }
1698 + },
1699 + "node_modules/sharp": {
1700 + "version": "0.34.5",
1701 + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
1702 + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
1703 + "hasInstallScript": true,
1704 + "license": "Apache-2.0",
1705 + "optional": true,
1706 + "dependencies": {
1707 + "@img/colour": "^1.0.0",
1708 + "detect-libc": "^2.1.2",
1709 + "semver": "^7.7.3"
1710 + },
1711 + "engines": {
1712 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
1713 + },
1714 + "funding": {
1715 + "url": "https://opencollective.com/libvips"
1716 + },
1717 + "optionalDependencies": {
1718 + "@img/sharp-darwin-arm64": "0.34.5",
1719 + "@img/sharp-darwin-x64": "0.34.5",
1720 + "@img/sharp-libvips-darwin-arm64": "1.2.4",
1721 + "@img/sharp-libvips-darwin-x64": "1.2.4",
1722 + "@img/sharp-libvips-linux-arm": "1.2.4",
1723 + "@img/sharp-libvips-linux-arm64": "1.2.4",
1724 + "@img/sharp-libvips-linux-ppc64": "1.2.4",
1725 + "@img/sharp-libvips-linux-riscv64": "1.2.4",
1726 + "@img/sharp-libvips-linux-s390x": "1.2.4",
1727 + "@img/sharp-libvips-linux-x64": "1.2.4",
1728 + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
1729 + "@img/sharp-libvips-linuxmusl-x64": "1.2.4",
1730 + "@img/sharp-linux-arm": "0.34.5",
1731 + "@img/sharp-linux-arm64": "0.34.5",
1732 + "@img/sharp-linux-ppc64": "0.34.5",
1733 + "@img/sharp-linux-riscv64": "0.34.5",
1734 + "@img/sharp-linux-s390x": "0.34.5",
1735 + "@img/sharp-linux-x64": "0.34.5",
1736 + "@img/sharp-linuxmusl-arm64": "0.34.5",
1737 + "@img/sharp-linuxmusl-x64": "0.34.5",
1738 + "@img/sharp-wasm32": "0.34.5",
1739 + "@img/sharp-win32-arm64": "0.34.5",
1740 + "@img/sharp-win32-ia32": "0.34.5",
1741 + "@img/sharp-win32-x64": "0.34.5"
1742 + }
1743 + },
1744 + "node_modules/source-map-js": {
1745 + "version": "1.2.1",
1746 + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
1747 + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
1748 + "license": "BSD-3-Clause",
1749 + "engines": {
1750 + "node": ">=0.10.0"
1751 + }
1752 + },
1753 + "node_modules/styled-jsx": {
1754 + "version": "5.1.6",
1755 + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
1756 + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==",
1757 + "license": "MIT",
1758 + "dependencies": {
1759 + "client-only": "0.0.1"
1760 + },
1761 + "engines": {
1762 + "node": ">= 12.0.0"
1763 + },
1764 + "peerDependencies": {
1765 + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0"
1766 + },
1767 + "peerDependenciesMeta": {
1768 + "@babel/core": {
1769 + "optional": true
1770 + },
1771 + "babel-plugin-macros": {
1772 + "optional": true
1773 + }
1774 + }
1775 + },
1776 + "node_modules/tailwind-merge": {
1777 + "version": "3.6.0",
1778 + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz",
1779 + "integrity": "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==",
1780 + "license": "MIT",
1781 + "funding": {
1782 + "type": "github",
1783 + "url": "https://github.com/sponsors/dcastil"
1784 + }
1785 + },
1786 + "node_modules/tailwindcss": {
1787 + "version": "4.3.3",
1788 + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz",
1789 + "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==",
1790 + "dev": true,
1791 + "license": "MIT"
1792 + },
1793 + "node_modules/tapable": {
1794 + "version": "2.3.3",
1795 + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",
1796 + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==",
1797 + "dev": true,
1798 + "license": "MIT",
1799 + "engines": {
1800 + "node": ">=6"
1801 + },
1802 + "funding": {
1803 + "type": "opencollective",
1804 + "url": "https://opencollective.com/webpack"
1805 + }
1806 + },
1807 + "node_modules/tslib": {
1808 + "version": "2.8.1",
1809 + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
1810 + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
1811 + "license": "0BSD"
1812 + },
1813 + "node_modules/typescript": {
1814 + "version": "5.9.3",
1815 + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
1816 + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
1817 + "dev": true,
1818 + "license": "Apache-2.0",
1819 + "bin": {
1820 + "tsc": "bin/tsc",
1821 + "tsserver": "bin/tsserver"
1822 + },
1823 + "engines": {
1824 + "node": ">=14.17"
1825 + }
1826 + },
1827 + "node_modules/undici-types": {
1828 + "version": "7.18.2",
1829 + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
1830 + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
1831 + "dev": true,
1832 + "license": "MIT"
1833 + }
1834 + }
1835 +}
added package.json +31 −0
@@ -0,0 +1,31 @@
1 +{
2 + "name": "spboucher-ai",
3 + "version": "1.0.0",
4 + "private": true,
5 + "description": "Personal academic and developer website for Simon-Pierre Boucher (www.spboucher.ai)",
6 + "author": "Simon-Pierre Boucher <contact@spboucher.ai>",
7 + "scripts": {
8 + "dev": "next dev",
9 + "build": "next build",
10 + "start": "next start",
11 + "typecheck": "tsc --noEmit"
12 + },
13 + "dependencies": {
14 + "class-variance-authority": "^0.7.1",
15 + "clsx": "^2.1.1",
16 + "framer-motion": "^12.0.0",
17 + "lucide-react": "^0.525.0",
18 + "next": "^16.0.0",
19 + "react": "^19.0.0",
20 + "react-dom": "^19.0.0",
21 + "tailwind-merge": "^3.0.0"
22 + },
23 + "devDependencies": {
24 + "@tailwindcss/postcss": "^4.0.0",
25 + "@types/node": "^24.0.0",
26 + "@types/react": "^19.0.0",
27 + "@types/react-dom": "^19.0.0",
28 + "tailwindcss": "^4.0.0",
29 + "typescript": "^5.8.0"
30 + }
31 +}
added postcss.config.mjs +14 −0
@@ -0,0 +1,14 @@
1 +/*
2 + postcss.config.mjs
3 + spboucher.ai Web
4 + Author: Simon-Pierre Boucher
5 + Mail: contact@spboucher.ai
6 +*/
7 +
8 +const config = {
9 + plugins: {
10 + "@tailwindcss/postcss": {},
11 + },
12 +};
13 +
14 +export default config;
added public/icons/zyquo-agent.png +0 −0

Binary file not shown.

added public/icons/zyquo-atlas.png +0 −0

Binary file not shown.

added public/icons/zyquo-cloud.png +0 −0

Binary file not shown.

added public/icons/zyquo-local.png +0 −0

Binary file not shown.

added public/icons/zyquo-mlx.png +0 −0

Binary file not shown.

added public/icons/zyquo-router.png +0 −0

Binary file not shown.

added scripts/start-production.sh +12 −0
@@ -0,0 +1,12 @@
1 +#!/bin/zsh
2 +# start-production.sh
3 +# spboucher.ai Web
4 +# Author: Simon-Pierre Boucher
5 +# Mail: contact@spboucher.ai
6 +
7 +# Starts the production Next.js server on port 3100 (port 3000 is used by
8 +# console.maclustr.io on node m2u64). Run under PM2 as process "spboucher".
9 +
10 +export PATH=/opt/homebrew/bin:/opt/homebrew/sbin:$PATH
11 +cd "$(dirname "$0")/.."
12 +exec npx next start -H 0.0.0.0 -p 3100
added tsconfig.json +45 −0
@@ -0,0 +1,45 @@
1 +// tsconfig.json
2 +// spboucher.ai Web
3 +// Author: Simon-Pierre Boucher
4 +// Mail: contact@spboucher.ai
5 +{
6 + "compilerOptions": {
7 + "target": "ES2022",
8 + "lib": [
9 + "dom",
10 + "dom.iterable",
11 + "esnext"
12 + ],
13 + "allowJs": true,
14 + "skipLibCheck": true,
15 + "strict": true,
16 + "noEmit": true,
17 + "esModuleInterop": true,
18 + "module": "esnext",
19 + "moduleResolution": "bundler",
20 + "resolveJsonModule": true,
21 + "isolatedModules": true,
22 + "jsx": "react-jsx",
23 + "incremental": true,
24 + "plugins": [
25 + {
26 + "name": "next"
27 + }
28 + ],
29 + "paths": {
30 + "@/*": [
31 + "./*"
32 + ]
33 + }
34 + },
35 + "include": [
36 + "next-env.d.ts",
37 + "**/*.ts",
38 + "**/*.tsx",
39 + ".next/types/**/*.ts",
40 + ".next/dev/types/**/*.ts"
41 + ],
42 + "exclude": [
43 + "node_modules"
44 + ]
45 +}
46