diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5ef6a52
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,41 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.*
+.yarn/*
+!.yarn/patches
+!.yarn/plugins
+!.yarn/releases
+!.yarn/versions
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+.pnpm-debug.log*
+
+# env files (can opt-in for committing if needed)
+.env*
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
diff --git a/README.md b/README.md
index d741acd..e215bc4 100644
--- a/README.md
+++ b/README.md
@@ -1,327 +1,36 @@
-# OllaHub
+This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
-Plataforma desktop multiplataforma para integração de modelos IA locais com servidores Model Context Protocol
+## Getting Started
----
+First, run the development server:
-## Visão Geral
+```bash
+npm run dev
+# or
+yarn dev
+# or
+pnpm dev
+# or
+bun dev
+```
-OllaHub é uma solução desktop nativa desenvolvida com Rust e Tauri que unifica o gerenciamento de Large Language Models locais através do Ollama com a extensibilidade do Model Context Protocol. A plataforma prioriza privacidade total, performance superior e experiência nativa, oferecendo uma alternativa local-first às soluções baseadas em nuvem.
+Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
-O projeto segue uma metodologia de desenvolvimento progressivo, iniciando com um MVP web para validação de conceitos antes da implementação completa da aplicação desktop.
+You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
-### Principais Características
+This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
-**Inferência 100% Local**
-Todo processamento de IA ocorre localmente através do Ollama, garantindo privacidade absoluta dos dados sem dependência de serviços externos.
+## Learn More
-**Integração Nativa com MCP**
-Suporte completo ao Model Context Protocol permitindo extensão de capacidades através de servidores especializados para acesso a sistemas de arquivos, APIs externas, bancos de dados e ferramentas de desenvolvimento.
+To learn more about Next.js, take a look at the following resources:
-**Performance Excepcional**
-Backend em Rust oferece velocidade de execução até 3x superior comparado a alternativas baseadas em Node.js ou Python, com consumo de memória reduzido em até 50%.
+- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
+- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
-**Bundle Compacto**
-Distribuição entre 3-5 MB comparado aos 85 MB típicos de aplicações Electron, facilitando download e instalação.
+You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
-**Experiência Desktop Nativa**
-Interface responsiva construída com Tauri 2.0 e SvelteKit, priorizando Linux com suporte planejado para Windows.
+## Deploy on Vercel
-**Streaming em Tempo Real**
-Sistema de streaming bidirecional para renderização progressiva de respostas longas, mantendo interface fluida durante processamento.
+The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
----
-
-## Por Que OllaHub?
-
-### Problemas Resolvidos
-
-**Fragmentação de Ferramentas**
-Elimina necessidade de múltiplas aplicações para gerenciar diferentes modelos IA e integrações contextuais.
-
-**Dependência de Cloud**
-Remove necessidade de conexão com serviços externos, permitindo uso completo offline após instalação inicial.
-
-**Complexidade de Integração**
-Abstrai complexidade do protocolo MCP oferecendo interface intuitiva para conexão com servidores especializados.
-
-**Privacidade de Dados**
-Garante que dados sensíveis nunca deixem a máquina do usuário durante interações com modelos.
-
-### Casos de Uso
-
-**Desenvolvimento Assistido por IA**
-Integração com sistema de arquivos via MCP permite análise contextual de código-fonte, sugestões de refatoração e documentação automática.
-
-**Análise Documental Local**
-Implementação de RAG com documentos locais para pesquisa semântica, síntese de informações e geração de resumos.
-
-**Automação de Workflows**
-Criação de scripts e automações com acesso controlado a APIs externas através de servidores MCP especializados.
-
-**Pesquisa e Agregação**
-Combinação de múltiplas fontes de dados como repositórios GitHub, bancos de dados locais e sistemas de arquivos para análises complexas.
-
----
-
-## Arquitetura
-
-### Stack Tecnológico
-
-#### Backend
-
-- Rust: Linguagem core garantindo segurança de memória e performance
-- Tauri 2.0: Framework desktop multiplataforma
-- Axum: Framework web assíncrono para servidor REST interno
-- Tokio: Runtime assíncrono de alta performance
-- Reqwest: Cliente HTTP com suporte a streaming
-- SQLx/Sled: Persistência de dados com queries type-safe
-- Serde: Serialização JSON type-safe
-
-#### Frontend
-
-- SvelteKit 2.x: Meta-framework reativo com roteamento integrado
-- TypeScript 5.x: Type-safety no frontend
-- TailwindCSS: Framework CSS utility-first
-- shadcn-svelte: Componentes UI acessíveis e customizáveis
-- Marked: Renderização de Markdown para respostas formatadas
-
-#### Infraestrutura
-
-- Ollama: Gerenciamento de modelos LLM locais
-- Model Context Protocol: Protocolo de extensibilidade via servidores especializados
-- GitHub Actions: CI/CD para builds multiplataforma
-
-### Componentes Principais
-
-**Módulo de Gerenciamento Ollama**
-Responsável por toda comunicação com a API REST do Ollama, incluindo listagem de modelos, inicialização de conversas, streaming de respostas e gerenciamento de contexto conversacional.
-
-**Cliente MCP**
-Implementa protocolo completo para descoberta, conexão e comunicação com servidores MCP via transports STDIO e HTTP+SSE, seguindo especificação JSON-RPC 2.0.
-
-**Sistema de Persistência**
-Gerencia armazenamento local de configurações, histórico de conversas, preferências do usuário e cache de dados utilizando banco embarcado para consultas eficientes.
-
-**Engine de Streaming**
-Implementa protocolo de streaming bidirecional permitindo renderização progressiva na interface durante geração de respostas longas.
-
-**Camada de Segurança**
-Gerencia autenticação de servidores MCP, validação de certificados, isolamento de processos e aplicação de políticas de segurança.
-
----
-
-## Roadmap de Desenvolvimento
-
-### Fase 1: Validação MVP Web (4-6 semanas)
-
-**Objetivo**: Validar arquitetura técnica e fluxos de experiência do usuário antes de investimento no desenvolvimento Tauri completo.
-
-**Sprint 1-2: Fundação Backend**
-Inicialização de projeto Axum com estrutura modular, implementação de cliente Ollama com streaming via Server-Sent Events, desenvolvimento de endpoints REST para listagem de modelos e chat completion, criação de testes unitários.
-
-**Sprint 3-4: Interface Web**
-Setup de projeto SvelteKit com TypeScript, desenvolvimento de componente de chat com área de mensagens e seletor de modelos, implementação de sistema de histórico com persistência em LocalStorage, deploy de teste para validação externa.
-
-**Sprint 5-6: Iteração e Refinamento**
-Testes de usabilidade com desenvolvedores e entusiastas de IA, coleta de feedback qualitativo e quantitativo, otimização de performance do streaming, documentação de lições aprendidas para informar desenvolvimento Tauri.
-
-### Fase 2: Aplicação Desktop Tauri (6-8 semanas)
-
-**Sprint 7-9: Migração para Tauri**
-Inicialização de projeto Tauri 2.0 com template SvelteKit, migração de lógica Axum para Tauri Commands, implementação de sistema IPC completo, desenvolvimento de sistema de eventos para comunicação backend-frontend.
-
-**Sprint 10-12: Integração MCP**
-Implementação completa de cliente MCP seguindo especificação oficial, sistema de descoberta automática de servidores MCP instalados, gerenciamento de múltiplas conexões simultâneas, interface de configuração para adicionar/remover servidores.
-
-**Sprint 13-14: Features Avançadas**
-Sistema de templates de prompt para casos de uso comuns, implementação de RAG básico com embeddings, configurações avançadas de modelos, sistema de plugins para extensibilidade, exportação de conversas em múltiplos formatos.
-
-### Fase 3: Polish e Lançamento (3-4 semanas)
-
-**Sprint 15-16: Qualidade e Performance**
-Testes de carga e stress, profiling detalhado identificando gargalos, otimizações targeted de latências críticas, implementação de telemetria anonimizada, code review completo.
-
-**Sprint 17-18: Documentação e Release**
-Documentação completa de usuário com guias de início rápido, documentação técnica para desenvolvedores, vídeos tutoriais, setup de CI/CD para builds automáticos, lançamento em GitHub Releases e AUR para Arch Linux.
-
----
-
-## Requisitos do Sistema
-
-### Requisitos Mínimos
-
-**Sistema Operacional**
-Linux com kernel 5.10 ou superior (prioritário), Windows 10/11 com WSL2 (suporte planejado)
-
-**Hardware**
-Processador: 4 cores físicos, Memória RAM: 8 GB, Armazenamento: 10 GB disponíveis, GPU: Recomendado NVIDIA com CUDA ou AMD com ROCm para aceleração
-
-**Dependências**
-Ollama instalado e configurado, Node.js 20 LTS ou superior para desenvolvimento, Rust toolchain 1.75 ou superior para compilação
-
-### Requisitos Recomendados
-
-Processador: 8 cores físicos ou superior, Memória RAM: 16 GB ou superior, GPU: NVIDIA RTX 3060 ou superior com 12GB VRAM, Armazenamento: SSD NVMe para melhor performance
-
----
-
-## Instalação
-
-### Pré-requisitos
-
-Antes de instalar o OllaHub, certifique-se que o Ollama está instalado e funcionando corretamente. Faça download do Ollama no site oficial e verifique a instalação executando o comando para listar modelos disponíveis.
-
-### Instalação via Binário Pré-compilado
-
-Faça download do binário apropriado para seu sistema operacional na seção Releases do repositório GitHub. Para Linux, escolha entre formato DEB para distribuições baseadas em Debian/Ubuntu, RPM para distribuições baseadas em Red Hat/Fedora, ou AppImage para distribuição universal.
-
-Após download, instale o pacote conforme método de seu sistema operacional ou execute o AppImage diretamente após conceder permissões de execução.
-
-### Compilação a partir do Código-fonte
-
-Clone o repositório do GitHub, instale as dependências do frontend utilizando gerenciador de pacotes Node.js, compile o backend Rust, e execute o build final que gerará binário otimizado para produção.
-
----
-
-## Uso
-
-### Primeira Execução
-
-Ao iniciar o OllaHub pela primeira vez, a aplicação realizará verificação de conexão com Ollama local. Caso não detecte instância em execução, será exibido alerta com instruções para iniciar o serviço.
-
-Após conexão estabelecida, interface principal exibirá lista de modelos disponíveis baixados no Ollama. Selecione modelo desejado e inicie nova conversa.
-
-### Gerenciamento de Modelos
-
-A seção de modelos permite visualizar todos modelos disponíveis instalados localmente, incluindo informações sobre tamanho, capacidades e versão. Para adicionar novos modelos, utilize comandos do Ollama diretamente no terminal, e atualize a lista na interface do OllaHub.
-
-### Integração com Servidores MCP
-
-Acesse seção de configurações para adicionar servidores MCP. Para servidores locais, configure caminho do executável e argumentos necessários para inicialização via STDIO. Para servidores remotos, configure URL do endpoint HTTP e credenciais de autenticação se necessário.
-
-Após configuração, OllaHub estabelecerá conexão e descobrirá automaticamente ferramentas e recursos disponíveis. Estas ferramentas aparecerão na interface e poderão ser invocadas durante conversas com modelos.
-
-### Templates e Workflows
-
-Utilize biblioteca de templates pré-configurados para casos de uso comuns como análise de código, revisão de documentos, geração de documentação técnica e debugging assistido. Templates podem ser customizados e salvos para reutilização futura.
-
-### Exportação de Conversas
-
-Conversas podem ser exportadas em formato Markdown para documentação, JSON para processamento programático, ou texto simples para compartilhamento. Acesse menu de contexto da conversa e selecione formato desejado.
-
----
-
-## Segurança e Privacidade
-
-### Processamento Local
-
-Todo processamento de inferência ocorre localmente através do Ollama. Nenhum dado é transmitido para servidores externos durante uso normal da aplicação.
-
-### Isolamento de Servidores MCP
-
-Servidores MCP executam em processos separados com comunicação via pipes, prevenindo comprometimento do processo principal. Para servidores não-confiáveis, recomenda-se execução em containers Docker para camada adicional de isolamento.
-
-### Persistência de Dados
-
-Histórico de conversas e configurações são armazenados localmente em diretório de dados do usuário. Dados são salvos em formato SQLite ou JSON dependendo da configuração, permitindo backup e migração simples.
-
-### Auditoria
-
-Sistema de logging estruturado registra todas operações sensíveis incluindo conexões com servidores MCP, invocações de ferramentas externas e modificações de configuração. Logs podem ser revisados para auditoria de segurança.
-
----
-
-## Contribuindo
-
-OllaHub é um projeto open source e contribuições são bem-vindas. Antes de contribuir, leia as diretrizes de contribuição no arquivo CONTRIBUTING.md do repositório.
-
-### Áreas de Contribuição
-
-**Desenvolvimento de Features**
-Implementação de novas funcionalidades seguindo roadmap do projeto ou propondo melhorias não planejadas.
-
-**Correção de Bugs**
-Identificação e correção de problemas reportados na seção Issues do GitHub.
-
-**Documentação**
-Melhoria de documentação de usuário, criação de tutoriais, tradução para outros idiomas.
-
-**Testes**
-Desenvolvimento de testes unitários e de integração, testes de usabilidade, identificação de casos extremos.
-
-**Servidores MCP**
-Desenvolvimento de servidores MCP especializados para integração com ferramentas e serviços específicos.
-
-### Processo de Contribuição
-
-Faça fork do repositório, crie branch para sua feature ou correção, implemente mudanças com commits descritivos, adicione testes quando apropriado, atualize documentação refletindo mudanças, envie pull request com descrição detalhada das alterações.
-
-Todas contribuições serão revisadas pelos mantenedores do projeto antes de merge. Feedback construtivo será fornecido para auxiliar no processo de aprovação.
-
----
-
-## Licença
-
-Este projeto é licenciado sob a licença MIT. Veja arquivo LICENSE para detalhes completos.
-
-A licença MIT permite uso comercial, modificação, distribuição e uso privado, desde que atribuição original seja mantida.
-
----
-
-## Suporte e Comunidade
-
-### Canais de Suporte
-
-**GitHub Issues**
-Para reportar bugs, solicitar features ou discutir aspectos técnicos do projeto.
-
-**GitHub Discussions**
-Para perguntas gerais, compartilhamento de casos de uso, discussões sobre arquitetura e decisões de design.
-
-**Discord Community** (em planejamento)
-Para suporte em tempo real, discussões da comunidade e compartilhamento de configurações e workflows.
-
-### Recursos Adicionais
-
-**Documentação Oficial**
-Guias detalhados disponíveis no diretório docs do repositório cobrindo instalação, configuração, desenvolvimento e troubleshooting.
-
-**Wiki do Projeto**
-Artigos comunitários sobre casos de uso específicos, integrações com ferramentas populares e otimizações de performance.
-
-**Exemplos de Código** (futuro)
-Repositório de exemplos demonstrando integração do OllaHub com diferentes servidores MCP e casos de uso avançados.
-
----
-
-## Reconhecimentos
-
-Este projeto utiliza e é inspirado por diversos projetos open source extraordinários:
-
-- Ollama pela plataforma de gerenciamento de modelos LLM locais
-- Anthropic pelo desenvolvimento do Model Context Protocol
-- Tauri pela framework de aplicações desktop com Rust
-- SvelteKit pelo framework web reativo e performático
-- Comunidade Rust pela linguagem e ecossistema de ferramentas excepcionais
-
-Agradecimentos especiais aos early adopters e contribuidores que forneceram feedback valioso durante desenvolvimento do MVP.
-
----
-
-## Status do Projeto
-
-**Fase Atual**: MVP Web em desenvolvimento (Fase 1 - Sprint 3)
-
-**Próximos Marcos**:
-
-- Conclusão de testes de usabilidade do MVP web (2 semanas)
-- Início da migração para Tauri (4 semanas)
-- Implementação completa do cliente MCP (8 semanas)
-- Lançamento da versão alpha para Linux (12 semanas)
-
-Acompanhe progresso do desenvolvimento na seção Projects do GitHub e participe das discussões sobre features futuras.
-
----
-
-**Desenvolvido com foco em privacidade, performance e experiência do usuário.**
+Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
diff --git a/app/api/ollama/chat/route.ts b/app/api/ollama/chat/route.ts
new file mode 100644
index 0000000..1c94e3e
--- /dev/null
+++ b/app/api/ollama/chat/route.ts
@@ -0,0 +1,62 @@
+import { NextRequest } from "next/server";
+import { chatWithStream } from "@/lib/ollama";
+import { convertToOllamaMessages } from "@/lib/chat";
+
+export const dynamic = "force-dynamic";
+
+export async function POST(req: NextRequest) {
+ try {
+ const { model, messages, options, system } = await req.json();
+
+ if (!model || !messages?.length) {
+ return new Response("Missing model or messages", { status: 400 });
+ }
+
+ const stream = new ReadableStream({
+ async start(controller) {
+ try {
+ const ollamaMessages = convertToOllamaMessages(messages);
+ const response = await chatWithStream(
+ model,
+ ollamaMessages,
+ options,
+ system
+ );
+
+ for await (const chunk of response) {
+ const token = chunk.message?.content || "";
+ if (token) {
+ controller.enqueue(
+ new TextEncoder().encode(JSON.stringify({ token }) + "\n")
+ );
+ }
+ }
+
+ controller.enqueue(
+ new TextEncoder().encode(JSON.stringify({ done: true }) + "\n")
+ );
+ controller.close();
+ } catch (error) {
+ console.error("Chat streaming error:", error);
+ controller.enqueue(
+ new TextEncoder().encode(
+ JSON.stringify({ error: "Failed to generate response" }) + "\n"
+ )
+ );
+ controller.close();
+ }
+ },
+ });
+
+ return new Response(stream, {
+ headers: {
+ "Content-Type": "text/event-stream",
+ "Cache-Control": "no-cache",
+ Connection: "keep-alive",
+ },
+ });
+ } catch (error) {
+ console.error("Chat API error:", error);
+ return new Response("Internal server error", { status: 500 });
+ }
+}
diff --git a/app/api/ollama/health/route.ts b/app/api/ollama/health/route.ts
new file mode 100644
index 0000000..e26c6ff
--- /dev/null
+++ b/app/api/ollama/health/route.ts
@@ -0,0 +1,18 @@
+import { NextResponse } from "next/server";
+import { listModelsViaSdk, listModelsViaHttp } from "@/lib/ollama";
+
+export const dynamic = "force-dynamic";
+
+export async function GET() {
+ try {
+ try {
+ await listModelsViaSdk();
+ return NextResponse.json({ ok: true });
+ } catch {
+ await listModelsViaHttp();
+ return NextResponse.json({ ok: true });
+ }
+ } catch {
+ return NextResponse.json({ ok: false });
+ }
+}
diff --git a/app/api/ollama/models/route.ts b/app/api/ollama/models/route.ts
new file mode 100644
index 0000000..9bd465d
--- /dev/null
+++ b/app/api/ollama/models/route.ts
@@ -0,0 +1,44 @@
+import { NextResponse } from "next/server";
+import { listModelsViaSdk, listModelsViaHttp } from "@/lib/ollama";
+import {
+ estimateCpuVram,
+ inferDevice,
+ inferQuantization,
+ mockModels,
+ type ModelInfo,
+} from "@/lib/models";
+
+export const dynamic = "force-dynamic";
+
+function normalizeModel(model: any): ModelInfo {
+ const name: string = model?.model || model?.name || "unknown";
+ const sizeBytes: number = model?.size || 0;
+ const sizeGB = Number(Math.max(0.1, sizeBytes / 1024 ** 3).toFixed(2));
+ const quantization = inferQuantization(name);
+ const device = inferDevice(model?.details || name);
+ const { cpuPct, vramGB } = estimateCpuVram({ sizeGB, quantization, device });
+ return {
+ id: name,
+ name,
+ sizeGB,
+ quantization,
+ device,
+ estCpuUsagePct: cpuPct,
+ estVramGB: vramGB,
+ };
+}
+
+export async function GET() {
+ try {
+ let models: any[] = [];
+ try {
+ models = await listModelsViaSdk();
+ } catch {
+ models = await listModelsViaHttp();
+ }
+ const normalized = models.map((m) => normalizeModel(m));
+ return NextResponse.json({ offline: false, models: normalized });
+ } catch (e) {
+ return NextResponse.json({ offline: true, models: mockModels });
+ }
+}
diff --git a/app/api/ollama/pull/route.ts b/app/api/ollama/pull/route.ts
new file mode 100644
index 0000000..7407e1a
--- /dev/null
+++ b/app/api/ollama/pull/route.ts
@@ -0,0 +1,54 @@
+import { NextRequest } from "next/server";
+import { pullModel } from "@/lib/ollama";
+
+export const dynamic = "force-dynamic";
+
+export async function POST(req: NextRequest) {
+ try {
+ const { model } = await req.json();
+
+ if (!model) {
+ return new Response("Missing model", { status: 400 });
+ }
+
+ const stream = new ReadableStream({
+ async start(controller) {
+ try {
+ const response = await pullModel(model);
+
+ for await (const chunk of response) {
+ controller.enqueue(
+ new TextEncoder().encode(JSON.stringify(chunk) + "\n"),
+ );
+ }
+
+ controller.enqueue(
+ new TextEncoder().encode(
+ JSON.stringify({ status: "success" }) + "\n",
+ ),
+ );
+ controller.close();
+ } catch (error) {
+ console.error("Pull streaming error:", error);
+ controller.enqueue(
+ new TextEncoder().encode(
+ JSON.stringify({ error: "Failed to pull model" }) + "\n",
+ ),
+ );
+ controller.close();
+ }
+ },
+ });
+
+ return new Response(stream, {
+ headers: {
+ "Content-Type": "text/event-stream",
+ "Cache-Control": "no-cache",
+ Connection: "keep-alive",
+ },
+ });
+ } catch (error) {
+ console.error("Pull API error:", error);
+ return new Response("Internal server error", { status: 500 });
+ }
+}
diff --git a/app/favicon.ico b/app/favicon.ico
new file mode 100644
index 0000000..718d6fe
Binary files /dev/null and b/app/favicon.ico differ
diff --git a/app/globals.css b/app/globals.css
new file mode 100644
index 0000000..9b92498
--- /dev/null
+++ b/app/globals.css
@@ -0,0 +1,33 @@
+@import "tailwindcss";
+
+:root {
+ --background: #ffffff;
+ --foreground: #0b0c0f;
+ --accent: #2563eb; /* blue-600 */
+ --accent-foreground: #ffffff;
+ --border: #e5e7ef;
+ --surface: #f6f7fb;
+}
+
+@theme inline {
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --color-accent: var(--accent);
+ --color-border: var(--border);
+ --font-sans: var(--font-inter);
+}
+
+.dark {
+ --background: #0b0c0f;
+ --foreground: #e6e8ef;
+ --accent: #3b82f6; /* blue-500 */
+ --accent-foreground: #0b0c0f;
+ --border: #2a2f3a;
+ --surface: #12141a;
+}
+
+body {
+ background: var(--background);
+ color: var(--foreground);
+ font-family: var(--font-inter), ui-sans-serif, system-ui;
+}
diff --git a/app/layout.tsx b/app/layout.tsx
new file mode 100644
index 0000000..2ea6b33
--- /dev/null
+++ b/app/layout.tsx
@@ -0,0 +1,25 @@
+import type { Metadata } from "next";
+import { Inter } from "next/font/google";
+import "./globals.css";
+import { ThemeProvider } from "../components/theme-provider";
+
+const inter = Inter({ subsets: ["latin"], variable: "--font-inter" });
+
+export const metadata: Metadata = {
+ title: "Create Next App",
+ description: "Generated by create next app",
+};
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ return (
+
+
+ {children}
+
+
+ );
+}
diff --git a/app/page.tsx b/app/page.tsx
new file mode 100644
index 0000000..17f5c3a
--- /dev/null
+++ b/app/page.tsx
@@ -0,0 +1,21 @@
+import { ModeToggle } from "@/components/mode-toggle";
+import { ChatContainer } from "@/components/chat/ChatContainer";
+import { ClientHome } from "@/components/ClientHome";
+
+async function fetchModels() {
+ try {
+ const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000";
+ const res = await fetch(`${baseUrl}/api/ollama/models`, {
+ cache: "no-store",
+ });
+ if (!res.ok) return { offline: true, models: [] };
+ return res.json();
+ } catch {
+ return { offline: true, models: [] };
+ }
+}
+
+export default async function Home() {
+ const { offline, models } = await fetchModels();
+ return ;
+}
diff --git a/biome.json b/biome.json
new file mode 100644
index 0000000..41b3b95
--- /dev/null
+++ b/biome.json
@@ -0,0 +1,37 @@
+{
+ "$schema": "https://biomejs.dev/schemas/2.2.0/schema.json",
+ "vcs": {
+ "enabled": true,
+ "clientKind": "git",
+ "useIgnoreFile": true
+ },
+ "files": {
+ "ignoreUnknown": true,
+ "includes": ["**", "!node_modules", "!.next", "!dist", "!build"]
+ },
+ "formatter": {
+ "enabled": true,
+ "indentStyle": "space",
+ "indentWidth": 2
+ },
+ "linter": {
+ "enabled": true,
+ "rules": {
+ "recommended": true,
+ "suspicious": {
+ "noUnknownAtRules": "off"
+ }
+ },
+ "domains": {
+ "next": "recommended",
+ "react": "recommended"
+ }
+ },
+ "assist": {
+ "actions": {
+ "source": {
+ "organizeImports": "on"
+ }
+ }
+ }
+}
diff --git a/components/ClientHome.tsx b/components/ClientHome.tsx
new file mode 100644
index 0000000..3e6903c
--- /dev/null
+++ b/components/ClientHome.tsx
@@ -0,0 +1,41 @@
+"use client";
+
+import { useState } from "react";
+import { ModeToggle } from "@/components/mode-toggle";
+import { ChatContainer } from "@/components/chat/ChatContainer";
+import {
+ SettingsButton,
+ SettingsModal,
+} from "@/components/settings/SettingsModal";
+
+interface ClientHomeProps {
+ offline: boolean;
+ models: any[];
+}
+
+export function ClientHome({ offline, models }: ClientHomeProps) {
+ const [open, setOpen] = useState(false);
+
+ return (
+
+
+
+
+
+
setOpen(false)} />
+
+ );
+}
diff --git a/components/chat/ChatContainer.tsx b/components/chat/ChatContainer.tsx
new file mode 100644
index 0000000..88e4991
--- /dev/null
+++ b/components/chat/ChatContainer.tsx
@@ -0,0 +1,296 @@
+"use client";
+
+import { useState, useEffect, useRef } from "react";
+import { ModelDropdown } from "@/components/model/ModelDropdown";
+import { ModelPullDialog } from "@/components/model/ModelPullDialog";
+import { ChatMessage, ChatInput } from "@/components/chat/ChatMessage";
+import { SystemPrompt } from "@/components/chat/SystemPrompt";
+import type { ModelInfo } from "@/lib/models";
+import type { Message } from "@/lib/chat";
+import { createMessage } from "@/lib/chat";
+import { useUserPrefs, buildOllamaOptions } from "@/hooks/useUserPrefs";
+
+interface ChatContainerProps {
+ models: ModelInfo[];
+ offline: boolean;
+}
+
+export function ChatContainer({ models, offline }: ChatContainerProps) {
+ const { prefs, ready } = useUserPrefs();
+ const [messages, setMessages] = useState([]);
+ const [selectedModel, setSelectedModel] = useState(null);
+ const [isStreaming, setIsStreaming] = useState(false);
+ const [streamingContent, setStreamingContent] = useState("");
+ const abortRef = useRef(null);
+ const [showPullDialog, setShowPullDialog] = useState(false);
+ const [modelToPull, setModelToPull] = useState("");
+ const [systemPrompt, setSystemPrompt] = useState("");
+ const messagesEndRef = useRef(null);
+
+ const scrollToBottom = () => {
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
+ };
+
+ useEffect(() => {
+ scrollToBottom();
+ }, [messages, streamingContent]);
+
+ const handleSendMessage = async (content: string) => {
+ if (!selectedModel || isStreaming) return;
+
+ const userMessage = createMessage("user", content);
+ const newMessages = [...messages, userMessage];
+
+ // Add system prompt if it exists and this is the first user message
+ const messagesToSend =
+ systemPrompt && messages.length === 0
+ ? [createMessage("system", systemPrompt), ...newMessages]
+ : newMessages;
+
+ setMessages(newMessages);
+ setIsStreaming(true);
+ setStreamingContent("");
+
+ try {
+ const options = buildOllamaOptions(
+ prefs.device,
+ prefs.numGpu,
+ prefs.gpuIndex
+ );
+ abortRef.current = new AbortController();
+ const response = await fetch("/api/ollama/chat", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ model: selectedModel,
+ messages: messagesToSend,
+ options,
+ system: systemPrompt || undefined,
+ }),
+ signal: abortRef.current.signal,
+ });
+
+ if (!response.ok) {
+ throw new Error("Failed to send message");
+ }
+
+ const reader = response.body?.getReader();
+ if (!reader) throw new Error("No response body");
+
+ const decoder = new TextDecoder();
+ let buffer = "";
+ let assistantContent = "";
+
+ while (true) {
+ const { done, value } = await reader.read();
+ if (done) break;
+
+ buffer += decoder.decode(value, { stream: true });
+ const lines = buffer.split("\n");
+ buffer = lines.pop() || "";
+
+ for (const line of lines) {
+ if (!line.trim()) continue;
+
+ try {
+ const data = JSON.parse(line);
+ if (data.token) {
+ assistantContent += data.token;
+ setStreamingContent(assistantContent);
+ }
+ if (data.done) {
+ const assistantMessage = createMessage(
+ "assistant",
+ assistantContent
+ );
+ setMessages((prev) => [...prev, assistantMessage]);
+ setStreamingContent("");
+ }
+ if (data.error) {
+ throw new Error(data.error);
+ }
+ } catch (e) {
+ console.error("Error parsing stream:", e);
+ }
+ }
+ }
+ } catch (error) {
+ console.error("Chat error:", error);
+ const errorMessage = createMessage(
+ "assistant",
+ "Sorry, I encountered an error. Please try again."
+ );
+ setMessages((prev) => [...prev, errorMessage]);
+ } finally {
+ setIsStreaming(false);
+ setStreamingContent("");
+ abortRef.current = null;
+ }
+ };
+
+ const handleStop = () => {
+ if (abortRef.current && isStreaming) {
+ abortRef.current.abort();
+ }
+ };
+
+ const handleSelectModel = (modelId: string) => {
+ const model = models.find((m) => m.id === modelId);
+ if (model) {
+ setSelectedModel(modelId);
+ // Optional: clear chat when switching models
+ // setMessages([]);
+ }
+ };
+
+ const handlePullModel = async (modelName: string) => {
+ try {
+ const response = await fetch("/api/ollama/pull", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ model: modelName }),
+ });
+
+ if (!response.ok) {
+ throw new Error("Failed to pull model");
+ }
+
+ const reader = response.body?.getReader();
+ if (!reader) throw new Error("No response body");
+
+ const decoder = new TextDecoder();
+ let buffer = "";
+
+ while (true) {
+ const { done, value } = await reader.read();
+ if (done) break;
+
+ buffer += decoder.decode(value, { stream: true });
+ const lines = buffer.split("\n");
+ buffer = lines.pop() || "";
+
+ for (const line of lines) {
+ if (!line.trim()) continue;
+
+ try {
+ const data = JSON.parse(line);
+ if (data.status === "success") {
+ // Model pulled successfully, refresh models list
+ window.location.reload();
+ }
+ } catch (e) {
+ console.error("Error parsing pull stream:", e);
+ }
+ }
+ }
+ } catch (error) {
+ throw error;
+ }
+ };
+
+ const selectedModelData = models.find((m) => m.id === selectedModel);
+
+ return (
+
+ {/* Sidebar */}
+
+
+
+ Models
+
+
+
+
+ {offline && (
+
+
+ Ollama is offline. Using mock data.
+
+
+ )}
+
+ {selectedModelData && (
+
+
Selected Model
+
+
Name: {selectedModelData.name}
+
Size: {selectedModelData.sizeGB} GB
+
Quantization: {selectedModelData.quantization}
+
Device: {selectedModelData.device}
+
+
+ )}
+
+
+
+
+ {/* Chat Area */}
+
+ {/* Chat Header */}
+
+
+ {selectedModelData
+ ? `Chat with ${selectedModelData.name}`
+ : "Select a model to start chatting"}
+
+
+
+ Stop
+
+
+
+
+ {/* Messages */}
+
+ {messages.length === 0 ? (
+
+
+
Welcome to Ollahub
+
Select a model and start chatting!
+
+
+ ) : (
+ <>
+ {messages.map((message) => (
+
+ ))}
+ {isStreaming && streamingContent && (
+
+ )}
+
+ >
+ )}
+
+
+ {/* Input */}
+
+
+
+ {/* Pull Dialog */}
+
setShowPullDialog(false)}
+ modelName={modelToPull}
+ onConfirmPull={handlePullModel}
+ />
+
+ );
+}
diff --git a/components/chat/ChatMessage.tsx b/components/chat/ChatMessage.tsx
new file mode 100644
index 0000000..10aa5e6
--- /dev/null
+++ b/components/chat/ChatMessage.tsx
@@ -0,0 +1,154 @@
+"use client";
+
+import { useState, useEffect, useRef } from "react";
+import { User, Bot, Send, Loader2 } from "lucide-react";
+import { MarkdownRenderer } from "@/components/message/MarkdownRenderer";
+import { MessageActions } from "@/components/message/MessageActions";
+import { extractCodeBlocks } from "@/hooks/useMarkdown";
+import type { Message } from "@/lib/chat";
+import { formatTimestamp } from "@/lib/chat";
+
+interface ChatMessageProps {
+ message: Message;
+ isStreaming?: boolean;
+}
+
+export function ChatMessage({
+ message,
+ isStreaming = false,
+}: ChatMessageProps) {
+ const isUser = message.role === "user";
+
+ return (
+
+ {!isUser && (
+
+
+
+ )}
+
+
+ {isUser ? (
+ message.content
+ ) : (
+
+ )}
+
+ {isStreaming &&
▊ }
+ {!isUser && !isStreaming && (
+
+
+
+ )}
+
+ {formatTimestamp(message.timestamp)}
+
+
+
+ {isUser && (
+
+
+
+ )}
+
+ );
+}
+
+interface ChatInputProps {
+ onSendMessage: (content: string) => void;
+ disabled?: boolean;
+ placeholder?: string;
+}
+
+export function ChatInput({
+ onSendMessage,
+ disabled = false,
+ placeholder = "Type a message...",
+}: ChatInputProps) {
+ const [content, setContent] = useState("");
+ const textareaRef = useRef(null);
+
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+ if (content.trim() && !disabled) {
+ onSendMessage(content.trim());
+ setContent("");
+ }
+ };
+
+ const handleKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === "Enter" && !e.shiftKey) {
+ e.preventDefault();
+ handleSubmit(e);
+ }
+ };
+
+ useEffect(() => {
+ if (textareaRef.current) {
+ textareaRef.current.style.height = "auto";
+ textareaRef.current.style.height = `${Math.min(
+ textareaRef.current.scrollHeight,
+ 120
+ )}px`;
+ }
+ }, [content]);
+
+ return (
+
+ );
+}
+
+interface StreamingTextProps {
+ text: string;
+ isStreaming: boolean;
+}
+
+export function StreamingText({ text, isStreaming }: StreamingTextProps) {
+ return (
+
+ {text}
+ {isStreaming && ▊ }
+
+ );
+}
diff --git a/components/chat/SystemPrompt.tsx b/components/chat/SystemPrompt.tsx
new file mode 100644
index 0000000..b050d89
--- /dev/null
+++ b/components/chat/SystemPrompt.tsx
@@ -0,0 +1,111 @@
+"use client";
+
+import { useState, useEffect } from "react";
+import { Settings, Save, RotateCcw } from "lucide-react";
+import { SystemPromptInfo } from "./SystemPromptInfo";
+
+interface SystemPromptProps {
+ onSystemPromptChange: (prompt: string) => void;
+}
+
+export function SystemPrompt({ onSystemPromptChange }: SystemPromptProps) {
+ const [systemPrompt, setSystemPrompt] = useState("");
+ const [isEditing, setIsEditing] = useState(false);
+ const [hasChanges, setHasChanges] = useState(false);
+
+ // Load from localStorage on mount
+ useEffect(() => {
+ const saved = localStorage.getItem("ollahub-system-prompt");
+ if (saved) {
+ setSystemPrompt(saved);
+ onSystemPromptChange(saved);
+ }
+ }, [onSystemPromptChange]);
+
+ const handleSave = () => {
+ localStorage.setItem("ollahub-system-prompt", systemPrompt);
+ onSystemPromptChange(systemPrompt);
+ setIsEditing(false);
+ setHasChanges(false);
+ };
+
+ const handleReset = () => {
+ const defaultPrompt = "";
+ setSystemPrompt(defaultPrompt);
+ localStorage.removeItem("ollahub-system-prompt");
+ onSystemPromptChange(defaultPrompt);
+ setIsEditing(false);
+ setHasChanges(false);
+ };
+
+ const handleChange = (value: string) => {
+ setSystemPrompt(value);
+ setHasChanges(true);
+ setIsEditing(true);
+ };
+
+ return (
+
+
+
+ System Prompt
+
+
+
+ {isEditing && (
+ <>
+
+
+
+
+
+
+ >
+ )}
+ setIsEditing(!isEditing)}
+ className="p-1 rounded hover:bg-[var(--background)]"
+ title="Edit system prompt"
+ >
+
+
+
+
+
+ {isEditing ? (
+
+
handleChange(e.target.value)}
+ placeholder="Enter system prompt (optional)..."
+ className="w-full h-24 resize-none rounded-md border border-[var(--border)] bg-[var(--background)] px-3 py-2 text-xs focus:outline-none focus:ring-2 focus:ring-[var(--accent)]"
+ />
+
+ System prompts help define the AI's behavior and personality.
+
+
+ ) : (
+
+ {systemPrompt ? (
+
+ {systemPrompt}
+
+ ) : (
+
+ No system prompt set
+
+ )}
+
+ )}
+
+ );
+}
diff --git a/components/chat/SystemPromptInfo.tsx b/components/chat/SystemPromptInfo.tsx
new file mode 100644
index 0000000..8476d34
--- /dev/null
+++ b/components/chat/SystemPromptInfo.tsx
@@ -0,0 +1,50 @@
+"use client";
+
+import { Info } from "lucide-react";
+import { useState } from "react";
+
+export function SystemPromptInfo() {
+ const [isOpen, setIsOpen] = useState(false);
+
+ return (
+
+
setIsOpen(!isOpen)}
+ className="p-1 rounded hover:bg-[var(--background)]"
+ title="About system prompts"
+ >
+
+
+
+ {isOpen && (
+
+
+
System Prompts
+
+ System prompts define the AI's behavior, personality, and response
+ style. They are sent once at the beginning of each conversation.
+
+
+
Examples:
+
+ "You are a helpful coding assistant"
+ "Respond in Portuguese, be concise"
+ "You are a creative writing coach"
+
+
+
+ Based on Ollama documentation - system messages help guide model
+ behavior.
+
+
+
setIsOpen(false)}
+ className="absolute top-2 right-2 p-1 rounded hover:bg-[var(--surface)]"
+ >
+ ×
+
+
+ )}
+
+ );
+}
diff --git a/components/message/MarkdownRenderer.tsx b/components/message/MarkdownRenderer.tsx
new file mode 100644
index 0000000..49a6d24
--- /dev/null
+++ b/components/message/MarkdownRenderer.tsx
@@ -0,0 +1,22 @@
+"use client";
+
+import React from "react";
+import ReactMarkdown from "react-markdown";
+import remarkGfm from "remark-gfm";
+import rehypeRaw from "rehype-raw";
+import rehypeHighlight from "rehype-highlight";
+
+type MarkdownRendererProps = {
+ content: string;
+};
+
+export function MarkdownRenderer({ content }: MarkdownRendererProps) {
+ return (
+
+ {content}
+
+ );
+}
diff --git a/components/message/MessageActions.tsx b/components/message/MessageActions.tsx
new file mode 100644
index 0000000..0f82c12
--- /dev/null
+++ b/components/message/MessageActions.tsx
@@ -0,0 +1,107 @@
+"use client";
+
+import { useState } from "react";
+import { Copy, Edit3, RefreshCw, Check } from "lucide-react";
+
+type MessageActionsProps = {
+ content: string;
+ disabled?: boolean;
+ onEdit?: () => void;
+ onRegenerate?: () => void;
+ extractCodeBlocks?: (
+ text: string
+ ) => Array<{ language: string; code: string }>;
+};
+
+export function MessageActions({
+ content,
+ disabled,
+ onEdit,
+ onRegenerate,
+ extractCodeBlocks,
+}: MessageActionsProps) {
+ const [copied, setCopied] = useState<"none" | "markdown" | "text" | "code">(
+ "none"
+ );
+
+ const copy = async (text: string, flag: MessageActionsProps["content"]) => {
+ await navigator.clipboard.writeText(text);
+ setCopied(flag as any);
+ setTimeout(() => setCopied("none"), 1500);
+ };
+
+ const stripMarkdown = (md: string) =>
+ md
+ .replace(/```[\s\S]*?```/g, (block) =>
+ block.replace(/```[\s\S]*?\n/, "").replace(/```$/, "")
+ )
+ .replace(/[#*_>`~-]/g, "");
+ const codeBlocks = extractCodeBlocks ? extractCodeBlocks(content) : [];
+
+ return (
+
+ copy(content, "markdown")}
+ disabled={disabled}
+ aria-label="Copy as markdown"
+ >
+ {copied === "markdown" ? (
+
+ ) : (
+
+ )}{" "}
+ MD
+
+ copy(stripMarkdown(content), "text")}
+ disabled={disabled}
+ aria-label="Copy as plain text"
+ >
+ {copied === "text" ? (
+
+ ) : (
+
+ )}{" "}
+ TXT
+
+ {codeBlocks.map((b, i) => (
+ copy(b.code, "code")}
+ disabled={disabled}
+ aria-label={`Copy ${b.language} code`}
+ >
+ {copied === "code" ? (
+
+ ) : (
+
+ )}{" "}
+ {b.language}
+
+ ))}
+ {onEdit && (
+
+ Edit
+
+ )}
+ {onRegenerate && (
+
+ Retry
+
+ )}
+
+ );
+}
diff --git a/components/mode-toggle.tsx b/components/mode-toggle.tsx
new file mode 100644
index 0000000..7b7f008
--- /dev/null
+++ b/components/mode-toggle.tsx
@@ -0,0 +1,27 @@
+"use client";
+
+import { Moon, Sun } from "lucide-react";
+import { useTheme } from "next-themes";
+import { useEffect, useState } from "react";
+
+export function ModeToggle() {
+ const { theme, setTheme } = useTheme();
+ const [mounted, setMounted] = useState(false);
+ useEffect(() => setMounted(true), []);
+ if (!mounted) return null;
+ const isDark =
+ theme === "dark" ||
+ (!theme &&
+ typeof window !== "undefined" &&
+ window.matchMedia("(prefers-color-scheme: dark)").matches);
+ return (
+ setTheme(isDark ? "light" : "dark")}
+ >
+ {isDark ? : }
+ {isDark ? "Light" : "Dark"}
+
+ );
+}
diff --git a/components/model/ModelCard.tsx b/components/model/ModelCard.tsx
new file mode 100644
index 0000000..1fb349d
--- /dev/null
+++ b/components/model/ModelCard.tsx
@@ -0,0 +1,61 @@
+"use client";
+
+import { Cpu, CircuitBoard, Info } from "lucide-react";
+import type { ModelInfo } from "@/lib/models";
+import { useState } from "react";
+
+export function ModelCard({ model }: { model: ModelInfo }) {
+ const [hover, setHover] = useState(false);
+ const isGpu = model.device === "GPU";
+ return (
+ setHover(true)}
+ onMouseLeave={() => setHover(false)}
+ onFocus={() => setHover(true)}
+ onBlur={() => setHover(false)}
+ className="group relative rounded-lg border border-[var(--border)] bg-[var(--background)] p-4 shadow-sm transition-all hover:shadow-md focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--accent)]"
+ >
+
+
+ {model.name}
+
+
+ {isGpu ? (
+
+ ) : (
+
+ )}
+ {model.device}
+
+
+
+
+ {model.quantization}
+
+
+ {model.sizeGB} GB
+
+
+
+
+
+
+
+ {hover && (
+
+
Detalhes (estimativa)
+
+ Uso de CPU
+ {model.estCpuUsagePct ?? "—"}%
+
+
+ Uso de VRAM
+ {model.estVramGB ?? 0} GB
+
+
+ )}
+
+ );
+}
diff --git a/components/model/ModelDropdown.tsx b/components/model/ModelDropdown.tsx
new file mode 100644
index 0000000..184c370
--- /dev/null
+++ b/components/model/ModelDropdown.tsx
@@ -0,0 +1,116 @@
+"use client";
+
+import { useState, useEffect, useRef } from "react";
+import { ChevronDown, Cpu, CircuitBoard } from "lucide-react";
+import type { ModelInfo } from "@/lib/models";
+
+interface ModelDropdownProps {
+ models: ModelInfo[];
+ selectedModel: string | null;
+ onSelectModel: (modelId: string) => void;
+ disabled?: boolean;
+}
+
+export function ModelDropdown({
+ models,
+ selectedModel,
+ onSelectModel,
+ disabled = false,
+}: ModelDropdownProps) {
+ const [isOpen, setIsOpen] = useState(false);
+ const dropdownRef = useRef(null);
+
+ const selectedModelData = models.find((m) => m.id === selectedModel);
+
+ useEffect(() => {
+ function handleClickOutside(event: MouseEvent) {
+ if (
+ dropdownRef.current &&
+ !dropdownRef.current.contains(event.target as Node)
+ ) {
+ setIsOpen(false);
+ }
+ }
+
+ document.addEventListener("mousedown", handleClickOutside);
+ return () => document.removeEventListener("mousedown", handleClickOutside);
+ }, []);
+
+ return (
+
+
setIsOpen(!isOpen)}
+ disabled={disabled}
+ className="flex w-full items-center justify-between rounded-lg border border-[var(--border)] bg-[var(--background)] px-3 py-2 text-left text-sm transition-colors hover:bg-[var(--surface)] disabled:opacity-50"
+ >
+
+ {selectedModelData ? (
+ <>
+ {selectedModelData.name}
+
+ {selectedModelData.device === "GPU" ? (
+
+ ) : (
+
+ )}
+ {selectedModelData.device}
+
+ >
+ ) : (
+
+ Select a model...
+
+ )}
+
+
+
+
+ {isOpen && (
+
+
+ {models.length === 0 ? (
+
+ No models available
+
+ ) : (
+ models.map((model) => (
+
{
+ onSelectModel(model.id);
+ setIsOpen(false);
+ }}
+ className="flex w-full items-center justify-between px-3 py-2 text-left text-sm transition-colors hover:bg-[var(--surface)]"
+ >
+
+
{model.name}
+
+
+ {model.quantization}
+
+
+ {model.sizeGB} GB
+
+
+ {model.device === "GPU" ? (
+
+ ) : (
+
+ )}
+ {model.device}
+
+
+
+
+ ))
+ )}
+
+
+ )}
+
+ );
+}
diff --git a/components/model/ModelList.tsx b/components/model/ModelList.tsx
new file mode 100644
index 0000000..90cf615
--- /dev/null
+++ b/components/model/ModelList.tsx
@@ -0,0 +1,21 @@
+"use client";
+
+import { ModelCard } from "./ModelCard";
+import type { ModelInfo } from "@/lib/models";
+
+export function ModelList({ models }: { models: ModelInfo[] }) {
+ if (!models?.length) {
+ return (
+
+ Nenhum modelo encontrado.
+
+ );
+ }
+ return (
+
+ {models.map((m) => (
+
+ ))}
+
+ );
+}
diff --git a/components/model/ModelPullDialog.tsx b/components/model/ModelPullDialog.tsx
new file mode 100644
index 0000000..8b02d74
--- /dev/null
+++ b/components/model/ModelPullDialog.tsx
@@ -0,0 +1,145 @@
+"use client";
+
+import { useState, useEffect } from "react";
+import { X, Download, Loader2 } from "lucide-react";
+
+interface ModelPullDialogProps {
+ isOpen: boolean;
+ onClose: () => void;
+ modelName: string;
+ onConfirmPull: (modelName: string) => Promise;
+}
+
+interface PullProgress {
+ status: string;
+ digest?: string;
+ total?: number;
+ completed?: number;
+ error?: string;
+}
+
+export function ModelPullDialog({
+ isOpen,
+ onClose,
+ modelName,
+ onConfirmPull,
+}: ModelPullDialogProps) {
+ const [isPulling, setIsPulling] = useState(false);
+ const [progress, setProgress] = useState(null);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ if (!isOpen) {
+ setIsPulling(false);
+ setProgress(null);
+ setError(null);
+ }
+ }, [isOpen]);
+
+ const handleConfirm = async () => {
+ setIsPulling(true);
+ setError(null);
+
+ try {
+ await onConfirmPull(modelName);
+ onClose();
+ } catch (err) {
+ setError(err instanceof Error ? err.message : "Failed to pull model");
+ } finally {
+ setIsPulling(false);
+ }
+ };
+
+ if (!isOpen) return null;
+
+ return (
+
+
+
+
+
Install Model
+
+
+
+
+
+
+
+ Install {modelName} ?
+
+
+ This will download the model to your local Ollama installation.
+
+
+
+ {progress && (
+
+
+ Downloading...
+
+ {progress.total && progress.completed
+ ? `${Math.round(
+ (progress.completed / progress.total) * 100,
+ )}%`
+ : "0%"}
+
+
+
+ {progress.digest && (
+
+ {progress.digest.slice(0, 12)}...
+
+ )}
+
+ )}
+
+ {error && (
+
+ )}
+
+
+
+ Cancel
+
+
+ {isPulling ? (
+ <>
+
+ Installing...
+ >
+ ) : (
+ <>
+
+ Install
+ >
+ )}
+
+
+
+
+ );
+}
diff --git a/components/settings/SettingsModal.tsx b/components/settings/SettingsModal.tsx
new file mode 100644
index 0000000..a9b3cd4
--- /dev/null
+++ b/components/settings/SettingsModal.tsx
@@ -0,0 +1,177 @@
+"use client";
+
+import { useEffect, useState } from "react";
+import { X, Settings } from "lucide-react";
+import { useUserPrefs, type DevicePref } from "@/hooks/useUserPrefs";
+
+export function SettingsButton({ onOpen }: { onOpen: () => void }) {
+ return (
+
+
+ Settings
+
+ );
+}
+
+export function SettingsModal({
+ isOpen,
+ onClose,
+}: {
+ isOpen: boolean;
+ onClose: () => void;
+}) {
+ const { prefs, ready, update } = useUserPrefs();
+ const [tab, setTab] = useState<"general" | "execution">("general");
+ const [localPrompt, setLocalPrompt] = useState("");
+ const [device, setDevice] = useState("auto");
+ const [gpuIndex, setGpuIndex] = useState(0);
+ const [numGpu, setNumGpu] = useState(1);
+
+ useEffect(() => {
+ if (!ready) return;
+ setLocalPrompt(prefs.systemPrompt);
+ setDevice(prefs.device);
+ setGpuIndex(prefs.gpuIndex);
+ setNumGpu(prefs.numGpu);
+ }, [ready, prefs]);
+
+ const handleSave = () => {
+ update({ systemPrompt: localPrompt, device, gpuIndex, numGpu });
+ onClose();
+ };
+
+ if (!isOpen) return null;
+
+ return (
+
+
+
+
+
Settings
+
+
+
+
+
+
+ setTab("general")}
+ >
+ General
+
+ setTab("execution")}
+ >
+ Execution
+
+
+
+ {tab === "general" && (
+
+
System Prompt
+
setLocalPrompt(e.target.value)}
+ placeholder="Define how the AI should behave..."
+ className="h-32 w-full resize-none rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-accent"
+ />
+
+ Sent at the start of a conversation to guide model behavior.
+
+
+ )}
+
+ {tab === "execution" && (
+
+
+
Device
+
+ {(["auto", "cpu", "gpu"] as DevicePref[]).map((d) => (
+ setDevice(d)}
+ className={`rounded-md border border-border px-3 py-1 text-sm ${
+ device === d
+ ? "bg-[var(--surface)]"
+ : "hover:bg-[var(--surface)]"
+ }`}
+ >
+ {d.toUpperCase()}
+
+ ))}
+
+
+ CPU best-effort via num_gpu:0. For hard CPU-only, set
+ CUDA_VISIBLE_DEVICES=-1 before starting Ollama.
+
+
+
+
+
+ Auto: no options sent. GPU: sends num_gpu/main_gpu. CPU: sends
+ num_gpu:0.
+
+
+ )}
+
+
+
+ Cancel
+
+
+ Save
+
+
+
+
+ );
+}
diff --git a/components/theme-provider.tsx b/components/theme-provider.tsx
new file mode 100644
index 0000000..f1d148e
--- /dev/null
+++ b/components/theme-provider.tsx
@@ -0,0 +1,20 @@
+"use client";
+
+import * as React from "react";
+import { ThemeProvider as NextThemesProvider } from "next-themes";
+
+export function ThemeProvider({
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/hooks/useMarkdown.ts b/hooks/useMarkdown.ts
new file mode 100644
index 0000000..e7ed37d
--- /dev/null
+++ b/hooks/useMarkdown.ts
@@ -0,0 +1,27 @@
+export function extractCodeBlocks(
+ text: string
+): Array<{ language: string; code: string }> {
+ const regex = /```(\w+)?\n([\s\S]*?)```/g;
+ const blocks: Array<{ language: string; code: string }> = [];
+ let match: RegExpExecArray | null;
+ while ((match = regex.exec(text)) !== null) {
+ const language = match[1] || "text";
+ const code = match[2]?.trimEnd() || "";
+ blocks.push({ language, code });
+ }
+ return blocks;
+}
+
+export function stripMarkdown(md: string): string {
+ // Remove code blocks first
+ let plain = md.replace(/```[\s\S]*?```/g, (block) =>
+ block.replace(/```[\s\S]*?\n/, "").replace(/```$/, "")
+ );
+ // Remove inline code
+ plain = plain.replace(/`([^`]+)`/g, "$1");
+ // Remove common markdown syntax
+ plain = plain
+ .replace(/(^|\s)[#>*_~`-]+/g, " ")
+ .replace(/\[(.*?)\]\((.*?)\)/g, "$1");
+ return plain;
+}
diff --git a/hooks/useUserPrefs.ts b/hooks/useUserPrefs.ts
new file mode 100644
index 0000000..37492ea
--- /dev/null
+++ b/hooks/useUserPrefs.ts
@@ -0,0 +1,77 @@
+"use client";
+
+import { useCallback, useEffect, useState } from "react";
+
+export type DevicePref = "auto" | "cpu" | "gpu";
+
+type Prefs = {
+ systemPrompt: string;
+ device: DevicePref;
+ gpuIndex: number; // main_gpu
+ numGpu: number; // num_gpu
+};
+
+const KEYS = {
+ systemPrompt: "ollahub-system-prompt",
+ device: "ollahub-device-pref",
+ gpuIndex: "ollahub-gpu-index",
+ numGpu: "ollahub-num-gpu",
+} as const;
+
+const defaultPrefs: Prefs = {
+ systemPrompt: "",
+ device: "auto",
+ gpuIndex: 0,
+ numGpu: 1,
+};
+
+export function useUserPrefs() {
+ const [prefs, setPrefs] = useState(defaultPrefs);
+ const [ready, setReady] = useState(false);
+
+ useEffect(() => {
+ if (typeof window === "undefined") return;
+ const p: Prefs = {
+ systemPrompt:
+ localStorage.getItem(KEYS.systemPrompt) || defaultPrefs.systemPrompt,
+ device:
+ (localStorage.getItem(KEYS.device) as DevicePref) ||
+ defaultPrefs.device,
+ gpuIndex: Number(
+ localStorage.getItem(KEYS.gpuIndex) || defaultPrefs.gpuIndex
+ ),
+ numGpu: Number(localStorage.getItem(KEYS.numGpu) || defaultPrefs.numGpu),
+ };
+ setPrefs(p);
+ setReady(true);
+ }, []);
+
+ const update = useCallback((next: Partial) => {
+ setPrefs((prev) => {
+ const merged: Prefs = { ...prev, ...next };
+ if (typeof window !== "undefined") {
+ localStorage.setItem(KEYS.systemPrompt, merged.systemPrompt);
+ localStorage.setItem(KEYS.device, merged.device);
+ localStorage.setItem(KEYS.gpuIndex, String(merged.gpuIndex));
+ localStorage.setItem(KEYS.numGpu, String(merged.numGpu));
+ }
+ return merged;
+ });
+ }, []);
+
+ return { prefs, ready, update };
+}
+
+export function buildOllamaOptions(
+ device: DevicePref,
+ numGpu: number,
+ gpuIndex: number
+) {
+ if (device === "auto") return undefined;
+ if (device === "cpu") return { num_gpu: 0 } as const;
+ // gpu
+ return {
+ num_gpu: Math.max(1, numGpu),
+ main_gpu: Math.max(0, gpuIndex),
+ } as const;
+}
diff --git a/lib/chat.ts b/lib/chat.ts
new file mode 100644
index 0000000..636a285
--- /dev/null
+++ b/lib/chat.ts
@@ -0,0 +1,50 @@
+export type ChatRole = "user" | "assistant" | "system";
+
+export type Message = {
+ id: string;
+ role: ChatRole;
+ content: string;
+ timestamp: number;
+};
+
+export type ChatState = {
+ messages: Message[];
+ selectedModel: string | null;
+ isStreaming: boolean;
+ streamingContent: string;
+};
+
+export function createMessage(role: ChatRole, content: string): Message {
+ return {
+ id: crypto.randomUUID(),
+ role,
+ content,
+ timestamp: Date.now(),
+ };
+}
+
+export function formatTimestamp(timestamp: number): string {
+ const now = Date.now();
+ const diff = now - timestamp;
+ const minutes = Math.floor(diff / 60000);
+
+ if (minutes < 1) return "agora";
+ if (minutes < 60) return `${minutes} min atrás`;
+
+ const hours = Math.floor(minutes / 60);
+ if (hours < 24) return `${hours}h atrás`;
+
+ const days = Math.floor(hours / 24);
+ return `${days}d atrás`;
+}
+
+export function convertToOllamaMessages(messages: Message[]) {
+ return messages.map((msg) => ({
+ role: msg.role,
+ content: msg.content,
+ }));
+}
+
+export function createSystemMessage(content: string): Message {
+ return createMessage("system", content);
+}
diff --git a/lib/models.ts b/lib/models.ts
new file mode 100644
index 0000000..fcaf4f3
--- /dev/null
+++ b/lib/models.ts
@@ -0,0 +1,100 @@
+export type Device = "CPU" | "GPU";
+
+export type ModelInfo = {
+ id: string;
+ name: string;
+ sizeGB: number;
+ quantization: string;
+ device: Device;
+ estCpuUsagePct?: number;
+ estVramGB?: number;
+};
+
+export const mockModels: ModelInfo[] = [
+ {
+ id: "llama3.1-8b-q4",
+ name: "llama3.1:8b-q4",
+ sizeGB: 4.8,
+ quantization: "Q4",
+ device: "CPU",
+ estCpuUsagePct: 85,
+ estVramGB: 0.0,
+ },
+ {
+ id: "llama3.1-8b-q8",
+ name: "llama3.1:8b-q8",
+ sizeGB: 8.9,
+ quantization: "Q8",
+ device: "GPU",
+ estCpuUsagePct: 20,
+ estVramGB: 8,
+ },
+ {
+ id: "mistral-7b-q4",
+ name: "mistral:7b-q4",
+ sizeGB: 4.1,
+ quantization: "Q4",
+ device: "CPU",
+ estCpuUsagePct: 75,
+ estVramGB: 0,
+ },
+ {
+ id: "mistral-7b-q8",
+ name: "mistral:7b-q8",
+ sizeGB: 7.9,
+ quantization: "Q8",
+ device: "GPU",
+ estCpuUsagePct: 18,
+ estVramGB: 8,
+ },
+ {
+ id: "qwen2.5-7b-q4",
+ name: "qwen2.5:7b-q4",
+ sizeGB: 4.2,
+ quantization: "Q4",
+ device: "CPU",
+ estCpuUsagePct: 80,
+ estVramGB: 0,
+ },
+ {
+ id: "qwen2.5-7b-q8",
+ name: "qwen2.5:7b-q8",
+ sizeGB: 8.1,
+ quantization: "Q8",
+ device: "GPU",
+ estCpuUsagePct: 22,
+ estVramGB: 8,
+ },
+];
+
+export function inferQuantization(nameOrDetails: string | unknown): string {
+ const s =
+ typeof nameOrDetails === "string" ? nameOrDetails.toLowerCase() : "";
+ if (s.includes("q8")) return "Q8";
+ if (s.includes("q5")) return "Q5";
+ if (s.includes("q4")) return "Q4";
+ return "Q?";
+}
+
+export function inferDevice(details?: unknown): Device {
+ const s = typeof details === "string" ? details.toLowerCase() : "";
+ if (s.includes("cuda") || s.includes("metal") || s.includes("gpu"))
+ return "GPU";
+ return "CPU";
+}
+
+export function estimateCpuVram(
+ info: Pick,
+) {
+ const q = info.quantization.toUpperCase();
+ let cpuPct = 50;
+ let vramGB = 0;
+ if (info.device === "GPU") {
+ vramGB = Math.max(4, Math.round(info.sizeGB));
+ cpuPct = 20;
+ } else {
+ // heurística simples: quantizações mais baixas usam mais CPU
+ cpuPct = q === "Q8" ? 60 : q === "Q5" ? 70 : 85;
+ }
+ return { cpuPct, vramGB };
+}
diff --git a/lib/ollama.ts b/lib/ollama.ts
new file mode 100644
index 0000000..265a1a1
--- /dev/null
+++ b/lib/ollama.ts
@@ -0,0 +1,42 @@
+import { Ollama } from "ollama";
+
+export const OLLAMA_HOST = process.env.OLLAMA_HOST || "http://localhost:11434";
+
+export async function listModelsViaSdk() {
+ try {
+ const client = new Ollama({ host: OLLAMA_HOST });
+ const res = await client.list();
+ return res?.models ?? [];
+ } catch (e) {
+ throw e;
+ }
+}
+
+export async function listModelsViaHttp() {
+ const url = `${OLLAMA_HOST}/api/tags`;
+ const res = await fetch(url, { cache: "no-store" });
+ if (!res.ok) throw new Error(`Ollama HTTP error ${res.status}`);
+ const data = await res.json();
+ return data?.models ?? [];
+}
+
+export async function chatWithStream(
+ model: string,
+ messages: Array<{ role: string; content: string }>,
+ options?: Record,
+ system?: string
+) {
+ const client = new Ollama({ host: OLLAMA_HOST });
+ return client.chat({
+ model,
+ messages,
+ stream: true,
+ options,
+ system,
+ });
+}
+
+export async function pullModel(model: string) {
+ const client = new Ollama({ host: OLLAMA_HOST });
+ return client.pull({ model, stream: true });
+}
diff --git a/lib/systemPrompt.ts b/lib/systemPrompt.ts
new file mode 100644
index 0000000..5bbae75
--- /dev/null
+++ b/lib/systemPrompt.ts
@@ -0,0 +1,30 @@
+export const DEFAULT_SYSTEM_PROMPT = `Você é um assistente de IA especializado em desenvolvimento de software e análise técnica.
+
+FORMATAÇÃO OBRIGATÓRIA:
+- Sempre use blocos de código markdown com especificação de linguagem
+- Para JavaScript/TypeScript: \`\`\`javascript ou \`\`\`typescript
+- Para Python: \`\`\`python
+- Para Bash/Shell: \`\`\`bash
+- Para SQL: \`\`\`sql
+- Para JSON: \`\`\`json
+- Para YAML: \`\`\`yaml
+- Para Docker: \`\`\`dockerfile
+- Para configurações: \`\`\`toml, \`\`\`ini, \`\`\`conf
+
+ESTRUTURA DE RESPOSTAS:
+1. Explicação conceitual clara
+2. Exemplo prático em bloco de código
+3. Explicação do código quando necessário
+4. Boas práticas
+5. Possíveis problemas e soluções
+
+CONTEXTO OLLAHUB:
+- Execução local via Ollama
+- Stack frequente: Next.js, React, Node.js
+- Priorize exemplos executáveis e explique comandos e configurações.`;
+
+export function mergeSystemPrompt(userPrompt?: string) {
+ if (!userPrompt || userPrompt.trim().length === 0)
+ return DEFAULT_SYSTEM_PROMPT;
+ return `${DEFAULT_SYSTEM_PROMPT}\n\nDiretivas do usuário:\n${userPrompt}`;
+}
diff --git a/next.config.ts b/next.config.ts
new file mode 100644
index 0000000..e9ffa30
--- /dev/null
+++ b/next.config.ts
@@ -0,0 +1,7 @@
+import type { NextConfig } from "next";
+
+const nextConfig: NextConfig = {
+ /* config options here */
+};
+
+export default nextConfig;
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..1576da4
--- /dev/null
+++ b/package.json
@@ -0,0 +1,38 @@
+{
+ "name": "ollahub",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "lint": "biome check",
+ "format": "biome format --write"
+ },
+ "dependencies": {
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "lucide-react": "^0.548.0",
+ "next": "16.0.0",
+ "next-themes": "^0.3.0",
+ "ollama": "^0.5.11",
+ "react": "19.2.0",
+ "react-dom": "19.2.0",
+ "react-markdown": "^10.1.0",
+ "react-syntax-highlighter": "^16.0.0",
+ "refractor": "^3.6.0",
+ "rehype-highlight": "^7.0.2",
+ "rehype-raw": "^7.0.0",
+ "remark-gfm": "^4.0.1",
+ "tailwind-merge": "^3.3.1"
+ },
+ "devDependencies": {
+ "@biomejs/biome": "2.2.0",
+ "@tailwindcss/postcss": "^4",
+ "@types/node": "^20",
+ "@types/react": "^19",
+ "@types/react-dom": "^19",
+ "tailwindcss": "^4",
+ "typescript": "^5"
+ }
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
new file mode 100644
index 0000000..acc8923
--- /dev/null
+++ b/pnpm-lock.yaml
@@ -0,0 +1,2340 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ dependencies:
+ class-variance-authority:
+ specifier: ^0.7.1
+ version: 0.7.1
+ clsx:
+ specifier: ^2.1.1
+ version: 2.1.1
+ lucide-react:
+ specifier: ^0.548.0
+ version: 0.548.0(react@19.2.0)
+ next:
+ specifier: 16.0.0
+ version: 16.0.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ next-themes:
+ specifier: ^0.3.0
+ version: 0.3.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ ollama:
+ specifier: ^0.5.11
+ version: 0.5.18
+ react:
+ specifier: 19.2.0
+ version: 19.2.0
+ react-dom:
+ specifier: 19.2.0
+ version: 19.2.0(react@19.2.0)
+ react-markdown:
+ specifier: ^10.1.0
+ version: 10.1.0(@types/react@19.2.2)(react@19.2.0)
+ react-syntax-highlighter:
+ specifier: ^16.0.0
+ version: 16.0.0(react@19.2.0)
+ refractor:
+ specifier: ^3.6.0
+ version: 3.6.0
+ rehype-highlight:
+ specifier: ^7.0.2
+ version: 7.0.2
+ rehype-raw:
+ specifier: ^7.0.0
+ version: 7.0.0
+ remark-gfm:
+ specifier: ^4.0.1
+ version: 4.0.1
+ tailwind-merge:
+ specifier: ^3.3.1
+ version: 3.3.1
+ devDependencies:
+ '@biomejs/biome':
+ specifier: 2.2.0
+ version: 2.2.0
+ '@tailwindcss/postcss':
+ specifier: ^4
+ version: 4.1.16
+ '@types/node':
+ specifier: ^20
+ version: 20.19.23
+ '@types/react':
+ specifier: ^19
+ version: 19.2.2
+ '@types/react-dom':
+ specifier: ^19
+ version: 19.2.2(@types/react@19.2.2)
+ tailwindcss:
+ specifier: ^4
+ version: 4.1.16
+ typescript:
+ specifier: ^5
+ version: 5.9.3
+
+packages:
+
+ '@alloc/quick-lru@5.2.0':
+ resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
+ engines: {node: '>=10'}
+
+ '@babel/runtime@7.28.4':
+ resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@biomejs/biome@2.2.0':
+ resolution: {integrity: sha512-3On3RSYLsX+n9KnoSgfoYlckYBoU6VRM22cw1gB4Y0OuUVSYd/O/2saOJMrA4HFfA1Ff0eacOvMN1yAAvHtzIw==}
+ engines: {node: '>=14.21.3'}
+ hasBin: true
+
+ '@biomejs/cli-darwin-arm64@2.2.0':
+ resolution: {integrity: sha512-zKbwUUh+9uFmWfS8IFxmVD6XwqFcENjZvEyfOxHs1epjdH3wyyMQG80FGDsmauPwS2r5kXdEM0v/+dTIA9FXAg==}
+ engines: {node: '>=14.21.3'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@biomejs/cli-darwin-x64@2.2.0':
+ resolution: {integrity: sha512-+OmT4dsX2eTfhD5crUOPw3RPhaR+SKVspvGVmSdZ9y9O/AgL8pla6T4hOn1q+VAFBHuHhsdxDRJgFCSC7RaMOw==}
+ engines: {node: '>=14.21.3'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@biomejs/cli-linux-arm64-musl@2.2.0':
+ resolution: {integrity: sha512-egKpOa+4FL9YO+SMUMLUvf543cprjevNc3CAgDNFLcjknuNMcZ0GLJYa3EGTCR2xIkIUJDVneBV3O9OcIlCEZQ==}
+ engines: {node: '>=14.21.3'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@biomejs/cli-linux-arm64@2.2.0':
+ resolution: {integrity: sha512-6eoRdF2yW5FnW9Lpeivh7Mayhq0KDdaDMYOJnH9aT02KuSIX5V1HmWJCQQPwIQbhDh68Zrcpl8inRlTEan0SXw==}
+ engines: {node: '>=14.21.3'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@biomejs/cli-linux-x64-musl@2.2.0':
+ resolution: {integrity: sha512-I5J85yWwUWpgJyC1CcytNSGusu2p9HjDnOPAFG4Y515hwRD0jpR9sT9/T1cKHtuCvEQ/sBvx+6zhz9l9wEJGAg==}
+ engines: {node: '>=14.21.3'}
+ cpu: [x64]
+ os: [linux]
+
+ '@biomejs/cli-linux-x64@2.2.0':
+ resolution: {integrity: sha512-5UmQx/OZAfJfi25zAnAGHUMuOd+LOsliIt119x2soA2gLggQYrVPA+2kMUxR6Mw5M1deUF/AWWP2qpxgH7Nyfw==}
+ engines: {node: '>=14.21.3'}
+ cpu: [x64]
+ os: [linux]
+
+ '@biomejs/cli-win32-arm64@2.2.0':
+ resolution: {integrity: sha512-n9a1/f2CwIDmNMNkFs+JI0ZjFnMO0jdOyGNtihgUNFnlmd84yIYY2KMTBmMV58ZlVHjgmY5Y6E1hVTnSRieggA==}
+ engines: {node: '>=14.21.3'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@biomejs/cli-win32-x64@2.2.0':
+ resolution: {integrity: sha512-Nawu5nHjP/zPKTIryh2AavzTc/KEg4um/MxWdXW0A6P/RZOyIpa7+QSjeXwAwX/utJGaCoXRPWtF3m5U/bB3Ww==}
+ engines: {node: '>=14.21.3'}
+ cpu: [x64]
+ os: [win32]
+
+ '@emnapi/runtime@1.6.0':
+ resolution: {integrity: sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA==}
+
+ '@img/colour@1.0.0':
+ resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==}
+ engines: {node: '>=18'}
+
+ '@img/sharp-darwin-arm64@0.34.4':
+ resolution: {integrity: sha512-sitdlPzDVyvmINUdJle3TNHl+AG9QcwiAMsXmccqsCOMZNIdW2/7S26w0LyU8euiLVzFBL3dXPwVCq/ODnf2vA==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@img/sharp-darwin-x64@0.34.4':
+ resolution: {integrity: sha512-rZheupWIoa3+SOdF/IcUe1ah4ZDpKBGWcsPX6MT0lYniH9micvIU7HQkYTfrx5Xi8u+YqwLtxC/3vl8TQN6rMg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [darwin]
+
+ '@img/sharp-libvips-darwin-arm64@1.2.3':
+ resolution: {integrity: sha512-QzWAKo7kpHxbuHqUC28DZ9pIKpSi2ts2OJnoIGI26+HMgq92ZZ4vk8iJd4XsxN+tYfNJxzH6W62X5eTcsBymHw==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@img/sharp-libvips-darwin-x64@1.2.3':
+ resolution: {integrity: sha512-Ju+g2xn1E2AKO6YBhxjj+ACcsPQRHT0bhpglxcEf+3uyPY+/gL8veniKoo96335ZaPo03bdDXMv0t+BBFAbmRA==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@img/sharp-libvips-linux-arm64@1.2.3':
+ resolution: {integrity: sha512-I4RxkXU90cpufazhGPyVujYwfIm9Nk1QDEmiIsaPwdnm013F7RIceaCc87kAH+oUB1ezqEvC6ga4m7MSlqsJvQ==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-arm@1.2.3':
+ resolution: {integrity: sha512-x1uE93lyP6wEwGvgAIV0gP6zmaL/a0tGzJs/BIDDG0zeBhMnuUPm7ptxGhUbcGs4okDJrk4nxgrmxpib9g6HpA==}
+ cpu: [arm]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-ppc64@1.2.3':
+ resolution: {integrity: sha512-Y2T7IsQvJLMCBM+pmPbM3bKT/yYJvVtLJGfCs4Sp95SjvnFIjynbjzsa7dY1fRJX45FTSfDksbTp6AGWudiyCg==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-s390x@1.2.3':
+ resolution: {integrity: sha512-RgWrs/gVU7f+K7P+KeHFaBAJlNkD1nIZuVXdQv6S+fNA6syCcoboNjsV2Pou7zNlVdNQoQUpQTk8SWDHUA3y/w==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-x64@1.2.3':
+ resolution: {integrity: sha512-3JU7LmR85K6bBiRzSUc/Ff9JBVIFVvq6bomKE0e63UXGeRw2HPVEjoJke1Yx+iU4rL7/7kUjES4dZ/81Qjhyxg==}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-libvips-linuxmusl-arm64@1.2.3':
+ resolution: {integrity: sha512-F9q83RZ8yaCwENw1GieztSfj5msz7GGykG/BA+MOUefvER69K/ubgFHNeSyUu64amHIYKGDs4sRCMzXVj8sEyw==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-libvips-linuxmusl-x64@1.2.3':
+ resolution: {integrity: sha512-U5PUY5jbc45ANM6tSJpsgqmBF/VsL6LnxJmIf11kB7J5DctHgqm0SkuXzVWtIY90GnJxKnC/JT251TDnk1fu/g==}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-linux-arm64@0.34.4':
+ resolution: {integrity: sha512-YXU1F/mN/Wu786tl72CyJjP/Ngl8mGHN1hST4BGl+hiW5jhCnV2uRVTNOcaYPs73NeT/H8Upm3y9582JVuZHrQ==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-linux-arm@0.34.4':
+ resolution: {integrity: sha512-Xyam4mlqM0KkTHYVSuc6wXRmM7LGN0P12li03jAnZ3EJWZqj83+hi8Y9UxZUbxsgsK1qOEwg7O0Bc0LjqQVtxA==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm]
+ os: [linux]
+
+ '@img/sharp-linux-ppc64@0.34.4':
+ resolution: {integrity: sha512-F4PDtF4Cy8L8hXA2p3TO6s4aDt93v+LKmpcYFLAVdkkD3hSxZzee0rh6/+94FpAynsuMpLX5h+LRsSG3rIciUQ==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@img/sharp-linux-s390x@0.34.4':
+ resolution: {integrity: sha512-qVrZKE9Bsnzy+myf7lFKvng6bQzhNUAYcVORq2P7bDlvmF6u2sCmK2KyEQEBdYk+u3T01pVsPrkj943T1aJAsw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [s390x]
+ os: [linux]
+
+ '@img/sharp-linux-x64@0.34.4':
+ resolution: {integrity: sha512-ZfGtcp2xS51iG79c6Vhw9CWqQC8l2Ot8dygxoDoIQPTat/Ov3qAa8qpxSrtAEAJW+UjTXc4yxCjNfxm4h6Xm2A==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-linuxmusl-arm64@0.34.4':
+ resolution: {integrity: sha512-8hDVvW9eu4yHWnjaOOR8kHVrew1iIX+MUgwxSuH2XyYeNRtLUe4VNioSqbNkB7ZYQJj9rUTT4PyRscyk2PXFKA==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-linuxmusl-x64@0.34.4':
+ resolution: {integrity: sha512-lU0aA5L8QTlfKjpDCEFOZsTYGn3AEiO6db8W5aQDxj0nQkVrZWmN3ZP9sYKWJdtq3PWPhUNlqehWyXpYDcI9Sg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-wasm32@0.34.4':
+ resolution: {integrity: sha512-33QL6ZO/qpRyG7woB/HUALz28WnTMI2W1jgX3Nu2bypqLIKx/QKMILLJzJjI+SIbvXdG9fUnmrxR7vbi1sTBeA==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [wasm32]
+
+ '@img/sharp-win32-arm64@0.34.4':
+ resolution: {integrity: sha512-2Q250do/5WXTwxW3zjsEuMSv5sUU4Tq9VThWKlU2EYLm4MB7ZeMwF+SFJutldYODXF6jzc6YEOC+VfX0SZQPqA==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [win32]
+
+ '@img/sharp-win32-ia32@0.34.4':
+ resolution: {integrity: sha512-3ZeLue5V82dT92CNL6rsal6I2weKw1cYu+rGKm8fOCCtJTR2gYeUfY3FqUnIJsMUPIH68oS5jmZ0NiJ508YpEw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [ia32]
+ os: [win32]
+
+ '@img/sharp-win32-x64@0.34.4':
+ resolution: {integrity: sha512-xIyj4wpYs8J18sVN3mSQjwrw7fKUqRw+Z5rnHNCy5fYTxigBz81u5mOMPmFumwjcn8+ld1ppptMBCLic1nz6ig==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [win32]
+
+ '@jridgewell/gen-mapping@0.3.13':
+ resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
+
+ '@jridgewell/remapping@2.3.5':
+ resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
+
+ '@jridgewell/resolve-uri@3.1.2':
+ resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/sourcemap-codec@1.5.5':
+ resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+
+ '@next/env@16.0.0':
+ resolution: {integrity: sha512-s5j2iFGp38QsG1LWRQaE2iUY3h1jc014/melHFfLdrsMJPqxqDQwWNwyQTcNoUSGZlCVZuM7t7JDMmSyRilsnA==}
+
+ '@next/swc-darwin-arm64@16.0.0':
+ resolution: {integrity: sha512-/CntqDCnk5w2qIwMiF0a9r6+9qunZzFmU0cBX4T82LOflE72zzH6gnOjCwUXYKOBlQi8OpP/rMj8cBIr18x4TA==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@next/swc-darwin-x64@16.0.0':
+ resolution: {integrity: sha512-hB4GZnJGKa8m4efvTGNyii6qs76vTNl+3dKHTCAUaksN6KjYy4iEO3Q5ira405NW2PKb3EcqWiRaL9DrYJfMHg==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@next/swc-linux-arm64-gnu@16.0.0':
+ resolution: {integrity: sha512-E2IHMdE+C1k+nUgndM13/BY/iJY9KGCphCftMh7SXWcaQqExq/pJU/1Hgn8n/tFwSoLoYC/yUghOv97tAsIxqg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@next/swc-linux-arm64-musl@16.0.0':
+ resolution: {integrity: sha512-xzgl7c7BVk4+7PDWldU+On2nlwnGgFqJ1siWp3/8S0KBBLCjonB6zwJYPtl4MUY7YZJrzzumdUpUoquu5zk8vg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@next/swc-linux-x64-gnu@16.0.0':
+ resolution: {integrity: sha512-sdyOg4cbiCw7YUr0F/7ya42oiVBXLD21EYkSwN+PhE4csJH4MSXUsYyslliiiBwkM+KsuQH/y9wuxVz6s7Nstg==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@next/swc-linux-x64-musl@16.0.0':
+ resolution: {integrity: sha512-IAXv3OBYqVaNOgyd3kxR4L3msuhmSy1bcchPHxDOjypG33i2yDWvGBwFD94OuuTjjTt/7cuIKtAmoOOml6kfbg==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@next/swc-win32-arm64-msvc@16.0.0':
+ resolution: {integrity: sha512-bmo3ncIJKUS9PWK1JD9pEVv0yuvp1KPuOsyJTHXTv8KDrEmgV/K+U0C75rl9rhIaODcS7JEb6/7eJhdwXI0XmA==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@next/swc-win32-x64-msvc@16.0.0':
+ resolution: {integrity: sha512-O1cJbT+lZp+cTjYyZGiDwsOjO3UHHzSqobkPNipdlnnuPb1swfcuY6r3p8dsKU4hAIEO4cO67ZCfVVH/M1ETXA==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@swc/helpers@0.5.15':
+ resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
+
+ '@tailwindcss/node@4.1.16':
+ resolution: {integrity: sha512-BX5iaSsloNuvKNHRN3k2RcCuTEgASTo77mofW0vmeHkfrDWaoFAFvNHpEgtu0eqyypcyiBkDWzSMxJhp3AUVcw==}
+
+ '@tailwindcss/oxide-android-arm64@4.1.16':
+ resolution: {integrity: sha512-8+ctzkjHgwDJ5caq9IqRSgsP70xhdhJvm+oueS/yhD5ixLhqTw9fSL1OurzMUhBwE5zK26FXLCz2f/RtkISqHA==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [android]
+
+ '@tailwindcss/oxide-darwin-arm64@4.1.16':
+ resolution: {integrity: sha512-C3oZy5042v2FOALBZtY0JTDnGNdS6w7DxL/odvSny17ORUnaRKhyTse8xYi3yKGyfnTUOdavRCdmc8QqJYwFKA==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@tailwindcss/oxide-darwin-x64@4.1.16':
+ resolution: {integrity: sha512-vjrl/1Ub9+JwU6BP0emgipGjowzYZMjbWCDqwA2Z4vCa+HBSpP4v6U2ddejcHsolsYxwL5r4bPNoamlV0xDdLg==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@tailwindcss/oxide-freebsd-x64@4.1.16':
+ resolution: {integrity: sha512-TSMpPYpQLm+aR1wW5rKuUuEruc/oOX3C7H0BTnPDn7W/eMw8W+MRMpiypKMkXZfwH8wqPIRKppuZoedTtNj2tg==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.16':
+ resolution: {integrity: sha512-p0GGfRg/w0sdsFKBjMYvvKIiKy/LNWLWgV/plR4lUgrsxFAoQBFrXkZ4C0w8IOXfslB9vHK/JGASWD2IefIpvw==}
+ engines: {node: '>= 10'}
+ cpu: [arm]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-arm64-gnu@4.1.16':
+ resolution: {integrity: sha512-DoixyMmTNO19rwRPdqviTrG1rYzpxgyYJl8RgQvdAQUzxC1ToLRqtNJpU/ATURSKgIg6uerPw2feW0aS8SNr/w==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-arm64-musl@4.1.16':
+ resolution: {integrity: sha512-H81UXMa9hJhWhaAUca6bU2wm5RRFpuHImrwXBUvPbYb+3jo32I9VIwpOX6hms0fPmA6f2pGVlybO6qU8pF4fzQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-x64-gnu@4.1.16':
+ resolution: {integrity: sha512-ZGHQxDtFC2/ruo7t99Qo2TTIvOERULPl5l0K1g0oK6b5PGqjYMga+FcY1wIUnrUxY56h28FxybtDEla+ICOyew==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-x64-musl@4.1.16':
+ resolution: {integrity: sha512-Oi1tAaa0rcKf1Og9MzKeINZzMLPbhxvm7rno5/zuP1WYmpiG0bEHq4AcRUiG2165/WUzvxkW4XDYCscZWbTLZw==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@tailwindcss/oxide-wasm32-wasi@4.1.16':
+ resolution: {integrity: sha512-B01u/b8LteGRwucIBmCQ07FVXLzImWESAIMcUU6nvFt/tYsQ6IHz8DmZ5KtvmwxD+iTYBtM1xwoGXswnlu9v0Q==}
+ engines: {node: '>=14.0.0'}
+ cpu: [wasm32]
+ bundledDependencies:
+ - '@napi-rs/wasm-runtime'
+ - '@emnapi/core'
+ - '@emnapi/runtime'
+ - '@tybys/wasm-util'
+ - '@emnapi/wasi-threads'
+ - tslib
+
+ '@tailwindcss/oxide-win32-arm64-msvc@4.1.16':
+ resolution: {integrity: sha512-zX+Q8sSkGj6HKRTMJXuPvOcP8XfYON24zJBRPlszcH1Np7xuHXhWn8qfFjIujVzvH3BHU+16jBXwgpl20i+v9A==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@tailwindcss/oxide-win32-x64-msvc@4.1.16':
+ resolution: {integrity: sha512-m5dDFJUEejbFqP+UXVstd4W/wnxA4F61q8SoL+mqTypId2T2ZpuxosNSgowiCnLp2+Z+rivdU0AqpfgiD7yCBg==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@tailwindcss/oxide@4.1.16':
+ resolution: {integrity: sha512-2OSv52FRuhdlgyOQqgtQHuCgXnS8nFSYRp2tJ+4WZXKgTxqPy7SMSls8c3mPT5pkZ17SBToGM5LHEJBO7miEdg==}
+ engines: {node: '>= 10'}
+
+ '@tailwindcss/postcss@4.1.16':
+ resolution: {integrity: sha512-Qn3SFGPXYQMKR/UtqS+dqvPrzEeBZHrFA92maT4zijCVggdsXnDBMsPFJo1eArX3J+O+Gi+8pV4PkqjLCNBk3A==}
+
+ '@types/debug@4.1.12':
+ resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
+
+ '@types/estree-jsx@1.0.5':
+ resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==}
+
+ '@types/estree@1.0.8':
+ resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
+
+ '@types/hast@2.3.10':
+ resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==}
+
+ '@types/hast@3.0.4':
+ resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
+
+ '@types/mdast@4.0.4':
+ resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
+
+ '@types/ms@2.1.0':
+ resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
+
+ '@types/node@20.19.23':
+ resolution: {integrity: sha512-yIdlVVVHXpmqRhtyovZAcSy0MiPcYWGkoO4CGe/+jpP0hmNuihm4XhHbADpK++MsiLHP5MVlv+bcgdF99kSiFQ==}
+
+ '@types/prismjs@1.26.5':
+ resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==}
+
+ '@types/react-dom@19.2.2':
+ resolution: {integrity: sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==}
+ peerDependencies:
+ '@types/react': ^19.2.0
+
+ '@types/react@19.2.2':
+ resolution: {integrity: sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==}
+
+ '@types/unist@2.0.11':
+ resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==}
+
+ '@types/unist@3.0.3':
+ resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
+
+ '@ungap/structured-clone@1.3.0':
+ resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
+
+ bail@2.0.2:
+ resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
+
+ caniuse-lite@1.0.30001751:
+ resolution: {integrity: sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==}
+
+ ccount@2.0.1:
+ resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
+
+ character-entities-html4@2.1.0:
+ resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==}
+
+ character-entities-legacy@1.1.4:
+ resolution: {integrity: sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==}
+
+ character-entities-legacy@3.0.0:
+ resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==}
+
+ character-entities@1.2.4:
+ resolution: {integrity: sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==}
+
+ character-entities@2.0.2:
+ resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==}
+
+ character-reference-invalid@1.1.4:
+ resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==}
+
+ character-reference-invalid@2.0.1:
+ resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==}
+
+ class-variance-authority@0.7.1:
+ resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
+
+ client-only@0.0.1:
+ resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
+
+ clsx@2.1.1:
+ resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
+ engines: {node: '>=6'}
+
+ comma-separated-tokens@1.0.8:
+ resolution: {integrity: sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==}
+
+ comma-separated-tokens@2.0.3:
+ resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
+
+ csstype@3.1.3:
+ resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
+
+ debug@4.4.3:
+ resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ decode-named-character-reference@1.2.0:
+ resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==}
+
+ dequal@2.0.3:
+ resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
+ engines: {node: '>=6'}
+
+ detect-libc@2.1.2:
+ resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
+ engines: {node: '>=8'}
+
+ devlop@1.1.0:
+ resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
+
+ enhanced-resolve@5.18.3:
+ resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==}
+ engines: {node: '>=10.13.0'}
+
+ entities@6.0.1:
+ resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
+ engines: {node: '>=0.12'}
+
+ escape-string-regexp@5.0.0:
+ resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
+ engines: {node: '>=12'}
+
+ estree-util-is-identifier-name@3.0.0:
+ resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==}
+
+ extend@3.0.2:
+ resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
+
+ fault@1.0.4:
+ resolution: {integrity: sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==}
+
+ format@0.2.2:
+ resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==}
+ engines: {node: '>=0.4.x'}
+
+ graceful-fs@4.2.11:
+ resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+
+ hast-util-from-parse5@8.0.3:
+ resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==}
+
+ hast-util-is-element@3.0.0:
+ resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==}
+
+ hast-util-parse-selector@2.2.5:
+ resolution: {integrity: sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==}
+
+ hast-util-parse-selector@4.0.0:
+ resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==}
+
+ hast-util-raw@9.1.0:
+ resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==}
+
+ hast-util-to-jsx-runtime@2.3.6:
+ resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==}
+
+ hast-util-to-parse5@8.0.0:
+ resolution: {integrity: sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==}
+
+ hast-util-to-text@4.0.2:
+ resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==}
+
+ hast-util-whitespace@3.0.0:
+ resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
+
+ hastscript@6.0.0:
+ resolution: {integrity: sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==}
+
+ hastscript@9.0.1:
+ resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==}
+
+ highlight.js@10.7.3:
+ resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==}
+
+ highlight.js@11.11.1:
+ resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==}
+ engines: {node: '>=12.0.0'}
+
+ highlightjs-vue@1.0.0:
+ resolution: {integrity: sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==}
+
+ html-url-attributes@3.0.1:
+ resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==}
+
+ html-void-elements@3.0.0:
+ resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
+
+ inline-style-parser@0.2.4:
+ resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==}
+
+ is-alphabetical@1.0.4:
+ resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==}
+
+ is-alphabetical@2.0.1:
+ resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==}
+
+ is-alphanumerical@1.0.4:
+ resolution: {integrity: sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==}
+
+ is-alphanumerical@2.0.1:
+ resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==}
+
+ is-decimal@1.0.4:
+ resolution: {integrity: sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==}
+
+ is-decimal@2.0.1:
+ resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==}
+
+ is-hexadecimal@1.0.4:
+ resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==}
+
+ is-hexadecimal@2.0.1:
+ resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==}
+
+ is-plain-obj@4.1.0:
+ resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
+ engines: {node: '>=12'}
+
+ jiti@2.6.1:
+ resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
+ hasBin: true
+
+ lightningcss-android-arm64@1.30.2:
+ resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [android]
+
+ lightningcss-darwin-arm64@1.30.2:
+ resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ lightningcss-darwin-x64@1.30.2:
+ resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ lightningcss-freebsd-x64@1.30.2:
+ resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [freebsd]
+
+ lightningcss-linux-arm-gnueabihf@1.30.2:
+ resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm]
+ os: [linux]
+
+ lightningcss-linux-arm64-gnu@1.30.2:
+ resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-arm64-musl@1.30.2:
+ resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-x64-gnu@1.30.2:
+ resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-linux-x64-musl@1.30.2:
+ resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-win32-arm64-msvc@1.30.2:
+ resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ lightningcss-win32-x64-msvc@1.30.2:
+ resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [win32]
+
+ lightningcss@1.30.2:
+ resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==}
+ engines: {node: '>= 12.0.0'}
+
+ longest-streak@3.1.0:
+ resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
+
+ lowlight@1.20.0:
+ resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==}
+
+ lowlight@3.3.0:
+ resolution: {integrity: sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==}
+
+ lucide-react@0.548.0:
+ resolution: {integrity: sha512-63b16z63jM9yc1MwxajHeuu0FRZFsDtljtDjYm26Kd86UQ5HQzu9ksEtoUUw4RBuewodw/tGFmvipePvRsKeDA==}
+ peerDependencies:
+ react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ magic-string@0.30.21:
+ resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
+
+ markdown-table@3.0.4:
+ resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==}
+
+ mdast-util-find-and-replace@3.0.2:
+ resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==}
+
+ mdast-util-from-markdown@2.0.2:
+ resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==}
+
+ mdast-util-gfm-autolink-literal@2.0.1:
+ resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==}
+
+ mdast-util-gfm-footnote@2.1.0:
+ resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==}
+
+ mdast-util-gfm-strikethrough@2.0.0:
+ resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==}
+
+ mdast-util-gfm-table@2.0.0:
+ resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==}
+
+ mdast-util-gfm-task-list-item@2.0.0:
+ resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==}
+
+ mdast-util-gfm@3.1.0:
+ resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==}
+
+ mdast-util-mdx-expression@2.0.1:
+ resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==}
+
+ mdast-util-mdx-jsx@3.2.0:
+ resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==}
+
+ mdast-util-mdxjs-esm@2.0.1:
+ resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==}
+
+ mdast-util-phrasing@4.1.0:
+ resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==}
+
+ mdast-util-to-hast@13.2.0:
+ resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==}
+
+ mdast-util-to-markdown@2.1.2:
+ resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==}
+
+ mdast-util-to-string@4.0.0:
+ resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==}
+
+ micromark-core-commonmark@2.0.3:
+ resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==}
+
+ micromark-extension-gfm-autolink-literal@2.1.0:
+ resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==}
+
+ micromark-extension-gfm-footnote@2.1.0:
+ resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==}
+
+ micromark-extension-gfm-strikethrough@2.1.0:
+ resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==}
+
+ micromark-extension-gfm-table@2.1.1:
+ resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==}
+
+ micromark-extension-gfm-tagfilter@2.0.0:
+ resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==}
+
+ micromark-extension-gfm-task-list-item@2.1.0:
+ resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==}
+
+ micromark-extension-gfm@3.0.0:
+ resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==}
+
+ micromark-factory-destination@2.0.1:
+ resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==}
+
+ micromark-factory-label@2.0.1:
+ resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==}
+
+ micromark-factory-space@2.0.1:
+ resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==}
+
+ micromark-factory-title@2.0.1:
+ resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==}
+
+ micromark-factory-whitespace@2.0.1:
+ resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==}
+
+ micromark-util-character@2.1.1:
+ resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==}
+
+ micromark-util-chunked@2.0.1:
+ resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==}
+
+ micromark-util-classify-character@2.0.1:
+ resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==}
+
+ micromark-util-combine-extensions@2.0.1:
+ resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==}
+
+ micromark-util-decode-numeric-character-reference@2.0.2:
+ resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==}
+
+ micromark-util-decode-string@2.0.1:
+ resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==}
+
+ micromark-util-encode@2.0.1:
+ resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==}
+
+ micromark-util-html-tag-name@2.0.1:
+ resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==}
+
+ micromark-util-normalize-identifier@2.0.1:
+ resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==}
+
+ micromark-util-resolve-all@2.0.1:
+ resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==}
+
+ micromark-util-sanitize-uri@2.0.1:
+ resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==}
+
+ micromark-util-subtokenize@2.1.0:
+ resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==}
+
+ micromark-util-symbol@2.0.1:
+ resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==}
+
+ micromark-util-types@2.0.2:
+ resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==}
+
+ micromark@4.0.2:
+ resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==}
+
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ nanoid@3.3.11:
+ resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ next-themes@0.3.0:
+ resolution: {integrity: sha512-/QHIrsYpd6Kfk7xakK4svpDI5mmXP0gfvCoJdGpZQ2TOrQZmsW0QxjaiLn8wbIKjtm4BTSqLoix4lxYYOnLJ/w==}
+ peerDependencies:
+ react: ^16.8 || ^17 || ^18
+ react-dom: ^16.8 || ^17 || ^18
+
+ next@16.0.0:
+ resolution: {integrity: sha512-nYohiNdxGu4OmBzggxy9rczmjIGI+TpR5vbKTsE1HqYwNm1B+YSiugSrFguX6omMOKnDHAmBPY4+8TNJk0Idyg==}
+ engines: {node: '>=20.9.0'}
+ hasBin: true
+ peerDependencies:
+ '@opentelemetry/api': ^1.1.0
+ '@playwright/test': ^1.51.1
+ babel-plugin-react-compiler: '*'
+ react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
+ react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
+ sass: ^1.3.0
+ peerDependenciesMeta:
+ '@opentelemetry/api':
+ optional: true
+ '@playwright/test':
+ optional: true
+ babel-plugin-react-compiler:
+ optional: true
+ sass:
+ optional: true
+
+ ollama@0.5.18:
+ resolution: {integrity: sha512-lTFqTf9bo7Cd3hpF6CviBe/DEhewjoZYd9N/uCe7O20qYTvGqrNOFOBDj3lbZgFWHUgDv5EeyusYxsZSLS8nvg==}
+
+ parse-entities@2.0.0:
+ resolution: {integrity: sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==}
+
+ parse-entities@4.0.2:
+ resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==}
+
+ parse5@7.3.0:
+ resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
+
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ postcss@8.4.31:
+ resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ postcss@8.5.6:
+ resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ prismjs@1.27.0:
+ resolution: {integrity: sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==}
+ engines: {node: '>=6'}
+
+ prismjs@1.30.0:
+ resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==}
+ engines: {node: '>=6'}
+
+ property-information@5.6.0:
+ resolution: {integrity: sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==}
+
+ property-information@6.5.0:
+ resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==}
+
+ property-information@7.1.0:
+ resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
+
+ react-dom@19.2.0:
+ resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==}
+ peerDependencies:
+ react: ^19.2.0
+
+ react-markdown@10.1.0:
+ resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==}
+ peerDependencies:
+ '@types/react': '>=18'
+ react: '>=18'
+
+ react-syntax-highlighter@16.0.0:
+ resolution: {integrity: sha512-xFteM6h1e6xzuHESS45eh5Nq84K2tnP9xsnzHMAuQyQjhVJFMp2CntURH4Hux/z1SyQf6cKSc2dR6LpxpetoGw==}
+ peerDependencies:
+ react: '>= 0.14.0'
+
+ react@19.2.0:
+ resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==}
+ engines: {node: '>=0.10.0'}
+
+ refractor@3.6.0:
+ resolution: {integrity: sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==}
+
+ refractor@5.0.0:
+ resolution: {integrity: sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw==}
+
+ rehype-highlight@7.0.2:
+ resolution: {integrity: sha512-k158pK7wdC2qL3M5NcZROZ2tR/l7zOzjxXd5VGdcfIyoijjQqpHd3JKtYSBDpDZ38UI2WJWuFAtkMDxmx5kstA==}
+
+ rehype-raw@7.0.0:
+ resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==}
+
+ remark-gfm@4.0.1:
+ resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==}
+
+ remark-parse@11.0.0:
+ resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==}
+
+ remark-rehype@11.1.2:
+ resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==}
+
+ remark-stringify@11.0.0:
+ resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}
+
+ scheduler@0.27.0:
+ resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
+
+ semver@7.7.3:
+ resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ sharp@0.34.4:
+ resolution: {integrity: sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ space-separated-tokens@1.1.5:
+ resolution: {integrity: sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==}
+
+ space-separated-tokens@2.0.2:
+ resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
+
+ stringify-entities@4.0.4:
+ resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==}
+
+ style-to-js@1.1.18:
+ resolution: {integrity: sha512-JFPn62D4kJaPTnhFUI244MThx+FEGbi+9dw1b9yBBQ+1CZpV7QAT8kUtJ7b7EUNdHajjF/0x8fT+16oLJoojLg==}
+
+ style-to-object@1.0.11:
+ resolution: {integrity: sha512-5A560JmXr7wDyGLK12Nq/EYS38VkGlglVzkis1JEdbGWSnbQIEhZzTJhzURXN5/8WwwFCs/f/VVcmkTppbXLow==}
+
+ styled-jsx@5.1.6:
+ resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==}
+ engines: {node: '>= 12.0.0'}
+ peerDependencies:
+ '@babel/core': '*'
+ babel-plugin-macros: '*'
+ react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0'
+ peerDependenciesMeta:
+ '@babel/core':
+ optional: true
+ babel-plugin-macros:
+ optional: true
+
+ tailwind-merge@3.3.1:
+ resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==}
+
+ tailwindcss@4.1.16:
+ resolution: {integrity: sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA==}
+
+ tapable@2.3.0:
+ resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
+ engines: {node: '>=6'}
+
+ trim-lines@3.0.1:
+ resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
+
+ trough@2.2.0:
+ resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==}
+
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
+ typescript@5.9.3:
+ resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ undici-types@6.21.0:
+ resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+
+ unified@11.0.5:
+ resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
+
+ unist-util-find-after@5.0.0:
+ resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==}
+
+ unist-util-is@6.0.1:
+ resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==}
+
+ unist-util-position@5.0.0:
+ resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==}
+
+ unist-util-stringify-position@4.0.0:
+ resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==}
+
+ unist-util-visit-parents@6.0.2:
+ resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==}
+
+ unist-util-visit@5.0.0:
+ resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==}
+
+ vfile-location@5.0.3:
+ resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==}
+
+ vfile-message@4.0.3:
+ resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==}
+
+ vfile@6.0.3:
+ resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}
+
+ web-namespaces@2.0.1:
+ resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
+
+ whatwg-fetch@3.6.20:
+ resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==}
+
+ xtend@4.0.2:
+ resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
+ engines: {node: '>=0.4'}
+
+ zwitch@2.0.4:
+ resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
+
+snapshots:
+
+ '@alloc/quick-lru@5.2.0': {}
+
+ '@babel/runtime@7.28.4': {}
+
+ '@biomejs/biome@2.2.0':
+ optionalDependencies:
+ '@biomejs/cli-darwin-arm64': 2.2.0
+ '@biomejs/cli-darwin-x64': 2.2.0
+ '@biomejs/cli-linux-arm64': 2.2.0
+ '@biomejs/cli-linux-arm64-musl': 2.2.0
+ '@biomejs/cli-linux-x64': 2.2.0
+ '@biomejs/cli-linux-x64-musl': 2.2.0
+ '@biomejs/cli-win32-arm64': 2.2.0
+ '@biomejs/cli-win32-x64': 2.2.0
+
+ '@biomejs/cli-darwin-arm64@2.2.0':
+ optional: true
+
+ '@biomejs/cli-darwin-x64@2.2.0':
+ optional: true
+
+ '@biomejs/cli-linux-arm64-musl@2.2.0':
+ optional: true
+
+ '@biomejs/cli-linux-arm64@2.2.0':
+ optional: true
+
+ '@biomejs/cli-linux-x64-musl@2.2.0':
+ optional: true
+
+ '@biomejs/cli-linux-x64@2.2.0':
+ optional: true
+
+ '@biomejs/cli-win32-arm64@2.2.0':
+ optional: true
+
+ '@biomejs/cli-win32-x64@2.2.0':
+ optional: true
+
+ '@emnapi/runtime@1.6.0':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@img/colour@1.0.0':
+ optional: true
+
+ '@img/sharp-darwin-arm64@0.34.4':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-arm64': 1.2.3
+ optional: true
+
+ '@img/sharp-darwin-x64@0.34.4':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-x64': 1.2.3
+ optional: true
+
+ '@img/sharp-libvips-darwin-arm64@1.2.3':
+ optional: true
+
+ '@img/sharp-libvips-darwin-x64@1.2.3':
+ optional: true
+
+ '@img/sharp-libvips-linux-arm64@1.2.3':
+ optional: true
+
+ '@img/sharp-libvips-linux-arm@1.2.3':
+ optional: true
+
+ '@img/sharp-libvips-linux-ppc64@1.2.3':
+ optional: true
+
+ '@img/sharp-libvips-linux-s390x@1.2.3':
+ optional: true
+
+ '@img/sharp-libvips-linux-x64@1.2.3':
+ optional: true
+
+ '@img/sharp-libvips-linuxmusl-arm64@1.2.3':
+ optional: true
+
+ '@img/sharp-libvips-linuxmusl-x64@1.2.3':
+ optional: true
+
+ '@img/sharp-linux-arm64@0.34.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm64': 1.2.3
+ optional: true
+
+ '@img/sharp-linux-arm@0.34.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm': 1.2.3
+ optional: true
+
+ '@img/sharp-linux-ppc64@0.34.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-ppc64': 1.2.3
+ optional: true
+
+ '@img/sharp-linux-s390x@0.34.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-s390x': 1.2.3
+ optional: true
+
+ '@img/sharp-linux-x64@0.34.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-x64': 1.2.3
+ optional: true
+
+ '@img/sharp-linuxmusl-arm64@0.34.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-arm64': 1.2.3
+ optional: true
+
+ '@img/sharp-linuxmusl-x64@0.34.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-x64': 1.2.3
+ optional: true
+
+ '@img/sharp-wasm32@0.34.4':
+ dependencies:
+ '@emnapi/runtime': 1.6.0
+ optional: true
+
+ '@img/sharp-win32-arm64@0.34.4':
+ optional: true
+
+ '@img/sharp-win32-ia32@0.34.4':
+ optional: true
+
+ '@img/sharp-win32-x64@0.34.4':
+ optional: true
+
+ '@jridgewell/gen-mapping@0.3.13':
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/remapping@2.3.5':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/resolve-uri@3.1.2': {}
+
+ '@jridgewell/sourcemap-codec@1.5.5': {}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ '@next/env@16.0.0': {}
+
+ '@next/swc-darwin-arm64@16.0.0':
+ optional: true
+
+ '@next/swc-darwin-x64@16.0.0':
+ optional: true
+
+ '@next/swc-linux-arm64-gnu@16.0.0':
+ optional: true
+
+ '@next/swc-linux-arm64-musl@16.0.0':
+ optional: true
+
+ '@next/swc-linux-x64-gnu@16.0.0':
+ optional: true
+
+ '@next/swc-linux-x64-musl@16.0.0':
+ optional: true
+
+ '@next/swc-win32-arm64-msvc@16.0.0':
+ optional: true
+
+ '@next/swc-win32-x64-msvc@16.0.0':
+ optional: true
+
+ '@swc/helpers@0.5.15':
+ dependencies:
+ tslib: 2.8.1
+
+ '@tailwindcss/node@4.1.16':
+ dependencies:
+ '@jridgewell/remapping': 2.3.5
+ enhanced-resolve: 5.18.3
+ jiti: 2.6.1
+ lightningcss: 1.30.2
+ magic-string: 0.30.21
+ source-map-js: 1.2.1
+ tailwindcss: 4.1.16
+
+ '@tailwindcss/oxide-android-arm64@4.1.16':
+ optional: true
+
+ '@tailwindcss/oxide-darwin-arm64@4.1.16':
+ optional: true
+
+ '@tailwindcss/oxide-darwin-x64@4.1.16':
+ optional: true
+
+ '@tailwindcss/oxide-freebsd-x64@4.1.16':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.16':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm64-gnu@4.1.16':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm64-musl@4.1.16':
+ optional: true
+
+ '@tailwindcss/oxide-linux-x64-gnu@4.1.16':
+ optional: true
+
+ '@tailwindcss/oxide-linux-x64-musl@4.1.16':
+ optional: true
+
+ '@tailwindcss/oxide-wasm32-wasi@4.1.16':
+ optional: true
+
+ '@tailwindcss/oxide-win32-arm64-msvc@4.1.16':
+ optional: true
+
+ '@tailwindcss/oxide-win32-x64-msvc@4.1.16':
+ optional: true
+
+ '@tailwindcss/oxide@4.1.16':
+ optionalDependencies:
+ '@tailwindcss/oxide-android-arm64': 4.1.16
+ '@tailwindcss/oxide-darwin-arm64': 4.1.16
+ '@tailwindcss/oxide-darwin-x64': 4.1.16
+ '@tailwindcss/oxide-freebsd-x64': 4.1.16
+ '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.16
+ '@tailwindcss/oxide-linux-arm64-gnu': 4.1.16
+ '@tailwindcss/oxide-linux-arm64-musl': 4.1.16
+ '@tailwindcss/oxide-linux-x64-gnu': 4.1.16
+ '@tailwindcss/oxide-linux-x64-musl': 4.1.16
+ '@tailwindcss/oxide-wasm32-wasi': 4.1.16
+ '@tailwindcss/oxide-win32-arm64-msvc': 4.1.16
+ '@tailwindcss/oxide-win32-x64-msvc': 4.1.16
+
+ '@tailwindcss/postcss@4.1.16':
+ dependencies:
+ '@alloc/quick-lru': 5.2.0
+ '@tailwindcss/node': 4.1.16
+ '@tailwindcss/oxide': 4.1.16
+ postcss: 8.5.6
+ tailwindcss: 4.1.16
+
+ '@types/debug@4.1.12':
+ dependencies:
+ '@types/ms': 2.1.0
+
+ '@types/estree-jsx@1.0.5':
+ dependencies:
+ '@types/estree': 1.0.8
+
+ '@types/estree@1.0.8': {}
+
+ '@types/hast@2.3.10':
+ dependencies:
+ '@types/unist': 2.0.11
+
+ '@types/hast@3.0.4':
+ dependencies:
+ '@types/unist': 3.0.3
+
+ '@types/mdast@4.0.4':
+ dependencies:
+ '@types/unist': 3.0.3
+
+ '@types/ms@2.1.0': {}
+
+ '@types/node@20.19.23':
+ dependencies:
+ undici-types: 6.21.0
+
+ '@types/prismjs@1.26.5': {}
+
+ '@types/react-dom@19.2.2(@types/react@19.2.2)':
+ dependencies:
+ '@types/react': 19.2.2
+
+ '@types/react@19.2.2':
+ dependencies:
+ csstype: 3.1.3
+
+ '@types/unist@2.0.11': {}
+
+ '@types/unist@3.0.3': {}
+
+ '@ungap/structured-clone@1.3.0': {}
+
+ bail@2.0.2: {}
+
+ caniuse-lite@1.0.30001751: {}
+
+ ccount@2.0.1: {}
+
+ character-entities-html4@2.1.0: {}
+
+ character-entities-legacy@1.1.4: {}
+
+ character-entities-legacy@3.0.0: {}
+
+ character-entities@1.2.4: {}
+
+ character-entities@2.0.2: {}
+
+ character-reference-invalid@1.1.4: {}
+
+ character-reference-invalid@2.0.1: {}
+
+ class-variance-authority@0.7.1:
+ dependencies:
+ clsx: 2.1.1
+
+ client-only@0.0.1: {}
+
+ clsx@2.1.1: {}
+
+ comma-separated-tokens@1.0.8: {}
+
+ comma-separated-tokens@2.0.3: {}
+
+ csstype@3.1.3: {}
+
+ debug@4.4.3:
+ dependencies:
+ ms: 2.1.3
+
+ decode-named-character-reference@1.2.0:
+ dependencies:
+ character-entities: 2.0.2
+
+ dequal@2.0.3: {}
+
+ detect-libc@2.1.2: {}
+
+ devlop@1.1.0:
+ dependencies:
+ dequal: 2.0.3
+
+ enhanced-resolve@5.18.3:
+ dependencies:
+ graceful-fs: 4.2.11
+ tapable: 2.3.0
+
+ entities@6.0.1: {}
+
+ escape-string-regexp@5.0.0: {}
+
+ estree-util-is-identifier-name@3.0.0: {}
+
+ extend@3.0.2: {}
+
+ fault@1.0.4:
+ dependencies:
+ format: 0.2.2
+
+ format@0.2.2: {}
+
+ graceful-fs@4.2.11: {}
+
+ hast-util-from-parse5@8.0.3:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/unist': 3.0.3
+ devlop: 1.1.0
+ hastscript: 9.0.1
+ property-information: 7.1.0
+ vfile: 6.0.3
+ vfile-location: 5.0.3
+ web-namespaces: 2.0.1
+
+ hast-util-is-element@3.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+
+ hast-util-parse-selector@2.2.5: {}
+
+ hast-util-parse-selector@4.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+
+ hast-util-raw@9.1.0:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/unist': 3.0.3
+ '@ungap/structured-clone': 1.3.0
+ hast-util-from-parse5: 8.0.3
+ hast-util-to-parse5: 8.0.0
+ html-void-elements: 3.0.0
+ mdast-util-to-hast: 13.2.0
+ parse5: 7.3.0
+ unist-util-position: 5.0.0
+ unist-util-visit: 5.0.0
+ vfile: 6.0.3
+ web-namespaces: 2.0.1
+ zwitch: 2.0.4
+
+ hast-util-to-jsx-runtime@2.3.6:
+ dependencies:
+ '@types/estree': 1.0.8
+ '@types/hast': 3.0.4
+ '@types/unist': 3.0.3
+ comma-separated-tokens: 2.0.3
+ devlop: 1.1.0
+ estree-util-is-identifier-name: 3.0.0
+ hast-util-whitespace: 3.0.0
+ mdast-util-mdx-expression: 2.0.1
+ mdast-util-mdx-jsx: 3.2.0
+ mdast-util-mdxjs-esm: 2.0.1
+ property-information: 7.1.0
+ space-separated-tokens: 2.0.2
+ style-to-js: 1.1.18
+ unist-util-position: 5.0.0
+ vfile-message: 4.0.3
+ transitivePeerDependencies:
+ - supports-color
+
+ hast-util-to-parse5@8.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+ comma-separated-tokens: 2.0.3
+ devlop: 1.1.0
+ property-information: 6.5.0
+ space-separated-tokens: 2.0.2
+ web-namespaces: 2.0.1
+ zwitch: 2.0.4
+
+ hast-util-to-text@4.0.2:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/unist': 3.0.3
+ hast-util-is-element: 3.0.0
+ unist-util-find-after: 5.0.0
+
+ hast-util-whitespace@3.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+
+ hastscript@6.0.0:
+ dependencies:
+ '@types/hast': 2.3.10
+ comma-separated-tokens: 1.0.8
+ hast-util-parse-selector: 2.2.5
+ property-information: 5.6.0
+ space-separated-tokens: 1.1.5
+
+ hastscript@9.0.1:
+ dependencies:
+ '@types/hast': 3.0.4
+ comma-separated-tokens: 2.0.3
+ hast-util-parse-selector: 4.0.0
+ property-information: 7.1.0
+ space-separated-tokens: 2.0.2
+
+ highlight.js@10.7.3: {}
+
+ highlight.js@11.11.1: {}
+
+ highlightjs-vue@1.0.0: {}
+
+ html-url-attributes@3.0.1: {}
+
+ html-void-elements@3.0.0: {}
+
+ inline-style-parser@0.2.4: {}
+
+ is-alphabetical@1.0.4: {}
+
+ is-alphabetical@2.0.1: {}
+
+ is-alphanumerical@1.0.4:
+ dependencies:
+ is-alphabetical: 1.0.4
+ is-decimal: 1.0.4
+
+ is-alphanumerical@2.0.1:
+ dependencies:
+ is-alphabetical: 2.0.1
+ is-decimal: 2.0.1
+
+ is-decimal@1.0.4: {}
+
+ is-decimal@2.0.1: {}
+
+ is-hexadecimal@1.0.4: {}
+
+ is-hexadecimal@2.0.1: {}
+
+ is-plain-obj@4.1.0: {}
+
+ jiti@2.6.1: {}
+
+ lightningcss-android-arm64@1.30.2:
+ optional: true
+
+ lightningcss-darwin-arm64@1.30.2:
+ optional: true
+
+ lightningcss-darwin-x64@1.30.2:
+ optional: true
+
+ lightningcss-freebsd-x64@1.30.2:
+ optional: true
+
+ lightningcss-linux-arm-gnueabihf@1.30.2:
+ optional: true
+
+ lightningcss-linux-arm64-gnu@1.30.2:
+ optional: true
+
+ lightningcss-linux-arm64-musl@1.30.2:
+ optional: true
+
+ lightningcss-linux-x64-gnu@1.30.2:
+ optional: true
+
+ lightningcss-linux-x64-musl@1.30.2:
+ optional: true
+
+ lightningcss-win32-arm64-msvc@1.30.2:
+ optional: true
+
+ lightningcss-win32-x64-msvc@1.30.2:
+ optional: true
+
+ lightningcss@1.30.2:
+ dependencies:
+ detect-libc: 2.1.2
+ optionalDependencies:
+ lightningcss-android-arm64: 1.30.2
+ lightningcss-darwin-arm64: 1.30.2
+ lightningcss-darwin-x64: 1.30.2
+ lightningcss-freebsd-x64: 1.30.2
+ lightningcss-linux-arm-gnueabihf: 1.30.2
+ lightningcss-linux-arm64-gnu: 1.30.2
+ lightningcss-linux-arm64-musl: 1.30.2
+ lightningcss-linux-x64-gnu: 1.30.2
+ lightningcss-linux-x64-musl: 1.30.2
+ lightningcss-win32-arm64-msvc: 1.30.2
+ lightningcss-win32-x64-msvc: 1.30.2
+
+ longest-streak@3.1.0: {}
+
+ lowlight@1.20.0:
+ dependencies:
+ fault: 1.0.4
+ highlight.js: 10.7.3
+
+ lowlight@3.3.0:
+ dependencies:
+ '@types/hast': 3.0.4
+ devlop: 1.1.0
+ highlight.js: 11.11.1
+
+ lucide-react@0.548.0(react@19.2.0):
+ dependencies:
+ react: 19.2.0
+
+ magic-string@0.30.21:
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ markdown-table@3.0.4: {}
+
+ mdast-util-find-and-replace@3.0.2:
+ dependencies:
+ '@types/mdast': 4.0.4
+ escape-string-regexp: 5.0.0
+ unist-util-is: 6.0.1
+ unist-util-visit-parents: 6.0.2
+
+ mdast-util-from-markdown@2.0.2:
+ dependencies:
+ '@types/mdast': 4.0.4
+ '@types/unist': 3.0.3
+ decode-named-character-reference: 1.2.0
+ devlop: 1.1.0
+ mdast-util-to-string: 4.0.0
+ micromark: 4.0.2
+ micromark-util-decode-numeric-character-reference: 2.0.2
+ micromark-util-decode-string: 2.0.1
+ micromark-util-normalize-identifier: 2.0.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+ unist-util-stringify-position: 4.0.0
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-gfm-autolink-literal@2.0.1:
+ dependencies:
+ '@types/mdast': 4.0.4
+ ccount: 2.0.1
+ devlop: 1.1.0
+ mdast-util-find-and-replace: 3.0.2
+ micromark-util-character: 2.1.1
+
+ mdast-util-gfm-footnote@2.1.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ micromark-util-normalize-identifier: 2.0.1
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-gfm-strikethrough@2.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-gfm-table@2.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ markdown-table: 3.0.4
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-gfm-task-list-item@2.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-gfm@3.1.0:
+ dependencies:
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-gfm-autolink-literal: 2.0.1
+ mdast-util-gfm-footnote: 2.1.0
+ mdast-util-gfm-strikethrough: 2.0.0
+ mdast-util-gfm-table: 2.0.0
+ mdast-util-gfm-task-list-item: 2.0.0
+ mdast-util-to-markdown: 2.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-mdx-expression@2.0.1:
+ dependencies:
+ '@types/estree-jsx': 1.0.5
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-mdx-jsx@3.2.0:
+ dependencies:
+ '@types/estree-jsx': 1.0.5
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ '@types/unist': 3.0.3
+ ccount: 2.0.1
+ devlop: 1.1.0
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ parse-entities: 4.0.2
+ stringify-entities: 4.0.4
+ unist-util-stringify-position: 4.0.0
+ vfile-message: 4.0.3
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-mdxjs-esm@2.0.1:
+ dependencies:
+ '@types/estree-jsx': 1.0.5
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-phrasing@4.1.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ unist-util-is: 6.0.1
+
+ mdast-util-to-hast@13.2.0:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ '@ungap/structured-clone': 1.3.0
+ devlop: 1.1.0
+ micromark-util-sanitize-uri: 2.0.1
+ trim-lines: 3.0.1
+ unist-util-position: 5.0.0
+ unist-util-visit: 5.0.0
+ vfile: 6.0.3
+
+ mdast-util-to-markdown@2.1.2:
+ dependencies:
+ '@types/mdast': 4.0.4
+ '@types/unist': 3.0.3
+ longest-streak: 3.1.0
+ mdast-util-phrasing: 4.1.0
+ mdast-util-to-string: 4.0.0
+ micromark-util-classify-character: 2.0.1
+ micromark-util-decode-string: 2.0.1
+ unist-util-visit: 5.0.0
+ zwitch: 2.0.4
+
+ mdast-util-to-string@4.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+
+ micromark-core-commonmark@2.0.3:
+ dependencies:
+ decode-named-character-reference: 1.2.0
+ devlop: 1.1.0
+ micromark-factory-destination: 2.0.1
+ micromark-factory-label: 2.0.1
+ micromark-factory-space: 2.0.1
+ micromark-factory-title: 2.0.1
+ micromark-factory-whitespace: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-chunked: 2.0.1
+ micromark-util-classify-character: 2.0.1
+ micromark-util-html-tag-name: 2.0.1
+ micromark-util-normalize-identifier: 2.0.1
+ micromark-util-resolve-all: 2.0.1
+ micromark-util-subtokenize: 2.1.0
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-extension-gfm-autolink-literal@2.1.0:
+ dependencies:
+ micromark-util-character: 2.1.1
+ micromark-util-sanitize-uri: 2.0.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-extension-gfm-footnote@2.1.0:
+ dependencies:
+ devlop: 1.1.0
+ micromark-core-commonmark: 2.0.3
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-normalize-identifier: 2.0.1
+ micromark-util-sanitize-uri: 2.0.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-extension-gfm-strikethrough@2.1.0:
+ dependencies:
+ devlop: 1.1.0
+ micromark-util-chunked: 2.0.1
+ micromark-util-classify-character: 2.0.1
+ micromark-util-resolve-all: 2.0.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-extension-gfm-table@2.1.1:
+ dependencies:
+ devlop: 1.1.0
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-extension-gfm-tagfilter@2.0.0:
+ dependencies:
+ micromark-util-types: 2.0.2
+
+ micromark-extension-gfm-task-list-item@2.1.0:
+ dependencies:
+ devlop: 1.1.0
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-extension-gfm@3.0.0:
+ dependencies:
+ micromark-extension-gfm-autolink-literal: 2.1.0
+ micromark-extension-gfm-footnote: 2.1.0
+ micromark-extension-gfm-strikethrough: 2.1.0
+ micromark-extension-gfm-table: 2.1.1
+ micromark-extension-gfm-tagfilter: 2.0.0
+ micromark-extension-gfm-task-list-item: 2.1.0
+ micromark-util-combine-extensions: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-factory-destination@2.0.1:
+ dependencies:
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-factory-label@2.0.1:
+ dependencies:
+ devlop: 1.1.0
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-factory-space@2.0.1:
+ dependencies:
+ micromark-util-character: 2.1.1
+ micromark-util-types: 2.0.2
+
+ micromark-factory-title@2.0.1:
+ dependencies:
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-factory-whitespace@2.0.1:
+ dependencies:
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-util-character@2.1.1:
+ dependencies:
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-util-chunked@2.0.1:
+ dependencies:
+ micromark-util-symbol: 2.0.1
+
+ micromark-util-classify-character@2.0.1:
+ dependencies:
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-util-combine-extensions@2.0.1:
+ dependencies:
+ micromark-util-chunked: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-util-decode-numeric-character-reference@2.0.2:
+ dependencies:
+ micromark-util-symbol: 2.0.1
+
+ micromark-util-decode-string@2.0.1:
+ dependencies:
+ decode-named-character-reference: 1.2.0
+ micromark-util-character: 2.1.1
+ micromark-util-decode-numeric-character-reference: 2.0.2
+ micromark-util-symbol: 2.0.1
+
+ micromark-util-encode@2.0.1: {}
+
+ micromark-util-html-tag-name@2.0.1: {}
+
+ micromark-util-normalize-identifier@2.0.1:
+ dependencies:
+ micromark-util-symbol: 2.0.1
+
+ micromark-util-resolve-all@2.0.1:
+ dependencies:
+ micromark-util-types: 2.0.2
+
+ micromark-util-sanitize-uri@2.0.1:
+ dependencies:
+ micromark-util-character: 2.1.1
+ micromark-util-encode: 2.0.1
+ micromark-util-symbol: 2.0.1
+
+ micromark-util-subtokenize@2.1.0:
+ dependencies:
+ devlop: 1.1.0
+ micromark-util-chunked: 2.0.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-util-symbol@2.0.1: {}
+
+ micromark-util-types@2.0.2: {}
+
+ micromark@4.0.2:
+ dependencies:
+ '@types/debug': 4.1.12
+ debug: 4.4.3
+ decode-named-character-reference: 1.2.0
+ devlop: 1.1.0
+ micromark-core-commonmark: 2.0.3
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-chunked: 2.0.1
+ micromark-util-combine-extensions: 2.0.1
+ micromark-util-decode-numeric-character-reference: 2.0.2
+ micromark-util-encode: 2.0.1
+ micromark-util-normalize-identifier: 2.0.1
+ micromark-util-resolve-all: 2.0.1
+ micromark-util-sanitize-uri: 2.0.1
+ micromark-util-subtokenize: 2.1.0
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+ transitivePeerDependencies:
+ - supports-color
+
+ ms@2.1.3: {}
+
+ nanoid@3.3.11: {}
+
+ next-themes@0.3.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
+ dependencies:
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+
+ next@16.0.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
+ dependencies:
+ '@next/env': 16.0.0
+ '@swc/helpers': 0.5.15
+ caniuse-lite: 1.0.30001751
+ postcss: 8.4.31
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ styled-jsx: 5.1.6(react@19.2.0)
+ optionalDependencies:
+ '@next/swc-darwin-arm64': 16.0.0
+ '@next/swc-darwin-x64': 16.0.0
+ '@next/swc-linux-arm64-gnu': 16.0.0
+ '@next/swc-linux-arm64-musl': 16.0.0
+ '@next/swc-linux-x64-gnu': 16.0.0
+ '@next/swc-linux-x64-musl': 16.0.0
+ '@next/swc-win32-arm64-msvc': 16.0.0
+ '@next/swc-win32-x64-msvc': 16.0.0
+ sharp: 0.34.4
+ transitivePeerDependencies:
+ - '@babel/core'
+ - babel-plugin-macros
+
+ ollama@0.5.18:
+ dependencies:
+ whatwg-fetch: 3.6.20
+
+ parse-entities@2.0.0:
+ dependencies:
+ character-entities: 1.2.4
+ character-entities-legacy: 1.1.4
+ character-reference-invalid: 1.1.4
+ is-alphanumerical: 1.0.4
+ is-decimal: 1.0.4
+ is-hexadecimal: 1.0.4
+
+ parse-entities@4.0.2:
+ dependencies:
+ '@types/unist': 2.0.11
+ character-entities-legacy: 3.0.0
+ character-reference-invalid: 2.0.1
+ decode-named-character-reference: 1.2.0
+ is-alphanumerical: 2.0.1
+ is-decimal: 2.0.1
+ is-hexadecimal: 2.0.1
+
+ parse5@7.3.0:
+ dependencies:
+ entities: 6.0.1
+
+ picocolors@1.1.1: {}
+
+ postcss@8.4.31:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ postcss@8.5.6:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ prismjs@1.27.0: {}
+
+ prismjs@1.30.0: {}
+
+ property-information@5.6.0:
+ dependencies:
+ xtend: 4.0.2
+
+ property-information@6.5.0: {}
+
+ property-information@7.1.0: {}
+
+ react-dom@19.2.0(react@19.2.0):
+ dependencies:
+ react: 19.2.0
+ scheduler: 0.27.0
+
+ react-markdown@10.1.0(@types/react@19.2.2)(react@19.2.0):
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ '@types/react': 19.2.2
+ devlop: 1.1.0
+ hast-util-to-jsx-runtime: 2.3.6
+ html-url-attributes: 3.0.1
+ mdast-util-to-hast: 13.2.0
+ react: 19.2.0
+ remark-parse: 11.0.0
+ remark-rehype: 11.1.2
+ unified: 11.0.5
+ unist-util-visit: 5.0.0
+ vfile: 6.0.3
+ transitivePeerDependencies:
+ - supports-color
+
+ react-syntax-highlighter@16.0.0(react@19.2.0):
+ dependencies:
+ '@babel/runtime': 7.28.4
+ highlight.js: 10.7.3
+ highlightjs-vue: 1.0.0
+ lowlight: 1.20.0
+ prismjs: 1.30.0
+ react: 19.2.0
+ refractor: 5.0.0
+
+ react@19.2.0: {}
+
+ refractor@3.6.0:
+ dependencies:
+ hastscript: 6.0.0
+ parse-entities: 2.0.0
+ prismjs: 1.27.0
+
+ refractor@5.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/prismjs': 1.26.5
+ hastscript: 9.0.1
+ parse-entities: 4.0.2
+
+ rehype-highlight@7.0.2:
+ dependencies:
+ '@types/hast': 3.0.4
+ hast-util-to-text: 4.0.2
+ lowlight: 3.3.0
+ unist-util-visit: 5.0.0
+ vfile: 6.0.3
+
+ rehype-raw@7.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+ hast-util-raw: 9.1.0
+ vfile: 6.0.3
+
+ remark-gfm@4.0.1:
+ dependencies:
+ '@types/mdast': 4.0.4
+ mdast-util-gfm: 3.1.0
+ micromark-extension-gfm: 3.0.0
+ remark-parse: 11.0.0
+ remark-stringify: 11.0.0
+ unified: 11.0.5
+ transitivePeerDependencies:
+ - supports-color
+
+ remark-parse@11.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ mdast-util-from-markdown: 2.0.2
+ micromark-util-types: 2.0.2
+ unified: 11.0.5
+ transitivePeerDependencies:
+ - supports-color
+
+ remark-rehype@11.1.2:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ mdast-util-to-hast: 13.2.0
+ unified: 11.0.5
+ vfile: 6.0.3
+
+ remark-stringify@11.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ mdast-util-to-markdown: 2.1.2
+ unified: 11.0.5
+
+ scheduler@0.27.0: {}
+
+ semver@7.7.3:
+ optional: true
+
+ sharp@0.34.4:
+ dependencies:
+ '@img/colour': 1.0.0
+ detect-libc: 2.1.2
+ semver: 7.7.3
+ optionalDependencies:
+ '@img/sharp-darwin-arm64': 0.34.4
+ '@img/sharp-darwin-x64': 0.34.4
+ '@img/sharp-libvips-darwin-arm64': 1.2.3
+ '@img/sharp-libvips-darwin-x64': 1.2.3
+ '@img/sharp-libvips-linux-arm': 1.2.3
+ '@img/sharp-libvips-linux-arm64': 1.2.3
+ '@img/sharp-libvips-linux-ppc64': 1.2.3
+ '@img/sharp-libvips-linux-s390x': 1.2.3
+ '@img/sharp-libvips-linux-x64': 1.2.3
+ '@img/sharp-libvips-linuxmusl-arm64': 1.2.3
+ '@img/sharp-libvips-linuxmusl-x64': 1.2.3
+ '@img/sharp-linux-arm': 0.34.4
+ '@img/sharp-linux-arm64': 0.34.4
+ '@img/sharp-linux-ppc64': 0.34.4
+ '@img/sharp-linux-s390x': 0.34.4
+ '@img/sharp-linux-x64': 0.34.4
+ '@img/sharp-linuxmusl-arm64': 0.34.4
+ '@img/sharp-linuxmusl-x64': 0.34.4
+ '@img/sharp-wasm32': 0.34.4
+ '@img/sharp-win32-arm64': 0.34.4
+ '@img/sharp-win32-ia32': 0.34.4
+ '@img/sharp-win32-x64': 0.34.4
+ optional: true
+
+ source-map-js@1.2.1: {}
+
+ space-separated-tokens@1.1.5: {}
+
+ space-separated-tokens@2.0.2: {}
+
+ stringify-entities@4.0.4:
+ dependencies:
+ character-entities-html4: 2.1.0
+ character-entities-legacy: 3.0.0
+
+ style-to-js@1.1.18:
+ dependencies:
+ style-to-object: 1.0.11
+
+ style-to-object@1.0.11:
+ dependencies:
+ inline-style-parser: 0.2.4
+
+ styled-jsx@5.1.6(react@19.2.0):
+ dependencies:
+ client-only: 0.0.1
+ react: 19.2.0
+
+ tailwind-merge@3.3.1: {}
+
+ tailwindcss@4.1.16: {}
+
+ tapable@2.3.0: {}
+
+ trim-lines@3.0.1: {}
+
+ trough@2.2.0: {}
+
+ tslib@2.8.1: {}
+
+ typescript@5.9.3: {}
+
+ undici-types@6.21.0: {}
+
+ unified@11.0.5:
+ dependencies:
+ '@types/unist': 3.0.3
+ bail: 2.0.2
+ devlop: 1.1.0
+ extend: 3.0.2
+ is-plain-obj: 4.1.0
+ trough: 2.2.0
+ vfile: 6.0.3
+
+ unist-util-find-after@5.0.0:
+ dependencies:
+ '@types/unist': 3.0.3
+ unist-util-is: 6.0.1
+
+ unist-util-is@6.0.1:
+ dependencies:
+ '@types/unist': 3.0.3
+
+ unist-util-position@5.0.0:
+ dependencies:
+ '@types/unist': 3.0.3
+
+ unist-util-stringify-position@4.0.0:
+ dependencies:
+ '@types/unist': 3.0.3
+
+ unist-util-visit-parents@6.0.2:
+ dependencies:
+ '@types/unist': 3.0.3
+ unist-util-is: 6.0.1
+
+ unist-util-visit@5.0.0:
+ dependencies:
+ '@types/unist': 3.0.3
+ unist-util-is: 6.0.1
+ unist-util-visit-parents: 6.0.2
+
+ vfile-location@5.0.3:
+ dependencies:
+ '@types/unist': 3.0.3
+ vfile: 6.0.3
+
+ vfile-message@4.0.3:
+ dependencies:
+ '@types/unist': 3.0.3
+ unist-util-stringify-position: 4.0.0
+
+ vfile@6.0.3:
+ dependencies:
+ '@types/unist': 3.0.3
+ vfile-message: 4.0.3
+
+ web-namespaces@2.0.1: {}
+
+ whatwg-fetch@3.6.20: {}
+
+ xtend@4.0.2: {}
+
+ zwitch@2.0.4: {}
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
new file mode 100644
index 0000000..fc3aeab
--- /dev/null
+++ b/pnpm-workspace.yaml
@@ -0,0 +1,2 @@
+ignoredBuiltDependencies:
+ - sharp
diff --git a/postcss.config.mjs b/postcss.config.mjs
new file mode 100644
index 0000000..61e3684
--- /dev/null
+++ b/postcss.config.mjs
@@ -0,0 +1,7 @@
+const config = {
+ plugins: {
+ "@tailwindcss/postcss": {},
+ },
+};
+
+export default config;
diff --git a/public/file.svg b/public/file.svg
new file mode 100644
index 0000000..004145c
--- /dev/null
+++ b/public/file.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/globe.svg b/public/globe.svg
new file mode 100644
index 0000000..567f17b
--- /dev/null
+++ b/public/globe.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/next.svg b/public/next.svg
new file mode 100644
index 0000000..5174b28
--- /dev/null
+++ b/public/next.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/vercel.svg b/public/vercel.svg
new file mode 100644
index 0000000..7705396
--- /dev/null
+++ b/public/vercel.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/window.svg b/public/window.svg
new file mode 100644
index 0000000..b2b2a44
--- /dev/null
+++ b/public/window.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..3a13f90
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,34 @@
+{
+ "compilerOptions": {
+ "target": "ES2017",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "react-jsx",
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "paths": {
+ "@/*": ["./*"]
+ }
+ },
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx",
+ ".next/types/**/*.ts",
+ ".next/dev/types/**/*.ts",
+ "**/*.mts"
+ ],
+ "exclude": ["node_modules"]
+}