diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 95783a1..350ef4d 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -37,6 +37,7 @@ jobs:
- name: Start API
run: |
+ mkdir -p output
uvicorn main:app --host 0.0.0.0 --port 8000 &
sleep 5
- name: Check API health
diff --git a/.gitignore b/.gitignore
index 3b13e98..1a74220 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,7 @@ report-generator-demo/frontend/dist/
.env
node_modules/
frontend/dist/
+frontend/node_modules
report/ssr-dist/
report/node_modules/
*.lock
diff --git a/README.md b/README.md
index 2ccfd66..669b4b9 100644
--- a/README.md
+++ b/README.md
@@ -1,99 +1,151 @@
-# report-generator-demo
+# Automatic Reporting
-Projeto com duas partes:
+Sistema de geração de relatórios PDF para o Data Nordeste. Combina dados de CSVs com textos descritivos do Google Docs e renderiza relatórios visuais em PDF.
-- **API em FastAPI** para gerar relatórios do Data Nordeste.
-- **Frontend em React + Vite** para escolher macrotema e cidade e disparar o relatório.
+## Arquitetura
-## Estrutura
+```
+Browser (React SPA)
+ │
+ ▼
+FastAPI (Python) ──────► Node.js (React SSR)
+ │ │
+ │ ▼
+ │ HTML renderizado
+ ▼ │
+WeasyPrint │
+ │ │
+ ▼ │
+ PDF ◄─────────────────────┘
+```
+
+- **Python/FastAPI**: processa dados, gerencia CSV, Docs, gráficos
+- **React SSR**: renderiza HTML da capa e conteúdo via `renderToStaticMarkup`
+- **WeasyPrint**: converte HTML em PDF
-- `main.py` — API FastAPI
-- `citys.txt` — lista de cidades usadas no frontend
-- `demografia.csv` — base de dados do relatório
-- `output/` — arquivos HTML/PDF e gráficos gerados
-- `frontend/` — interface web
+## Stack
+
+- **Backend**: FastAPI (Python 3.11+)
+- **Frontend**: React + Vite
+- **PDF**: WeasyPrint + React SSR
+- **Build**: npm workspaces (monorepo)
## Requisitos
-- Python 3.10+
+- Python 3.11+
- Node.js 18+
+- npm
-## Instalação da API
-
-Dentro da pasta `report-generator-demo`:
+## Quick start
```bash
-python -m venv .venv
-source .venv/bin/activate
-pip install -r requirements.txt
+# Ativar ambiente virtual e rodar
+source .venv/bin/activate && ./build.sh
```
-## Como executar a API
+Isso instala dependências Python e JS, builda o SSR e o frontend, e sobe os servidores.
+
+## Componentes do build
```bash
-uvicorn main:app --reload --host 0.0.0.0 --port 8000
+pip install -r requirements.txt # Python deps
+npm install # Node deps
+npm run build -w report # Build React SSR → report/ssr-dist/
+npm run build -w frontend # Build SPA → frontend/dist/
```
-Se quiser usar um Google Docs específico como texto-base do relatório:
+## Como rodar
```bash
-export DATANE_DOCS_URL="https://docs.google.com/document/d/SEU_DOC_ID/edit"
+source .venv/bin/activate && ./build.sh
```
-Se essa variável não for definida, a API usa o documento padrão já configurado no `main.py`.
+O `build.sh` inicia:
+- Servidor SSR (Node.js) na porta 3001
+- API FastAPI na porta 8000
-## Instalação do frontend
+## Endpoints da API
-Dentro da pasta `report-generator-demo/frontend`:
+| Método | URL | Descrição |
+|--------|-----|-----------|
+| GET | `/cities` | Lista de cidades |
+| GET | `/macrotemas` | Lista de macrotemas |
+| GET | `/relatorios` | Relatórios gerados |
+| GET | `/relatorio/{cidade}?macrotema={tema}` | Gera relatório |
+| DELETE | `/relatorios/{arquivo}` | Remove relatório |
-```bash
-yarn install
-```
+## Macrotemas
-## Como executar o frontend
+- `todos` — Todos os temas concatenados
+- `demografia` — Demografia
+- `educacao` — Educação
+- `saude` — Saúde
+- `economia-renda` — Economia e Renda
+- `saneamento` — Saneamento
+- `hidraulica` — Segurança Hídrica
-```bash
-yarn dev
-```
-
-O frontend abre normalmente em `http://localhost:5173`.
+## Estrutura de diretórios
-Se quiser, também funciona:
-
-```bash
-yarn start
+```
+/
+├── main.py # FastAPI app
+├── reports.py # Lógica de geração (orquestrador)
+├── config.py # Configurações e variáveis de ambiente
+├── plotting.py # Gráficos matplotlib
+│
+├── frontend/ # SPA React (interface)
+│ └── src/App.jsx
+│
+├── report/ # React SSR (renderização PDF)
+│ └── src/components/ # Cover, Report, Brand, etc.
+│
+├── utils/
+│ ├── cover.py # Monta objeto "cover" para o React
+│ ├── renderer.py # Converte markdown Docs → HTML
+│ ├── ssr.py # Chama Node.js para renderizar
+│ ├── docs.py # Baixa Google Docs
+│ ├── cities.py # Filtro e список cidades
+│ ├── macrotemas.py # Config dos temas
+│ └── maps.py # Mapas (Contentful ou gerado)
+│
+├── docs/
+│ └── ARCHITECTURE.md # Documentação da arquitetura
+│
+└── output/ # Relatórios gerados (.html + .pdf)
```
-## Como usar
-1. Rode a API.
-2. Rode o frontend.
-3. Abra o frontend no navegador.
-4. Clique em **Gerar relatório**.
-5. Escolha o macrotema e a cidade.
-6. Clique em **Gerar relatório** novamente para abrir o relatório.
+### Resumo das etapas
-Por enquanto, o macrotema é apenas visual no formulário; a API recebe apenas a cidade.
+| Etapa | O que acontece |
+|-------|----------------|
+| 1 | Carrega CSV do tema selecionado (pandas) |
+| 2 | Filtra linhas pela cidade informada |
+| 3 | Gera gráficos matplotlib (só se demografia) |
+| 4 | Busca texto no Google Docs (com cache) |
+| 5 | Extrai seções marcadas (##resumo_tema, etc.) |
+| 6 | Preenche cover com métricas, score, indicadores |
+| 7 | Converte markdown dos Docs em HTML |
+| 8 | React SSR renderiza HTML final |
+| 9 | Salva HTML, PDF gerado em background |
-## O que a API faz
-Quando você acessa `/relatorio/{cidade}`:
+## Variáveis de ambiente
-1. lê o CSV `demografia.csv`;
-2. procura a cidade informada, com ou sem UF;
-3. busca o texto-base no Google Docs;
-4. renderiza o HTML do relatório;
-5. gera gráfico de população por sexo;
-6. salva os arquivos em `output/`;
-7. devolve o HTML no navegador.
+```bash
+# URLs dos CSVs (por tema)
+DEMOGRAFIA_CSV_URL, EDUCACAO_CSV_URL, SAUDE_CSV_URL, etc.
-## Exemplo de uso direto da API
+# URLs dos Docs (por tema)
+DEMOGRAFIA_DOCS_URL, EDUCACAO_DOCS_URL, etc.
-- `http://127.0.0.1:8000/docs`
-- `http://127.0.0.1:8000/relatorio/Caruaru%20(PE)`
+# Contentful (mapas)
+CONTENTFUL_SPACE_ID, CONTENTFUL_ACCESS_TOKEN
+```
-## Arquivos importantes do frontend
+## Docker
-- `frontend/src/App.jsx` — tela principal
-- `frontend/src/styles.css` — estilos da interface
-- `frontend/package.json` — scripts do frontend
+```bash
+docker build -t automatic-reporting .
+docker run -p 8000:8000 automatic-reporting
+```
\ No newline at end of file
diff --git a/build.sh b/build.sh
new file mode 100755
index 0000000..4d204f8
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+pip install -r requirements.txt
+npm install
+npm run build -w report
+npm run build -w frontend
+pkill -f "node report/ssr-dist/server.js"
+node report/ssr-dist/server.js & uvicorn main:app --reload
diff --git a/config.py b/config.py
index 82bf05e..d70191d 100644
--- a/config.py
+++ b/config.py
@@ -35,86 +35,7 @@ def get_config_value(name: str) -> str | None:
SANEAMENTO_DOCS_URL = get_config_value("SANEAMENTO_DOCS_URL")
HIDRAULICA_DOCS_URL = get_config_value("HIDRAULICA_DOCS_URL")
-MACROTEMAS = {
- "demografia": {
- "nome": "Demografia",
- "docs_url": DEMOGRAFIA_DOCS_URL,
- "docs_env": "DEMOGRAFIA_DOCS_URL",
- "csv_url": DEMOGRAFIA_CSV_URL,
- "csv_env": "DEMOGRAFIA_CSV_URL",
- },
- "educacao": {
- "nome": "Educação",
- "docs_url": EDUCACAO_DOCS_URL,
- "docs_env": "EDUCACAO_DOCS_URL",
- "csv_url": EDUCACAO_CSV_URL,
- "csv_env": "EDUCACAO_CSV_URL",
- },
- "saude": {
- "nome": "Saúde",
- "docs_url": SAUDE_DOCS_URL,
- "docs_env": "SAUDE_DOCS_URL",
- "csv_url": SAUDE_CSV_URL,
- "csv_env": "SAUDE_CSV_URL",
- },
- "economia-renda": {
- "nome": "Economia e Renda",
- "docs_url": ECONOMIA_RENDA_DOCS_URL,
- "docs_env": "ECONOMIA_RENDA_DOCS_URL",
- "csv_url": ECONOMIA_RENDA_CSV_URL,
- "csv_env": "ECONOMIA_RENDA_CSV_URL",
- },
- "saneamento": {
- "nome": "Saneamento",
- "docs_url": SANEAMENTO_DOCS_URL,
- "docs_env": "SANEAMENTO_DOCS_URL",
- "csv_url": SANEAMENTO_CSV_URL,
- "csv_env": "SANEAMENTO_CSV_URL",
- },
- "hidraulica": {
- "nome": "Hidráulica",
- "docs_url": HIDRAULICA_DOCS_URL,
- "docs_env": "HIDRAULICA_DOCS_URL",
- "csv_url": HIDRAULICA_CSV_URL,
- "csv_env": "HIDRAULICA_CSV_URL",
- },
-}
-
-MACROTEMA_SECOES = {
- "demografia": {
- "numero": "01",
- "titulo": "Demografia",
- "aliases": ["demografia"],
- },
- "educacao": {
- "numero": "02",
- "titulo": "Educação",
- "aliases": ["educacao", "educação"],
- },
- "saude": {
- "numero": "03",
- "titulo": "Saúde",
- "aliases": ["saude", "saúde"],
- },
- "economia-renda": {
- "numero": "04",
- "titulo": "Economia e Renda",
- "aliases": ["economia", "economia e renda"],
- },
- "saneamento": {
- "numero": "05",
- "titulo": "Infraestrutura e Saneamento",
- "aliases": ["saneamento", "infraestrutura e saneamento"],
- },
- "hidraulica": {
- "numero": "06",
- "titulo": "Segurança Hídrica",
- "aliases": ["hidraulica", "hidráulica", "seguranca hidrica", "segurança hídrica"],
- },
-}
-OUTPUT_DIR = BASE_DIR / "output"
-CITIES_FILE = BASE_DIR / "citys.txt"
def resolve_csv_source(source: str | None, env_name: str = "CSV_URL") -> str | Path:
if not source:
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
index 413130e..facd165 100644
--- a/docs/ARCHITECTURE.md
+++ b/docs/ARCHITECTURE.md
@@ -12,104 +12,173 @@ Cada camada tem uma responsabilidade única: Python processa dados, React render
---
-## Fluxo detalhado
+## Fluxo de execução
+
+### Visão geral em 5 passos
```
-┌─────────────────────────────────────────────────────────────────────┐
-│ Browser (React SPA) │
-│ frontend/src/App.jsx │
-│ Interface pra selecionar cidade + macrotema │
-│ Comunica com FastAPI via fetch() │
-└───────────────────────────┬─────────────────────────────────────────┘
- │ GET /relatorio/{cidade}?macrotema={tema}
- ▼
-┌─────────────────────────────────────────────────────────────────────┐
-│ FastAPI (Python) — main.py :8000 │
-│ │
-│ reports.py — gerar_relatorio_handler(): │
-│ │
-│ 1. Lê CSV do Google Drive (pandas) │
-│ └── utils/macrotemas.py — URLs de cada tema (demografia, │
-│ educação, saúde, etc.) │
-│ │
-│ 2. Filtra por cidade │
-│ └── utils/cities.py — filtrar_linhas_por_cidade() │
-│ │
-│ 3. Lê texto descritivo do Google Docs │
-│ └── utils/docs.py — carregar_texto_do_docs() │
-│ └── extrai resumo_tema / descricao_tema via marcadores │
-│ │
-│ 4. Gera gráficos matplotlib │
-│ └── plotting.py — gerar_grafico_sexo(), _porte(), _top_cidades()│
-│ │
-│ 5. Monta objeto "cover" (dicionário Python) │
-│ └── utils/cover.py — montar_capa_relatorio() │
-│ ├── metricas (4 cards: área, população, IDH, PIB) │
-│ ├── score (valor, max, status, descricao) │
-│ ├── macrotema (nome, icone, status, resumo, indicadores) │
-│ └── data_extenso, cidade_nome, uf │
-│ │
-│ 6. Converte texto markdown dos Docs → HTML │
-│ └── utils/renderer.py — texto_para_html() │
-│ ├── processa %%chart, ##componente, $placeholders │
-│ ├── gera
,
, , , figure-caption │
-│ └── output: string HTML pronta pra injetar no template │
-│ │
-│ 7. Chama React SSR (HTTP POST p/ servidor persistente) ─────┐ │
-│ └── utils/ssr.py — render_react_ssr(props) │ │
-│ │ httpx.post("http://127.0.0.1:3001")│ │
-│ │
-│ 8. WeasyPrint converte HTML → PDF │
-│ └── HTML(string=..., base_url=...).write_pdf(...) │
-│ │
-│ 9. Salva .html e .pdf em output/ │
-│ 10. Retorna HTMLResponse pro browser │
-└───────────────────────────────────────────────────┬─────────────────┘
- │
- ▼
-┌─────────────────────────────────────────────────────────────────────┐
-│ Node.js — React SSR (report/) │
-│ │
-│ Chamado via: subprocess.run(["node", "ssr-dist/ssr-entry.js", │
-│ json.dumps(props)]) │
-│ │
-│ src/ssr-entry.jsx: │
-│ 1. Lê JSON de process.argv[2] │
-│ 2. renderToStaticMarkup()│
-│ 3. Escreve HTML em stdout │
-│ │
-│ Componentes (src/components/): │
-│ │
-│ Report.jsx ─── componente raiz │
-│ ├── , (styles), │
-│ ├── PdfPageHeader (só aparece no @media print) │
-│ ├── Cover ──────────────────────────────────────────────────┐ │
-│ │ ├── cover-header (brand + data) │ │
-│ │ ├── cover-city (nome da cidade) │ │
-│ │ ├── MetricCard × 4 (grid de métricas) │ │
-│ │ ├── RadarChart (SVG fixo) │ │
-│ │ ├── ScoreCard (valor + status) │ │
-│ │ ├── ScoreLegend (legenda fixa) │ │
-│ │ ├── MacrothemeCard (ícone + nome + status) │ │
-│ │ ├── MacrothemeSummary (resumo) │ │
-│ │ └── IndicatorScoreCard × N (grid de indicadores) │ │
-│ ├── ThemeDetail (se houver descricao_paragrafos) │ │
-│ ├── (HTML injetado via │ │
-│ │ dangerouslySetInnerHTML) │ │
-│ └── PdfFooter │ │
-│ │
-│ Brand.jsx — SVGs e ícones: │
-│ MACROTHEME_ICONS: health, book, people, drop, water + fallback │
-│ INDICATOR_ICONS: hospital, vaccine, birth, shield, book, ... │
-│ MetricIcon, ScoreIcon, CoverBrand, PdfPageHeaderBrand │
-│ │
-│ styles.js — CSS (extraído do TEMPLATE_STRING original) │
-│ Inclui @page, @media print, @media (max-width) │
-└─────────────────────────────────────────────────────────────────────┘
+┌──────────────────┐ GET /relatorio/{cidade}?macrotema={tema} ┌─────────────────────┐
+│ Browser (SPA) │ ───────────────────────────────────────────────▶ │ FastAPI :8000 │
+│ frontend/src/ │ │ reports.py │
+└──────────────────┘ └──────────┬──────────┘
+ ▲ │
+ │ HTMLResponse ▼
+ │ ┌─────────────────────┐
+ │ │ 1. Carrega CSV │
+ │ │ 2. Filtra cidade │
+ │ │ 3. Carrega Docs │
+ │ │ 4. Gera gráficos │
+ │ │ 5. Prepara cover │
+ │ │ 6. Converte Docs→ │
+ │ │ HTML │
+ │ └──────────┬──────────┘
+ │ │
+ │ ▼
+ │ ┌─────────────────────┐
+ │ │ React SSR (Node) │
+ │ │ render_react_ssr() │
+ │ │ renderiza cover + │
+ │ │ docsHtml em HTML │
+ │ └──────────┬──────────┘
+ │ │
+ │ ▼
+ │ ┌─────────────────────┐
+ │ │ WeasyPrint │
+ │ │ HTML → PDF │
+ │ └─────────────────────┘
+ │
+ │ HTML (one-shot, após PDF em background)
+ ◀───────────────────────────────────────────────────────────
```
---
+### Fluxo detalhado por etapas
+
+#### 1. Usuário abre formulário no browser (App.jsx)
+
+```jsx
+// frontend/src/App.jsx
+const url = `${API_BASE}/relatorio/${encodeURIComponent(cidade)}?macrotema=${macrotema}`;
+window.open(url, "_blank");
+```
+
+O browser abre uma nova aba com a URL: `GET /relatorio/{cidade}?macrotema={tema}`
+
+#### 2. FastAPI recebe a requisição (main.py)
+
+```python
+@app.get("/relatorio/{cidade}", response_class=HTMLResponse)
+async def gerar_relatorio(cidade: str, macrotema: str = "demografia", ...):
+ return await gerar_relatorio_handler(cidade, macrotema, charts, background_tasks=background_tasks)
+```
+
+#### 3. `gerar_relatorio_handler` processa dados (reports.py)
+
+O handler executa em loop para cada macrotema solicitado:
+
+**3.1 Para cada macrotema — carrega CSV:**
+```python
+df = pd.read_csv(csv_source, delimiter=";")
+df = normalizar_colunas_macrotema(df, macrotema_slug)
+linhas_df = filtrar_linhas_por_cidade(df, cidade) # utils/cities.py
+linhas_macrotema = linhas_df.to_dict("records")
+```
+
+**3.2 Na primeira iteração — gera cover:**
+```python
+cover = montar_capa_relatorio(linhas[0], gerado_em, macrotema_dados["nome"])
+# utils/cover.py — retorna dicionário com métricas, score, macrotema, indicadores
+```
+
+**3.3 Se demografia — gera gráficos:**
+```python
+chart_file = gerar_grafico_sexo(linhas_macrotema[0], OUTPUT_DIR, safe_report)
+chart_file = gerar_grafico_porte(df, OUTPUT_DIR, safe_report)
+chart_file = gerar_grafico_top_cidades(df, OUTPUT_DIR)
+# plotting.py — matplotlib
+```
+
+**3.4 Carrega texto dos Docs:**
+```python
+docs_texto = carregar_texto_do_docs(docs_url) # utils/docs.py
+# Busca texto do Google Docs e faz cache local
+```
+
+**3.5 Extrai seções do documento:**
+```python
+resumo_tema, docs_texto = extrair_resumo_tema(docs_texto)
+resumo_relatorio, docs_texto = extrair_resumo_relatorio(docs_texto)
+resumo_cidade, docs_texto = extrair_resumo_cidade(docs_texto)
+descricao_tema, docs_texto = extrair_descricao_tema(docs_texto)
+# Extrai blocos marcados no Docs (ex: ##resumo_tema, ##descricao_tema)
+```
+
+**3.6 Preenche cover (só na primeira iteração):**
+```python
+cover["macrotema"]["resumo"] = substituir_placeholders(resumo_tema, ...)
+cover["macrotema"]["descricao"] = substituir_placeholders(descricao_tema, ...)
+```
+
+**3.7 Detecta mapa:**
+```python
+if "*mapa_geografico" in docs_texto:
+ mapa_principal = render_mapa_marker(linhas_macrotema[0], safe_report)
+# utils/renderer.py — tenta Contentful ou gera mapa local
+```
+
+**3.8 Converte texto Docs em HTML:**
+```python
+docs_html_parts.append(
+ texto_para_html(docs_texto_sem_mapa, linhas_macrotema[0], namespace=macrotema_slug)
+)
+# utils/renderer.py — processa marcadores (%%grafico, ##componente, $placeholders)
+```
+
+#### 4. Chama React SSR (reports.py → ssr.py → Node)
+
+```python
+html_content = await render_react_ssr({
+ "cover": cover,
+ "docsHtml": docs_html, # HTML de todos os macrotemas concatenados
+ "dados": linhas,
+})
+```
+
+O Python inicia um **subprocess Node.js** que executa React no servidor:
+
+```
+reports.py ssr.py Node.js
+ │ │ │
+ │ render_react_ssr(props) ───────▶│ │
+ │ │ subprocess.run(["node", │
+ │ │ "entry.js", JSON]) ──────▶│
+ │ │ │ React.createElement()
+ │ │ │ renderToStaticMarkup()
+ │ │◀──── HTML via stdout ────────│
+ │◀─── HTML string ─────────────────│ │
+```
+
+O React **não chama Python** — é puramente renderização one-shot. Node.js recebe JSON via argumento, renderiza, e devolve HTML via stdout.
+
+#### 5. Salva HTML e agenda PDF (reports.py)
+
+```python
+output_file = OUTPUT_DIR / f"relatorio_{safe_report}.html"
+output_file.write_text(html_content, encoding="utf-8")
+
+pdf_file = OUTPUT_DIR / f"relatorio_{safe_report}.pdf"
+background_tasks.add_task(_gerar_pdf, html_content, pdf_file)
+```
+
+O PDF é gerado em background via **WeasyPrint** (não é blocking).
+
+#### 6. Browser recebe HTML e mostra relatório
+
+O browser recebe o HTML já renderizado (cover + conteúdo). O PDF é gerado em paralelo.
+
+---
+
## Estrutura de diretórios
```
@@ -118,14 +187,12 @@ Cada camada tem uma responsabilidade única: Python processa dados, React render
├── config.py # Config (OUTPUT_DIR, variáveis de ambiente)
├── reports.py # Lógica de geração de relatório (orquestrador)
├── plotting.py # Gráficos matplotlib (sexo, porte, top cidades)
-├── generate_report.py # CLI tool standalone (gera HTML de CSV)
├── requirements.txt # Dependências Python
├── Dockerfile # Build em 3 estágios
│
├── frontend/ # SPA React (interface do usuário)
│ ├── package.json
│ ├── vite.config.js
-│ ├── index.html
│ └── src/
│ ├── main.jsx # Entry point
│ ├── App.jsx # Componente principal (formulário + lista)
@@ -134,26 +201,25 @@ Cada camada tem uma responsabilidade única: Python processa dados, React render
├── report/ # React SSR (geração de PDF)
│ ├── package.json
│ ├── vite.config.js
-│ ├── src/
-│ │ ├── ssr-entry.jsx # Entry point SSR (lê JSON, chama renderToStaticMarkup)
-│ │ ├── styles.js # CSS do relatório PDF
-│ │ └── components/
-│ │ ├── Report.jsx # Componente raiz
-│ │ ├── Cover.jsx # Capa
-│ │ ├── Brand.jsx # SVGs e ícones
-│ │ ├── ThemeDetail.jsx # Detalhamento do tema
-│ │ └── PdfLayout.jsx # Header/footer do PDF
-│ └── ssr-dist/ # Build output (gitignored)
+│ └── src/
+│ ├── ssr-entry.jsx # Entry point SSR (lê JSON, chama renderToStaticMarkup)
+│ ├── ssr-server.jsx # Servidor HTTP persistente para SSR
+│ ├── styles.js # CSS do relatório PDF
+│ └── components/
+│ ├── Report.jsx # Componente raiz
+│ ├── Cover.jsx # Capa
+│ ├── Brand.jsx # SVGs e ícones
+│ ├── ThemeDetail.jsx # Detalhamento do tema
+│ └── PdfLayout.jsx # Header/footer do PDF
│
├── utils/
│ ├── cover.py # Monta objeto "cover" pros componentes
-│ ├── renderer.py # Converte markdown dos Docs → HTML
+│ ├── renderer.py # Converte markdown dos Docs → HTML
│ ├── ssr.py # Chama Node.js SSR via subprocess
│ ├── docs.py # Baixa e processa Google Docs
│ ├── cities.py # Filtro por cidade
-│ ├── macrotemas.py # Config dos temas (URLs, seções, aliases)
-│ ├── tables.py # Tabela resumo
-│ └── maps.py # Mapas (placeholder)
+│ ├── macrotemas.py # Config dos temas (URLs de CSV/Docs)
+│ └── maps.py # Mapas (Contentful ou gerado localmente)
│
├── output/ # Relatórios gerados (.html + .pdf)
└── assets/ # Recursos estáticos
@@ -171,6 +237,7 @@ O Python monta um dicionário e passa como `props` pro React SSR:
"data_extenso": str, # "27 de maio de 2026"
"cidade_nome": str, # "Recife"
"uf": str, # "PE" (ou "")
+ "mapa_principal": str, # HTML do
com mapa
"metricas": [ # 4 cards
{"rotulo": str, "valor": str, "sufixo": str, "fonte": str, "caption": str}
],
@@ -186,11 +253,16 @@ O Python monta um dicionário e passa como `props` pro React SSR:
"icone": str, # "health" | "book" | "people" | "drop" | "water" | "chart"
"status": str, # "Muito acima da média nacional"
"resumo": str, # texto
- "descricao_paragrafos": [str], # parágrafos de detalhamento (ou [])
+ "descricao_paragrafos": [str],
+ "descricao_html": [str],
"indicadores": [
{"nome": str, "fonte": str, "score": str, "classe": str, "icone": str}
]
- }
+ },
+ "resumo_relatorio": str,
+ "resumo_relatorio_html": [str],
+ "resumo_cidade": str,
+ "resumo_cidade_html": [str],
},
"docsHtml": str, # HTML do Google Docs (processado por texto_para_html)
"dados": [dict] # Linhas do CSV (usado pra loop, conteúdo via docsHtml)
@@ -215,7 +287,6 @@ npm run build -w report # → report/ssr-dist/
pip install -r requirements.txt
# 5. Rodar
-node report/ssr-dist/server.js & # Servidor SSR persistente (porta 3001)
uvicorn main:app --host 0.0.0.0 --port 8000
```
@@ -230,7 +301,7 @@ O Dockerfile tem 4 estágios:
1. `deps` — instala dependências JS (monorepo, uma única `node_modules`)
2. `frontend-build` — compila a SPA React
3. `ssr-build` — compila o bundle SSR (entry + server)
-4. `runtime` — Python + Node.js + WeasyPrint (rota o servidor SSR em background)
+4. `runtime` — Python + Node.js + WeasyPrint
---
@@ -250,50 +321,12 @@ O Dockerfile tem 4 estágios:
| Aspecto | Impacto |
|---|---|
-| **Build extra** | Precisa rodar `npm run build -w report` (antes não tinha build step) |
+| **Build extra** | Precisa rodar `npm run build -w report` |
| **Curva** | Jinja é só HTML + tags; React JSX exige conhecimento do ecossistema |
| **Dois runtimes** | Python + Node.js rodando lado a lado (mais processos que antes só Python) |
---
-## Sugestões de melhoria
-
-### ~~1. Servidor Node persistente~~ ✅ Implementado
-`report/src/ssr-server.jsx` roda um servidor HTTP que mantém o React carregado em memória. Python faz POST em vez de subprocess.
-
-```
-Antes: subprocess.run → ~200ms cada requisição
-Depois: HTTP POST → ~5ms cada requisição
-```
-
-### ~~2. Monorepo com npm workspaces~~ ✅ Implementado
-`package.json` na raiz com `workspaces: ["frontend", "report"]`. React e react-dom instalados uma vez só em `/node_modules`.
-
-```
-/
-├── package.json # workspaces: ["frontend", "report"]
-├── frontend/
-├── report/
-└── node_modules/ # único, compartilhado
-```
-
-### 3. CSS Modules ou Tailwind
-Em vez de uma string CSS gigante em `styles.js`, usar CSS Modules (`Cover.module.css`) ou Tailwind (sem impacto no bundle do cliente, já que é SSR).
-
-### 4. Testes de snapshot nos componentes
-```js
-import { renderToStaticMarkup } from 'react-dom/server';
-import Cover from './Cover';
-
-test('Cover renderiza métricas', () => {
- const html = renderToStaticMarkup();
- expect(html).toContain('metric-card');
- expect(html).toContain('Recife');
-});
-```
-
----
-
## Referências
| Arquivo | Função |
@@ -306,4 +339,4 @@ test('Cover renderiza métricas', () => {
| `report/src/components/Cover.jsx` | Componente React da capa |
| `report/src/styles.js` | CSS do PDF |
-> Documento gerado em 27/05/2026. Mantenha atualizado conforme mudanças na arquitetura.
+> Documento gerado em 27/05/2026. Atualizado em 12/06/2026.
\ No newline at end of file
diff --git a/generate_report.py b/generate_report.py
deleted file mode 100644
index 87f0313..0000000
--- a/generate_report.py
+++ /dev/null
@@ -1,312 +0,0 @@
-from __future__ import annotations
-
-import argparse
-import csv
-from datetime import datetime
-import html
-import io
-from pathlib import Path
-from typing import Any
-from urllib.parse import parse_qs, urlparse
-
-import requests
-
-
-def parse_drive_file_id(value: str) -> str | None:
- if "/d/" in value:
- marker = value.split("/d/", 1)[1]
- return marker.split("/", 1)[0]
-
- parsed = urlparse(value)
- query = parse_qs(parsed.query)
-
- if "id" in query and query["id"]:
- return query["id"][0]
-
- if parsed.netloc and "drive.google.com" in parsed.netloc and parsed.path:
- parts = [part for part in parsed.path.split("/") if part]
- if len(parts) == 1 and parts[0] != "file":
- return parts[0]
-
- if "/" not in value and len(value) > 20:
- return value
-
- return None
-
-
-def build_drive_csv_url(file_id: str) -> str:
- return f"https://drive.google.com/uc?export=download&id={file_id}"
-
-
-def read_csv_from_text(csv_text: str) -> list[dict[str, Any]]:
- sample = csv_text[:4096]
- try:
- dialect = csv.Sniffer().sniff(sample, delimiters=",;\t|")
- except csv.Error:
- dialect = csv.excel
-
- reader = csv.DictReader(io.StringIO(csv_text), dialect=dialect)
- rows: list[dict[str, Any]] = []
- for row in reader:
- cleaned_row = {
- (key or "").strip().lstrip("\ufeff"): (
- value.strip() if isinstance(value, str) else value
- )
- for key, value in row.items()
- }
- rows.append(cleaned_row)
- return rows
-
-
-def fetch_csv_rows(source: str) -> list[dict[str, Any]]:
- path = Path(source)
- if path.exists():
- return read_csv_from_text(path.read_text(encoding="utf-8"))
-
- file_id = parse_drive_file_id(source)
- url = build_drive_csv_url(file_id) if file_id else source
-
- response = requests.get(url, timeout=30)
- response.raise_for_status()
-
- content_type = response.headers.get("Content-Type", "")
- if "text/html" in content_type.lower() and "drive.google.com" in url:
- raise ValueError(
- "Não foi possível baixar o CSV do Google Drive. "
- "Verifique se o arquivo está compartilhado como 'Qualquer pessoa com o link'."
- )
-
- return read_csv_from_text(response.text)
-
-
-def _set_nested_value(target: dict[str, Any], keys: list[str], value: Any) -> None:
- current = target
- for key in keys[:-1]:
- if key not in current or not isinstance(current[key], dict):
- current[key] = {}
- current = current[key]
- current[keys[-1]] = value
-
-
-def nest_dot_keys(row: dict[str, Any]) -> dict[str, Any]:
- nested: dict[str, Any] = {}
- for key, value in row.items():
- if "." in key:
- _set_nested_value(nested, key.split("."), value)
- else:
- nested[key] = value
- return nested
-
-
-def normalize_text(value: str) -> str:
- return " ".join(value.strip().lower().split())
-
-
-def find_city_column(columns: list[str], explicit_column: str | None) -> str:
- if explicit_column:
- return explicit_column
-
- candidates = [
- "city",
- "cidade",
- "municipio",
- "município",
- "nm_mun",
- "nome_municipio",
- "nome município",
- ]
- normalized_map = {normalize_text(col): col for col in columns}
- for candidate in candidates:
- if candidate in normalized_map:
- return normalized_map[candidate]
-
- raise ValueError(
- "Não encontrei coluna de cidade automaticamente. "
- "Use --city-column para informar o nome da coluna."
- )
-
-
-def find_year_column(columns: list[str], explicit_column: str | None) -> str | None:
- if explicit_column:
- return explicit_column
-
- candidates = ["year", "ano"]
- normalized_map = {normalize_text(col): col for col in columns}
- for candidate in candidates:
- if candidate in normalized_map:
- return normalized_map[candidate]
- return None
-
-
-def select_city_row(
- rows: list[dict[str, Any]], city_name: str, city_column: str
-) -> dict[str, Any]:
- expected = normalize_text(city_name)
- for row in rows:
- if normalize_text(str(row.get(city_column, ""))) == expected:
- return row
-
- raise ValueError(
- f"Cidade '{city_name}' não encontrada na coluna '{city_column}'."
- )
-
-
-def build_demografia_context(row: dict[str, Any]) -> dict[str, Any]:
- base_nested = nest_dot_keys(row)
- demografia = dict(base_nested.get("demografia", {}))
- flat_keys = [
- "pop_total",
- "pop_mulher",
- "pop_mulher_per",
- "pop_homem",
- "pop_homem_per",
- "cres_pop",
- "cor_raca_pri",
- "porte",
- ]
- for key in flat_keys:
- if key in row and key not in demografia:
- demografia[key] = row[key]
- return demografia
-
-
-def maybe_generate_pdf(html_path: Path, pdf_path: Path) -> None:
- try:
- from weasyprint import HTML
- except ImportError as exc:
- raise RuntimeError(
- "Para gerar PDF, instale dependência opcional: pip install weasyprint"
- ) from exc
-
- pdf_path.parent.mkdir(parents=True, exist_ok=True)
- HTML(filename=str(html_path)).write_pdf(str(pdf_path))
-
-
-def render_html(output_path: Path, title: str, generated_at: str, city: str = "", year: str = "", demografia: dict[str, Any] | None = None) -> None:
-
- e = html.escape
- ctx = {
- "title": e(title),
- "generated_at": e(generated_at),
- "city": e(city),
- "year": e(year),
- }
-
- rows_html = ""
- if demografia:
- rows_html = "\n"
- for key, value in demografia.items():
- rows_html += f"| {e(key)} | {e(str(value))} |
\n"
- rows_html += "
"
-
- html_content = f"""
-
-
-
- {ctx["title"]}
-
-
-
- {ctx["title"]}
-
- Cidade: {ctx["city"]} •
- Ano: {ctx["year"]} •
- Gerado em: {ctx["generated_at"]}
-
- {rows_html}
-
-"""
-
- output_path.parent.mkdir(parents=True, exist_ok=True)
- output_path.write_text(html_content, encoding="utf-8")
-
-
-def main() -> None:
- parser = argparse.ArgumentParser(
- description="Gera um relatório HTML simples a partir de um CSV local ou do Google Drive."
- )
- parser.add_argument(
- "source",
- help="Caminho local, URL do CSV, URL do Google Drive ou apenas file_id do Drive.",
- )
- parser.add_argument(
- "--output",
- default="output/report.html",
- help="Caminho do HTML gerado.",
- )
- parser.add_argument(
- "--title",
- default="Relatório CSV",
- help="Título exibido no relatório.",
- )
- parser.add_argument(
- "--city",
- help="Nome da cidade/município para gerar relatório individual.",
- )
- parser.add_argument(
- "--city-column",
- help="Nome da coluna de cidade no CSV (ex.: cidade, municipio, city).",
- )
- parser.add_argument(
- "--year",
- help="Ano para exibição no relatório. Se omitido, tenta usar coluna ano/year.",
- )
- parser.add_argument(
- "--year-column",
- help="Nome da coluna de ano no CSV (ex.: ano, year).",
- )
- parser.add_argument(
- "--pdf-output",
- help="Caminho do PDF de saída (opcional). Ex.: output/report.pdf",
- )
-
- args = parser.parse_args()
-
- rows = fetch_csv_rows(args.source)
- generated_at = datetime.now().strftime("%d/%m/%Y %H:%M")
-
- if args.city:
- if not rows:
- raise ValueError("CSV vazio. Não há linhas para selecionar município.")
-
- columns = list(rows[0].keys())
- city_column = find_city_column(columns, args.city_column)
- selected_row = select_city_row(rows, args.city, city_column)
- demografia_context = build_demografia_context(selected_row)
- city_value = selected_row.get(city_column, args.city)
-
- year_column = find_year_column(columns, args.year_column)
- year_value = args.year or (selected_row.get(year_column, "") if year_column else "")
-
- render_html(
- output_path=Path(args.output),
- title=args.title,
- generated_at=generated_at,
- city=city_value,
- year=year_value,
- demografia=demografia_context,
- )
- else:
- # Fallback: render table with all rows
- render_html(
- output_path=Path(args.output),
- title=args.title,
- generated_at=generated_at,
- city=f"{len(rows)} linhas",
- year="",
- )
-
- print(f"Relatório gerado em: {args.output}")
- if args.pdf_output:
- maybe_generate_pdf(Path(args.output), Path(args.pdf_output))
- print(f"PDF gerado em: {args.pdf_output}")
-
-
-if __name__ == "__main__":
- main()
diff --git a/report-generator-demo/frontend/yarn.lock b/report-generator-demo/frontend/yarn.lock
deleted file mode 100644
index 2fa812d..0000000
--- a/report-generator-demo/frontend/yarn.lock
+++ /dev/null
@@ -1,970 +0,0 @@
-# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
-# yarn lockfile v1
-
-
-"@esbuild/aix-ppc64@0.21.5":
- version "0.21.5"
- resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f"
- integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==
-
-"@esbuild/android-arm64@0.21.5":
- version "0.21.5"
- resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052"
- integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==
-
-"@esbuild/android-arm@0.21.5":
- version "0.21.5"
- resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28"
- integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==
-
-"@esbuild/android-x64@0.21.5":
- version "0.21.5"
- resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e"
- integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==
-
-"@esbuild/darwin-arm64@0.21.5":
- version "0.21.5"
- resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a"
- integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==
-
-"@esbuild/darwin-x64@0.21.5":
- version "0.21.5"
- resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22"
- integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==
-
-"@esbuild/freebsd-arm64@0.21.5":
- version "0.21.5"
- resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e"
- integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==
-
-"@esbuild/freebsd-x64@0.21.5":
- version "0.21.5"
- resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261"
- integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==
-
-"@esbuild/linux-arm64@0.21.5":
- version "0.21.5"
- resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b"
- integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==
-
-"@esbuild/linux-arm@0.21.5":
- version "0.21.5"
- resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9"
- integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==
-
-"@esbuild/linux-ia32@0.21.5":
- version "0.21.5"
- resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2"
- integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==
-
-"@esbuild/linux-loong64@0.21.5":
- version "0.21.5"
- resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df"
- integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==
-
-"@esbuild/linux-mips64el@0.21.5":
- version "0.21.5"
- resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe"
- integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==
-
-"@esbuild/linux-ppc64@0.21.5":
- version "0.21.5"
- resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4"
- integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==
-
-"@esbuild/linux-riscv64@0.21.5":
- version "0.21.5"
- resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc"
- integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==
-
-"@esbuild/linux-s390x@0.21.5":
- version "0.21.5"
- resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de"
- integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==
-
-"@esbuild/linux-x64@0.21.5":
- version "0.21.5"
- resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz"
- integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==
-
-"@esbuild/netbsd-x64@0.21.5":
- version "0.21.5"
- resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047"
- integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==
-
-"@esbuild/openbsd-x64@0.21.5":
- version "0.21.5"
- resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70"
- integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==
-
-"@esbuild/sunos-x64@0.21.5":
- version "0.21.5"
- resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b"
- integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==
-
-"@esbuild/win32-arm64@0.21.5":
- version "0.21.5"
- resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d"
- integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==
-
-"@esbuild/win32-ia32@0.21.5":
- version "0.21.5"
- resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b"
- integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==
-
-"@esbuild/win32-x64@0.21.5":
- version "0.21.5"
- resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c"
- integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==
-
-"@eslint-community/eslint-utils@^4.8.0":
- version "4.9.1"
- resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz"
- integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==
- dependencies:
- eslint-visitor-keys "^3.4.3"
-
-"@eslint-community/regexpp@^4.12.1":
- version "4.12.2"
- resolved "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz"
- integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==
-
-"@eslint/config-array@^0.21.2":
- version "0.21.2"
- resolved "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz"
- integrity sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==
- dependencies:
- "@eslint/object-schema" "^2.1.7"
- debug "^4.3.1"
- minimatch "^3.1.5"
-
-"@eslint/config-helpers@^0.4.2":
- version "0.4.2"
- resolved "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz"
- integrity sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==
- dependencies:
- "@eslint/core" "^0.17.0"
-
-"@eslint/core@^0.17.0":
- version "0.17.0"
- resolved "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz"
- integrity sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==
- dependencies:
- "@types/json-schema" "^7.0.15"
-
-"@eslint/eslintrc@^3.3.5":
- version "3.3.5"
- resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz"
- integrity sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==
- dependencies:
- ajv "^6.14.0"
- debug "^4.3.2"
- espree "^10.0.1"
- globals "^14.0.0"
- ignore "^5.2.0"
- import-fresh "^3.2.1"
- js-yaml "^4.1.1"
- minimatch "^3.1.5"
- strip-json-comments "^3.1.1"
-
-"@eslint/js@9.39.4", "@eslint/js@^9.25.1":
- version "9.39.4"
- resolved "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz"
- integrity sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==
-
-"@eslint/object-schema@^2.1.7":
- version "2.1.7"
- resolved "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz"
- integrity sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==
-
-"@eslint/plugin-kit@^0.4.1":
- version "0.4.1"
- resolved "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz"
- integrity sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==
- dependencies:
- "@eslint/core" "^0.17.0"
- levn "^0.4.1"
-
-"@humanfs/core@^0.19.2":
- version "0.19.2"
- resolved "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz"
- integrity sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==
- dependencies:
- "@humanfs/types" "^0.15.0"
-
-"@humanfs/node@^0.16.6":
- version "0.16.8"
- resolved "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz"
- integrity sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==
- dependencies:
- "@humanfs/core" "^0.19.2"
- "@humanfs/types" "^0.15.0"
- "@humanwhocodes/retry" "^0.4.0"
-
-"@humanfs/types@^0.15.0":
- version "0.15.0"
- resolved "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz"
- integrity sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==
-
-"@humanwhocodes/module-importer@^1.0.1":
- version "1.0.1"
- resolved "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz"
- integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==
-
-"@humanwhocodes/retry@^0.4.0", "@humanwhocodes/retry@^0.4.2":
- version "0.4.3"
- resolved "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz"
- integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==
-
-"@rollup/rollup-android-arm-eabi@4.60.1":
- version "4.60.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz#043f145716234529052ef9e1ce1d847ffbe9e674"
- integrity sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==
-
-"@rollup/rollup-android-arm64@4.60.1":
- version "4.60.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz#023e1bd146e7519087dfd9e8b29e4cf9f8ecd35c"
- integrity sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==
-
-"@rollup/rollup-darwin-arm64@4.60.1":
- version "4.60.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz#55ccb5487c02419954c57a7a80602885d616e1ee"
- integrity sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==
-
-"@rollup/rollup-darwin-x64@4.60.1":
- version "4.60.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz#254b65404b14488c83225e88b8819376ad71a784"
- integrity sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==
-
-"@rollup/rollup-freebsd-arm64@4.60.1":
- version "4.60.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz#6377ff38c052c76fcaffb7b2728d3172fe676fe6"
- integrity sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==
-
-"@rollup/rollup-freebsd-x64@4.60.1":
- version "4.60.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz#ba3902309d088eaf7139b916f09b7140b28b406d"
- integrity sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==
-
-"@rollup/rollup-linux-arm-gnueabihf@4.60.1":
- version "4.60.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz#e011b9a14638267e53b446286e838dbdaf53f167"
- integrity sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==
-
-"@rollup/rollup-linux-arm-musleabihf@4.60.1":
- version "4.60.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz#0bce9ce9a009490abd28fd922dd97ed521311afe"
- integrity sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==
-
-"@rollup/rollup-linux-arm64-gnu@4.60.1":
- version "4.60.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz#6f6cfbbf324fbb4ceff213abdf7f322fd45d25ff"
- integrity sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==
-
-"@rollup/rollup-linux-arm64-musl@4.60.1":
- version "4.60.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz#f7cb3eecaea9c151ef77342af05f38ae924bf795"
- integrity sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==
-
-"@rollup/rollup-linux-loong64-gnu@4.60.1":
- version "4.60.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz#499bfac6bb669fd88bb664357bf6be996a28b92f"
- integrity sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==
-
-"@rollup/rollup-linux-loong64-musl@4.60.1":
- version "4.60.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz#127dfac08764764396bbe04453c545d38a3ab518"
- integrity sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==
-
-"@rollup/rollup-linux-ppc64-gnu@4.60.1":
- version "4.60.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz#6a72f4d95852aac18326c5bf708393e8f3a41b70"
- integrity sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==
-
-"@rollup/rollup-linux-ppc64-musl@4.60.1":
- version "4.60.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz#ba8674666b00d6f9066cb9a5771a8430c34d2de6"
- integrity sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==
-
-"@rollup/rollup-linux-riscv64-gnu@4.60.1":
- version "4.60.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz#17cc38b2a71e302547cad29bcf78d0db2618c922"
- integrity sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==
-
-"@rollup/rollup-linux-riscv64-musl@4.60.1":
- version "4.60.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz#e36a41e2d8bd247331bd5cfc13b8c951d33454a2"
- integrity sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==
-
-"@rollup/rollup-linux-s390x-gnu@4.60.1":
- version "4.60.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz#1687265f1f4bdea0726c761a58c2db9933609d68"
- integrity sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==
-
-"@rollup/rollup-linux-x64-gnu@4.60.1":
- version "4.60.1"
- resolved "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz"
- integrity sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==
-
-"@rollup/rollup-linux-x64-musl@4.60.1":
- version "4.60.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz#bc240ebb5b9fd8d41ca8a80cb458452e8c187e0f"
- integrity sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==
-
-"@rollup/rollup-openbsd-x64@4.60.1":
- version "4.60.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz#6f80d48a006c4b2ffa7724e95a3e33f6975872af"
- integrity sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==
-
-"@rollup/rollup-openharmony-arm64@4.60.1":
- version "4.60.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz#8f6db6f70d0a48abd833b263cd6dd3e7199c4c0e"
- integrity sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==
-
-"@rollup/rollup-win32-arm64-msvc@4.60.1":
- version "4.60.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz#b68989bfa815d0b3d4e302ecd90bda744438b177"
- integrity sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==
-
-"@rollup/rollup-win32-ia32-msvc@4.60.1":
- version "4.60.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz#c098e45338c50f22f1b288476354f025b746285b"
- integrity sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==
-
-"@rollup/rollup-win32-x64-gnu@4.60.1":
- version "4.60.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz#2c9e15be155b79d05999953b1737b2903842e903"
- integrity sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==
-
-"@rollup/rollup-win32-x64-msvc@4.60.1":
- version "4.60.1"
- resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz#23b860113e9f87eea015d1fa3a4240a52b42fcd4"
- integrity sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==
-
-"@types/estree@1.0.8", "@types/estree@^1.0.6":
- version "1.0.8"
- resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz"
- integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
-
-"@types/json-schema@^7.0.15":
- version "7.0.15"
- resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz"
- integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
-
-acorn-jsx@^5.3.2:
- version "5.3.2"
- resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
- integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
-
-acorn@^8.15.0:
- version "8.16.0"
- resolved "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz"
- integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==
-
-ajv@^6.14.0:
- version "6.15.0"
- resolved "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz"
- integrity sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==
- dependencies:
- fast-deep-equal "^3.1.1"
- fast-json-stable-stringify "^2.0.0"
- json-schema-traverse "^0.4.1"
- uri-js "^4.2.2"
-
-ansi-styles@^4.1.0:
- version "4.3.0"
- resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz"
- integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
- dependencies:
- color-convert "^2.0.1"
-
-argparse@^2.0.1:
- version "2.0.1"
- resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz"
- integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
-
-balanced-match@^1.0.0:
- version "1.0.2"
- resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz"
- integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
-
-brace-expansion@^1.1.7:
- version "1.1.14"
- resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz"
- integrity sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==
- dependencies:
- balanced-match "^1.0.0"
- concat-map "0.0.1"
-
-callsites@^3.0.0:
- version "3.1.0"
- resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz"
- integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
-
-chalk@^4.0.0:
- version "4.1.2"
- resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz"
- integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
- dependencies:
- ansi-styles "^4.1.0"
- supports-color "^7.1.0"
-
-color-convert@^2.0.1:
- version "2.0.1"
- resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz"
- integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
- dependencies:
- color-name "~1.1.4"
-
-color-name@~1.1.4:
- version "1.1.4"
- resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
- integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
-
-concat-map@0.0.1:
- version "0.0.1"
- resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
- integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
-
-cross-spawn@^7.0.6:
- version "7.0.6"
- resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz"
- integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
- dependencies:
- path-key "^3.1.0"
- shebang-command "^2.0.0"
- which "^2.0.1"
-
-debug@^4.3.1, debug@^4.3.2:
- version "4.4.3"
- resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz"
- integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
- dependencies:
- ms "^2.1.3"
-
-deep-is@^0.1.3:
- version "0.1.4"
- resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz"
- integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
-
-esbuild@^0.21.3:
- version "0.21.5"
- resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz"
- integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==
- optionalDependencies:
- "@esbuild/aix-ppc64" "0.21.5"
- "@esbuild/android-arm" "0.21.5"
- "@esbuild/android-arm64" "0.21.5"
- "@esbuild/android-x64" "0.21.5"
- "@esbuild/darwin-arm64" "0.21.5"
- "@esbuild/darwin-x64" "0.21.5"
- "@esbuild/freebsd-arm64" "0.21.5"
- "@esbuild/freebsd-x64" "0.21.5"
- "@esbuild/linux-arm" "0.21.5"
- "@esbuild/linux-arm64" "0.21.5"
- "@esbuild/linux-ia32" "0.21.5"
- "@esbuild/linux-loong64" "0.21.5"
- "@esbuild/linux-mips64el" "0.21.5"
- "@esbuild/linux-ppc64" "0.21.5"
- "@esbuild/linux-riscv64" "0.21.5"
- "@esbuild/linux-s390x" "0.21.5"
- "@esbuild/linux-x64" "0.21.5"
- "@esbuild/netbsd-x64" "0.21.5"
- "@esbuild/openbsd-x64" "0.21.5"
- "@esbuild/sunos-x64" "0.21.5"
- "@esbuild/win32-arm64" "0.21.5"
- "@esbuild/win32-ia32" "0.21.5"
- "@esbuild/win32-x64" "0.21.5"
-
-escape-string-regexp@^4.0.0:
- version "4.0.0"
- resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz"
- integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
-
-eslint-plugin-react-hooks@^5.2.0:
- version "5.2.0"
- resolved "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz"
- integrity sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==
-
-eslint-plugin-react-refresh@^0.4.19:
- version "0.4.26"
- resolved "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz"
- integrity sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==
-
-eslint-scope@^8.4.0:
- version "8.4.0"
- resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz"
- integrity sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==
- dependencies:
- esrecurse "^4.3.0"
- estraverse "^5.2.0"
-
-eslint-visitor-keys@^3.4.3:
- version "3.4.3"
- resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz"
- integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
-
-eslint-visitor-keys@^4.2.1:
- version "4.2.1"
- resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz"
- integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==
-
-eslint@^9.25.1:
- version "9.39.4"
- resolved "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz"
- integrity sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==
- dependencies:
- "@eslint-community/eslint-utils" "^4.8.0"
- "@eslint-community/regexpp" "^4.12.1"
- "@eslint/config-array" "^0.21.2"
- "@eslint/config-helpers" "^0.4.2"
- "@eslint/core" "^0.17.0"
- "@eslint/eslintrc" "^3.3.5"
- "@eslint/js" "9.39.4"
- "@eslint/plugin-kit" "^0.4.1"
- "@humanfs/node" "^0.16.6"
- "@humanwhocodes/module-importer" "^1.0.1"
- "@humanwhocodes/retry" "^0.4.2"
- "@types/estree" "^1.0.6"
- ajv "^6.14.0"
- chalk "^4.0.0"
- cross-spawn "^7.0.6"
- debug "^4.3.2"
- escape-string-regexp "^4.0.0"
- eslint-scope "^8.4.0"
- eslint-visitor-keys "^4.2.1"
- espree "^10.4.0"
- esquery "^1.5.0"
- esutils "^2.0.2"
- fast-deep-equal "^3.1.3"
- file-entry-cache "^8.0.0"
- find-up "^5.0.0"
- glob-parent "^6.0.2"
- ignore "^5.2.0"
- imurmurhash "^0.1.4"
- is-glob "^4.0.0"
- json-stable-stringify-without-jsonify "^1.0.1"
- lodash.merge "^4.6.2"
- minimatch "^3.1.5"
- natural-compare "^1.4.0"
- optionator "^0.9.3"
-
-espree@^10.0.1, espree@^10.4.0:
- version "10.4.0"
- resolved "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz"
- integrity sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==
- dependencies:
- acorn "^8.15.0"
- acorn-jsx "^5.3.2"
- eslint-visitor-keys "^4.2.1"
-
-esquery@^1.5.0:
- version "1.7.0"
- resolved "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz"
- integrity sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==
- dependencies:
- estraverse "^5.1.0"
-
-esrecurse@^4.3.0:
- version "4.3.0"
- resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz"
- integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==
- dependencies:
- estraverse "^5.2.0"
-
-estraverse@^5.1.0, estraverse@^5.2.0:
- version "5.3.0"
- resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz"
- integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
-
-esutils@^2.0.2:
- version "2.0.3"
- resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz"
- integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
-
-fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
- version "3.1.3"
- resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
- integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
-
-fast-json-stable-stringify@^2.0.0:
- version "2.1.0"
- resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz"
- integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
-
-fast-levenshtein@^2.0.6:
- version "2.0.6"
- resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz"
- integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
-
-file-entry-cache@^8.0.0:
- version "8.0.0"
- resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz"
- integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==
- dependencies:
- flat-cache "^4.0.0"
-
-find-up@^5.0.0:
- version "5.0.0"
- resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz"
- integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
- dependencies:
- locate-path "^6.0.0"
- path-exists "^4.0.0"
-
-flat-cache@^4.0.0:
- version "4.0.1"
- resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz"
- integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==
- dependencies:
- flatted "^3.2.9"
- keyv "^4.5.4"
-
-flatted@^3.2.9:
- version "3.4.2"
- resolved "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz"
- integrity sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==
-
-fsevents@~2.3.2, fsevents@~2.3.3:
- version "2.3.3"
- resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
- integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
-
-glob-parent@^6.0.2:
- version "6.0.2"
- resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz"
- integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==
- dependencies:
- is-glob "^4.0.3"
-
-globals@^14.0.0:
- version "14.0.0"
- resolved "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz"
- integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==
-
-globals@^16.2.0:
- version "16.5.0"
- resolved "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz"
- integrity sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==
-
-has-flag@^4.0.0:
- version "4.0.0"
- resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz"
- integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
-
-ignore@^5.2.0:
- version "5.3.2"
- resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz"
- integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==
-
-import-fresh@^3.2.1:
- version "3.3.1"
- resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz"
- integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==
- dependencies:
- parent-module "^1.0.0"
- resolve-from "^4.0.0"
-
-imurmurhash@^0.1.4:
- version "0.1.4"
- resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz"
- integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==
-
-is-extglob@^2.1.1:
- version "2.1.1"
- resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz"
- integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
-
-is-glob@^4.0.0, is-glob@^4.0.3:
- version "4.0.3"
- resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz"
- integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
- dependencies:
- is-extglob "^2.1.1"
-
-isexe@^2.0.0:
- version "2.0.0"
- resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz"
- integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
-
-"js-tokens@^3.0.0 || ^4.0.0":
- version "4.0.0"
- resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"
- integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
-
-js-yaml@^4.1.1:
- version "4.1.1"
- resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz"
- integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==
- dependencies:
- argparse "^2.0.1"
-
-json-buffer@3.0.1:
- version "3.0.1"
- resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz"
- integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
-
-json-schema-traverse@^0.4.1:
- version "0.4.1"
- resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz"
- integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
-
-json-stable-stringify-without-jsonify@^1.0.1:
- version "1.0.1"
- resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz"
- integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
-
-keyv@^4.5.4:
- version "4.5.4"
- resolved "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz"
- integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==
- dependencies:
- json-buffer "3.0.1"
-
-levn@^0.4.1:
- version "0.4.1"
- resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz"
- integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==
- dependencies:
- prelude-ls "^1.2.1"
- type-check "~0.4.0"
-
-locate-path@^6.0.0:
- version "6.0.0"
- resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz"
- integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
- dependencies:
- p-locate "^5.0.0"
-
-lodash.merge@^4.6.2:
- version "4.6.2"
- resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz"
- integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
-
-loose-envify@^1.1.0:
- version "1.4.0"
- resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz"
- integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
- dependencies:
- js-tokens "^3.0.0 || ^4.0.0"
-
-minimatch@^3.1.5:
- version "3.1.5"
- resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz"
- integrity sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==
- dependencies:
- brace-expansion "^1.1.7"
-
-ms@^2.1.3:
- version "2.1.3"
- resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
- integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
-
-nanoid@^3.3.11:
- version "3.3.11"
- resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz"
- integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==
-
-natural-compare@^1.4.0:
- version "1.4.0"
- resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz"
- integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
-
-optionator@^0.9.3:
- version "0.9.4"
- resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz"
- integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==
- dependencies:
- deep-is "^0.1.3"
- fast-levenshtein "^2.0.6"
- levn "^0.4.1"
- prelude-ls "^1.2.1"
- type-check "^0.4.0"
- word-wrap "^1.2.5"
-
-p-limit@^3.0.2:
- version "3.1.0"
- resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz"
- integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
- dependencies:
- yocto-queue "^0.1.0"
-
-p-locate@^5.0.0:
- version "5.0.0"
- resolved "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz"
- integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
- dependencies:
- p-limit "^3.0.2"
-
-parent-module@^1.0.0:
- version "1.0.1"
- resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz"
- integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
- dependencies:
- callsites "^3.0.0"
-
-path-exists@^4.0.0:
- version "4.0.0"
- resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz"
- integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
-
-path-key@^3.1.0:
- version "3.1.1"
- resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz"
- integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
-
-picocolors@^1.1.1:
- version "1.1.1"
- resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz"
- integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
-
-postcss@^8.4.43:
- version "8.5.10"
- resolved "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz"
- integrity sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==
- dependencies:
- nanoid "^3.3.11"
- picocolors "^1.1.1"
- source-map-js "^1.2.1"
-
-prelude-ls@^1.2.1:
- version "1.2.1"
- resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz"
- integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
-
-punycode@^2.1.0:
- version "2.3.1"
- resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz"
- integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
-
-react-dom@^18.3.1:
- version "18.3.1"
- resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz"
- integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==
- dependencies:
- loose-envify "^1.1.0"
- scheduler "^0.23.2"
-
-react@^18.3.1:
- version "18.3.1"
- resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz"
- integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==
- dependencies:
- loose-envify "^1.1.0"
-
-resolve-from@^4.0.0:
- version "4.0.0"
- resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz"
- integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
-
-rollup@^4.20.0:
- version "4.60.1"
- resolved "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz"
- integrity sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==
- dependencies:
- "@types/estree" "1.0.8"
- optionalDependencies:
- "@rollup/rollup-android-arm-eabi" "4.60.1"
- "@rollup/rollup-android-arm64" "4.60.1"
- "@rollup/rollup-darwin-arm64" "4.60.1"
- "@rollup/rollup-darwin-x64" "4.60.1"
- "@rollup/rollup-freebsd-arm64" "4.60.1"
- "@rollup/rollup-freebsd-x64" "4.60.1"
- "@rollup/rollup-linux-arm-gnueabihf" "4.60.1"
- "@rollup/rollup-linux-arm-musleabihf" "4.60.1"
- "@rollup/rollup-linux-arm64-gnu" "4.60.1"
- "@rollup/rollup-linux-arm64-musl" "4.60.1"
- "@rollup/rollup-linux-loong64-gnu" "4.60.1"
- "@rollup/rollup-linux-loong64-musl" "4.60.1"
- "@rollup/rollup-linux-ppc64-gnu" "4.60.1"
- "@rollup/rollup-linux-ppc64-musl" "4.60.1"
- "@rollup/rollup-linux-riscv64-gnu" "4.60.1"
- "@rollup/rollup-linux-riscv64-musl" "4.60.1"
- "@rollup/rollup-linux-s390x-gnu" "4.60.1"
- "@rollup/rollup-linux-x64-gnu" "4.60.1"
- "@rollup/rollup-linux-x64-musl" "4.60.1"
- "@rollup/rollup-openbsd-x64" "4.60.1"
- "@rollup/rollup-openharmony-arm64" "4.60.1"
- "@rollup/rollup-win32-arm64-msvc" "4.60.1"
- "@rollup/rollup-win32-ia32-msvc" "4.60.1"
- "@rollup/rollup-win32-x64-gnu" "4.60.1"
- "@rollup/rollup-win32-x64-msvc" "4.60.1"
- fsevents "~2.3.2"
-
-scheduler@^0.23.2:
- version "0.23.2"
- resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz"
- integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==
- dependencies:
- loose-envify "^1.1.0"
-
-shebang-command@^2.0.0:
- version "2.0.0"
- resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz"
- integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
- dependencies:
- shebang-regex "^3.0.0"
-
-shebang-regex@^3.0.0:
- version "3.0.0"
- resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz"
- integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
-
-source-map-js@^1.2.1:
- version "1.2.1"
- resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz"
- integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
-
-strip-json-comments@^3.1.1:
- version "3.1.1"
- resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz"
- integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
-
-supports-color@^7.1.0:
- version "7.2.0"
- resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz"
- integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
- dependencies:
- has-flag "^4.0.0"
-
-type-check@^0.4.0, type-check@~0.4.0:
- version "0.4.0"
- resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz"
- integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==
- dependencies:
- prelude-ls "^1.2.1"
-
-uri-js@^4.2.2:
- version "4.4.1"
- resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz"
- integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
- dependencies:
- punycode "^2.1.0"
-
-vite@^5.4.10:
- version "5.4.21"
- resolved "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz"
- integrity sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==
- dependencies:
- esbuild "^0.21.3"
- postcss "^8.4.43"
- rollup "^4.20.0"
- optionalDependencies:
- fsevents "~2.3.3"
-
-which@^2.0.1:
- version "2.0.2"
- resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz"
- integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
- dependencies:
- isexe "^2.0.0"
-
-word-wrap@^1.2.5:
- version "1.2.5"
- resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz"
- integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
-
-yocto-queue@^0.1.0:
- version "0.1.0"
- resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz"
- integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
diff --git a/report/src/components/Brand.jsx b/report/src/components/Brand.jsx
index 9ee7477..36c305c 100644
--- a/report/src/components/Brand.jsx
+++ b/report/src/components/Brand.jsx
@@ -61,7 +61,7 @@ export function PdfCoverBrand() {
export function MetricIcon() {
return (
-