feat: add tunnel configuration tools

This commit is contained in:
2025-09-06 18:53:38 +07:00
parent d5ffb87e1a
commit 9701d8766e
7 changed files with 1655 additions and 313 deletions

View File

@ -1,64 +0,0 @@
"use client"
import { useState } from "react"
export default function Card() {
const [copied, setCopied] = useState(false)
const command = "ssh id.tunnl.live -p 2200 -R 443:localhost:8000"
const copyToClipboard = () => {
navigator.clipboard.writeText(command)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}
return (
<div className="mb-16">
<h2 className="text-2xl font-bold mb-6">Connect with a single command</h2>
<div className="relative">
<div className="bg-gray-900 rounded-lg p-4 border border-gray-800 font-mono text-sm sm:text-base overflow-x-auto">
<pre className="whitespace-pre-wrap break-all sm:break-normal">{command}</pre>
</div>
<button
onClick={copyToClipboard}
className="absolute right-3 top-3 h-8 w-8 flex items-center justify-center rounded-md text-gray-400 hover:text-white hover:bg-gray-800"
aria-label="Copy command"
>
{copied ? (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="text-emerald-400"
>
<path d="M20 6 9 17l-5-5" />
</svg>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
</svg>
)}
</button>
</div>
<p className="mt-4 text-sm text-gray-400">
This command creates a secure tunnel from our server to your localhost:8000
</p>
</div>
)
}

View File

@ -1,11 +1,23 @@
import Card from "./card" "use client"
import { useState } from "react"
import TunnelConfig, { type TunnelConfig as TunnelConfigType, type Server } from "@/components/tunnel-config"
const defaultConfig: TunnelConfigType = {
type: "http",
serverPort: 443,
localPort: 8000,
}
export default function Home() { export default function Home() {
const [selectedServer, setSelectedServer] = useState<Server | null>(null)
const [tunnelConfig, setTunnelConfig] = useState<TunnelConfigType>(defaultConfig)
return ( return (
<div className="flex min-h-screen flex-col bg-gray-950 text-white"> <div className="flex min-h-screen flex-col bg-gray-950 text-white">
<main className="flex-1 flex flex-col items-center justify-center px-4"> <main className="flex-1 flex flex-col items-center justify-center px-4 py-8">
<div className="w-full max-w-3xl mx-auto text-center"> <div className="w-full max-w-4xl mx-auto">
<div className="mb-12"> <div className="text-center mb-12">
<h1 className="text-4xl font-bold tracking-tight sm:text-5xl md:text-6xl mb-6"> <h1 className="text-4xl font-bold tracking-tight sm:text-5xl md:text-6xl mb-6">
<span className="text-emerald-400">tunnl</span>.live <span className="text-emerald-400">tunnl</span>.live
</h1> </h1>
@ -13,7 +25,15 @@ export default function Home() {
Expose your local services to the internet securely with our fast and reliable SSH tunneling service. Expose your local services to the internet securely with our fast and reliable SSH tunneling service.
</p> </p>
</div> </div>
<Card />
<TunnelConfig
config={tunnelConfig}
onConfigChange={setTunnelConfig}
selectedServer={selectedServer}
onServerSelect={setSelectedServer}
/>
<div className="max-w-3xl mx-auto">
<div className="grid gap-8 md:grid-cols-3 mb-16"> <div className="grid gap-8 md:grid-cols-3 mb-16">
<div className="bg-gray-900 p-6 rounded-lg border border-gray-800"> <div className="bg-gray-900 p-6 rounded-lg border border-gray-800">
<div className="mb-4 inline-block rounded-full bg-emerald-950 p-3"> <div className="mb-4 inline-block rounded-full bg-emerald-950 p-3">
@ -36,7 +56,7 @@ export default function Home() {
</div> </div>
<h3 className="text-xl font-bold mb-2">Global Network</h3> <h3 className="text-xl font-bold mb-2">Global Network</h3>
<p className="text-gray-400"> <p className="text-gray-400">
We offer low-latency, high-availability servers located in Singapore for optimal performance. Choose from servers in US, Singapore, and Indonesia for optimal performance.
</p> </p>
</div> </div>
<div className="bg-gray-900 p-6 rounded-lg border border-gray-800"> <div className="bg-gray-900 p-6 rounded-lg border border-gray-800">
@ -75,14 +95,14 @@ export default function Home() {
strokeLinejoin="round" strokeLinejoin="round"
className="text-emerald-400" className="text-emerald-400"
> >
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /> <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1-2-2h6" />
<polyline points="15 3 21 3 21 9" /> <polyline points="15 3 21 3 21 9" />
<line x1="10" x2="21" y1="14" y2="3" /> <line x1="10" x2="21" y1="14" y2="3" />
</svg> </svg>
</div> </div>
<h3 className="text-xl font-bold mb-2">Easy Sharing</h3> <h3 className="text-xl font-bold mb-2">Flexible Configuration</h3>
<p className="text-gray-400"> <p className="text-gray-400">
Share your local development with clients or teammates without complex setup. Support for both HTTP/HTTPS and TCP tunneling with custom port configuration.
</p> </p>
</div> </div>
</div> </div>
@ -94,6 +114,7 @@ export default function Home() {
</p> </p>
</div> </div>
</div> </div>
</div>
</main> </main>
<footer className="border-t border-gray-800 py-6 px-4"> <footer className="border-t border-gray-800 py-6 px-4">
<div className="max-w-3xl mx-auto text-center"> <div className="max-w-3xl mx-auto text-center">

View File

@ -0,0 +1,106 @@
"use client"
import Image from "next/image"
import Link from "next/link"
export default function TunnelNotFound() {
const exampleUrl = "example.com"
return (
<div className="flex min-h-screen flex-col bg-gray-950 text-white">
<main className="flex-1 flex flex-col items-center justify-center px-4 py-8">
<div className="w-full max-w-2xl mx-auto text-center">
{/* Mascot */}
<div className="mb-8">
<Image
src="/mascot-confused.png"
alt="Confused tunnel mascot"
width={200}
height={200}
className="mx-auto"
/>
</div>
{/* Error Message */}
<div className="mb-12">
<h1 className="text-4xl font-bold mb-6">Tunnel Not Found</h1>
<p className="text-gray-400 mb-6 text-lg">We couldn't find an active tunnel for:</p>
<div className="bg-gray-900 rounded-lg p-4 border border-gray-800 font-mono text-emerald-400 text-xl max-w-md mx-auto mb-8">
{exampleUrl}
</div>
<p className="text-gray-300 text-lg">This means no SSH tunnel is currently running for this domain.</p>
</div>
{/* Instructions */}
<div className="bg-gray-900 rounded-lg p-8 border border-gray-800 mb-8 text-left">
<h2 className="text-xl font-bold mb-6 text-center">To create a tunnel:</h2>
<div className="space-y-4">
<div className="flex items-start gap-4">
<span className="bg-emerald-600 text-white rounded-full w-8 h-8 flex items-center justify-center text-sm font-bold flex-shrink-0 mt-1">
1
</span>
<div>
<p className="text-gray-300 font-medium">Go to the main page</p>
<p className="text-gray-400 text-sm">Configure your tunnel settings and choose a server</p>
</div>
</div>
<div className="flex items-start gap-4">
<span className="bg-emerald-600 text-white rounded-full w-8 h-8 flex items-center justify-center text-sm font-bold flex-shrink-0 mt-1">
2
</span>
<div>
<p className="text-gray-300 font-medium">Make sure your local service is running</p>
<p className="text-gray-400 text-sm">Your application should be accessible on localhost</p>
</div>
</div>
<div className="flex items-start gap-4">
<span className="bg-emerald-600 text-white rounded-full w-8 h-8 flex items-center justify-center text-sm font-bold flex-shrink-0 mt-1">
3
</span>
<div>
<p className="text-gray-300 font-medium">Run the SSH command</p>
<p className="text-gray-400 text-sm">Copy and paste the generated command into your terminal</p>
</div>
</div>
<div className="flex items-start gap-4">
<span className="bg-emerald-600 text-white rounded-full w-8 h-8 flex items-center justify-center text-sm font-bold flex-shrink-0 mt-1">
4
</span>
<div>
<p className="text-gray-300 font-medium">Access your tunnel</p>
<p className="text-gray-400 text-sm">Your service will be available through the tunnel URL</p>
</div>
</div>
</div>
</div>
{/* Call to Action */}
<div className="space-y-4">
<Link
href="/"
className="inline-flex items-center gap-3 bg-emerald-600 hover:bg-emerald-700 text-white px-6 py-3 rounded-lg font-medium transition-colors"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M5 12h14" />
<path d="M12 5l7 7-7 7" />
</svg>
Create Your Tunnel
</Link>
<p className="text-gray-400 text-sm">Need help? Check our documentation or contact support.</p>
</div>
</div>
</main>
</div>
)
}

View File

@ -0,0 +1,771 @@
"use client"
import { useState, useEffect } from "react"
import { ComposableMap, Geographies, Geography, Marker } from "react-simple-maps"
export interface TunnelConfig {
type: "http" | "tcp"
serverPort: number
localPort: number
}
interface Server {
id: string
name: string
location: string
subdomain: string
coordinates: [number, number]
ping: number | null
status: "online" | "offline" | "maintenance"
pingStatus: "idle" | "testing" | "success" | "failed" | "timeout"
}
const geoUrl = "https://cdn.jsdelivr.net/npm/world-atlas@2/countries-110m.json"
interface TunnelConfigProps {
config: TunnelConfig
onConfigChange: (config: TunnelConfig) => void
selectedServer: Server | null
onServerSelect: (server: Server) => void
}
const fetchServers = async (): Promise<Server[]> => {
await new Promise((resolve) => setTimeout(resolve, 2000))
const mockServers: Server[] = [
{
id: "sgp",
name: "Singapore",
location: "Singapore",
subdomain: "sgp.tunnl.live",
coordinates: [103.8198, 1.3521],
ping: null,
status: "online",
pingStatus: "idle",
},
{
id: "id",
name: "Indonesia",
location: "Jakarta",
subdomain: "id.tunnl.live",
coordinates: [106.8456, -6.2088],
ping: null,
status: "online",
pingStatus: "idle",
},
]
return mockServers.filter((server) => server.status === "online")
}
const testServerPing = (
server: Server,
): Promise<{ server: Server; ping: number | null; status: Server["pingStatus"] }> => {
return new Promise((resolve) => {
const startTime = Date.now()
const timeout = 5000
let resolved = false
const pingUrl = `wss://ping.${server.subdomain}`
try {
const ws = new WebSocket(pingUrl)
const timeoutId = setTimeout(() => {
if (!resolved) {
resolved = true
ws.close()
resolve({
server,
ping: null,
status: "timeout",
})
}
}, timeout)
ws.onopen = () => {
console.log(`Connected to ${pingUrl}`)
}
ws.onmessage = (event) => {
if (event.data === "pong" && !resolved) {
resolved = true
const ping = Date.now() - startTime
clearTimeout(timeoutId)
ws.close()
resolve({
server,
ping,
status: "success",
})
}
}
ws.onclose = (event) => {
if (!resolved) {
resolved = true
clearTimeout(timeoutId)
resolve({
server,
ping: null,
status: "failed",
})
}
}
ws.onerror = (error) => {
if (!resolved) {
resolved = true
clearTimeout(timeoutId)
console.error(`WebSocket error for ${pingUrl}:`, error)
resolve({
server,
ping: null,
status: "failed",
})
}
}
} catch (error) {
console.error(`Failed to create WebSocket for ${pingUrl}:`, error)
resolve({
server,
ping: null,
status: "failed",
})
}
})
}
export default function TunnelConfig({ config, onConfigChange, selectedServer, onServerSelect }: TunnelConfigProps) {
const [localConfig, setLocalConfig] = useState<TunnelConfig>(config)
const [servers, setServers] = useState<Server[]>([])
const [isLoadingServers, setIsLoadingServers] = useState(true)
const [isTestingPings, setIsTestingPings] = useState(false)
const [hasAutoTested, setHasAutoTested] = useState(false)
const [copied, setCopied] = useState(false)
const [serverError, setServerError] = useState<string | null>(null)
useEffect(() => {
const loadServers = async () => {
try {
setIsLoadingServers(true)
setServerError(null)
const serverData = await fetchServers()
setServers(serverData)
if (serverData.length === 0) {
setServerError("No servers are currently available. Please try again later.")
}
} catch (error) {
setServerError("Failed to load servers. Please check your connection and try again.")
} finally {
setIsLoadingServers(false)
}
}
loadServers()
}, [])
useEffect(() => {
if (servers.length > 0 && !isLoadingServers && !hasAutoTested) {
const autoTestPings = async () => {
setIsTestingPings(true)
setHasAutoTested(true)
setServers((prevServers) =>
prevServers.map((server) => ({
...server,
pingStatus: "testing" as const,
})),
)
try {
const pingPromises = servers.map((server) => testServerPing(server))
const results = await Promise.all(pingPromises)
const updatedServers = results.map((result) => ({
...result.server,
ping: result.ping,
pingStatus: result.status,
}))
setServers(updatedServers)
const successfulServers = updatedServers.filter((s) => s.pingStatus === "success" && s.ping !== null)
if (successfulServers.length > 0) {
const bestServer = successfulServers.reduce((prev, current) =>
prev.ping! < current.ping! ? prev : current,
)
onServerSelect(bestServer)
} else if (updatedServers.length > 0) {
onServerSelect(updatedServers[0])
}
} catch (error) {
console.error("Error testing pings:", error)
} finally {
setIsTestingPings(false)
}
}
autoTestPings()
}
}, [servers.length, isLoadingServers, hasAutoTested, onServerSelect])
const updateConfig = (updates: Partial<TunnelConfig>) => {
const newConfig = { ...localConfig, ...updates }
if (updates.serverPort && (updates.serverPort === 80 || updates.serverPort === 443)) {
newConfig.type = "http"
}
setLocalConfig(newConfig)
onConfigChange(newConfig)
}
const generateCommand = () => {
if (!selectedServer) return ""
const { serverPort, localPort } = localConfig
return `ssh ${selectedServer.subdomain} -p 2200 -R ${serverPort}:localhost:${localPort}`
}
const copyToClipboard = () => {
const command = generateCommand()
if (command) {
navigator.clipboard.writeText(command)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}
}
const getPingColor = (server: Server) => {
if (server.pingStatus === "testing") return "text-gray-400"
if (server.pingStatus === "failed" || server.pingStatus === "timeout") return "text-red-400"
if (server.pingStatus === "idle" || !server.ping) return "text-gray-400"
if (server.ping < 50) return "text-green-400"
if (server.ping < 100) return "text-yellow-400"
if (server.ping < 150) return "text-orange-400"
return "text-red-400"
}
const getPingDisplay = (server: Server) => {
if (server.pingStatus === "testing") return "Testing..."
if (server.pingStatus === "timeout") return "Timeout"
if (server.pingStatus === "failed") return "Failed"
if (server.pingStatus === "idle") return "Click to test"
if (server.ping === null) return "N/A"
return `${server.ping}ms`
}
const getPingStatus = (server: Server) => {
if (server.pingStatus === "testing") return "Testing..."
if (server.pingStatus === "timeout") return "Timeout (5s)"
if (server.pingStatus === "failed") return "Connection Failed"
if (server.pingStatus === "idle") return "Not tested"
if (!server.ping) return "Unknown"
if (server.ping < 50) return "Excellent"
if (server.ping < 100) return "Good"
if (server.ping < 150) return "Fair"
return "Poor"
}
const getMarkerColor = (server: Server) => {
if (selectedServer?.id === server.id) return "#10b981"
if (server.pingStatus === "failed" || server.pingStatus === "timeout") return "#ef4444"
if (server.pingStatus === "success" && server.ping !== null) {
if (server.ping < 50) return "#10b981"
if (server.ping < 100) return "#eab308"
if (server.ping < 150) return "#f97316"
return "#ef4444"
}
return "#6b7280"
}
const getMarkerStroke = (server: Server) => {
if (selectedServer?.id === server.id) return "#34d399"
if (server.pingStatus === "failed" || server.pingStatus === "timeout") return "#f87171"
if (server.pingStatus === "success" && server.ping !== null) {
if (server.ping < 50) return "#34d399"
if (server.ping < 100) return "#facc15"
if (server.ping < 150) return "#fb923c"
return "#f87171"
}
return "#9ca3af"
}
const testPingForServer = async (server: Server) => {
setServers((prevServers) =>
prevServers.map((s) => (s.id === server.id ? { ...s, pingStatus: "testing", ping: null } : s)),
)
try {
const timeoutPromise = new Promise<{ server: Server; ping: number | null; status: Server["pingStatus"] }>(
(_, reject) => {
setTimeout(() => reject(new Error("Timeout")), 5000)
},
)
const result = await Promise.race([testServerPing(server), timeoutPromise])
setServers((prevServers) =>
prevServers.map((s) => (s.id === server.id ? { ...s, ping: result.ping, pingStatus: result.status } : s)),
)
} catch (error) {
console.error("Error testing ping:", error)
setServers((prevServers) =>
prevServers.map((s) => (s.id === server.id ? { ...s, ping: null, pingStatus: "timeout" } : s)),
)
}
}
const testAllPings = async () => {
if (servers.length === 0 || isTestingPings) return
setIsTestingPings(true)
setServers((prevServers) =>
prevServers.map((server) => ({
...server,
ping: null,
pingStatus: "testing" as const,
})),
)
try {
const pingPromises = servers.map((server) => testServerPing(server))
const results = await Promise.all(pingPromises)
const updatedServers = results.map((result) => ({
...result.server,
ping: result.ping,
pingStatus: result.status,
}))
setServers(updatedServers)
} catch (error) {
console.error("Error testing pings:", error)
} finally {
setIsTestingPings(false)
}
}
return (
<div className="bg-gray-900 rounded-lg border border-gray-800 p-6 mb-8">
<h3 className="text-lg font-bold mb-6">Tunnel Configuration</h3>
<div className="mb-6">
<div className="flex items-center justify-between mb-4">
<h4 className="text-md font-medium">Choose Your Server Location</h4>
{servers.length > 0 && hasAutoTested && (
<button
onClick={testAllPings}
disabled={isTestingPings}
className="text-sm text-emerald-400 hover:text-emerald-300 disabled:text-gray-500 disabled:cursor-not-allowed flex items-center gap-1"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={isTestingPings ? "animate-spin" : ""}
>
<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" />
<path d="M21 3v5h-5" />
<path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" />
<path d="M3 21v-5h5" />
</svg>
{isTestingPings ? "Testing All..." : "Test All Pings"}
</button>
)}
</div>
{isLoadingServers ? (
<div className="bg-gray-800 rounded-lg border border-gray-700 p-8 text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-emerald-400 mx-auto mb-4"></div>
<p className="text-gray-400">Loading available servers...</p>
</div>
) : serverError ? (
<div className="bg-red-950 rounded-lg border border-red-800 p-6 text-center">
<div className="mb-4">
<svg
xmlns="http://www.w3.org/2000/svg"
width="48"
height="48"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="text-red-400 mx-auto"
>
<circle cx="12" cy="12" r="10" />
<line x1="15" x2="9" y1="9" y2="15" />
<line x1="9" x2="15" y1="9" y2="15" />
</svg>
</div>
<p className="text-red-400 font-medium mb-2">Server Unavailable</p>
<p className="text-red-300 text-sm">{serverError}</p>
<button
onClick={() => window.location.reload()}
className="mt-4 px-4 py-2 bg-red-800 hover:bg-red-700 text-white rounded-lg text-sm transition-colors"
>
Retry
</button>
</div>
) : servers.length === 0 ? (
<div className="bg-yellow-950 rounded-lg border border-yellow-800 p-6 text-center">
<div className="mb-4">
<svg
xmlns="http://www.w3.org/2000/svg"
width="48"
height="48"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="text-yellow-400 mx-auto"
>
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
<line x1="12" x2="12" y1="9" y2="13" />
<line x1="12" x2="12.01" y1="17" y2="17" />
</svg>
</div>
<p className="text-yellow-400 font-medium mb-2">No Servers Available</p>
<p className="text-yellow-300 text-sm">All servers are currently offline for maintenance.</p>
</div>
) : (
<>
<div className="bg-gray-800 rounded-lg border border-gray-700 p-3 mb-4">
<ComposableMap
projection="geoMercator"
projectionConfig={{
scale: 100,
center: [0, 20],
}}
width={600}
height={250}
style={{
width: "100%",
height: "auto",
}}
>
<Geographies geography={geoUrl}>
{({ geographies }) =>
geographies.map((geo) => (
<Geography
key={geo.rsmKey}
geography={geo}
fill="#374151"
stroke="#4b5563"
strokeWidth={0.5}
style={{
default: { outline: "none" },
hover: { outline: "none", fill: "#4b5563" },
pressed: { outline: "none" },
}}
/>
))
}
</Geographies>
{servers.map((server) => (
<Marker
key={server.id}
coordinates={server.coordinates}
onClick={() => {
if (server.pingStatus === "failed" || server.pingStatus === "timeout") {
return
}
onServerSelect(server)
}}
>
<g>
{selectedServer?.id === server.id && (
<circle r="12" fill="none" stroke="#10b981" strokeWidth="2" opacity="0.6">
<animate attributeName="r" values="6;15;6" dur="2s" repeatCount="indefinite" />
<animate attributeName="opacity" values="0.6;0;0.6" dur="2s" repeatCount="indefinite" />
</circle>
)}
<circle r="6" fill={getMarkerColor(server)} stroke={getMarkerStroke(server)} strokeWidth="2" />
<text
textAnchor="middle"
y="-12"
style={{
fontFamily: "system-ui",
fontSize: "10px",
fontWeight: "bold",
fill: "white",
pointerEvents: "none",
}}
>
{server.location}
</text>
</g>
</Marker>
))}
</ComposableMap>
</div>
<div className="grid gap-3 md:grid-cols-3">
{servers.map((server) => (
<div
key={server.id}
onClick={() => {
if (server.pingStatus === "failed" || server.pingStatus === "timeout") {
return
}
onServerSelect(server)
}}
className={`p-3 rounded-lg border transition-all duration-200 ${selectedServer?.id === server.id
? "bg-emerald-950 border-emerald-500"
: server.pingStatus === "failed" || server.pingStatus === "timeout"
? "bg-red-950 border-red-800 cursor-not-allowed opacity-75"
: "bg-gray-800 border-gray-700 hover:border-gray-600 cursor-pointer"
}`}
>
<div className="flex items-center justify-between mb-1">
<h5 className="font-medium text-sm">{server.name}</h5>
<div
className={`w-2 h-2 rounded-full ${selectedServer?.id === server.id
? "bg-emerald-400"
: server.pingStatus === "failed" || server.pingStatus === "timeout"
? "bg-red-400"
: server.pingStatus === "success" && server.ping !== null
? server.ping < 50
? "bg-green-400"
: server.ping < 100
? "bg-yellow-400"
: server.ping < 150
? "bg-orange-400"
: "bg-red-400"
: "bg-gray-600"
}`}
/>
</div>
<p className="text-xs text-gray-400 mb-1">{server.location}</p>
<p className="text-xs font-mono text-gray-500 mb-2">{server.subdomain}</p>
<div className="flex items-center justify-between">
<span className="text-xs">Ping:</span>
<div className="flex items-center gap-1">
{server.pingStatus === "testing" && (
<div className="animate-spin rounded-full h-3 w-3 border border-gray-400 border-t-transparent"></div>
)}
{hasAutoTested ? (
<button
onClick={(e) => {
e.stopPropagation()
if (server.pingStatus !== "testing") {
testPingForServer(server)
}
}}
disabled={server.pingStatus === "testing"}
className={`text-xs font-bold hover:underline disabled:no-underline disabled:cursor-not-allowed ${getPingColor(
server,
)}`}
>
{getPingDisplay(server)}
</button>
) : (
<span className={`text-xs font-bold ${getPingColor(server)}`}>{getPingDisplay(server)}</span>
)}
</div>
</div>
<p className="text-xs text-gray-500 mt-1">{getPingStatus(server)}</p>
{(server.pingStatus === "failed" || server.pingStatus === "timeout") && (
<p className="text-xs text-red-400 mt-1 font-medium">Cannot select - Server unavailable</p>
)}
</div>
))}
</div>
</>
)}
</div>
{servers.length > 0 && (
<>
<div className="mb-6">
<label className="block text-sm font-medium mb-3">Forwarding Type</label>
<div className="flex gap-4">
<label className="flex items-center cursor-pointer">
<input
type="radio"
name="forwardingType"
value="http"
checked={localConfig.type === "http"}
onChange={() => updateConfig({ type: "http", serverPort: 443 })}
className="sr-only"
/>
<div
className={`flex items-center gap-2 px-4 py-2 rounded-lg border transition-all ${localConfig.type === "http"
? "bg-emerald-950 border-emerald-500 text-emerald-400"
: "bg-gray-800 border-gray-700 text-gray-300 hover:border-gray-600"
}`}
>
<div
className={`w-2 h-2 rounded-full ${localConfig.type === "http" ? "bg-emerald-400" : "bg-gray-500"}`}
/>
<span className="font-medium">HTTP/HTTPS</span>
</div>
</label>
<label className="flex items-center cursor-pointer">
<input
type="radio"
name="forwardingType"
value="tcp"
checked={localConfig.type === "tcp"}
onChange={() => updateConfig({ type: "tcp", serverPort: 8080 })}
className="sr-only"
/>
<div
className={`flex items-center gap-2 px-4 py-2 rounded-lg border transition-all ${localConfig.type === "tcp"
? "bg-emerald-950 border-emerald-500 text-emerald-400"
: "bg-gray-800 border-gray-700 text-gray-300 hover:border-gray-600"
}`}
>
<div
className={`w-2 h-2 rounded-full ${localConfig.type === "tcp" ? "bg-emerald-400" : "bg-gray-500"}`}
/>
<span className="font-medium">TCP</span>
</div>
</label>
</div>
<p className="text-sm text-gray-400 mt-2">
{localConfig.type === "http"
? "Best for web applications and APIs. Uses HTTPS (port 443) or HTTP (port 80)."
: "For any TCP service like databases, game servers, or custom applications."}
</p>
</div>
<div className="grid gap-4 md:grid-cols-2 mb-6">
<div>
<label className="block text-sm font-medium mb-2">Server Port (Internet Access)</label>
{localConfig.type === "http" ? (
<select
value={localConfig.serverPort}
onChange={(e) => updateConfig({ serverPort: Number.parseInt(e.target.value) })}
className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white font-mono focus:border-emerald-500 focus:outline-none"
>
<option value={443}>443 (HTTPS)</option>
<option value={80}>80 (HTTP)</option>
</select>
) : (
<input
type="number"
value={localConfig.serverPort}
onChange={(e) => updateConfig({ serverPort: Number.parseInt(e.target.value) || 8080 })}
className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white font-mono focus:border-emerald-500 focus:outline-none"
placeholder="8080"
min="1024"
max="65535"
/>
)}
<p className="text-xs text-gray-400 mt-1">
{localConfig.type === "http" ? "Standard web ports" : "Port accessible from the internet"}
</p>
</div>
<div>
<label className="block text-sm font-medium mb-2">Local Port (Your Service)</label>
<input
type="number"
value={localConfig.localPort}
onChange={(e) => updateConfig({ localPort: Number.parseInt(e.target.value) || 8000 })}
className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white font-mono focus:border-emerald-500 focus:outline-none"
placeholder="8000"
min="1"
max="65535"
/>
<p className="text-xs text-gray-400 mt-1">Port where your local service is running</p>
</div>
</div>
{selectedServer && (
<div className="mb-6">
<label className="block text-sm font-medium mb-2">SSH Command</label>
<div className="relative">
<div className="bg-gray-800 rounded-lg p-4 border border-gray-700 font-mono text-sm overflow-x-auto">
<pre className="whitespace-pre-wrap break-all sm:break-normal">{generateCommand()}</pre>
</div>
<button
onClick={copyToClipboard}
className="absolute right-3 top-3 h-8 w-8 flex items-center justify-center rounded-md text-gray-400 hover:text-white hover:bg-gray-700 transition-colors"
aria-label="Copy command"
>
{copied ? (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="text-emerald-400"
>
<path d="M20 6 9 17l-5-5" />
</svg>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
</svg>
)}
</button>
</div>
<p className="text-xs text-gray-400 mt-2">Run this command in your terminal to create the tunnel</p>
</div>
)}
<div className="p-3 bg-gray-800 rounded-lg border border-gray-700">
<p className="text-sm text-gray-300">
<span className="font-medium">Traffic Flow:</span> Internet {" "}
<span className="text-emerald-400 font-mono">
{selectedServer ? selectedServer.location : "Server"}:{localConfig.serverPort}
</span>{" "}
<span className="text-emerald-400 font-mono">localhost:{localConfig.localPort}</span>
</p>
<p className="text-xs text-gray-400 mt-1">
{localConfig.type === "http" ? (
<>
Your local service on port {localConfig.localPort} will be accessible via{" "}
{localConfig.serverPort === 443 ? "HTTPS" : "HTTP"}
</>
) : (
<>
TCP traffic to server port {localConfig.serverPort} will be forwarded to your localhost:
{localConfig.localPort}
</>
)}
</p>
</div>
</>
)}
</div>
)
}
export type { Server }

183
components/world-map.tsx Normal file
View File

@ -0,0 +1,183 @@
"use client"
import { useState, useEffect } from "react"
import { ComposableMap, Geographies, Geography, Marker } from "react-simple-maps"
interface Server {
id: string
name: string
location: string
subdomain: string
coordinates: [number, number] // [longitude, latitude]
ping: number | null
}
const servers: Server[] = [
{
id: "sgp",
name: "Singapore",
location: "Singapore",
subdomain: "sgp.tunnl.live",
coordinates: [103.8198, 1.3521],
ping: null,
},
{
id: "id",
name: "Indonesia",
location: "Jakarta",
subdomain: "id.tunnl.live",
coordinates: [106.8456, -6.2088],
ping: null,
},
]
const geoUrl = "https://cdn.jsdelivr.net/npm/world-atlas@2/countries-110m.json"
interface WorldMapProps {
onServerSelect: (server: Server) => void
selectedServer: Server
}
export default function WorldMap({ onServerSelect, selectedServer }: WorldMapProps) {
const [serverPings, setServerPings] = useState<Server[]>(servers)
const [isLoading, setIsLoading] = useState(true)
const getPingColor = (ping: number | null) => {
if (!ping) return "text-gray-400"
if (ping < 50) return "text-green-400"
if (ping < 100) return "text-yellow-400"
if (ping < 150) return "text-orange-400"
return "text-red-400"
}
const getPingStatus = (ping: number | null) => {
if (!ping) return "Testing..."
if (ping < 50) return "Excellent"
if (ping < 100) return "Good"
if (ping < 150) return "Fair"
return "Poor"
}
const getMarkerColor = (server: Server) => {
if (selectedServer.id === server.id) return "#10b981"
return "#6b7280"
}
const getMarkerStroke = (server: Server) => {
if (selectedServer.id === server.id) return "#34d399"
return "#9ca3af"
}
return (
<div className="w-full">
<h3 className="text-xl font-bold text-center mb-6">Choose Your Server Location</h3>
<div className="relative bg-gray-900 rounded-lg border border-gray-800 p-4 mb-6 overflow-hidden">
<ComposableMap
projection="geoMercator"
projectionConfig={{
scale: 120,
center: [0, 20],
}}
width={800}
height={400}
style={{
width: "100%",
height: "auto",
}}
>
<Geographies geography={geoUrl}>
{({ geographies }) =>
geographies.map((geo) => (
<Geography
key={geo.rsmKey}
geography={geo}
fill="#374151"
stroke="#4b5563"
strokeWidth={0.5}
style={{
default: { outline: "none" },
hover: { outline: "none", fill: "#4b5563" },
pressed: { outline: "none" },
}}
/>
))
}
</Geographies>
{serverPings.map((server) => (
<Marker
key={server.id}
coordinates={server.coordinates}
onClick={() => onServerSelect(server)}
>
<g>
{selectedServer.id === server.id && (
<circle r="15" fill="none" stroke="#10b981" strokeWidth="2" opacity="0.6">
<animate attributeName="r" values="8;20;8" dur="2s" repeatCount="indefinite" />
<animate attributeName="opacity" values="0.6;0;0.6" dur="2s" repeatCount="indefinite" />
</circle>
)}
<circle
r="8"
fill={getMarkerColor(server)}
stroke={getMarkerStroke(server)}
strokeWidth="2"
className="transition-all duration-200 hover:r-10"
/>
<text
textAnchor="middle"
y="-15"
style={{
fontFamily: "system-ui",
fontSize: "12px",
fontWeight: "bold",
fill: "white",
pointerEvents: "none",
}}
>
{server.location}
</text>
</g>
</Marker>
))}
</ComposableMap>
</div>
<div className="grid gap-4 md:grid-cols-3">
{serverPings.map((server) => (
<div
key={server.id}
onClick={() => onServerSelect(server)}
className={`p-4 rounded-lg border cursor-pointer transition-all duration-200 ${
selectedServer.id === server.id
? "bg-emerald-950 border-emerald-500"
: "bg-gray-900 border-gray-800 hover:border-gray-700"
}`}
>
<div className="flex items-center justify-between mb-2">
<h4 className="font-bold">{server.name}</h4>
<div
className={`w-3 h-3 rounded-full ${selectedServer.id === server.id ? "bg-emerald-400" : "bg-gray-600"}`}
/>
</div>
<p className="text-sm text-gray-400 mb-2">{server.location}</p>
<p className="text-xs font-mono text-gray-500 mb-2">{server.subdomain}</p>
<div className="flex items-center justify-between">
<span className="text-sm">Ping:</span>
{isLoading ? (
<span className="text-sm text-gray-400">Testing...</span>
) : (
<span className={`text-sm font-bold ${getPingColor(server.ping)}`}>
{server.ping}ms ({getPingStatus(server.ping)})
</span>
)}
</div>
</div>
))}
</div>
</div>
)
}

639
package-lock.json generated
View File

@ -8,9 +8,10 @@
"name": "tunnlpls_frontend", "name": "tunnlpls_frontend",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"next": "15.3.1", "next": "^15.5.2",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"react-simple-maps": "^3.0.0",
"tailwindcss-animate": "^1.0.7" "tailwindcss-animate": "^1.0.7"
}, },
"devDependencies": { "devDependencies": {
@ -18,6 +19,7 @@
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",
"@types/react-simple-maps": "^3.0.6",
"tailwindcss": "^4", "tailwindcss": "^4",
"typescript": "^5" "typescript": "^5"
} }
@ -36,9 +38,9 @@
} }
}, },
"node_modules/@emnapi/runtime": { "node_modules/@emnapi/runtime": {
"version": "1.4.3", "version": "1.5.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz",
"integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==",
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
@ -46,9 +48,9 @@
} }
}, },
"node_modules/@img/sharp-darwin-arm64": { "node_modules/@img/sharp-darwin-arm64": {
"version": "0.34.1", "version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.1.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.3.tgz",
"integrity": "sha512-pn44xgBtgpEbZsu+lWf2KNb6OAf70X68k+yk69Ic2Xz11zHR/w24/U49XT7AeRwJ0Px+mhALhU5LPci1Aymk7A==", "integrity": "sha512-ryFMfvxxpQRsgZJqBd4wsttYQbCxsJksrv9Lw/v798JcQ8+w84mBWuXwl+TT0WJ/WrYOLaYpwQXi3sA9nTIaIg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -64,13 +66,13 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.1.0" "@img/sharp-libvips-darwin-arm64": "1.2.0"
} }
}, },
"node_modules/@img/sharp-darwin-x64": { "node_modules/@img/sharp-darwin-x64": {
"version": "0.34.1", "version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.1.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.3.tgz",
"integrity": "sha512-VfuYgG2r8BpYiOUN+BfYeFo69nP/MIwAtSJ7/Zpxc5QF3KS22z8Pvg3FkrSFJBPNQ7mmcUcYQFBmEQp7eu1F8Q==", "integrity": "sha512-yHpJYynROAj12TA6qil58hmPmAwxKKC7reUqtGLzsOHfP7/rniNGTL8tjWX6L3CTV4+5P4ypcS7Pp+7OB+8ihA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -86,13 +88,13 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.1.0" "@img/sharp-libvips-darwin-x64": "1.2.0"
} }
}, },
"node_modules/@img/sharp-libvips-darwin-arm64": { "node_modules/@img/sharp-libvips-darwin-arm64": {
"version": "1.1.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.0.tgz",
"integrity": "sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA==", "integrity": "sha512-sBZmpwmxqwlqG9ueWFXtockhsxefaV6O84BMOrhtg/YqbTaRdqDE7hxraVE3y6gVM4eExmfzW4a8el9ArLeEiQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -106,9 +108,9 @@
} }
}, },
"node_modules/@img/sharp-libvips-darwin-x64": { "node_modules/@img/sharp-libvips-darwin-x64": {
"version": "1.1.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.0.tgz",
"integrity": "sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==", "integrity": "sha512-M64XVuL94OgiNHa5/m2YvEQI5q2cl9d/wk0qFTDVXcYzi43lxuiFTftMR1tOnFQovVXNZJ5TURSDK2pNe9Yzqg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -122,9 +124,9 @@
} }
}, },
"node_modules/@img/sharp-libvips-linux-arm": { "node_modules/@img/sharp-libvips-linux-arm": {
"version": "1.1.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.0.tgz",
"integrity": "sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==", "integrity": "sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -138,9 +140,9 @@
} }
}, },
"node_modules/@img/sharp-libvips-linux-arm64": { "node_modules/@img/sharp-libvips-linux-arm64": {
"version": "1.1.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.0.tgz",
"integrity": "sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==", "integrity": "sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -154,9 +156,9 @@
} }
}, },
"node_modules/@img/sharp-libvips-linux-ppc64": { "node_modules/@img/sharp-libvips-linux-ppc64": {
"version": "1.1.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.0.tgz",
"integrity": "sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==", "integrity": "sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ==",
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
@ -170,9 +172,9 @@
} }
}, },
"node_modules/@img/sharp-libvips-linux-s390x": { "node_modules/@img/sharp-libvips-linux-s390x": {
"version": "1.1.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.0.tgz",
"integrity": "sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==", "integrity": "sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
@ -186,9 +188,9 @@
} }
}, },
"node_modules/@img/sharp-libvips-linux-x64": { "node_modules/@img/sharp-libvips-linux-x64": {
"version": "1.1.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.0.tgz",
"integrity": "sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==", "integrity": "sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -202,9 +204,9 @@
} }
}, },
"node_modules/@img/sharp-libvips-linuxmusl-arm64": { "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
"version": "1.1.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.0.tgz",
"integrity": "sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==", "integrity": "sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -218,9 +220,9 @@
} }
}, },
"node_modules/@img/sharp-libvips-linuxmusl-x64": { "node_modules/@img/sharp-libvips-linuxmusl-x64": {
"version": "1.1.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.0.tgz",
"integrity": "sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==", "integrity": "sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -234,9 +236,9 @@
} }
}, },
"node_modules/@img/sharp-linux-arm": { "node_modules/@img/sharp-linux-arm": {
"version": "0.34.1", "version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.1.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.3.tgz",
"integrity": "sha512-anKiszvACti2sGy9CirTlNyk7BjjZPiML1jt2ZkTdcvpLU1YH6CXwRAZCA2UmRXnhiIftXQ7+Oh62Ji25W72jA==", "integrity": "sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -252,13 +254,13 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.1.0" "@img/sharp-libvips-linux-arm": "1.2.0"
} }
}, },
"node_modules/@img/sharp-linux-arm64": { "node_modules/@img/sharp-linux-arm64": {
"version": "0.34.1", "version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.1.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.3.tgz",
"integrity": "sha512-kX2c+vbvaXC6vly1RDf/IWNXxrlxLNpBVWkdpRq5Ka7OOKj6nr66etKy2IENf6FtOgklkg9ZdGpEu9kwdlcwOQ==", "integrity": "sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -274,13 +276,35 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.1.0" "@img/sharp-libvips-linux-arm64": "1.2.0"
}
},
"node_modules/@img/sharp-linux-ppc64": {
"version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.3.tgz",
"integrity": "sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA==",
"cpu": [
"ppc64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-ppc64": "1.2.0"
} }
}, },
"node_modules/@img/sharp-linux-s390x": { "node_modules/@img/sharp-linux-s390x": {
"version": "0.34.1", "version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.1.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.3.tgz",
"integrity": "sha512-7s0KX2tI9mZI2buRipKIw2X1ufdTeaRgwmRabt5bi9chYfhur+/C1OXg3TKg/eag1W+6CCWLVmSauV1owmRPxA==", "integrity": "sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
@ -296,13 +320,13 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-s390x": "1.1.0" "@img/sharp-libvips-linux-s390x": "1.2.0"
} }
}, },
"node_modules/@img/sharp-linux-x64": { "node_modules/@img/sharp-linux-x64": {
"version": "0.34.1", "version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.1.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.3.tgz",
"integrity": "sha512-wExv7SH9nmoBW3Wr2gvQopX1k8q2g5V5Iag8Zk6AVENsjwd+3adjwxtp3Dcu2QhOXr8W9NusBU6XcQUohBZ5MA==", "integrity": "sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -318,13 +342,13 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-x64": "1.1.0" "@img/sharp-libvips-linux-x64": "1.2.0"
} }
}, },
"node_modules/@img/sharp-linuxmusl-arm64": { "node_modules/@img/sharp-linuxmusl-arm64": {
"version": "0.34.1", "version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.1.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.3.tgz",
"integrity": "sha512-DfvyxzHxw4WGdPiTF0SOHnm11Xv4aQexvqhRDAoD00MzHekAj9a/jADXeXYCDFH/DzYruwHbXU7uz+H+nWmSOQ==", "integrity": "sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -340,13 +364,13 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linuxmusl-arm64": "1.1.0" "@img/sharp-libvips-linuxmusl-arm64": "1.2.0"
} }
}, },
"node_modules/@img/sharp-linuxmusl-x64": { "node_modules/@img/sharp-linuxmusl-x64": {
"version": "0.34.1", "version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.1.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.3.tgz",
"integrity": "sha512-pax/kTR407vNb9qaSIiWVnQplPcGU8LRIJpDT5o8PdAx5aAA7AS3X9PS8Isw1/WfqgQorPotjrZL3Pqh6C5EBg==", "integrity": "sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -362,20 +386,20 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linuxmusl-x64": "1.1.0" "@img/sharp-libvips-linuxmusl-x64": "1.2.0"
} }
}, },
"node_modules/@img/sharp-wasm32": { "node_modules/@img/sharp-wasm32": {
"version": "0.34.1", "version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.1.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.3.tgz",
"integrity": "sha512-YDybQnYrLQfEpzGOQe7OKcyLUCML4YOXl428gOOzBgN6Gw0rv8dpsJ7PqTHxBnXnwXr8S1mYFSLSa727tpz0xg==", "integrity": "sha512-+CyRcpagHMGteySaWos8IbnXcHgfDn7pO2fiC2slJxvNq9gDipYBN42/RagzctVRKgxATmfqOSulgZv5e1RdMg==",
"cpu": [ "cpu": [
"wasm32" "wasm32"
], ],
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"@emnapi/runtime": "^1.4.0" "@emnapi/runtime": "^1.4.4"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
@ -384,10 +408,29 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
} }
}, },
"node_modules/@img/sharp-win32-arm64": {
"version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.3.tgz",
"integrity": "sha512-MjnHPnbqMXNC2UgeLJtX4XqoVHHlZNd+nPt1kRPmj63wURegwBhZlApELdtxM2OIZDRv/DFtLcNhVbd1z8GYXQ==",
"cpu": [
"arm64"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-ia32": { "node_modules/@img/sharp-win32-ia32": {
"version": "0.34.1", "version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.1.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.3.tgz",
"integrity": "sha512-WKf/NAZITnonBf3U1LfdjoMgNO5JYRSlhovhRhMxXVdvWYveM4kM3L8m35onYIdh75cOMCo1BexgVQcCDzyoWw==", "integrity": "sha512-xuCdhH44WxuXgOM714hn4amodJMZl3OEvf0GVTm0BEyMeA2to+8HEdRPShH0SLYptJY1uBw+SCFP9WVQi1Q/cw==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@ -404,9 +447,9 @@
} }
}, },
"node_modules/@img/sharp-win32-x64": { "node_modules/@img/sharp-win32-x64": {
"version": "0.34.1", "version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.1.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.3.tgz",
"integrity": "sha512-hw1iIAHpNE8q3uMIRCgGOeDoz9KtFNarFLQclLxr/LK1VBkj8nby18RjFvr6aP7USRYAjTZW6yisnBWMX571Tw==", "integrity": "sha512-OWwz05d++TxzLEv4VnsTz5CmZ6mI6S05sfQGEMrNrQcOEERbX46332IvE7pO/EUiw7jUrrS40z/M7kPyjfl04g==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -423,15 +466,15 @@
} }
}, },
"node_modules/@next/env": { "node_modules/@next/env": {
"version": "15.3.1", "version": "15.5.2",
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.3.1.tgz", "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.2.tgz",
"integrity": "sha512-cwK27QdzrMblHSn9DZRV+DQscHXRuJv6MydlJRpFSqJWZrTYMLzKDeyueJNN9MGd8NNiUKzDQADAf+dMLXX7YQ==", "integrity": "sha512-Qe06ew4zt12LeO6N7j8/nULSOe3fMXE4dM6xgpBQNvdzyK1sv5y4oAP3bq4LamrvGCZtmRYnW8URFCeX5nFgGg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@next/swc-darwin-arm64": { "node_modules/@next/swc-darwin-arm64": {
"version": "15.3.1", "version": "15.5.2",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.3.1.tgz", "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.2.tgz",
"integrity": "sha512-hjDw4f4/nla+6wysBL07z52Gs55Gttp5Bsk5/8AncQLJoisvTBP0pRIBK/B16/KqQyH+uN4Ww8KkcAqJODYH3w==", "integrity": "sha512-8bGt577BXGSd4iqFygmzIfTYizHb0LGWqH+qgIF/2EDxS5JsSdERJKA8WgwDyNBZgTIIA4D8qUtoQHmxIIquoQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -445,9 +488,9 @@
} }
}, },
"node_modules/@next/swc-darwin-x64": { "node_modules/@next/swc-darwin-x64": {
"version": "15.3.1", "version": "15.5.2",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.3.1.tgz", "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.2.tgz",
"integrity": "sha512-q+aw+cJ2ooVYdCEqZVk+T4Ni10jF6Fo5DfpEV51OupMaV5XL6pf3GCzrk6kSSZBsMKZtVC1Zm/xaNBFpA6bJ2g==", "integrity": "sha512-2DjnmR6JHK4X+dgTXt5/sOCu/7yPtqpYt8s8hLkHFK3MGkka2snTv3yRMdHvuRtJVkPwCGsvBSwmoQCHatauFQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -461,9 +504,9 @@
} }
}, },
"node_modules/@next/swc-linux-arm64-gnu": { "node_modules/@next/swc-linux-arm64-gnu": {
"version": "15.3.1", "version": "15.5.2",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.3.1.tgz", "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.2.tgz",
"integrity": "sha512-wBQ+jGUI3N0QZyWmmvRHjXjTWFy8o+zPFLSOyAyGFI94oJi+kK/LIZFJXeykvgXUk1NLDAEFDZw/NVINhdk9FQ==", "integrity": "sha512-3j7SWDBS2Wov/L9q0mFJtEvQ5miIqfO4l7d2m9Mo06ddsgUK8gWfHGgbjdFlCp2Ek7MmMQZSxpGFqcC8zGh2AA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -477,9 +520,9 @@
} }
}, },
"node_modules/@next/swc-linux-arm64-musl": { "node_modules/@next/swc-linux-arm64-musl": {
"version": "15.3.1", "version": "15.5.2",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.3.1.tgz", "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.2.tgz",
"integrity": "sha512-IIxXEXRti/AulO9lWRHiCpUUR8AR/ZYLPALgiIg/9ENzMzLn3l0NSxVdva7R/VDcuSEBo0eGVCe3evSIHNz0Hg==", "integrity": "sha512-s6N8k8dF9YGc5T01UPQ08yxsK6fUow5gG1/axWc1HVVBYQBgOjca4oUZF7s4p+kwhkB1bDSGR8QznWrFZ/Rt5g==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -493,9 +536,9 @@
} }
}, },
"node_modules/@next/swc-linux-x64-gnu": { "node_modules/@next/swc-linux-x64-gnu": {
"version": "15.3.1", "version": "15.5.2",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.3.1.tgz", "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.2.tgz",
"integrity": "sha512-bfI4AMhySJbyXQIKH5rmLJ5/BP7bPwuxauTvVEiJ/ADoddaA9fgyNNCcsbu9SlqfHDoZmfI6g2EjzLwbsVTr5A==", "integrity": "sha512-o1RV/KOODQh6dM6ZRJGZbc+MOAHww33Vbs5JC9Mp1gDk8cpEO+cYC/l7rweiEalkSm5/1WGa4zY7xrNwObN4+Q==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -509,9 +552,9 @@
} }
}, },
"node_modules/@next/swc-linux-x64-musl": { "node_modules/@next/swc-linux-x64-musl": {
"version": "15.3.1", "version": "15.5.2",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.3.1.tgz", "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.2.tgz",
"integrity": "sha512-FeAbR7FYMWR+Z+M5iSGytVryKHiAsc0x3Nc3J+FD5NVbD5Mqz7fTSy8CYliXinn7T26nDMbpExRUI/4ekTvoiA==", "integrity": "sha512-/VUnh7w8RElYZ0IV83nUcP/J4KJ6LLYliiBIri3p3aW2giF+PAVgZb6mk8jbQSB3WlTai8gEmCAr7kptFa1H6g==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -525,9 +568,9 @@
} }
}, },
"node_modules/@next/swc-win32-arm64-msvc": { "node_modules/@next/swc-win32-arm64-msvc": {
"version": "15.3.1", "version": "15.5.2",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.3.1.tgz", "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.2.tgz",
"integrity": "sha512-yP7FueWjphQEPpJQ2oKmshk/ppOt+0/bB8JC8svPUZNy0Pi3KbPx2Llkzv1p8CoQa+D2wknINlJpHf3vtChVBw==", "integrity": "sha512-sMPyTvRcNKXseNQ/7qRfVRLa0VhR0esmQ29DD6pqvG71+JdVnESJaHPA8t7bc67KD5spP3+DOCNLhqlEI2ZgQg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -541,9 +584,9 @@
} }
}, },
"node_modules/@next/swc-win32-x64-msvc": { "node_modules/@next/swc-win32-x64-msvc": {
"version": "15.3.1", "version": "15.5.2",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.3.1.tgz", "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.2.tgz",
"integrity": "sha512-3PMvF2zRJAifcRNni9uMk/gulWfWS+qVI/pagd+4yLF5bcXPZPPH2xlYRYOsUjmCJOXSTAC2PjRzbhsRzR2fDQ==", "integrity": "sha512-W5VvyZHnxG/2ukhZF/9Ikdra5fdNftxI6ybeVKYvBPDtyx7x4jPPSNduUkfH5fo3zG0JQ0bPxgy41af2JX5D4Q==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -556,12 +599,6 @@
"node": ">= 10" "node": ">= 10"
} }
}, },
"node_modules/@swc/counter": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
"license": "Apache-2.0"
},
"node_modules/@swc/helpers": { "node_modules/@swc/helpers": {
"version": "0.5.15", "version": "0.5.15",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
@ -839,6 +876,58 @@
"tailwindcss": "4.1.5" "tailwindcss": "4.1.5"
} }
}, },
"node_modules/@types/d3-color": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-2.0.6.tgz",
"integrity": "sha512-tbaFGDmJWHqnenvk3QGSvD3RVwr631BjKRD7Sc7VLRgrdX5mk5hTyoeBL6rXZaeoXzmZwIl1D2HPogEdt1rHBg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/d3-geo": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-2.0.7.tgz",
"integrity": "sha512-RIXlxPdxvX+LAZFv+t78CuYpxYag4zuw9mZc+AwfB8tZpKU90rMEn2il2ADncmeZlb7nER9dDsJpRisA3lRvjA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/geojson": "*"
}
},
"node_modules/@types/d3-interpolate": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-2.0.5.tgz",
"integrity": "sha512-UINE41RDaUMbulp+bxQMDnhOi51rh5lA2dG+dWZU0UY/IwQiG/u2x8TfnWYU9+xwGdXsJoAvrBYUEQl0r91atg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/d3-color": "^2"
}
},
"node_modules/@types/d3-selection": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-2.0.5.tgz",
"integrity": "sha512-71BorcY0yXl12S7lvb01JdaN9TpeUHBDb4RRhSq8U8BEkX/nIk5p7Byho+ZRTsx5nYLMpAbY3qt5EhqFzfGJlw==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/d3-zoom": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-2.0.7.tgz",
"integrity": "sha512-JWke4E8ZyrKUQ68ESTWSK16fVb0OYnaiJ+WXJRYxKLn4aXU0o4CLYxMWBEiouUfO3TTCoyroOrGPcBG6u1aAxA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/d3-interpolate": "^2",
"@types/d3-selection": "^2"
}
},
"node_modules/@types/geojson": {
"version": "7946.0.16",
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
"integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "20.17.32", "version": "20.17.32",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.32.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.32.tgz",
@ -869,15 +958,17 @@
"@types/react": "^19.0.0" "@types/react": "^19.0.0"
} }
}, },
"node_modules/busboy": { "node_modules/@types/react-simple-maps": {
"version": "1.6.0", "version": "3.0.6",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", "resolved": "https://registry.npmjs.org/@types/react-simple-maps/-/react-simple-maps-3.0.6.tgz",
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", "integrity": "sha512-hR01RXt6VvsE41FxDd+Bqm1PPGdKbYjCYVtCgh38YeBPt46z3SwmWPWu2L3EdCAP6bd6VYEgztucihRw1C0Klg==",
"dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"streamsearch": "^1.1.0" "@types/d3-geo": "^2",
}, "@types/d3-zoom": "^2",
"engines": { "@types/geojson": "*",
"node": ">=10.16.0" "@types/react": "*"
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
@ -951,6 +1042,12 @@
"simple-swizzle": "^0.2.2" "simple-swizzle": "^0.2.2"
} }
}, },
"node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"license": "MIT"
},
"node_modules/csstype": { "node_modules/csstype": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
@ -958,6 +1055,135 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/d3-array": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
"license": "ISC",
"dependencies": {
"internmap": "1 - 2"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-color": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-dispatch": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
"integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-drag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz",
"integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
"license": "ISC",
"dependencies": {
"d3-dispatch": "1 - 3",
"d3-selection": "3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-ease": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-geo": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz",
"integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==",
"license": "ISC",
"dependencies": {
"d3-array": "2.5.0 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-interpolate": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
"license": "ISC",
"dependencies": {
"d3-color": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-selection": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-timer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-transition": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz",
"integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
"license": "ISC",
"dependencies": {
"d3-color": "1 - 3",
"d3-dispatch": "1 - 3",
"d3-ease": "1 - 3",
"d3-interpolate": "1 - 3",
"d3-timer": "1 - 3"
},
"engines": {
"node": ">=12"
},
"peerDependencies": {
"d3-selection": "2 - 3"
}
},
"node_modules/d3-zoom": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz",
"integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
"license": "ISC",
"dependencies": {
"d3-dispatch": "1 - 3",
"d3-drag": "2 - 3",
"d3-interpolate": "1 - 3",
"d3-selection": "2 - 3",
"d3-transition": "2 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/detect-libc": { "node_modules/detect-libc": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
@ -989,6 +1215,15 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/internmap": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/is-arrayish": { "node_modules/is-arrayish": {
"version": "0.3.2", "version": "0.3.2",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
@ -1006,6 +1241,13 @@
"jiti": "lib/jiti-cli.mjs" "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==",
"license": "MIT",
"peer": true
},
"node_modules/lightningcss": { "node_modules/lightningcss": {
"version": "1.29.2", "version": "1.29.2",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz",
@ -1245,6 +1487,19 @@
"url": "https://opencollective.com/parcel" "url": "https://opencollective.com/parcel"
} }
}, },
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"license": "MIT",
"peer": true,
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
}
},
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.11", "version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
@ -1264,15 +1519,13 @@
} }
}, },
"node_modules/next": { "node_modules/next": {
"version": "15.3.1", "version": "15.5.2",
"resolved": "https://registry.npmjs.org/next/-/next-15.3.1.tgz", "resolved": "https://registry.npmjs.org/next/-/next-15.5.2.tgz",
"integrity": "sha512-8+dDV0xNLOgHlyBxP1GwHGVaNXsmp+2NhZEYrXr24GWLHtt27YrBPbPuHvzlhi7kZNYjeJNR93IF5zfFu5UL0g==", "integrity": "sha512-H8Otr7abj1glFhbGnvUt3gz++0AF1+QoCXEBmd/6aKbfdFwrn0LpA836Ed5+00va/7HQSDD+mOoVhn3tNy3e/Q==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@next/env": "15.3.1", "@next/env": "15.5.2",
"@swc/counter": "0.1.3",
"@swc/helpers": "0.5.15", "@swc/helpers": "0.5.15",
"busboy": "1.6.0",
"caniuse-lite": "^1.0.30001579", "caniuse-lite": "^1.0.30001579",
"postcss": "8.4.31", "postcss": "8.4.31",
"styled-jsx": "5.1.6" "styled-jsx": "5.1.6"
@ -1284,19 +1537,19 @@
"node": "^18.18.0 || ^19.8.0 || >= 20.0.0" "node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@next/swc-darwin-arm64": "15.3.1", "@next/swc-darwin-arm64": "15.5.2",
"@next/swc-darwin-x64": "15.3.1", "@next/swc-darwin-x64": "15.5.2",
"@next/swc-linux-arm64-gnu": "15.3.1", "@next/swc-linux-arm64-gnu": "15.5.2",
"@next/swc-linux-arm64-musl": "15.3.1", "@next/swc-linux-arm64-musl": "15.5.2",
"@next/swc-linux-x64-gnu": "15.3.1", "@next/swc-linux-x64-gnu": "15.5.2",
"@next/swc-linux-x64-musl": "15.3.1", "@next/swc-linux-x64-musl": "15.5.2",
"@next/swc-win32-arm64-msvc": "15.3.1", "@next/swc-win32-arm64-msvc": "15.5.2",
"@next/swc-win32-x64-msvc": "15.3.1", "@next/swc-win32-x64-msvc": "15.5.2",
"sharp": "^0.34.1" "sharp": "^0.34.3"
}, },
"peerDependencies": { "peerDependencies": {
"@opentelemetry/api": "^1.1.0", "@opentelemetry/api": "^1.1.0",
"@playwright/test": "^1.41.2", "@playwright/test": "^1.51.1",
"babel-plugin-react-compiler": "*", "babel-plugin-react-compiler": "*",
"react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
"react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
@ -1345,6 +1598,16 @@
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
} }
}, },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@ -1380,6 +1643,18 @@
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
} }
}, },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.13.1"
}
},
"node_modules/react": { "node_modules/react": {
"version": "19.1.0", "version": "19.1.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
@ -1401,6 +1676,30 @@
"react": "^19.1.0" "react": "^19.1.0"
} }
}, },
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"license": "MIT",
"peer": true
},
"node_modules/react-simple-maps": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/react-simple-maps/-/react-simple-maps-3.0.0.tgz",
"integrity": "sha512-vKNFrvpPG8Vyfdjnz5Ne1N56rZlDfHXv5THNXOVZMqbX1rWZA48zQuYT03mx6PAKanqarJu/PDLgshIZAfHHqw==",
"license": "MIT",
"dependencies": {
"d3-geo": "^2.0.2",
"d3-selection": "^2.0.0",
"d3-zoom": "^2.0.0",
"topojson-client": "^3.1.0"
},
"peerDependencies": {
"prop-types": "^15.7.2",
"react": "^16.8.0 || 17.x || 18.x",
"react-dom": "^16.8.0 || 17.x || 18.x"
}
},
"node_modules/scheduler": { "node_modules/scheduler": {
"version": "0.26.0", "version": "0.26.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
@ -1408,9 +1707,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/semver": { "node_modules/semver": {
"version": "7.7.1", "version": "7.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
"license": "ISC", "license": "ISC",
"optional": true, "optional": true,
"bin": { "bin": {
@ -1421,16 +1720,16 @@
} }
}, },
"node_modules/sharp": { "node_modules/sharp": {
"version": "0.34.1", "version": "0.34.3",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.1.tgz", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.3.tgz",
"integrity": "sha512-1j0w61+eVxu7DawFJtnfYcvSv6qPFvfTaqzTQ2BLknVhHTwGS8sc63ZBF4rzkWMBVKybo4S5OBtDdZahh2A1xg==", "integrity": "sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg==",
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"color": "^4.2.3", "color": "^4.2.3",
"detect-libc": "^2.0.3", "detect-libc": "^2.0.4",
"semver": "^7.7.1" "semver": "^7.7.2"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
@ -1439,26 +1738,28 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-darwin-arm64": "0.34.1", "@img/sharp-darwin-arm64": "0.34.3",
"@img/sharp-darwin-x64": "0.34.1", "@img/sharp-darwin-x64": "0.34.3",
"@img/sharp-libvips-darwin-arm64": "1.1.0", "@img/sharp-libvips-darwin-arm64": "1.2.0",
"@img/sharp-libvips-darwin-x64": "1.1.0", "@img/sharp-libvips-darwin-x64": "1.2.0",
"@img/sharp-libvips-linux-arm": "1.1.0", "@img/sharp-libvips-linux-arm": "1.2.0",
"@img/sharp-libvips-linux-arm64": "1.1.0", "@img/sharp-libvips-linux-arm64": "1.2.0",
"@img/sharp-libvips-linux-ppc64": "1.1.0", "@img/sharp-libvips-linux-ppc64": "1.2.0",
"@img/sharp-libvips-linux-s390x": "1.1.0", "@img/sharp-libvips-linux-s390x": "1.2.0",
"@img/sharp-libvips-linux-x64": "1.1.0", "@img/sharp-libvips-linux-x64": "1.2.0",
"@img/sharp-libvips-linuxmusl-arm64": "1.1.0", "@img/sharp-libvips-linuxmusl-arm64": "1.2.0",
"@img/sharp-libvips-linuxmusl-x64": "1.1.0", "@img/sharp-libvips-linuxmusl-x64": "1.2.0",
"@img/sharp-linux-arm": "0.34.1", "@img/sharp-linux-arm": "0.34.3",
"@img/sharp-linux-arm64": "0.34.1", "@img/sharp-linux-arm64": "0.34.3",
"@img/sharp-linux-s390x": "0.34.1", "@img/sharp-linux-ppc64": "0.34.3",
"@img/sharp-linux-x64": "0.34.1", "@img/sharp-linux-s390x": "0.34.3",
"@img/sharp-linuxmusl-arm64": "0.34.1", "@img/sharp-linux-x64": "0.34.3",
"@img/sharp-linuxmusl-x64": "0.34.1", "@img/sharp-linuxmusl-arm64": "0.34.3",
"@img/sharp-wasm32": "0.34.1", "@img/sharp-linuxmusl-x64": "0.34.3",
"@img/sharp-win32-ia32": "0.34.1", "@img/sharp-wasm32": "0.34.3",
"@img/sharp-win32-x64": "0.34.1" "@img/sharp-win32-arm64": "0.34.3",
"@img/sharp-win32-ia32": "0.34.3",
"@img/sharp-win32-x64": "0.34.3"
} }
}, },
"node_modules/simple-swizzle": { "node_modules/simple-swizzle": {
@ -1480,14 +1781,6 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/streamsearch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/styled-jsx": { "node_modules/styled-jsx": {
"version": "5.1.6", "version": "5.1.6",
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
@ -1536,6 +1829,20 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/topojson-client": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz",
"integrity": "sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==",
"license": "ISC",
"dependencies": {
"commander": "2"
},
"bin": {
"topo2geo": "bin/topo2geo",
"topomerge": "bin/topomerge",
"topoquantize": "bin/topoquantize"
}
},
"node_modules/tslib": { "node_modules/tslib": {
"version": "2.8.1", "version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",

View File

@ -2,6 +2,22 @@
"name": "tunnlpls_frontend", "name": "tunnlpls_frontend",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"overrides": {
"react-simple-maps": {
"d3-geo": "^3.1.0",
"d3-color": "^3.1.0",
"d3-selection": "^3.0.0",
"d3-zoom": "^3.0.0",
"react": "^16.8.0 || 17.x || 18.x || 19.x",
"react-dom": "^16.8.0 || 17.x || 18.x || 19.x"
},
"d3-interpolate": {
"d3-color": "^3.1.0"
},
"d3-transition": {
"d3-color": "^3.1.0"
}
},
"scripts": { "scripts": {
"dev": "next dev --turbopack", "dev": "next dev --turbopack",
"build": "next build", "build": "next build",
@ -9,9 +25,10 @@
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {
"next": "15.3.1", "next": "^15.5.2",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"react-simple-maps": "^3.0.0",
"tailwindcss-animate": "^1.0.7" "tailwindcss-animate": "^1.0.7"
}, },
"devDependencies": { "devDependencies": {
@ -19,6 +36,7 @@
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",
"@types/react-simple-maps": "^3.0.6",
"tailwindcss": "^4", "tailwindcss": "^4",
"typescript": "^5" "typescript": "^5"
} }