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>
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 | +[](https://www.spboucher.ai) | |
| 21 | +[](https://nextjs.org) | |
| 22 | +[](https://react.dev) | |
| 23 | +[](https://www.typescriptlang.org) | |
| 24 | + | |
| 25 | +[](https://tailwindcss.com) | |
| 26 | +[](https://ui.shadcn.com) | |
| 27 | +[](https://www.framer.com/motion/) | |
| 28 | +[](https://pm2.keymetrics.io) | |
| 29 | +[](https://ngrok.com) | |
| 30 | +[](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., & 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 & 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., & 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 | ||