├── src
├── index.css
├── components
│ └── ui
│ │ ├── use-theme.tsx
│ │ ├── label.tsx
│ │ ├── input.tsx
│ │ ├── card.tsx
│ │ ├── alert.tsx
│ │ ├── theme-provider.tsx
│ │ ├── button.tsx
│ │ ├── form.tsx
│ │ └── select.tsx
├── types.d.ts
├── App.tsx
├── lib
│ └── utils.ts
├── react.svg
├── index.html
├── frontend.tsx
├── index.tsx
├── APITester.tsx
├── logo.svg
└── GasFeeApp.tsx
├── bunfig.toml
├── tsconfig.json
├── components.json
├── .gitignore
├── package.json
├── LICENSE
├── README.md
├── styles
└── globals.css
└── bun.lock
/src/index.css:
--------------------------------------------------------------------------------
1 | @import "../styles/globals.css";
2 |
--------------------------------------------------------------------------------
/src/components/ui/use-theme.tsx:
--------------------------------------------------------------------------------
1 | export { useTheme } from "./theme-provider";
2 |
--------------------------------------------------------------------------------
/bunfig.toml:
--------------------------------------------------------------------------------
1 | [serve.static]
2 | plugins = ["bun-plugin-tailwind"]
3 | env = "BUN_PUBLIC_*"
4 |
--------------------------------------------------------------------------------
/src/types.d.ts:
--------------------------------------------------------------------------------
1 | declare module "*.svg" {
2 | const content: string;
3 | export default content;
4 | }
5 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import "./index.css";
2 | import GasFeeApp from "./GasFeeApp";
3 |
4 | export function App() {
5 | return ;
6 | }
7 |
8 | export default App;
9 |
--------------------------------------------------------------------------------
/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
7 |
--------------------------------------------------------------------------------
/src/react.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "jsx": "react-jsx",
4 | "allowJs": true,
5 |
6 | // Bundler mode
7 | "moduleResolution": "bundler",
8 | "module": "preserve",
9 | "allowImportingTsExtensions": true,
10 | "verbatimModuleSyntax": true,
11 | "noEmit": true,
12 |
13 | "baseUrl": ".",
14 | "paths": {
15 | "@/*": ["./src/*"]
16 | }
17 | },
18 | "include": ["**/*.ts", "**/*.tsx"]
19 | }
20 |
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "new-york",
4 | "rsc": false,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "",
8 | "css": "styles/globals.css",
9 | "baseColor": "zinc",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils",
16 | "ui": "@/components/ui",
17 | "lib": "@/lib",
18 | "hooks": "@/hooks"
19 | },
20 | "iconLibrary": "lucide"
21 | }
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # dependencies (bun install)
2 | node_modules
3 |
4 | # output
5 | out
6 | dist
7 | *.tgz
8 |
9 | # code coverage
10 | coverage
11 | *.lcov
12 |
13 | # logs
14 | logs
15 | _.log
16 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
17 |
18 | # dotenv environment variable files
19 | .env
20 | .env.development.local
21 | .env.test.local
22 | .env.production.local
23 | .env.local
24 |
25 | # caches
26 | .eslintcache
27 | .cache
28 | *.tsbuildinfo
29 |
30 | # IntelliJ based IDEs
31 | .idea
32 |
33 | # Finder (MacOS) folder config
34 | .DS_Store
35 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
11 | Blockchain Gas Fee Predictor
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as LabelPrimitive from "@radix-ui/react-label"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | function Label({
9 | className,
10 | ...props
11 | }: React.ComponentProps) {
12 | return (
13 |
21 | )
22 | }
23 |
24 | export { Label }
25 |
--------------------------------------------------------------------------------
/src/frontend.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * This file is the entry point for the React app, it sets up the root
3 | * element and renders the App component to the DOM.
4 | *
5 | * It is included in `src/index.html`.
6 | */
7 |
8 | import { createRoot } from "react-dom/client";
9 | import { StrictMode } from "react";
10 | import { App } from "./App";
11 |
12 | const elem = document.getElementById("root")!;
13 | const app = (
14 |
15 |
16 |
17 | );
18 |
19 | if (import.meta.hot) {
20 | // With hot module reloading, `import.meta.hot.data` is persisted.
21 | const root = (import.meta.hot.data.root ??= createRoot(elem));
22 | root.render(app);
23 | } else {
24 | // The hot module reloading API is not available in production.
25 | createRoot(elem).render(app);
26 | }
27 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { serve } from "bun";
2 | import index from "./index.html";
3 |
4 | const server = serve({
5 | routes: {
6 | // Serve index.html for all unmatched routes.
7 | "/*": index,
8 |
9 | "/api/hello": {
10 | async GET(req) {
11 | return Response.json({
12 | message: "Hello, world!",
13 | method: "GET",
14 | });
15 | },
16 | async PUT(req) {
17 | return Response.json({
18 | message: "Hello, world!",
19 | method: "PUT",
20 | });
21 | },
22 | },
23 |
24 | "/api/hello/:name": async (req) => {
25 | const name = req.params.name;
26 | return Response.json({
27 | message: `Hello, ${name}!`,
28 | });
29 | },
30 | },
31 |
32 | development: process.env.NODE_ENV !== "production",
33 | });
34 |
35 | console.log(`🚀 Server running at ${server.url}`);
36 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bun-react-template",
3 | "version": "0.1.0",
4 | "private": true,
5 | "type": "module",
6 | "main": "src/index.tsx",
7 | "module": "src/index.tsx",
8 | "scripts": {
9 | "dev": "bun --hot src/index.tsx",
10 | "start": "NODE_ENV=production bun src/index.tsx",
11 | "build": "bun run build.ts --outdir=public"
12 | },
13 | "dependencies": {
14 | "@hookform/resolvers": "^4.1.0",
15 | "@radix-ui/react-label": "^2.1.2",
16 | "@radix-ui/react-select": "^2.1.6",
17 | "@radix-ui/react-slot": "^1.1.2",
18 | "bun-plugin-tailwind": "^0.0.14",
19 | "class-variance-authority": "^0.7.1",
20 | "clsx": "^2.1.1",
21 | "lucide-react": "^0.477.0",
22 | "react": "^19",
23 | "react-dom": "^19",
24 | "react-hook-form": "^7.54.2",
25 | "recharts": "^2.15.1",
26 | "tailwind-merge": "^3.0.1",
27 | "tailwindcss": "^4.0.6",
28 | "tailwindcss-animate": "^1.0.7",
29 | "zod": "^3.24.2"
30 | },
31 | "devDependencies": {
32 | "@types/react": "^19",
33 | "@types/react-dom": "^19",
34 | "@types/bun": "latest"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 aliezza hn
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | function Input({ className, type, ...props }: React.ComponentProps<"input">) {
6 | return (
7 |
16 | )
17 | }
18 |
19 | export { Input }
20 |
--------------------------------------------------------------------------------
/src/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | function Card({ className, ...props }: React.ComponentProps<"div">) {
6 | return (
7 |
15 | )
16 | }
17 |
18 | function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
19 | return (
20 |
25 | )
26 | }
27 |
28 | function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
29 | return (
30 |
35 | )
36 | }
37 |
38 | function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
39 | return (
40 |
45 | )
46 | }
47 |
48 | function CardContent({ className, ...props }: React.ComponentProps<"div">) {
49 | return (
50 |
55 | )
56 | }
57 |
58 | function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
59 | return (
60 |
65 | )
66 | }
67 |
68 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
69 |
--------------------------------------------------------------------------------
/src/components/ui/alert.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva, type VariantProps } from "class-variance-authority"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const alertVariants = cva(
7 | "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
8 | {
9 | variants: {
10 | variant: {
11 | default: "bg-background text-foreground",
12 | destructive:
13 | "text-destructive-foreground [&>svg]:text-current *:data-[slot=alert-description]:text-destructive-foreground/80",
14 | },
15 | },
16 | defaultVariants: {
17 | variant: "default",
18 | },
19 | }
20 | )
21 |
22 | function Alert({
23 | className,
24 | variant,
25 | ...props
26 | }: React.ComponentProps<"div"> & VariantProps) {
27 | return (
28 |
34 | )
35 | }
36 |
37 | function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
38 | return (
39 |
47 | )
48 | }
49 |
50 | function AlertDescription({
51 | className,
52 | ...props
53 | }: React.ComponentProps<"div">) {
54 | return (
55 |
63 | )
64 | }
65 |
66 | export { Alert, AlertTitle, AlertDescription }
67 |
--------------------------------------------------------------------------------
/src/components/ui/theme-provider.tsx:
--------------------------------------------------------------------------------
1 | import React, { createContext, useContext, useEffect, useState } from "react";
2 |
3 | type Theme = "dark" | "light" | "system";
4 |
5 | type ThemeProviderProps = {
6 | children: React.ReactNode;
7 | defaultTheme?: Theme;
8 | storageKey?: string;
9 | };
10 |
11 | type ThemeProviderState = {
12 | theme: Theme;
13 | setTheme: (theme: Theme) => void;
14 | };
15 |
16 | const initialState: ThemeProviderState = {
17 | theme: "system",
18 | setTheme: () => null,
19 | };
20 |
21 | const ThemeProviderContext = createContext(initialState);
22 |
23 | export function ThemeProvider({
24 | children,
25 | defaultTheme = "system",
26 | storageKey = "vite-ui-theme",
27 | ...props
28 | }: ThemeProviderProps) {
29 | const [theme, setTheme] = useState(
30 | () => (localStorage.getItem(storageKey) as Theme) || defaultTheme
31 | );
32 |
33 | useEffect(() => {
34 | const root = window.document.documentElement;
35 | root.classList.remove("light", "dark");
36 |
37 | if (theme === "system") {
38 | const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
39 | .matches
40 | ? "dark"
41 | : "light";
42 |
43 | root.classList.add(systemTheme);
44 | return;
45 | }
46 |
47 | root.classList.add(theme);
48 | localStorage.setItem(storageKey, theme);
49 | }, [theme]);
50 |
51 | const value = {
52 | theme,
53 | setTheme: (theme: Theme) => {
54 | localStorage.setItem(storageKey, theme);
55 | setTheme(theme);
56 | },
57 | };
58 |
59 | return (
60 |
61 | {children}
62 |
63 | );
64 | }
65 |
66 | export const useTheme = () => {
67 | const context = useContext(ThemeProviderContext);
68 |
69 | if (context === undefined)
70 | throw new Error("useTheme must be used within a ThemeProvider");
71 |
72 | return context;
73 | };
74 |
--------------------------------------------------------------------------------
/src/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-[color,box-shadow] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 ring-ring/10 dark:ring-ring/20 dark:outline-ring/40 outline-ring/50 focus-visible:ring-4 focus-visible:outline-1 aria-invalid:focus-visible:ring-0",
9 | {
10 | variants: {
11 | variant: {
12 | default:
13 | "bg-primary text-primary-foreground shadow-sm hover:bg-primary/90",
14 | destructive:
15 | "bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90",
16 | outline:
17 | "border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground",
18 | secondary:
19 | "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
20 | ghost: "hover:bg-accent hover:text-accent-foreground",
21 | link: "text-primary underline-offset-4 hover:underline",
22 | },
23 | size: {
24 | default: "h-9 px-4 py-2 has-[>svg]:px-3",
25 | sm: "h-8 rounded-md px-3 has-[>svg]:px-2.5",
26 | lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
27 | icon: "size-9",
28 | },
29 | },
30 | defaultVariants: {
31 | variant: "default",
32 | size: "default",
33 | },
34 | }
35 | )
36 |
37 | function Button({
38 | className,
39 | variant,
40 | size,
41 | asChild = false,
42 | ...props
43 | }: React.ComponentProps<"button"> &
44 | VariantProps & {
45 | asChild?: boolean
46 | }) {
47 | const Comp = asChild ? Slot : "button"
48 |
49 | return (
50 |
55 | )
56 | }
57 |
58 | export { Button, buttonVariants }
59 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Gas Fee Predictor
2 |
3 | ## Overview
4 |
5 | Gas Fee Predictor is a comprehensive blockchain analytics dashboard designed to help cryptocurrency users and blockchain enthusiasts understand and optimize their transaction strategies. The application provides real-time insights into Ethereum network performance, gas prices, transaction volumes, and more.
6 |
7 | ## Features
8 |
9 | ### 1. Trends Tab
10 |
11 | - **Gas Price Trends**: A dynamic line chart displaying real-time gas price fluctuations over the past 24 hours.
12 | - **Transaction Volume**: A bar chart showing the number of transactions and their distribution across different time periods.
13 |
14 | ### 2. Insights Tab
15 |
16 | - **Network Performance**: A line chart visualizing block times and network efficiency.
17 | - **Transaction Types**: A pie chart breaking down transaction categories including:
18 | - DeFi Transactions
19 | - NFT Interactions
20 | - Smart Contracts
21 | - Token Transfers
22 |
23 | ### 3. Timing Tab
24 |
25 | - **Optimal Transaction Time**: Identifies the most cost-effective moment for executing blockchain transactions.
26 | - **Cost Saving Recommendations**: Provides actionable advice on when to perform transactions based on current gas prices.
27 | - Low gas prices: Recommended time for transactions
28 | - Moderate gas prices: Suggests timing for non-urgent transactions
29 | - High gas prices: Advises delaying non-critical interactions
30 |
31 | ### 4. Settings Tab
32 |
33 | - **Theme Customization**: Toggle between dark and light modes
34 | - **Network Information**: Currently configured for Ethereum Mainnet
35 |
36 | ## User Interface
37 |
38 | The application features a clean, modern design with:
39 |
40 | - Responsive layout adapting to different screen sizes
41 | - Intuitive bottom navigation for easy tab switching
42 | - Top navigation with theme toggle
43 | - Recharts-powered interactive visualizations
44 | - Shadcn UI components for consistent styling
45 |
46 | ## Technical Highlights
47 |
48 | - Built with React
49 | - Uses Recharts for data visualization
50 | - Implements responsive design
51 | - Supports dark and light themes
52 | - Generates mock blockchain data for demonstration
53 |
54 | ## Use Cases
55 |
56 | - Blockchain Investors
57 | - Cryptocurrency Traders
58 | - Smart Contract Developers
59 | - Network Analysts
60 | - DeFi Enthusiasts
61 |
62 | Perfect for anyone looking to optimize transaction timing and understand blockchain network dynamics.
63 |
--------------------------------------------------------------------------------
/src/APITester.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef, type FormEvent } from "react";
2 | import { Button } from "@/components/ui/button";
3 | import { Input } from "@/components/ui/input";
4 | import {
5 | Select,
6 | SelectContent,
7 | SelectItem,
8 | SelectTrigger,
9 | SelectValue,
10 | } from "@/components/ui/select";
11 | import { cn } from "@/lib/utils";
12 |
13 | export function APITester() {
14 | const responseInputRef = useRef(null);
15 |
16 | const testEndpoint = async (e: FormEvent) => {
17 | e.preventDefault();
18 |
19 | try {
20 | const form = e.currentTarget;
21 | const formData = new FormData(form);
22 | const endpoint = formData.get("endpoint") as string;
23 | const url = new URL(endpoint, location.href);
24 | const method = formData.get("method") as string;
25 | const res = await fetch(url, { method });
26 |
27 | const data = await res.json();
28 | responseInputRef.current!.value = JSON.stringify(data, null, 2);
29 | } catch (error) {
30 | responseInputRef.current!.value = String(error);
31 | }
32 | };
33 |
34 | return (
35 |
36 |
66 |
67 |
78 |
79 | );
80 | }
81 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/ui/form.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as LabelPrimitive from "@radix-ui/react-label"
3 | import { Slot } from "@radix-ui/react-slot"
4 | import {
5 | Controller,
6 | ControllerProps,
7 | FieldPath,
8 | FieldValues,
9 | FormProvider,
10 | useFormContext,
11 | useFormState,
12 | } from "react-hook-form"
13 |
14 | import { cn } from "@/lib/utils"
15 | import { Label } from "@/components/ui/label"
16 |
17 | const Form = FormProvider
18 |
19 | type FormFieldContextValue<
20 | TFieldValues extends FieldValues = FieldValues,
21 | TName extends FieldPath = FieldPath,
22 | > = {
23 | name: TName
24 | }
25 |
26 | const FormFieldContext = React.createContext(
27 | {} as FormFieldContextValue
28 | )
29 |
30 | const FormField = <
31 | TFieldValues extends FieldValues = FieldValues,
32 | TName extends FieldPath = FieldPath,
33 | >({
34 | ...props
35 | }: ControllerProps) => {
36 | return (
37 |
38 |
39 |
40 | )
41 | }
42 |
43 | const useFormField = () => {
44 | const fieldContext = React.useContext(FormFieldContext)
45 | const itemContext = React.useContext(FormItemContext)
46 | const { getFieldState } = useFormContext()
47 | const formState = useFormState({ name: fieldContext.name })
48 | const fieldState = getFieldState(fieldContext.name, formState)
49 |
50 | if (!fieldContext) {
51 | throw new Error("useFormField should be used within ")
52 | }
53 |
54 | const { id } = itemContext
55 |
56 | return {
57 | id,
58 | name: fieldContext.name,
59 | formItemId: `${id}-form-item`,
60 | formDescriptionId: `${id}-form-item-description`,
61 | formMessageId: `${id}-form-item-message`,
62 | ...fieldState,
63 | }
64 | }
65 |
66 | type FormItemContextValue = {
67 | id: string
68 | }
69 |
70 | const FormItemContext = React.createContext(
71 | {} as FormItemContextValue
72 | )
73 |
74 | function FormItem({ className, ...props }: React.ComponentProps<"div">) {
75 | const id = React.useId()
76 |
77 | return (
78 |
79 |
84 |
85 | )
86 | }
87 |
88 | function FormLabel({
89 | className,
90 | ...props
91 | }: React.ComponentProps) {
92 | const { error, formItemId } = useFormField()
93 |
94 | return (
95 |
102 | )
103 | }
104 |
105 | function FormControl({ ...props }: React.ComponentProps) {
106 | const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
107 |
108 | return (
109 |
120 | )
121 | }
122 |
123 | function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
124 | const { formDescriptionId } = useFormField()
125 |
126 | return (
127 |
133 | )
134 | }
135 |
136 | function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
137 | const { error, formMessageId } = useFormField()
138 | const body = error ? String(error?.message) : props.children
139 |
140 | if (!body) {
141 | return null
142 | }
143 |
144 | return (
145 |
151 | {body}
152 |
153 | )
154 | }
155 |
156 | export {
157 | useFormField,
158 | Form,
159 | FormItem,
160 | FormLabel,
161 | FormControl,
162 | FormDescription,
163 | FormMessage,
164 | FormField,
165 | }
166 |
--------------------------------------------------------------------------------
/styles/globals.css:
--------------------------------------------------------------------------------
1 | @import "tailwindcss";
2 |
3 | @plugin "tailwindcss-animate";
4 |
5 | @custom-variant dark (&:is(.dark *));
6 |
7 | :root {
8 | --background: hsl(0 0% 100%);
9 | --foreground: hsl(240 10% 3.9%);
10 | --card: hsl(0 0% 100%);
11 | --card-foreground: hsl(240 10% 3.9%);
12 | --popover: hsl(0 0% 100%);
13 | --popover-foreground: hsl(240 10% 3.9%);
14 | --primary: hsl(240 5.9% 10%);
15 | --primary-foreground: hsl(0 0% 98%);
16 | --secondary: hsl(240 4.8% 95.9%);
17 | --secondary-foreground: hsl(240 5.9% 10%);
18 | --muted: hsl(240 4.8% 95.9%);
19 | --muted-foreground: hsl(240 3.8% 46.1%);
20 | --accent: hsl(240 4.8% 95.9%);
21 | --accent-foreground: hsl(240 5.9% 10%);
22 | --destructive: hsl(0 84.2% 60.2%);
23 | --destructive-foreground: hsl(0 0% 98%);
24 | --border: hsl(240 5.9% 90%);
25 | --input: hsl(240 5.9% 90%);
26 | --ring: hsl(240 10% 3.9%);
27 | --chart-1: hsl(12 76% 61%);
28 | --chart-2: hsl(173 58% 39%);
29 | --chart-3: hsl(197 37% 24%);
30 | --chart-4: hsl(43 74% 66%);
31 | --chart-5: hsl(27 87% 67%);
32 | --radius: 0.6rem;
33 | --sidebar-background: hsl(0 0% 98%);
34 | --sidebar-foreground: hsl(240 5.3% 26.1%);
35 | --sidebar-primary: hsl(240 5.9% 10%);
36 | --sidebar-primary-foreground: hsl(0 0% 98%);
37 | --sidebar-accent: hsl(240 4.8% 95.9%);
38 | --sidebar-accent-foreground: hsl(240 5.9% 10%);
39 | --sidebar-border: hsl(220 13% 91%);
40 | --sidebar-ring: hsl(217.2 91.2% 59.8%);
41 | }
42 |
43 | .dark {
44 | --background: hsl(240 10% 3.9%);
45 | --foreground: hsl(0 0% 98%);
46 | --card: hsl(240 10% 3.9%);
47 | --card-foreground: hsl(0 0% 98%);
48 | --popover: hsl(240 10% 3.9%);
49 | --popover-foreground: hsl(0 0% 98%);
50 | --primary: hsl(0 0% 98%);
51 | --primary-foreground: hsl(240 5.9% 10%);
52 | --secondary: hsl(240 3.7% 15.9%);
53 | --secondary-foreground: hsl(0 0% 98%);
54 | --muted: hsl(240 3.7% 15.9%);
55 | --muted-foreground: hsl(240 5% 64.9%);
56 | --accent: hsl(240 3.7% 15.9%);
57 | --accent-foreground: hsl(0 0% 98%);
58 | --destructive: hsl(0 62.8% 30.6%);
59 | --destructive-foreground: hsl(0 0% 98%);
60 | --border: hsl(240 3.7% 15.9%);
61 | --input: hsl(240 3.7% 15.9%);
62 | --ring: hsl(240 4.9% 83.9%);
63 | --chart-1: hsl(220 70% 50%);
64 | --chart-2: hsl(160 60% 45%);
65 | --chart-3: hsl(30 80% 55%);
66 | --chart-4: hsl(280 65% 60%);
67 | --chart-5: hsl(340 75% 55%);
68 | --sidebar-background: hsl(240 5.9% 10%);
69 | --sidebar-foreground: hsl(240 4.8% 95.9%);
70 | --sidebar-primary: hsl(224.3 76.3% 48%);
71 | --sidebar-primary-foreground: hsl(0 0% 100%);
72 | --sidebar-accent: hsl(240 3.7% 15.9%);
73 | --sidebar-accent-foreground: hsl(240 4.8% 95.9%);
74 | --sidebar-border: hsl(240 3.7% 15.9%);
75 | --sidebar-ring: hsl(217.2 91.2% 59.8%);
76 | }
77 |
78 | @theme inline {
79 | --color-background: var(--background);
80 | --color-foreground: var(--foreground);
81 | --color-card: var(--card);
82 | --color-card-foreground: var(--card-foreground);
83 | --color-popover: var(--popover);
84 | --color-popover-foreground: var(--popover-foreground);
85 | --color-primary: var(--primary);
86 | --color-primary-foreground: var(--primary-foreground);
87 | --color-secondary: var(--secondary);
88 | --color-secondary-foreground: var(--secondary-foreground);
89 | --color-muted: var(--muted);
90 | --color-muted-foreground: var(--muted-foreground);
91 | --color-accent: var(--accent);
92 | --color-accent-foreground: var(--accent-foreground);
93 | --color-destructive: var(--destructive);
94 | --color-destructive-foreground: var(--destructive-foreground);
95 | --color-border: var(--border);
96 | --color-input: var(--input);
97 | --color-ring: var(--ring);
98 | --color-chart-1: var(--chart-1);
99 | --color-chart-2: var(--chart-2);
100 | --color-chart-3: var(--chart-3);
101 | --color-chart-4: var(--chart-4);
102 | --color-chart-5: var(--chart-5);
103 | --radius-sm: calc(var(--radius) - 4px);
104 | --radius-md: calc(var(--radius) - 2px);
105 | --radius-lg: var(--radius);
106 | --radius-xl: calc(var(--radius) + 4px);
107 | --color-sidebar-ring: var(--sidebar-ring);
108 | --color-sidebar-border: var(--sidebar-border);
109 | --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
110 | --color-sidebar-accent: var(--sidebar-accent);
111 | --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
112 | --color-sidebar-primary: var(--sidebar-primary);
113 | --color-sidebar-foreground: var(--sidebar-foreground);
114 | --color-sidebar: var(--sidebar-background);
115 | --animate-accordion-down: accordion-down 0.2s ease-out;
116 | --animate-accordion-up: accordion-up 0.2s ease-out;
117 |
118 | @keyframes accordion-down {
119 | from {
120 | height: 0;
121 | }
122 | to {
123 | height: var(--radix-accordion-content-height);
124 | }
125 | }
126 |
127 | @keyframes accordion-up {
128 | from {
129 | height: var(--radix-accordion-content-height);
130 | }
131 | to {
132 | height: 0;
133 | }
134 | }
135 | }
136 |
137 | @layer base {
138 | * {
139 | @apply border-border outline-ring/50;
140 | }
141 | body {
142 | @apply bg-background text-foreground;
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/src/components/ui/select.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as SelectPrimitive from "@radix-ui/react-select"
3 | import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | function Select({
8 | ...props
9 | }: React.ComponentProps) {
10 | return
11 | }
12 |
13 | function SelectGroup({
14 | ...props
15 | }: React.ComponentProps) {
16 | return
17 | }
18 |
19 | function SelectValue({
20 | ...props
21 | }: React.ComponentProps) {
22 | return
23 | }
24 |
25 | function SelectTrigger({
26 | className,
27 | children,
28 | ...props
29 | }: React.ComponentProps) {
30 | return (
31 | span]:line-clamp-1",
35 | className
36 | )}
37 | {...props}
38 | >
39 | {children}
40 |
41 |
42 |
43 |
44 | )
45 | }
46 |
47 | function SelectContent({
48 | className,
49 | children,
50 | position = "popper",
51 | ...props
52 | }: React.ComponentProps) {
53 | return (
54 |
55 |
66 |
67 |
74 | {children}
75 |
76 |
77 |
78 |
79 | )
80 | }
81 |
82 | function SelectLabel({
83 | className,
84 | ...props
85 | }: React.ComponentProps) {
86 | return (
87 |
92 | )
93 | }
94 |
95 | function SelectItem({
96 | className,
97 | children,
98 | ...props
99 | }: React.ComponentProps) {
100 | return (
101 |
109 |
110 |
111 |
112 |
113 |
114 | {children}
115 |
116 | )
117 | }
118 |
119 | function SelectSeparator({
120 | className,
121 | ...props
122 | }: React.ComponentProps) {
123 | return (
124 |
129 | )
130 | }
131 |
132 | function SelectScrollUpButton({
133 | className,
134 | ...props
135 | }: React.ComponentProps) {
136 | return (
137 |
145 |
146 |
147 | )
148 | }
149 |
150 | function SelectScrollDownButton({
151 | className,
152 | ...props
153 | }: React.ComponentProps) {
154 | return (
155 |
163 |
164 |
165 | )
166 | }
167 |
168 | export {
169 | Select,
170 | SelectContent,
171 | SelectGroup,
172 | SelectItem,
173 | SelectLabel,
174 | SelectScrollDownButton,
175 | SelectScrollUpButton,
176 | SelectSeparator,
177 | SelectTrigger,
178 | SelectValue,
179 | }
180 |
--------------------------------------------------------------------------------
/src/GasFeeApp.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import {
3 | LineChart,
4 | Line,
5 | BarChart,
6 | Bar,
7 | XAxis,
8 | YAxis,
9 | CartesianGrid,
10 | Tooltip,
11 | Legend,
12 | PieChart,
13 | Pie,
14 | Cell,
15 | ResponsiveContainer,
16 | } from "recharts";
17 | import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
18 | import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
19 | import { Button } from "@/components/ui/button";
20 | import {
21 | TrendingUp,
22 | Clock,
23 | Zap,
24 | Moon,
25 | Sun,
26 | BarChart as BarChartIcon,
27 | Clock as ClockIcon,
28 | Settings,
29 | PieChart as PieChartIcon,
30 | } from "lucide-react";
31 | import { ThemeProvider } from "@/components/ui/theme-provider";
32 | import { useTheme } from "@/components/ui/use-theme";
33 |
34 | // Enhanced Mock Data Generation
35 | const generateMockBlockchainData = () => {
36 | const gasData = [];
37 | const transactionData = [];
38 | const networkData = [];
39 | const now = new Date();
40 |
41 | for (let i = 24; i >= 0; i--) {
42 | const date = new Date(now.getTime() - i * 60 * 60 * 1000);
43 |
44 | // Gas Price Data
45 | gasData.push({
46 | time: date.toLocaleTimeString(),
47 | gasPrice: Math.floor(Math.random() * 50 + 10),
48 | networkCongestion: Math.random() * 100,
49 | });
50 |
51 | // Transaction Volume Data
52 | transactionData.push({
53 | time: date.toLocaleTimeString(),
54 | transactions: Math.floor(Math.random() * 1000 + 500),
55 | successRate: Math.random() * 100,
56 | });
57 |
58 | // Network Performance Data
59 | networkData.push({
60 | time: date.toLocaleTimeString(),
61 | blockTime: Math.random() * 20,
62 | networkLoad: Math.random() * 100,
63 | });
64 | }
65 |
66 | // Pie Chart Data for Transaction Types
67 | const transactionTypeData = [
68 | { name: "DeFi", value: Math.floor(Math.random() * 100) },
69 | { name: "NFT", value: Math.floor(Math.random() * 100) },
70 | { name: "Smart Contracts", value: Math.floor(Math.random() * 100) },
71 | { name: "Token Transfer", value: Math.floor(Math.random() * 100) },
72 | ];
73 |
74 | return {
75 | gasData,
76 | transactionData,
77 | networkData,
78 | transactionTypeData,
79 | };
80 | };
81 |
82 | // Top Navigation Component
83 | const TopNavigation = ({ activeTab, setActiveTab }) => {
84 | const { theme, setTheme } = useTheme();
85 |
86 | return (
87 |
88 |
89 |
Gas Fee Predictor
90 |
101 |
102 |
103 | );
104 | };
105 |
106 | // Bottom Navigation Component (Same as previous implementation)
107 | const BottomNavigation = ({ activeTab, setActiveTab }) => {
108 | const navItems = [
109 | {
110 | icon: BarChartIcon,
111 | label: "Trends",
112 | tab: "trends",
113 | },
114 | {
115 | icon: PieChartIcon,
116 | label: "Insights",
117 | tab: "insights",
118 | },
119 | {
120 | icon: ClockIcon,
121 | label: "Timing",
122 | tab: "timing",
123 | },
124 | {
125 | icon: Settings,
126 | label: "Settings",
127 | tab: "settings",
128 | },
129 | ];
130 |
131 | return (
132 |
133 |
134 | {navItems.map((item) => (
135 |
150 | ))}
151 |
152 |
153 | );
154 | };
155 |
156 | const COLORS = ["#0088FE", "#00C49F", "#FFBB28", "#FF8042"];
157 |
158 | const GasFeePredictor = () => {
159 | const { theme, setTheme } = useTheme();
160 | const [blockchainData, setBlockchainData] = useState(null);
161 | const [activeTab, setActiveTab] = useState("trends");
162 | const [optimalTransactionTime, setOptimalTransactionTime] = useState(null);
163 | const [costSavingRecommendation, setCostSavingRecommendation] = useState("");
164 |
165 | useEffect(() => {
166 | const data = generateMockBlockchainData();
167 | setBlockchainData(data);
168 |
169 | const lowestGasPriceEntry = data.gasData.reduce((prev, current) =>
170 | prev.gasPrice < current.gasPrice ? prev : current
171 | );
172 |
173 | setOptimalTransactionTime(lowestGasPriceEntry.time);
174 |
175 | if (lowestGasPriceEntry.gasPrice < 20) {
176 | setCostSavingRecommendation(
177 | "Excellent time to execute transactions! Gas prices are low."
178 | );
179 | } else if (lowestGasPriceEntry.gasPrice < 35) {
180 | setCostSavingRecommendation(
181 | "Good time to execute non-urgent transactions."
182 | );
183 | } else {
184 | setCostSavingRecommendation(
185 | "Consider delaying non-critical smart contract interactions."
186 | );
187 | }
188 | }, []);
189 |
190 | if (!blockchainData) return null;
191 |
192 | return (
193 |
194 | {/* Header with Theme Toggle */}
195 |
196 |
197 |
198 | {/* Trends Tab */}
199 | {activeTab === "trends" && (
200 |
201 |
202 |
203 | Gas Price Trends
204 |
205 |
206 |
207 |
208 |
209 |
210 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 | Transaction Volume
227 |
228 |
229 |
230 |
231 |
232 |
233 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 | )}
248 |
249 | {/* Insights Tab */}
250 | {activeTab === "insights" && (
251 |
252 |
253 |
254 | Network Performance
255 |
256 |
257 |
258 |
259 |
260 |
261 |
268 |
269 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 | Transaction Types
282 |
283 |
284 |
285 |
286 |
295 | {blockchainData.transactionTypeData.map(
296 | (entry, index) => (
297 | |
301 | )
302 | )}
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 | )}
312 |
313 | {/* Timing Tab */}
314 | {activeTab === "timing" && (
315 |
316 |
317 |
318 |
319 | Optimal Transaction Time
320 |
321 |
322 |
323 | {optimalTransactionTime && (
324 |
325 |
326 | {optimalTransactionTime}
327 |
328 |
329 | Lowest gas prices detected
330 |
331 |
332 | )}
333 |
334 |
335 |
336 |
337 |
338 |
339 | Cost Saving Recommendations
340 |
341 |
342 |
343 |
344 | Transaction Strategy
345 |
346 | {costSavingRecommendation}
347 |
348 |
349 |
350 |
351 |
352 | )}
353 |
354 | {/* Settings Tab - Same as previous implementation */}
355 | {activeTab === "settings" && (
356 |
357 |
358 |
359 | Application Settings
360 |
361 |
362 |
363 |
364 |
Theme
365 |
366 |
367 | {theme === "dark" ? "Dark Mode" : "Light Mode"}
368 |
369 |
376 |
377 |
378 |
379 |
380 | Blockchain Network
381 |
382 |
Ethereum Mainnet
383 |
384 |
385 |
386 |
387 |
388 | )}
389 |
390 |
391 | {/* Bottom Navigation */}
392 |
393 |
394 | );
395 | };
396 |
397 | // Wrapper Component with Theme Provider
398 | const GasFeeApp = () => {
399 | return (
400 |
401 |
402 |
403 | );
404 | };
405 |
406 | export default GasFeeApp;
407 |
--------------------------------------------------------------------------------
/bun.lock:
--------------------------------------------------------------------------------
1 | {
2 | "lockfileVersion": 1,
3 | "workspaces": {
4 | "": {
5 | "name": "bun-react-template",
6 | "dependencies": {
7 | "@hookform/resolvers": "^4.1.0",
8 | "@radix-ui/react-label": "^2.1.2",
9 | "@radix-ui/react-select": "^2.1.6",
10 | "@radix-ui/react-slot": "^1.1.2",
11 | "bun-plugin-tailwind": "^0.0.14",
12 | "class-variance-authority": "^0.7.1",
13 | "clsx": "^2.1.1",
14 | "lucide-react": "^0.477.0",
15 | "react": "^19",
16 | "react-dom": "^19",
17 | "react-hook-form": "^7.54.2",
18 | "recharts": "^2.15.1",
19 | "tailwind-merge": "^3.0.1",
20 | "tailwindcss": "^4.0.6",
21 | "tailwindcss-animate": "^1.0.7",
22 | "zod": "^3.24.2",
23 | },
24 | "devDependencies": {
25 | "@types/bun": "latest",
26 | "@types/react": "^19",
27 | "@types/react-dom": "^19",
28 | },
29 | },
30 | },
31 | "packages": {
32 | "@babel/runtime": ["@babel/runtime@7.26.9", "", { "dependencies": { "regenerator-runtime": "^0.14.0" } }, "sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg=="],
33 |
34 | "@floating-ui/core": ["@floating-ui/core@1.6.9", "", { "dependencies": { "@floating-ui/utils": "^0.2.9" } }, "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw=="],
35 |
36 | "@floating-ui/dom": ["@floating-ui/dom@1.6.13", "", { "dependencies": { "@floating-ui/core": "^1.6.0", "@floating-ui/utils": "^0.2.9" } }, "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w=="],
37 |
38 | "@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.2", "", { "dependencies": { "@floating-ui/dom": "^1.0.0" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A=="],
39 |
40 | "@floating-ui/utils": ["@floating-ui/utils@0.2.9", "", {}, "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg=="],
41 |
42 | "@hookform/resolvers": ["@hookform/resolvers@4.1.3", "", { "dependencies": { "@standard-schema/utils": "^0.3.0" }, "peerDependencies": { "react-hook-form": "^7.0.0" } }, "sha512-Jsv6UOWYTrEFJ/01ZrnwVXs7KDvP8XIo115i++5PWvNkNvkrsTfGiLS6w+eJ57CYtUtDQalUWovCZDHFJ8u1VQ=="],
43 |
44 | "@radix-ui/number": ["@radix-ui/number@1.1.0", "", {}, "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ=="],
45 |
46 | "@radix-ui/primitive": ["@radix-ui/primitive@1.1.1", "", {}, "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA=="],
47 |
48 | "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.2", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-G+KcpzXHq24iH0uGG/pF8LyzpFJYGD4RfLjCIBfGdSLXvjLHST31RUiRVrupIBMvIppMgSzQ6l66iAxl03tdlg=="],
49 |
50 | "@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-slot": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9z54IEKRxIa9VityapoEYMuByaG42iSy1ZXlY2KcuLSEtq8x4987/N6m15ppoMffgZX72gER2uHe1D9Y6Unlcw=="],
51 |
52 | "@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw=="],
53 |
54 | "@radix-ui/react-context": ["@radix-ui/react-context@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q=="],
55 |
56 | "@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg=="],
57 |
58 | "@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.5", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-escape-keydown": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg=="],
59 |
60 | "@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg=="],
61 |
62 | "@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-zxwE80FCU7lcXUGWkdt6XpTTCKPitG1XKOwViTxHVKIJhZl9MvIl2dVHeZENCWD9+EdWv05wlaEkRXUykU27RA=="],
63 |
64 | "@radix-ui/react-id": ["@radix-ui/react-id@1.1.0", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA=="],
65 |
66 | "@radix-ui/react-label": ["@radix-ui/react-label@2.1.2", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-zo1uGMTaNlHehDyFQcDZXRJhUPDuukcnHz0/jnrup0JA6qL+AFpAnty+7VKa9esuU5xTblAZzTGYJKSKaBxBhw=="],
67 |
68 | "@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.2", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.2", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-rect": "1.1.0", "@radix-ui/react-use-size": "1.1.0", "@radix-ui/rect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Rvqc3nOpwseCyj/rgjlJDYAgyfw7OC1tTkKn2ivhaMGcYt8FSBlahHOZak2i3QwkRXUXgGgzeEe2RuqeEHuHgA=="],
69 |
70 | "@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.4", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA=="],
71 |
72 | "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.2", "", { "dependencies": { "@radix-ui/react-slot": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w=="],
73 |
74 | "@radix-ui/react-select": ["@radix-ui/react-select@2.1.6", "", { "dependencies": { "@radix-ui/number": "1.1.0", "@radix-ui/primitive": "1.1.1", "@radix-ui/react-collection": "1.1.2", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-dismissable-layer": "1.1.5", "@radix-ui/react-focus-guards": "1.1.1", "@radix-ui/react-focus-scope": "1.1.2", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-popper": "1.2.2", "@radix-ui/react-portal": "1.1.4", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-slot": "1.1.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-previous": "1.1.0", "@radix-ui/react-visually-hidden": "1.1.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-T6ajELxRvTuAMWH0YmRJ1qez+x4/7Nq7QIx7zJ0VK3qaEWdnWpNbEDnmWldG1zBDwqrLy5aLMUWcoGirVj5kMg=="],
75 |
76 | "@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ=="],
77 |
78 | "@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw=="],
79 |
80 | "@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.1.0", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw=="],
81 |
82 | "@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.0", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw=="],
83 |
84 | "@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w=="],
85 |
86 | "@radix-ui/react-use-previous": ["@radix-ui/react-use-previous@1.1.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og=="],
87 |
88 | "@radix-ui/react-use-rect": ["@radix-ui/react-use-rect@1.1.0", "", { "dependencies": { "@radix-ui/rect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ=="],
89 |
90 | "@radix-ui/react-use-size": ["@radix-ui/react-use-size@1.1.0", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw=="],
91 |
92 | "@radix-ui/react-visually-hidden": ["@radix-ui/react-visually-hidden@1.1.2", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-1SzA4ns2M1aRlvxErqhLHsBHoS5eI5UUcI2awAMgGUp4LoaoWOKYmvqDY2s/tltuPkh3Yk77YF/r3IRj+Amx4Q=="],
93 |
94 | "@radix-ui/rect": ["@radix-ui/rect@1.1.0", "", {}, "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg=="],
95 |
96 | "@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="],
97 |
98 | "@types/bun": ["@types/bun@1.2.4", "", { "dependencies": { "bun-types": "1.2.4" } }, "sha512-QtuV5OMR8/rdKJs213iwXDpfVvnskPXY/S0ZiFbsTjQZycuqPbMW8Gf/XhLfwE5njW8sxI2WjISURXPlHypMFA=="],
99 |
100 | "@types/d3-array": ["@types/d3-array@3.2.1", "", {}, "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg=="],
101 |
102 | "@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="],
103 |
104 | "@types/d3-ease": ["@types/d3-ease@3.0.2", "", {}, "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="],
105 |
106 | "@types/d3-interpolate": ["@types/d3-interpolate@3.0.4", "", { "dependencies": { "@types/d3-color": "*" } }, "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA=="],
107 |
108 | "@types/d3-path": ["@types/d3-path@3.1.1", "", {}, "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="],
109 |
110 | "@types/d3-scale": ["@types/d3-scale@4.0.9", "", { "dependencies": { "@types/d3-time": "*" } }, "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw=="],
111 |
112 | "@types/d3-shape": ["@types/d3-shape@3.1.7", "", { "dependencies": { "@types/d3-path": "*" } }, "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg=="],
113 |
114 | "@types/d3-time": ["@types/d3-time@3.0.4", "", {}, "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="],
115 |
116 | "@types/d3-timer": ["@types/d3-timer@3.0.2", "", {}, "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="],
117 |
118 | "@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
119 |
120 | "@types/react": ["@types/react@19.0.10", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g=="],
121 |
122 | "@types/react-dom": ["@types/react-dom@19.0.4", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg=="],
123 |
124 | "@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="],
125 |
126 | "aria-hidden": ["aria-hidden@1.2.4", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A=="],
127 |
128 | "bun-plugin-tailwind": ["bun-plugin-tailwind@0.0.14", "", { "dependencies": { "tailwindcss": "4.0.0-beta.9" }, "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-Ge8M8DQsRDErCzH/uI8pYjx5vZWXxQvnwM/xMQMElxQqHieGbAopfYo/q/kllkPkRbFHiwhnHwTpRMAMJZCjug=="],
129 |
130 | "bun-types": ["bun-types@1.2.4", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-nDPymR207ZZEoWD4AavvEaa/KZe/qlrbMSchqpQwovPZCKc7pwMoENjEtHgMKaAjJhy+x6vfqSBA1QU3bJgs0Q=="],
131 |
132 | "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="],
133 |
134 | "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
135 |
136 | "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
137 |
138 | "d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="],
139 |
140 | "d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="],
141 |
142 | "d3-ease": ["d3-ease@3.0.1", "", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="],
143 |
144 | "d3-format": ["d3-format@3.1.0", "", {}, "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA=="],
145 |
146 | "d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="],
147 |
148 | "d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="],
149 |
150 | "d3-scale": ["d3-scale@4.0.2", "", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="],
151 |
152 | "d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="],
153 |
154 | "d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="],
155 |
156 | "d3-time-format": ["d3-time-format@4.1.0", "", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="],
157 |
158 | "d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="],
159 |
160 | "decimal.js-light": ["decimal.js-light@2.5.1", "", {}, "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="],
161 |
162 | "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="],
163 |
164 | "dom-helpers": ["dom-helpers@5.2.1", "", { "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" } }, "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA=="],
165 |
166 | "eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="],
167 |
168 | "fast-equals": ["fast-equals@5.2.2", "", {}, "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw=="],
169 |
170 | "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="],
171 |
172 | "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="],
173 |
174 | "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
175 |
176 | "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
177 |
178 | "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
179 |
180 | "lucide-react": ["lucide-react@0.477.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-yCf7aYxerFZAbd8jHJxjwe1j7jEMPptjnaOqdYeirFnEy85cNR3/L+o0I875CYFYya+eEVzZSbNuRk8BZPDpVw=="],
181 |
182 | "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
183 |
184 | "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
185 |
186 | "react": ["react@19.0.0", "", {}, "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ=="],
187 |
188 | "react-dom": ["react-dom@19.0.0", "", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ=="],
189 |
190 | "react-hook-form": ["react-hook-form@7.54.2", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-eHpAUgUjWbZocoQYUHposymRb4ZP6d0uwUnooL2uOybA9/3tPUvoAKqEWK1WaSiTxxOfTpffNZP7QwlnM3/gEg=="],
191 |
192 | "react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
193 |
194 | "react-remove-scroll": ["react-remove-scroll@2.6.3", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ=="],
195 |
196 | "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="],
197 |
198 | "react-smooth": ["react-smooth@4.0.4", "", { "dependencies": { "fast-equals": "^5.0.1", "prop-types": "^15.8.1", "react-transition-group": "^4.4.5" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q=="],
199 |
200 | "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
201 |
202 | "react-transition-group": ["react-transition-group@4.4.5", "", { "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", "loose-envify": "^1.4.0", "prop-types": "^15.6.2" }, "peerDependencies": { "react": ">=16.6.0", "react-dom": ">=16.6.0" } }, "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g=="],
203 |
204 | "recharts": ["recharts@2.15.1", "", { "dependencies": { "clsx": "^2.0.0", "eventemitter3": "^4.0.1", "lodash": "^4.17.21", "react-is": "^18.3.1", "react-smooth": "^4.0.4", "recharts-scale": "^0.4.4", "tiny-invariant": "^1.3.1", "victory-vendor": "^36.6.8" }, "peerDependencies": { "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-v8PUTUlyiDe56qUj82w/EDVuzEFXwEHp9/xOowGAZwfLjB9uAy3GllQVIYMWF6nU+qibx85WF75zD7AjqoT54Q=="],
205 |
206 | "recharts-scale": ["recharts-scale@0.4.5", "", { "dependencies": { "decimal.js-light": "^2.4.1" } }, "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w=="],
207 |
208 | "regenerator-runtime": ["regenerator-runtime@0.14.1", "", {}, "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="],
209 |
210 | "scheduler": ["scheduler@0.25.0", "", {}, "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA=="],
211 |
212 | "tailwind-merge": ["tailwind-merge@3.0.2", "", {}, "sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw=="],
213 |
214 | "tailwindcss": ["tailwindcss@4.0.10", "", {}, "sha512-Z8U/6E2BWSdDkt3IWPiphoV+8V6aNzRmu2SriSbuhm6i3QIcY3TdUJzUP5NX8M8MZuIl+v4/77Rer8u4YSrSsg=="],
215 |
216 | "tailwindcss-animate": ["tailwindcss-animate@1.0.7", "", { "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders" } }, "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA=="],
217 |
218 | "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="],
219 |
220 | "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
221 |
222 | "typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="],
223 |
224 | "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
225 |
226 | "use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="],
227 |
228 | "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="],
229 |
230 | "victory-vendor": ["victory-vendor@36.9.2", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ=="],
231 |
232 | "zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="],
233 |
234 | "bun-plugin-tailwind/tailwindcss": ["tailwindcss@4.0.0-beta.9", "", {}, "sha512-96KpsfQi+/sFIOfyFnGzyy5pobuzf1iMBD9NVtelerPM/lPI2XUS4Kikw9yuKRniXXw77ov1sl7gCSKLsn6CJA=="],
235 |
236 | "prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
237 | }
238 | }
239 |
--------------------------------------------------------------------------------