diff --git a/app/README.md b/app/README.md
index fc8f3e0..2d81ed7 100644
--- a/app/README.md
+++ b/app/README.md
@@ -1,13 +1,13 @@
# 1. Run Applicaiton
-1. Removes all cached Python packages stored by pip, removes local Python cache files, clears the cache used by uv, and forcibly clear the cache for Node.js.
+1. Remove all cached Python packages stored by pip, remove local Python cache files, clear the cache used by uv, and forcibly clear the cache for Node.js.
```bash
uv tool install cleanpy
pip cache purge && cleanpy . && uv cache clean && npm cache clean --force
```
-2. Resolve dependencies from your *pyproject.toml* and upgrade all packages. Synchronize the virtual environment with the dependencies specified in the *uv.lock* including packages needed for development.
+2. Resolve dependencies from *pyproject.toml* and upgrade all packages. Synchronize the virtual environment with the dependencies specified in the *uv.lock* including packages needed for **development**.
```bash
cd backend
@@ -21,16 +21,15 @@
uv run ruff check --fix && uv run pytest
```
-4. Starts a local development API server, visible at port 8000, and automatically reloads the server as you make code changes.
+4. Start a local **development** API server, visible at port 8000, and automatically reloads the server when code changes are made.
```bash
uv run uvicorn src.main:app --reload --port 8000
```
-5. Scans dependencies for security vulnerabilities and attempts to automatically fix them by force-updating to the latest secure versions.
+5. Open a new terminal. Scan dependencies for security vulnerabilities and attempt to automatically fix them by force-updating to the latest secure versions.
```bash
- cd ..
cd frontend
npm audit fix --force
```
@@ -38,13 +37,11 @@
6. Install dependencies from *package.json*, then update those dependencies to the latest allowed versions based on version ranges. Next, check the source code for stylistic and syntax errors according to configured rules. Finally, compile or bundle the application for deployment or production use.
```bash
- cd frontend
npm install && npm update && npm run lint && npm run build
```
-7. Execute start script in *package.json*, launch your Node.js application in production mode.
+7. Execute start script in *package.json*, launch Node.js application in **development** mode.
```bash
- cd frontend
- npm run start
+ npm run dev
```
diff --git a/app/backend/operations/health/service.py b/app/backend/operations/health/service.py
index f7e56e3..4c36589 100644
--- a/app/backend/operations/health/service.py
+++ b/app/backend/operations/health/service.py
@@ -66,9 +66,9 @@ async def check_database_status() -> ComponentCheck:
"""
async def db_logic():
# IMPORTANT: Replace this sleep simulation with the actual async DB client call (e.g., await database.ping())
- await asyncio.sleep(0.042)
+ await asyncio.sleep(0.045)
- return await _run_check_with_timeout(db_logic(), name="postgres", timeout_ms=100)
+ return await _run_check_with_timeout(db_logic(), name="postgres", timeout_ms=50)
async def check_media_server_status() -> ComponentCheck:
"""
diff --git a/app/frontend/Dockerfile b/app/frontend/Dockerfile
index ca0d5a5..df6a594 100644
--- a/app/frontend/Dockerfile
+++ b/app/frontend/Dockerfile
@@ -13,14 +13,16 @@ FROM node:22-slim AS base
WORKDIR /app
ENV NPM_CONFIG_LOGLEVEL=warn \
- NODE_OPTIONS="--enable-source-maps"
+ NODE_OPTIONS="--enable-source-maps" \
+ NEXT_TELEMETRY_DISABLED=1
# ------------------------------------------------------------------------------
# Dependencies cache
# ------------------------------------------------------------------------------
FROM base AS deps
COPY package*.json ./
-RUN if [ -f package-lock.json ]; then npm ci; else npm install; fi
+RUN if [ -f package-lock.json ]; then npm ci; else npm install; fi \
+ && chown -R node:node /app
# ------------------------------------------------------------------------------
# Production dependencies only (pruned to omit dev tooling)
@@ -37,6 +39,7 @@ FROM base AS builder
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NODE_ENV=production
+ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build
# ------------------------------------------------------------------------------
@@ -53,6 +56,7 @@ COPY --from=prod-deps --chown=node:node /app/node_modules ./node_modules
COPY --from=builder --chown=node:node /app/.next ./.next
COPY --from=builder --chown=node:node /app/public ./public
COPY --from=builder --chown=node:node /app/package.json ./package.json
+COPY --from=builder --chown=node:node /app/package-lock.json ./package-lock.json
EXPOSE 3000
CMD ["npm", "run", "start"]
diff --git a/app/frontend/app/globals.css b/app/frontend/app/globals.css
index 142a74a..03a45b9 100644
--- a/app/frontend/app/globals.css
+++ b/app/frontend/app/globals.css
@@ -1,305 +1,35 @@
-:root {
- color-scheme: dark;
- font-family: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
+@import "tailwindcss";
+
+@source "./app/**/*.{js,ts,jsx,tsx,mdx}";
+@source "./components/**/*.{js,ts,jsx,tsx,mdx}";
+
+@theme {
+ --color-ink: #0f172a;
+ --color-inkSoft: #1e293b;
+ --color-inkMuted: #64748b;
+ --color-sand: #f7f7fb;
+ --color-card: #ffffff;
+ --color-success: #22c55e;
+ --color-danger: #ef4444;
+ --color-pulse: #f59e0b;
+ --color-accent-blue: #60a5fa;
+ --color-accent-mint: #34d399;
+ --color-accent-coral: #fb7185;
+ --font-sans: "Plus Jakarta Sans", "Inter", system-ui, -apple-system, "Segoe UI",
+ sans-serif;
+ --font-display: "Plus Jakarta Sans", "Inter", system-ui, -apple-system, "Segoe UI",
sans-serif;
- line-height: 1.5;
- background: #04060d;
- color: #f6f7fb;
}
-* {
- box-sizing: border-box;
+:root {
+ color-scheme: light;
+ background-color: #f7f7fb;
}
body {
- margin: 0;
- min-height: 100vh;
- background: radial-gradient(circle at 20% 20%, #182147 0%, transparent 30%),
- radial-gradient(circle at 80% 10%, #1d3754 0%, transparent 25%),
- radial-gradient(circle at 40% 80%, #1b2744 0%, transparent 30%),
- #04060d;
+ @apply min-h-screen bg-gradient-to-br from-[#f9fafb] via-[#f2f4f6] to-[#e7eaee] text-ink antialiased;
}
-.page {
- position: relative;
- max-width: 1200px;
- margin: 0 auto;
- padding: 40px 24px 80px;
-}
-
-.starfall {
- position: fixed;
- inset: 0;
- pointer-events: none;
- opacity: 0;
- background-image: radial-gradient(2px 2px at 10% 20%, #d6e9ff, transparent),
- radial-gradient(2px 2px at 30% 0%, #c7ddff, transparent),
- radial-gradient(2px 2px at 60% 10%, #c0f2ff, transparent),
- radial-gradient(2px 2px at 80% 25%, #f3d5ff, transparent),
- radial-gradient(2px 2px at 50% 5%, #bfe2ff, transparent),
- radial-gradient(2px 2px at 15% 5%, #d6e9ff, transparent);
- background-size: 200px 200px, 260px 260px, 220px 220px, 240px 240px,
- 210px 210px, 230px 230px;
- z-index: 3;
-}
-
-.starfall.active {
- opacity: 1;
- animation: starfall-move 1.4s ease-out forwards;
-}
-
-@keyframes starfall-move {
- 0% {
- transform: translateY(-30px);
- opacity: 0.9;
- }
- 100% {
- transform: translateY(80px);
- opacity: 0;
- }
-}
-
-.nebula {
- position: absolute;
- inset: 0;
- pointer-events: none;
- background: radial-gradient(
- circle at 20% 30%,
- rgba(121, 160, 255, 0.22),
- transparent 32%
- ),
- radial-gradient(
- circle at 80% 20%,
- rgba(255, 120, 200, 0.12),
- transparent 28%
- ),
- radial-gradient(
- circle at 50% 80%,
- rgba(80, 200, 255, 0.14),
- transparent 30%
- );
- filter: blur(60px);
- z-index: 0;
-}
-
-.topbar {
- position: sticky;
- top: 0;
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 12px;
- padding: 12px 16px;
- margin-bottom: 32px;
- border-radius: 14px;
- background: rgba(255, 255, 255, 0.04);
- border: 1px solid rgba(255, 255, 255, 0.06);
- backdrop-filter: blur(16px);
- z-index: 2;
-}
-
-.brand {
- display: inline-flex;
- align-items: center;
- gap: 10px;
- font-weight: 700;
- letter-spacing: 0.02em;
-}
-
-.logo-wrap {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 32px;
- height: 32px;
- border-radius: 10px;
- background: rgba(255, 255, 255, 0.08);
- border: 1px solid rgba(255, 255, 255, 0.08);
-}
-
-.logo {
- border-radius: 6px;
-}
-
-.hero {
- position: relative;
- z-index: 1;
- padding: 12px;
- color: #f6f7fb;
-}
-
-.eyebrow {
- margin: 0 0 8px;
- text-transform: uppercase;
- letter-spacing: 0.16em;
- font-size: 0.78rem;
- color: #9fb3ff;
-}
-
-.hero h1 {
- margin: 0 0 12px;
- font-size: 2.4rem;
- letter-spacing: -0.02em;
-}
-
-.lede {
- margin: 0 0 18px;
- max-width: 720px;
- color: #c7cee7;
-}
-
-.chip-row {
- display: flex;
- flex-wrap: wrap;
- gap: 10px;
- align-items: center;
-}
-
-.pill {
- display: inline-flex;
- align-items: center;
- gap: 8px;
- padding: 8px 14px;
- border-radius: 999px;
- font-weight: 600;
- border: 1px solid rgba(255, 255, 255, 0.12);
-}
-
-.pill.subtle {
- background: rgba(255, 255, 255, 0.04);
- color: #cdd5f5;
-}
-
-.pulse {
- position: relative;
- overflow: hidden;
-}
-
-.pulse::after {
- content: "";
- position: absolute;
- inset: -2px;
- border-radius: 999px;
- background: radial-gradient(
- circle at 20% 20%,
- rgba(124, 211, 255, 0.35),
- transparent 50%
- );
- animation: ping 0.9s ease-out;
- pointer-events: none;
-}
-
-@keyframes ping {
- 0% {
- opacity: 0.75;
- transform: scale(0.98);
- }
- 100% {
- opacity: 0;
- transform: scale(1.25);
- }
-}
-
-.status-pill {
- display: inline-flex;
- align-items: center;
- gap: 6px;
- padding: 8px 14px;
- border-radius: 999px;
- font-weight: 700;
- text-transform: uppercase;
- letter-spacing: 0.04em;
- border: 1px solid transparent;
-}
-
-.status-pill.ok {
- color: #92ffba;
- background: rgba(28, 187, 124, 0.14);
- border-color: rgba(28, 187, 124, 0.4);
-}
-
-.status-pill.bad {
- color: #ffc7c7;
- background: rgba(255, 99, 99, 0.12);
- border-color: rgba(255, 99, 99, 0.3);
-}
-
-.grid {
- position: relative;
- z-index: 1;
- margin-top: 28px;
- display: grid;
- gap: 18px;
- grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
-}
-
-.card {
- position: relative;
- border-radius: 18px;
- padding: 20px;
- overflow: hidden;
-}
-
-.glass {
- background: linear-gradient(
- 135deg,
- rgba(255, 255, 255, 0.05),
- rgba(255, 255, 255, 0.02)
- ),
- rgba(255, 255, 255, 0.02);
- border: 1px solid rgba(255, 255, 255, 0.08);
- box-shadow: 0 24px 80px rgba(0, 0, 0, 0.35);
- backdrop-filter: blur(20px);
-}
-
-.card-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- gap: 10px;
- margin-bottom: 10px;
-}
-
-.muted {
- color: #a5acc7;
- font-size: 0.95rem;
-}
-
-.meta {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
- gap: 12px;
- margin-top: 14px;
-}
-
-.meta-label {
- color: #7f88a8;
- margin: 0 0 4px;
- font-size: 0.85rem;
- letter-spacing: 0.01em;
-}
-
-.meta-value {
- margin: 0;
- color: #f6f7fb;
- font-weight: 700;
-}
-
-code {
- padding: 4px 8px;
- border-radius: 8px;
- background: rgba(255, 255, 255, 0.05);
- border: 1px solid rgba(255, 255, 255, 0.06);
- font-size: 0.9rem;
-}
-
-@media (max-width: 640px) {
- .topbar {
- flex-direction: column;
- align-items: flex-start;
- }
-
- .hero h1 {
- font-size: 1.8rem;
- }
+* {
+ @apply selection:bg-blue-200 selection:text-ink;
}
diff --git a/app/frontend/app/layout.tsx b/app/frontend/app/layout.tsx
index 99916a9..f67fcb7 100644
--- a/app/frontend/app/layout.tsx
+++ b/app/frontend/app/layout.tsx
@@ -1,10 +1,10 @@
import "./globals.css";
import type { Metadata } from "next";
-import { ReactNode } from "react";
+import type { ReactNode } from "react";
export const metadata: Metadata = {
- title: "avaaz.ai",
- description: "Health check frontend for avaaz.ai",
+ title: "avaaz.ai | Live Health Console",
+ description: "Live ECG-style monitoring for avaaz.ai health endpoints.",
icons: {
icon: [{ url: "/favicon.png", type: "image/png" }],
shortcut: ["/favicon.png"],
@@ -14,7 +14,7 @@ export const metadata: Metadata = {
export default function RootLayout({ children }: { children: ReactNode }) {
return (
-
{children}
+ {children}
);
}
diff --git a/app/frontend/app/page.tsx b/app/frontend/app/page.tsx
index 71f0570..2028d9c 100644
--- a/app/frontend/app/page.tsx
+++ b/app/frontend/app/page.tsx
@@ -1,194 +1,596 @@
"use client";
import Image from "next/image";
-import { useEffect, useMemo, useState } from "react";
+import {
+ useCallback,
+ useEffect,
+ useMemo,
+ useRef,
+ useState,
+} from "react";
+import { ECGMonitorCard } from "@/components/ECGMonitorCard";
+import type { PollState } from "@/types/monitor";
-type HealthSummary = {
+interface HealthSummary {
status: string;
version?: string;
serviceId?: string;
description?: string;
- checks: Record;
-};
+ checks?:
+ | Record
+ | {
+ name?: string;
+ key?: string;
+ check?: string;
+ status: string;
+ output?: string;
+ details?: string;
+ }[];
+}
-export default function Home() {
- const [health, setHealth] = useState(null);
- const [ready, setReady] = useState(null);
- const [error, setError] = useState(null);
- const [readyError, setReadyError] = useState(null);
- const [attemptedHealthUrl, setAttemptedHealthUrl] = useState(
- null
- );
- const [attemptedReadyUrl, setAttemptedReadyUrl] = useState(
- null
- );
- const [lastUpdated, setLastUpdated] = useState(null);
- const [isPolling, setIsPolling] = useState(false);
- const [starsActive, setStarsActive] = useState(false);
+const POLL_INTERVALS = {
+ live: 10_000,
+ ready: 30_000,
+ health: 60_000,
+} as const;
- const apiBase = useMemo(
- () => process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000",
- []
- );
+const WAVE_SAMPLES = 140;
+const WAVE_WIDTH = 360;
+const BASELINE = 68;
+const MIN_Y = 20;
+const MAX_Y = 110;
+const NOISE = 1.4;
+
+function formatTime(timestamp: string | null) {
+ if (!timestamp) return "Pending";
+ return new Date(timestamp).toLocaleTimeString([], {
+ hour: "2-digit",
+ minute: "2-digit",
+ second: "2-digit",
+ });
+}
+
+function useNowString() {
+ const [now, setNow] = useState("");
useEffect(() => {
- async function fetchHealth() {
- try {
- const url = `${apiBase}/health`;
- setAttemptedHealthUrl(url);
- const res = await fetch(url);
- if (!res.ok) throw new Error(`Health check failed: ${res.status}`);
- const payload = (await res.json()) as HealthSummary;
- setHealth(payload);
- setError(null);
- return true;
- } catch (err) {
- const message = err instanceof Error ? err.message : "Unknown error";
- setError(message);
- setHealth(null);
- return false;
- }
- }
- async function fetchReady() {
- try {
- const url = `${apiBase}/health/ready`;
- setAttemptedReadyUrl(url);
- const res = await fetch(url);
- if (!res.ok) throw new Error(`Ready check failed: ${res.status}`);
- const payload = await res.text();
- setReady(payload);
- setReadyError(null);
- return true;
- } catch (err) {
- const message = err instanceof Error ? err.message : "Unknown error";
- setReadyError(message);
- setReady(null);
- return false;
- }
- }
+ const tick = () =>
+ setNow(
+ new Date().toLocaleTimeString([], {
+ hour: "2-digit",
+ minute: "2-digit",
+ second: "2-digit",
+ })
+ );
- const run = async () => {
- setIsPolling(true);
- const [healthOk, readyOk] = await Promise.all([fetchHealth(), fetchReady()]);
- setLastUpdated(new Date().toLocaleTimeString());
- if (healthOk && readyOk) {
- setStarsActive(true);
- setTimeout(() => setStarsActive(false), 1400);
- }
- setTimeout(() => setIsPolling(false), 900);
- };
-
- run();
- const id = setInterval(run, 10_000);
+ tick();
+ const id = setInterval(tick, 1_000);
return () => clearInterval(id);
- }, [apiBase]);
+ }, []);
- const badgeClass =
- health && health.status === "ok" && !error
- ? "status-pill ok"
- : "status-pill bad";
+ return now;
+}
- const readyClass =
- ready && !readyError
- ? "status-pill ok"
- : "status-pill bad";
+function hasStatusField(value: unknown): value is { status?: unknown } {
+ return typeof value === "object" && value !== null && "status" in value;
+}
+
+function isStatusHealthy(value: unknown): boolean {
+ if (!value) return false;
+ if (typeof value === "string") {
+ return ["ok", "pass", "healthy", "up", "ready", "live"].includes(
+ value.toLowerCase()
+ );
+ }
+ if (hasStatusField(value) && typeof value.status === "string") {
+ return ["ok", "pass", "healthy", "up", "ready", "live"].includes(
+ value.status.toLowerCase()
+ );
+ }
+ return false;
+}
+
+/**
+ * Drives the shared ECG waveform: keeps the line scrolling and injects pulse spikes
+ * whenever a poll occurs so the right edge always shows the freshest activity.
+ */
+function useWaveform() {
+ const [wave, setWave] = useState(() =>
+ Array.from({ length: WAVE_SAMPLES }, () => BASELINE)
+ );
+ const queueRef = useRef([]);
+ const tickRef = useRef(0);
+
+ // Queue sharp spikes that get blended into the scrolling ECG line.
+ const triggerPulse = useCallback((strength = 18) => {
+ queueRef.current.push(
+ BASELINE - 6,
+ BASELINE + strength,
+ BASELINE - 10,
+ BASELINE + strength * 0.6
+ );
+ }, []);
+
+ useEffect(() => {
+ const id = setInterval(() => {
+ setWave((prev) => {
+ const queue = queueRef.current;
+ const drift =
+ Math.sin(tickRef.current / 6) * NOISE +
+ Math.cos(tickRef.current / 9) * (NOISE / 1.6);
+ tickRef.current += 1;
+ const nextVal = queue.length
+ ? queue.shift() ?? BASELINE
+ : BASELINE + drift;
+ const clamped = Math.max(MIN_Y, Math.min(MAX_Y, nextVal));
+ const next = prev.slice(1);
+ next.push(clamped);
+ return next;
+ });
+ }, 110);
+ return () => clearInterval(id);
+ }, []);
+
+ const wavePoints = useMemo(
+ () =>
+ wave
+ .map((value, idx) => {
+ const x = (idx / (wave.length - 1)) * WAVE_WIDTH;
+ return `${x.toFixed(1)},${value.toFixed(1)}`;
+ })
+ .join(" "),
+ [wave]
+ );
+
+ return { wavePoints, waveHeight: wave[wave.length - 1], triggerPulse };
+}
+
+/**
+ * Polls an endpoint on a cadence, parsing the response and surfacing status metadata.
+ */
+function usePoller(opts: {
+ baseUrl: string;
+ path: string;
+ intervalMs: number;
+ parser: (res: Response) => Promise;
+ onPoll?: () => void;
+}): { state: PollState; pollNow: () => Promise } {
+ const { baseUrl, path, intervalMs, parser, onPoll } = opts;
+ const [state, setState] = useState>({
+ data: null,
+ error: null,
+ loading: false,
+ lastUpdated: null,
+ attemptedUrl: `${baseUrl}${path}`,
+ });
+
+ const poll = useCallback(async () => {
+ const url = `${baseUrl}${path}`;
+ onPoll?.();
+ setState((prev) => ({
+ ...prev,
+ loading: true,
+ attemptedUrl: url,
+ }));
+
+ try {
+ const res = await fetch(url, { cache: "no-store" });
+ if (!res.ok) throw new Error(`${path} responded with ${res.status}`);
+ const parsed = await parser(res);
+ setState((prev) => ({
+ ...prev,
+ data: parsed,
+ error: null,
+ lastUpdated: new Date().toISOString(),
+ loading: false,
+ }));
+ } catch (error) {
+ const message = error instanceof Error ? error.message : "Unknown error";
+ setState((prev) => ({
+ ...prev,
+ data: null,
+ error: message,
+ lastUpdated: new Date().toISOString(),
+ loading: false,
+ }));
+ }
+ }, [baseUrl, path, parser, onPoll]);
+
+ useEffect(() => {
+ void poll();
+ const id = setInterval(() => void poll(), intervalMs);
+ return () => clearInterval(id);
+ }, [poll, intervalMs]);
+
+ return { state, pollNow: poll };
+}
+
+export default function Home() {
+ const apiBase = useMemo(
+ () => process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:8000",
+ []
+ );
+ const now = useNowString();
+ const unifiedWave = useWaveform();
+ const liveWave = useWaveform();
+ const readyWave = useWaveform();
+ const healthWave = useWaveform();
+
+ const parseHealth = useCallback(async (res: Response) => {
+ return (await res.json()) as HealthSummary;
+ }, []);
+ const parseText = useCallback(async (res: Response) => res.text(), []);
+
+ const pulseHealth = useCallback(() => {
+ unifiedWave.triggerPulse(22);
+ healthWave.triggerPulse(22);
+ }, [unifiedWave.triggerPulse, healthWave.triggerPulse]);
+ const pulseReady = useCallback(() => {
+ unifiedWave.triggerPulse(16);
+ readyWave.triggerPulse(16);
+ }, [unifiedWave.triggerPulse, readyWave.triggerPulse]);
+ const pulseLive = useCallback(() => {
+ unifiedWave.triggerPulse(12);
+ liveWave.triggerPulse(12);
+ }, [unifiedWave.triggerPulse, liveWave.triggerPulse]);
+
+ const { state: healthState, pollNow: pollHealth } = usePoller({
+ baseUrl: apiBase,
+ path: "/health",
+ intervalMs: POLL_INTERVALS.health,
+ parser: parseHealth,
+ onPoll: pulseHealth,
+ });
+
+ const { state: readyState, pollNow: pollReady } = usePoller({
+ baseUrl: apiBase,
+ path: "/health/ready",
+ intervalMs: POLL_INTERVALS.ready,
+ parser: parseText,
+ onPoll: pulseReady,
+ });
+
+ const { state: liveState, pollNow: pollLive } = usePoller({
+ baseUrl: apiBase,
+ path: "/health/live",
+ intervalMs: POLL_INTERVALS.live,
+ parser: parseText,
+ onPoll: pulseLive,
+ });
+
+ const healthOk = isStatusHealthy(healthState.data?.status) && !healthState.error;
+ const readyOk = isStatusHealthy(readyState.data) && !readyState.error;
+ const liveOk = isStatusHealthy(liveState.data) && !liveState.error;
+ const overallOk = healthOk && readyOk && liveOk;
+
+ const checks = useMemo(() => {
+ const source = healthState.data?.checks;
+ if (!source) return [];
+ if (Array.isArray(source)) {
+ return source.map((item, idx) => ({
+ label: item.name ?? item.key ?? item.check ?? `Check ${idx + 1}`,
+ status: item.status ?? "unknown",
+ output: item.output ?? item.details ?? "",
+ }));
+ }
+ return Object.entries(source).map(([label, val]) => ({
+ label,
+ status: (val as { status?: string }).status ?? "unknown",
+ output:
+ (val as { output?: string; details?: string }).output ??
+ (val as { details?: string }).details ??
+ "",
+ }));
+ }, [healthState.data?.checks]);
+
+ const overallLabel = overallOk
+ ? "All probes healthy"
+ : "Attention needed";
+ const overallLoading =
+ healthState.loading || readyState.loading || liveState.loading;
+ const overallStrokeColor = overallLoading
+ ? "var(--color-pulse)"
+ : overallOk
+ ? "var(--color-success)"
+ : "var(--color-danger)";
return (
-
-
-
-
-
-
-
-
- avaaz.ai status
-
-
- Polling every 10s
-
-
+
+
+
-
-
-
System overview
-
Minimal health & readiness monitor
-
- Inspired by gemini.google.com — lightweight cards, soft gradients,
- and live probes for backend health.
-
-
-
- Health: {health && !error ? health.status : "unhealthy"}
+
+
+
+
+
-
- Ready: {ready && !readyError ? ready : "not ready"}
+
+
avaaz.ai
+
+ Unified ECG monitoring for live, ready, and health probes.
+
- {lastUpdated && (
-
Updated {lastUpdated}
- )}
-
-
-
-
-
-
-
/health
-
- {health && !error ? health.status : "unhealthy"}
+
+
+
+ {overallLabel}
+
+
+ Live clock
+ {now || "— — : — —"}
-
- {attemptedHealthUrl || `${apiBase}/health`}
-
- {health ? (
-
-
-
Status
-
{health.status}
+
+
+
+
+
+ Live dashboard
+
+
+ ECG-style observability for avaaz.ai
+
+
+ A continuous, scrolling signal shows every poll with sharp blips
+ whenever a probe fires. Color shifts between healthy (green),
+ unhealthy (red), and active polling (amber).
+
+
+
+ /health/live
+
+ {liveOk ? "Live" : "Down"} · 10s cadence
+
- {Object.entries(health.checks || {}).map(([key, val]) => (
-
-
{key}
-
{val.status}
+
+ /health/ready
+
+ {readyOk ? "Ready" : "Not ready"} · 30s cadence
+
+
+
+ /health
+
+ {healthOk ? "Healthy" : "Degraded"} · 60s cadence
+
+
+
+
+
+
+
+
+
+ Unified ECG strip
+
+
+
+ Healthy
+
+ Unhealthy
+
+ Polling
+
+
+
+
+
+
+ Last health update
+
+
+ {formatTime(healthState.lastUpdated)}
+
+
+
+
+ Next pulse cadence
+
+
+ 10s / 30s / 60s
+
+
+
+
+
+
+
+
+
+
+ Signal
+
+
+ {liveState.data ?? liveState.error ?? "No response yet"}
+
+
+
+
+
+
+
+ Response
+
+
+ {readyState.data ?? readyState.error ?? "No response yet"}
+
+
+
+
+
+
+ {healthState.data?.version && (
+
+
+ Version
+
+
{healthState.data.version}
- ))}
+ )}
+ {healthState.data?.serviceId && (
+
+
+ Service
+
+
+ {healthState.data.serviceId}
+
+
+ )}
+ {healthState.data?.description && (
+
+
+ Notes
+
+
{healthState.data.description}
+
+ )}
- ) : (
- {error || "Waiting for response..."}
- )}
-
-
-
-
- /health/ready
-
- {ready && !readyError ? ready : "not ready"}
-
-
-
- {attemptedReadyUrl || `${apiBase}/health/ready`}
-
- {ready ? (
-
-
+
+
+ Checks
+
+ {checks.length > 0 ? (
+
+ {checks.map((check) => (
+
+
+ {check.label}
+
+
+
{check.status}
+ {check.output && (
+
{check.output}
+ )}
+
+ ))}
+
+ ) : (
+
Awaiting check details.
+ )}
- ) : (
-
{readyError || "Waiting for response..."}
- )}
-
-
+
+
+
);
}
diff --git a/app/frontend/components/ECGMonitorCard.tsx b/app/frontend/components/ECGMonitorCard.tsx
new file mode 100644
index 0000000..4cb8cf0
--- /dev/null
+++ b/app/frontend/components/ECGMonitorCard.tsx
@@ -0,0 +1,162 @@
+import type { ReactNode } from "react";
+import type { PollState } from "@/types/monitor";
+
+type Tone = "mint" | "blue" | "coral";
+
+const toneStyles: Record = {
+ mint: "from-white via-white to-emerald-50 ring-emerald-100",
+ blue: "from-white via-white to-sky-50 ring-sky-100",
+ coral: "from-white via-white to-rose-50 ring-rose-100",
+};
+
+interface ECGMonitorCardProps {
+ title: string;
+ endpoint: string;
+ intervalLabel: string;
+ tone?: Tone;
+ now: string;
+ wavePoints: string;
+ waveHeight: number;
+ healthy: boolean;
+ loading: boolean;
+ statusLabel: string;
+ statusDetail: string;
+ lastUpdatedLabel: string;
+ state: PollState;
+ children?: ReactNode;
+ onManualTrigger: () => void;
+}
+
+/**
+ * Presentational card that renders endpoint status and the shared ECG waveform with
+ * consistent tone, status badges, and contextual metadata.
+ */
+export function ECGMonitorCard({
+ title,
+ endpoint,
+ intervalLabel,
+ tone = "mint",
+ now,
+ wavePoints,
+ waveHeight,
+ healthy,
+ loading,
+ statusLabel,
+ statusDetail,
+ lastUpdatedLabel,
+ state,
+ children,
+ onManualTrigger,
+}: ECGMonitorCardProps) {
+ const signalId = `${endpoint.replace(/[^a-z0-9]/gi, "-")}-stroke`;
+
+ const badgeColor = loading
+ ? "border-amber-200 bg-amber-50 text-amber-800"
+ : healthy
+ ? "border-emerald-200 bg-emerald-50 text-emerald-800"
+ : "border-rose-200 bg-rose-50 text-rose-800";
+
+ const dotColor = loading ? "bg-pulse" : healthy ? "bg-success" : "bg-danger";
+ const strokeColor = loading ? "#f59e0b" : healthy ? "#22c55e" : "#ef4444";
+
+ return (
+
+
+
+
+
+ {endpoint}
+
+
+
{title}
+
every {intervalLabel}
+
+
+
+
+
+ {statusLabel}
+
+
+
+ Poll now
+
+
+
+ {now}
+
+
+
+
+
+
+
+ Last updated
+
+
{lastUpdatedLabel}
+
+
+
+
+
+ ECG signal
+
+
+ {loading ? "Polling" : healthy ? "Healthy" : "Unhealthy"}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Status
+
{statusDetail}
+
+
+
Endpoint
+
{state.attemptedUrl}
+
+
+
+ {children}
+
+
+ );
+}
diff --git a/app/frontend/eslint.config.mjs b/app/frontend/eslint.config.mjs
new file mode 100644
index 0000000..3b254ee
--- /dev/null
+++ b/app/frontend/eslint.config.mjs
@@ -0,0 +1,44 @@
+import nextPlugin from "@next/eslint-plugin-next";
+import globals from "globals";
+import tseslint from "typescript-eslint";
+
+export default tseslint.config(
+ {
+ ignores: [
+ "**/.next/**",
+ "node_modules/**",
+ "dist/**",
+ "tailwind.config.js",
+ "postcss.config.mjs",
+ ],
+ },
+ {
+ files: ["**/*.{js,jsx,ts,tsx}"],
+ extends: [
+ ...tseslint.configs.recommendedTypeChecked,
+ ...tseslint.configs.stylisticTypeChecked,
+ nextPlugin.configs.recommended,
+ nextPlugin.configs["core-web-vitals"],
+ ],
+ languageOptions: {
+ parserOptions: {
+ project: "./tsconfig.json",
+ },
+ globals: {
+ ...globals.browser,
+ ...globals.node,
+ },
+ },
+ rules: {
+ "@typescript-eslint/consistent-type-imports": [
+ "error",
+ { prefer: "type-imports", fixStyle: "separate-type-imports" },
+ ],
+ "@typescript-eslint/no-misused-promises": [
+ "error",
+ { checksVoidReturn: false },
+ ],
+ "@typescript-eslint/no-floating-promises": "error",
+ },
+ }
+);
diff --git a/app/frontend/package-lock.json b/app/frontend/package-lock.json
index 8748ef6..be4e74e 100644
--- a/app/frontend/package-lock.json
+++ b/app/frontend/package-lock.json
@@ -8,24 +8,282 @@
"name": "avaaz-frontend",
"version": "0.1.0",
"dependencies": {
- "next": "15.5.6",
- "react": "18.3.1",
- "react-dom": "18.3.1"
+ "@tailwindcss/postcss": "^4.1.17",
+ "next": "^16.0.5",
+ "react": "^19.2.0",
+ "react-dom": "^19.2.0"
},
"devDependencies": {
- "@types/node": "22.9.4",
- "@types/react": "18.3.18",
- "@types/react-dom": "18.3.1",
- "eslint": "9.39.1",
- "eslint-config-next": "15.5.6",
- "typescript": "5.7.3"
+ "@types/node": "^24.10.1",
+ "@types/react": "^19.2.7",
+ "@types/react-dom": "^19.2.3",
+ "autoprefixer": "^10.4.20",
+ "eslint": "^9.39.1",
+ "eslint-config-next": "^16.0.5",
+ "globals": "^16.5.0",
+ "postcss": "^8.4.49",
+ "tailwindcss": "^4.1.17",
+ "typescript": "^5.9.3",
+ "typescript-eslint": "^8.48.0"
+ }
+ },
+ "node_modules/@alloc/quick-lru": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz",
+ "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
+ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.5",
+ "@babel/helper-compilation-targets": "^7.27.2",
+ "@babel/helper-module-transforms": "^7.28.3",
+ "@babel/helpers": "^7.28.4",
+ "@babel/parser": "^7.28.5",
+ "@babel/template": "^7.27.2",
+ "@babel/traverse": "^7.28.5",
+ "@babel/types": "^7.28.5",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz",
+ "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.5",
+ "@babel/types": "^7.28.5",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
+ "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.27.2",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
+ "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
+ "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "@babel/traverse": "^7.28.3"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz",
+ "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.4"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
+ "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.5"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
+ "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/parser": "^7.27.2",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz",
+ "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.5",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.28.5",
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.5",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
+ "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
}
},
"node_modules/@emnapi/core": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz",
"integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==",
- "dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -47,7 +305,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz",
"integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==",
- "dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -161,6 +418,19 @@
"url": "https://opencollective.com/eslint"
}
},
+ "node_modules/@eslint/eslintrc/node_modules/globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/@eslint/js": {
"version": "9.39.1",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz",
@@ -716,11 +986,55 @@
"url": "https://opencollective.com/libvips"
}
},
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
"node_modules/@napi-rs/wasm-runtime": {
"version": "0.2.12",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
"integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==",
- "dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -730,15 +1044,15 @@
}
},
"node_modules/@next/env": {
- "version": "15.5.6",
- "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.6.tgz",
- "integrity": "sha512-3qBGRW+sCGzgbpc5TS1a0p7eNxnOarGVQhZxfvTdnV0gFI61lX7QNtQ4V1TSREctXzYn5NetbUsLvyqwLFJM6Q==",
+ "version": "16.0.5",
+ "resolved": "https://registry.npmjs.org/@next/env/-/env-16.0.5.tgz",
+ "integrity": "sha512-jRLOw822AE6aaIm9oh0NrauZEM0Vtx5xhYPgqx89txUmv/UmcRwpcXmGeQOvYNT/1bakUwA+nG5CA74upYVVDw==",
"license": "MIT"
},
"node_modules/@next/eslint-plugin-next": {
- "version": "15.5.6",
- "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.5.6.tgz",
- "integrity": "sha512-YxDvsT2fwy1j5gMqk3ppXlsgDopHnkM4BoxSVASbvvgh5zgsK8lvWerDzPip8k3WVzsTZ1O7A7si1KNfN4OZfQ==",
+ "version": "16.0.5",
+ "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.0.5.tgz",
+ "integrity": "sha512-m1zPz6hsBvQt1CMRz7rTga8OXpRE9rVW4JHCSjW+tswTxiEU+6ev+GTlgm7ZzcCiMEVQAHTNhpEGFzDtVha9qg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -746,9 +1060,9 @@
}
},
"node_modules/@next/swc-darwin-arm64": {
- "version": "15.5.6",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.6.tgz",
- "integrity": "sha512-ES3nRz7N+L5Umz4KoGfZ4XX6gwHplwPhioVRc25+QNsDa7RtUF/z8wJcbuQ2Tffm5RZwuN2A063eapoJ1u4nPg==",
+ "version": "16.0.5",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.0.5.tgz",
+ "integrity": "sha512-65Mfo1rD+mVbJuBTlXbNelNOJ5ef+5pskifpFHsUt3cnOWjDNKctHBwwSz9tJlPp7qADZtiN/sdcG7mnc0El8Q==",
"cpu": [
"arm64"
],
@@ -762,9 +1076,9 @@
}
},
"node_modules/@next/swc-darwin-x64": {
- "version": "15.5.6",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.6.tgz",
- "integrity": "sha512-JIGcytAyk9LQp2/nuVZPAtj8uaJ/zZhsKOASTjxDug0SPU9LAM3wy6nPU735M1OqacR4U20LHVF5v5Wnl9ptTA==",
+ "version": "16.0.5",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.0.5.tgz",
+ "integrity": "sha512-2fDzXD/JpEjY500VUF0uuGq3YZcpC6XxmGabePPLyHCKbw/YXRugv3MRHH7MxE2hVHtryXeSYYnxcESb/3OUIQ==",
"cpu": [
"x64"
],
@@ -778,9 +1092,9 @@
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
- "version": "15.5.6",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.6.tgz",
- "integrity": "sha512-qvz4SVKQ0P3/Im9zcS2RmfFL/UCQnsJKJwQSkissbngnB/12c6bZTCB0gHTexz1s6d/mD0+egPKXAIRFVS7hQg==",
+ "version": "16.0.5",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.0.5.tgz",
+ "integrity": "sha512-meSLB52fw4tgDpPnyuhwA280EWLwwIntrxLYjzKU3e3730ur2WJAmmqoZ1LPIZ2l3eDfh9SBHnJGTczbgPeNeA==",
"cpu": [
"arm64"
],
@@ -794,9 +1108,9 @@
}
},
"node_modules/@next/swc-linux-arm64-musl": {
- "version": "15.5.6",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.6.tgz",
- "integrity": "sha512-FsbGVw3SJz1hZlvnWD+T6GFgV9/NYDeLTNQB2MXoPN5u9VA9OEDy6fJEfePfsUKAhJufFbZLgp0cPxMuV6SV0w==",
+ "version": "16.0.5",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.0.5.tgz",
+ "integrity": "sha512-aAJtQkvUzz5t0xVAmK931SIhWnSQAaEoTyG/sKPCYq2u835K/E4a14A+WRPd4dkhxIHNudE8dI+FpHekgdrA4g==",
"cpu": [
"arm64"
],
@@ -810,9 +1124,9 @@
}
},
"node_modules/@next/swc-linux-x64-gnu": {
- "version": "15.5.6",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.6.tgz",
- "integrity": "sha512-3QnHGFWlnvAgyxFxt2Ny8PTpXtQD7kVEeaFat5oPAHHI192WKYB+VIKZijtHLGdBBvc16tiAkPTDmQNOQ0dyrA==",
+ "version": "16.0.5",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.0.5.tgz",
+ "integrity": "sha512-bYwbjBwooMWRhy6vRxenaYdguTM2hlxFt1QBnUF235zTnU2DhGpETm5WU93UvtAy0uhC5Kgqsl8RyNXlprFJ6Q==",
"cpu": [
"x64"
],
@@ -826,9 +1140,9 @@
}
},
"node_modules/@next/swc-linux-x64-musl": {
- "version": "15.5.6",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.6.tgz",
- "integrity": "sha512-OsGX148sL+TqMK9YFaPFPoIaJKbFJJxFzkXZljIgA9hjMjdruKht6xDCEv1HLtlLNfkx3c5w2GLKhj7veBQizQ==",
+ "version": "16.0.5",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.0.5.tgz",
+ "integrity": "sha512-iGv2K/4gW3mkzh+VcZTf2gEGX5o9xdb5oPqHjgZvHdVzCw0iSAJ7n9vKzl3SIEIIHZmqRsgNasgoLd0cxaD+tg==",
"cpu": [
"x64"
],
@@ -842,9 +1156,9 @@
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
- "version": "15.5.6",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.6.tgz",
- "integrity": "sha512-ONOMrqWxdzXDJNh2n60H6gGyKed42Ieu6UTVPZteXpuKbLZTH4G4eBMsr5qWgOBA+s7F+uB4OJbZnrkEDnZ5Fg==",
+ "version": "16.0.5",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.0.5.tgz",
+ "integrity": "sha512-6xf52Hp4SH9+4jbYmfUleqkuxvdB9JJRwwFlVG38UDuEGPqpIA+0KiJEU9lxvb0RGNo2i2ZUhc5LHajij9H9+A==",
"cpu": [
"arm64"
],
@@ -858,9 +1172,9 @@
}
},
"node_modules/@next/swc-win32-x64-msvc": {
- "version": "15.5.6",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.6.tgz",
- "integrity": "sha512-pxK4VIjFRx1MY92UycLOOw7dTdvccWsNETQ0kDHkBlcFH1GrTLUjSiHU1ohrznnux6TqRHgv5oflhfIWZwVROQ==",
+ "version": "16.0.5",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.0.5.tgz",
+ "integrity": "sha512-06kTaOh+Qy/kguN+MMK+/VtKmRkQJrPlGQMvCUbABk1UxI5SKTgJhbmMj9Hf0qWwrS6g9JM6/Zk+etqeMyvHAw==",
"cpu": [
"x64"
],
@@ -928,13 +1242,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/@rushstack/eslint-patch": {
- "version": "1.15.0",
- "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.15.0.tgz",
- "integrity": "sha512-ojSshQPKwVvSMR8yT2L/QtUkV5SXi/IfDiJ4/8d6UbTPjiHVmxZzUAzGD8Tzks1b9+qQkZa0isUOvYObedITaw==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/@swc/helpers": {
"version": "0.5.15",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
@@ -944,11 +1251,266 @@
"tslib": "^2.8.0"
}
},
+ "node_modules/@tailwindcss/node": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.17.tgz",
+ "integrity": "sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.4",
+ "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.17"
+ }
+ },
+ "node_modules/@tailwindcss/oxide": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.17.tgz",
+ "integrity": "sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ },
+ "optionalDependencies": {
+ "@tailwindcss/oxide-android-arm64": "4.1.17",
+ "@tailwindcss/oxide-darwin-arm64": "4.1.17",
+ "@tailwindcss/oxide-darwin-x64": "4.1.17",
+ "@tailwindcss/oxide-freebsd-x64": "4.1.17",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.17",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.1.17",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.1.17",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.1.17",
+ "@tailwindcss/oxide-linux-x64-musl": "4.1.17",
+ "@tailwindcss/oxide-wasm32-wasi": "4.1.17",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.1.17",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.1.17"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-android-arm64": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.17.tgz",
+ "integrity": "sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.17.tgz",
+ "integrity": "sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-x64": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.17.tgz",
+ "integrity": "sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-freebsd-x64": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.17.tgz",
+ "integrity": "sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.17.tgz",
+ "integrity": "sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.17.tgz",
+ "integrity": "sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.17.tgz",
+ "integrity": "sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.17.tgz",
+ "integrity": "sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-musl": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.17.tgz",
+ "integrity": "sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.17.tgz",
+ "integrity": "sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==",
+ "bundleDependencies": [
+ "@napi-rs/wasm-runtime",
+ "@emnapi/core",
+ "@emnapi/runtime",
+ "@tybys/wasm-util",
+ "@emnapi/wasi-threads",
+ "tslib"
+ ],
+ "cpu": [
+ "wasm32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.6.0",
+ "@emnapi/runtime": "^1.6.0",
+ "@emnapi/wasi-threads": "^1.1.0",
+ "@napi-rs/wasm-runtime": "^1.0.7",
+ "@tybys/wasm-util": "^0.10.1",
+ "tslib": "^2.4.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.17.tgz",
+ "integrity": "sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.17.tgz",
+ "integrity": "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/postcss": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.17.tgz",
+ "integrity": "sha512-+nKl9N9mN5uJ+M7dBOOCzINw94MPstNR/GtIhz1fpZysxL/4a+No64jCBD6CPN+bIHWFx3KWuu8XJRrj/572Dw==",
+ "license": "MIT",
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "@tailwindcss/node": "4.1.17",
+ "@tailwindcss/oxide": "4.1.17",
+ "postcss": "^8.4.41",
+ "tailwindcss": "4.1.17"
+ }
+ },
"node_modules/@tybys/wasm-util": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
"integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
- "dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -977,41 +1539,34 @@
"license": "MIT"
},
"node_modules/@types/node": {
- "version": "22.9.4",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.4.tgz",
- "integrity": "sha512-d9RWfoR7JC/87vj7n+PVTzGg9hDyuFjir3RxUHbjFSKNd9mpxbxwMEyaCim/ddCmy4IuW7HjTzF3g9p3EtWEOg==",
+ "version": "24.10.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
+ "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "undici-types": "~6.19.8"
+ "undici-types": "~7.16.0"
}
},
- "node_modules/@types/prop-types": {
- "version": "15.7.15",
- "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
- "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/@types/react": {
- "version": "18.3.18",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz",
- "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==",
+ "version": "19.2.7",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
+ "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
- "@types/prop-types": "*",
- "csstype": "^3.0.2"
+ "csstype": "^3.2.2"
}
},
"node_modules/@types/react-dom": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz",
- "integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==",
+ "version": "19.2.3",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
+ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "@types/react": "*"
+ "peerDependencies": {
+ "@types/react": "^19.2.0"
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
@@ -1230,6 +1785,19 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
+ "version": "7.7.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/@typescript-eslint/utils": {
"version": "8.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.48.0.tgz",
@@ -1792,6 +2360,44 @@
"node": ">= 0.4"
}
},
+ "node_modules/autoprefixer": {
+ "version": "10.4.22",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.22.tgz",
+ "integrity": "sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.27.0",
+ "caniuse-lite": "^1.0.30001754",
+ "fraction.js": "^5.3.4",
+ "normalize-range": "^0.1.2",
+ "picocolors": "^1.1.1",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "bin": {
+ "autoprefixer": "bin/autoprefixer"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
"node_modules/available-typed-arrays": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
@@ -1835,6 +2441,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.8.31",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.31.tgz",
+ "integrity": "sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.js"
+ }
+ },
"node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
@@ -1859,6 +2475,41 @@
"node": ">=8"
}
},
+ "node_modules/browserslist": {
+ "version": "4.28.0",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz",
+ "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "baseline-browser-mapping": "^2.8.25",
+ "caniuse-lite": "^1.0.30001754",
+ "electron-to-chromium": "^1.5.249",
+ "node-releases": "^2.0.27",
+ "update-browserslist-db": "^1.1.4"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
"node_modules/call-bind": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
@@ -1989,6 +2640,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -2138,7 +2796,6 @@
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"license": "Apache-2.0",
- "optional": true,
"engines": {
"node": ">=8"
}
@@ -2171,6 +2828,13 @@
"node": ">= 0.4"
}
},
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.262",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.262.tgz",
+ "integrity": "sha512-NlAsMteRHek05jRUxUR0a5jpjYq9ykk6+kO0yRaMi5moe7u0fVIOeQ3Y30A8dIiWFBNUoQGi1ljb1i5VtS9WQQ==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
@@ -2178,6 +2842,19 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/enhanced-resolve": {
+ "version": "5.18.3",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
+ "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==",
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
"node_modules/es-abstract": {
"version": "1.24.0",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz",
@@ -2355,6 +3032,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -2430,25 +3117,24 @@
}
},
"node_modules/eslint-config-next": {
- "version": "15.5.6",
- "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.5.6.tgz",
- "integrity": "sha512-cGr3VQlPsZBEv8rtYp4BpG1KNXDqGvPo9VC1iaCgIA11OfziC/vczng+TnAS3WpRIR3Q5ye/6yl+CRUuZ1fPGg==",
+ "version": "16.0.5",
+ "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.0.5.tgz",
+ "integrity": "sha512-9rBjZ/biSpolkIUiqvx/iwJJaz8sxJ6pKWSPptJenpj01HlWbCDeaA1v0yG3a71IIPMplxVCSXhmtP27SXqMdg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@next/eslint-plugin-next": "15.5.6",
- "@rushstack/eslint-patch": "^1.10.3",
- "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0",
- "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0",
+ "@next/eslint-plugin-next": "16.0.5",
"eslint-import-resolver-node": "^0.3.6",
"eslint-import-resolver-typescript": "^3.5.2",
- "eslint-plugin-import": "^2.31.0",
+ "eslint-plugin-import": "^2.32.0",
"eslint-plugin-jsx-a11y": "^6.10.0",
"eslint-plugin-react": "^7.37.0",
- "eslint-plugin-react-hooks": "^5.0.0"
+ "eslint-plugin-react-hooks": "^7.0.0",
+ "globals": "16.4.0",
+ "typescript-eslint": "^8.46.0"
},
"peerDependencies": {
- "eslint": "^7.23.0 || ^8.0.0 || ^9.0.0",
+ "eslint": ">=9.0.0",
"typescript": ">=3.3.1"
},
"peerDependenciesMeta": {
@@ -2457,6 +3143,19 @@
}
}
},
+ "node_modules/eslint-config-next/node_modules/globals": {
+ "version": "16.4.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz",
+ "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/eslint-import-resolver-node": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz",
@@ -2587,16 +3286,6 @@
"ms": "^2.1.1"
}
},
- "node_modules/eslint-plugin-import/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
"node_modules/eslint-plugin-jsx-a11y": {
"version": "6.10.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz",
@@ -2661,13 +3350,20 @@
}
},
"node_modules/eslint-plugin-react-hooks": {
- "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==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz",
+ "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.24.4",
+ "@babel/parser": "^7.24.4",
+ "hermes-parser": "^0.25.1",
+ "zod": "^3.25.0 || ^4.0.0",
+ "zod-validation-error": "^3.5.0 || ^4.0.0"
+ },
"engines": {
- "node": ">=10"
+ "node": ">=18"
},
"peerDependencies": {
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
@@ -2691,16 +3387,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/eslint-plugin-react/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
"node_modules/eslint-scope": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
@@ -2936,6 +3622,20 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/fraction.js": {
+ "version": "5.3.4",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
+ "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/rawify"
+ }
+ },
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -2987,6 +3687,16 @@
"node": ">= 0.4"
}
},
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
@@ -3071,9 +3781,9 @@
}
},
"node_modules/globals": {
- "version": "14.0.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
- "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "version": "16.5.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz",
+ "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -3113,6 +3823,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "license": "ISC"
+ },
"node_modules/graphemer": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
@@ -3214,6 +3930,23 @@
"node": ">= 0.4"
}
},
+ "node_modules/hermes-estree": {
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
+ "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/hermes-parser": {
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz",
+ "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hermes-estree": "0.25.1"
+ }
+ },
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -3347,6 +4080,19 @@
"semver": "^7.7.1"
}
},
+ "node_modules/is-bun-module/node_modules/semver": {
+ "version": "7.7.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/is-callable": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
@@ -3700,10 +4446,20 @@
"node": ">= 0.4"
}
},
+ "node_modules/jiti": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
+ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true,
"license": "MIT"
},
"node_modules/js-yaml": {
@@ -3719,6 +4475,19 @@
"js-yaml": "bin/js-yaml.js"
}
},
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
@@ -3741,16 +4510,16 @@
"license": "MIT"
},
"node_modules/json5": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
- "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "minimist": "^1.2.0"
- },
"bin": {
"json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
}
},
"node_modules/jsx-ast-utils": {
@@ -3813,6 +4582,255 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/lightningcss": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",
+ "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==",
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "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"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz",
+ "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz",
+ "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz",
+ "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz",
+ "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz",
+ "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz",
+ "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz",
+ "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz",
+ "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz",
+ "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz",
+ "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz",
+ "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -3840,6 +4858,7 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
@@ -3848,6 +4867,25 @@
"loose-envify": "cli.js"
}
},
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -3954,12 +4992,12 @@
"license": "MIT"
},
"node_modules/next": {
- "version": "15.5.6",
- "resolved": "https://registry.npmjs.org/next/-/next-15.5.6.tgz",
- "integrity": "sha512-zTxsnI3LQo3c9HSdSf91O1jMNsEzIXDShXd4wVdg9y5shwLqBXi4ZtUUJyB86KGVSJLZx0PFONvO54aheGX8QQ==",
+ "version": "16.0.5",
+ "resolved": "https://registry.npmjs.org/next/-/next-16.0.5.tgz",
+ "integrity": "sha512-XUPsFqSqu/NDdPfn/cju9yfIedkDI7ytDoALD9todaSMxk1Z5e3WcbUjfI9xsanFTys7xz62lnRWNFqJordzkQ==",
"license": "MIT",
"dependencies": {
- "@next/env": "15.5.6",
+ "@next/env": "16.0.5",
"@swc/helpers": "0.5.15",
"caniuse-lite": "^1.0.30001579",
"postcss": "8.4.31",
@@ -3969,18 +5007,18 @@
"next": "dist/bin/next"
},
"engines": {
- "node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
+ "node": ">=20.9.0"
},
"optionalDependencies": {
- "@next/swc-darwin-arm64": "15.5.6",
- "@next/swc-darwin-x64": "15.5.6",
- "@next/swc-linux-arm64-gnu": "15.5.6",
- "@next/swc-linux-arm64-musl": "15.5.6",
- "@next/swc-linux-x64-gnu": "15.5.6",
- "@next/swc-linux-x64-musl": "15.5.6",
- "@next/swc-win32-arm64-msvc": "15.5.6",
- "@next/swc-win32-x64-msvc": "15.5.6",
- "sharp": "^0.34.3"
+ "@next/swc-darwin-arm64": "16.0.5",
+ "@next/swc-darwin-x64": "16.0.5",
+ "@next/swc-linux-arm64-gnu": "16.0.5",
+ "@next/swc-linux-arm64-musl": "16.0.5",
+ "@next/swc-linux-x64-gnu": "16.0.5",
+ "@next/swc-linux-x64-musl": "16.0.5",
+ "@next/swc-win32-arm64-msvc": "16.0.5",
+ "@next/swc-win32-x64-msvc": "16.0.5",
+ "sharp": "^0.34.4"
},
"peerDependencies": {
"@opentelemetry/api": "^1.1.0",
@@ -4005,6 +5043,51 @@
}
}
},
+ "node_modules/next/node_modules/postcss": {
+ "version": "8.4.31",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
+ "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.6",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.27",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
+ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/normalize-range": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
+ "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -4266,9 +5349,9 @@
}
},
"node_modules/postcss": {
- "version": "8.4.31",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
- "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"funding": [
{
"type": "opencollective",
@@ -4284,15 +5367,23 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
- "nanoid": "^3.3.6",
- "picocolors": "^1.0.0",
- "source-map-js": "^1.0.2"
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -4347,30 +5438,26 @@
"license": "MIT"
},
"node_modules/react": {
- "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==",
+ "version": "19.2.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
+ "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
"license": "MIT",
"peer": true,
- "dependencies": {
- "loose-envify": "^1.1.0"
- },
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
- "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "version": "19.2.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
+ "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
"license": "MIT",
"peer": true,
"dependencies": {
- "loose-envify": "^1.1.0",
- "scheduler": "^0.23.2"
+ "scheduler": "^0.27.0"
},
"peerDependencies": {
- "react": "^18.3.1"
+ "react": "^19.2.0"
}
},
"node_modules/react-is": {
@@ -4556,25 +5643,19 @@
}
},
"node_modules/scheduler": {
- "version": "0.23.2",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
- "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
- "license": "MIT",
- "dependencies": {
- "loose-envify": "^1.1.0"
- }
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "license": "MIT"
},
"node_modules/semver": {
- "version": "7.7.3",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
- "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
- "devOptional": true,
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
}
},
"node_modules/set-function-length": {
@@ -4671,6 +5752,19 @@
"@img/sharp-win32-x64": "0.34.5"
}
},
+ "node_modules/sharp/node_modules/semver": {
+ "version": "7.7.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "license": "ISC",
+ "optional": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -4985,6 +6079,25 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/tailwindcss": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz",
+ "integrity": "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==",
+ "license": "MIT"
+ },
+ "node_modules/tapable": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
+ "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
"node_modules/tinyglobby": {
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
@@ -5073,6 +6186,19 @@
"strip-bom": "^3.0.0"
}
},
+ "node_modules/tsconfig-paths/node_modules/json5": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
+ "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "minimist": "^1.2.0"
+ },
+ "bin": {
+ "json5": "lib/cli.js"
+ }
+ },
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@@ -5171,9 +6297,9 @@
}
},
"node_modules/typescript": {
- "version": "5.7.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
- "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
@@ -5185,6 +6311,30 @@
"node": ">=14.17"
}
},
+ "node_modules/typescript-eslint": {
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.48.0.tgz",
+ "integrity": "sha512-fcKOvQD9GUn3Xw63EgiDqhvWJ5jsyZUaekl3KVpGsDJnN46WJTe3jWxtQP9lMZm1LJNkFLlTaWAxK2vUQR+cqw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/eslint-plugin": "8.48.0",
+ "@typescript-eslint/parser": "8.48.0",
+ "@typescript-eslint/typescript-estree": "8.48.0",
+ "@typescript-eslint/utils": "8.48.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
"node_modules/unbox-primitive": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",
@@ -5205,9 +6355,9 @@
}
},
"node_modules/undici-types": {
- "version": "6.19.8",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
- "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
+ "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
"dev": true,
"license": "MIT"
},
@@ -5246,6 +6396,37 @@
"@unrs/resolver-binding-win32-x64-msvc": "1.11.1"
}
},
+ "node_modules/update-browserslist-db": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz",
+ "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
@@ -5371,6 +6552,13 @@
"node": ">=0.10.0"
}
},
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
@@ -5383,6 +6571,30 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
+ },
+ "node_modules/zod": {
+ "version": "4.1.13",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz",
+ "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zod-validation-error": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz",
+ "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "zod": "^3.25.0 || ^4.0.0"
+ }
}
}
}
diff --git a/app/frontend/package.json b/app/frontend/package.json
index 578c138..40d96fc 100644
--- a/app/frontend/package.json
+++ b/app/frontend/package.json
@@ -4,21 +4,28 @@
"private": true,
"scripts": {
"dev": "next dev",
- "build": "next build",
+ "build": "next build --webpack",
"start": "next start",
- "lint": "next lint"
+ "lint": "eslint ."
},
+ "type": "module",
"dependencies": {
- "next": "15.5.6",
- "react": "18.3.1",
- "react-dom": "18.3.1"
+ "@tailwindcss/postcss": "^4.1.17",
+ "next": "^16.0.5",
+ "react": "^19.2.0",
+ "react-dom": "^19.2.0"
},
"devDependencies": {
- "@types/node": "22.9.4",
- "@types/react": "18.3.18",
- "@types/react-dom": "18.3.1",
- "eslint": "9.39.1",
- "eslint-config-next": "15.5.6",
- "typescript": "5.7.3"
+ "@types/node": "^24.10.1",
+ "@types/react": "^19.2.7",
+ "@types/react-dom": "^19.2.3",
+ "autoprefixer": "^10.4.20",
+ "eslint": "^9.39.1",
+ "eslint-config-next": "^16.0.5",
+ "globals": "^16.5.0",
+ "postcss": "^8.4.49",
+ "tailwindcss": "^4.1.17",
+ "typescript": "^5.9.3",
+ "typescript-eslint": "^8.48.0"
}
}
diff --git a/app/frontend/postcss.config.mjs b/app/frontend/postcss.config.mjs
new file mode 100644
index 0000000..f69c5d4
--- /dev/null
+++ b/app/frontend/postcss.config.mjs
@@ -0,0 +1,6 @@
+export default {
+ plugins: {
+ "@tailwindcss/postcss": {},
+ autoprefixer: {},
+ },
+};
diff --git a/app/frontend/public/.gitkeep b/app/frontend/public/.gitkeep
deleted file mode 100644
index 98430de..0000000
--- a/app/frontend/public/.gitkeep
+++ /dev/null
@@ -1 +0,0 @@
-# Placeholder to ensure public assets directory exists for builds.
diff --git a/app/frontend/tsconfig.json b/app/frontend/tsconfig.json
index 56e7077..842da0a 100644
--- a/app/frontend/tsconfig.json
+++ b/app/frontend/tsconfig.json
@@ -1,21 +1,21 @@
{
"compilerOptions": {
- "target": "es2017",
+ "target": "ES2022",
"lib": [
"dom",
"dom.iterable",
- "esnext"
+ "ES2022"
],
"allowJs": false,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
- "module": "esnext",
- "moduleResolution": "bundler",
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
"resolveJsonModule": true,
"isolatedModules": true,
- "jsx": "preserve",
+ "jsx": "react-jsx",
"incremental": true,
"types": [
"node"
@@ -24,13 +24,19 @@
{
"name": "next"
}
- ]
+ ],
+ "paths": {
+ "@/*": [
+ "./*"
+ ]
+ }
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
- ".next/types/**/*.ts"
+ ".next/types/**/*.ts",
+ ".next/dev/types/**/*.ts"
],
"exclude": [
"node_modules"
diff --git a/app/frontend/types/monitor.ts b/app/frontend/types/monitor.ts
new file mode 100644
index 0000000..60a8560
--- /dev/null
+++ b/app/frontend/types/monitor.ts
@@ -0,0 +1,7 @@
+export interface PollState {
+ data: T | null;
+ error: string | null;
+ loading: boolean;
+ lastUpdated: string | null;
+ attemptedUrl: string;
+}
diff --git a/tmp/Dockerfile b/tmp/Dockerfile
new file mode 100644
index 0000000..ca0d5a5
--- /dev/null
+++ b/tmp/Dockerfile
@@ -0,0 +1,71 @@
+#
+# FRONTEND DOCKERFILE
+#
+# Multi-stage image for the Next.js SPA/SSR frontend.
+# - runner: production server with minimal footprint
+# - builder: compiles the Next.js app
+# - dev: hot-reload friendly image
+#
+# COMPOSE_PROFILES decides which stage is used by docker-compose.yml.
+#
+
+FROM node:22-slim AS base
+WORKDIR /app
+
+ENV NPM_CONFIG_LOGLEVEL=warn \
+ NODE_OPTIONS="--enable-source-maps"
+
+# ------------------------------------------------------------------------------
+# Dependencies cache
+# ------------------------------------------------------------------------------
+FROM base AS deps
+COPY package*.json ./
+RUN if [ -f package-lock.json ]; then npm ci; else npm install; fi
+
+# ------------------------------------------------------------------------------
+# Production dependencies only (pruned to omit dev tooling)
+# ------------------------------------------------------------------------------
+FROM base AS prod-deps
+COPY package*.json ./
+RUN if [ -f package-lock.json ]; then npm ci --omit=dev; else npm install --omit=dev; fi
+
+# ------------------------------------------------------------------------------
+# Builder: compile the application for production
+# ------------------------------------------------------------------------------
+FROM base AS builder
+
+COPY --from=deps /app/node_modules ./node_modules
+COPY . .
+ENV NODE_ENV=production
+RUN npm run build
+
+# ------------------------------------------------------------------------------
+# Production runner: serve the built Next.js app
+# ------------------------------------------------------------------------------
+FROM node:22-slim AS runner
+WORKDIR /app
+
+ENV NODE_ENV=production \
+ NEXT_TELEMETRY_DISABLED=1
+
+USER node
+COPY --from=prod-deps --chown=node:node /app/node_modules ./node_modules
+COPY --from=builder --chown=node:node /app/.next ./.next
+COPY --from=builder --chown=node:node /app/public ./public
+COPY --from=builder --chown=node:node /app/package.json ./package.json
+
+EXPOSE 3000
+CMD ["npm", "run", "start"]
+
+# ------------------------------------------------------------------------------
+# Development: keeps node_modules and sources mounted for hot reload
+# ------------------------------------------------------------------------------
+FROM deps AS dev
+WORKDIR /app
+
+ENV NODE_ENV=development \
+ NEXT_TELEMETRY_DISABLED=1
+
+USER node
+EXPOSE 3000
+CMD ["npm", "run", "dev", "--", "--hostname", "0.0.0.0", "--port", "3000"]