feat: Add Consulta_API page and routing, implement API request functionality

- Introduced a new page for API consultation (`Consulta_API`) with a form to send requests.
- Added routing for the new page and a 404 error page.
- Created a `MetodoSelect` component for selecting HTTP methods.
- Implemented a `MiBoton` component for a customizable button.
- Updated the `HomePage` layout to include a sidebar (`DoubleNavbar`) and main content area.
- Enhanced the `Welcome` component with a new greeting and description.
- Added a holographic shader background to the 404 error page.
- Updated dependencies in `yarn.lock` for new components and features.
- Styled the sidebar and main content for better user experience.
This commit is contained in:
2025-05-06 00:12:54 +02:00
parent 613cd90662
commit 27f71a05f3
14 changed files with 1012 additions and 25 deletions
+201 -5
View File
@@ -10,6 +10,8 @@
"dependencies": {
"@mantine/core": "8.0.0",
"@mantine/hooks": "8.0.0",
"@react-three/fiber": "^9.1.2",
"@tabler/icons-react": "^3.31.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-router-dom": "^7.4.0"
@@ -1474,6 +1476,62 @@
"node": ">=14"
}
},
"node_modules/@react-three/fiber": {
"version": "9.1.2",
"resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.1.2.tgz",
"integrity": "sha512-k8FR9yVHV9kIF3iuOD0ds5hVymXYXfgdKklqziBVod9ZEJ8uk05Zjw29J/omU3IKeUfLNAIHfxneN3TUYM4I2w==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.17.8",
"@types/react-reconciler": "^0.28.9",
"@types/webxr": "*",
"base64-js": "^1.5.1",
"buffer": "^6.0.3",
"its-fine": "^2.0.0",
"react-reconciler": "^0.31.0",
"react-use-measure": "^2.1.7",
"scheduler": "^0.25.0",
"suspend-react": "^0.1.3",
"use-sync-external-store": "^1.4.0",
"zustand": "^5.0.3"
},
"peerDependencies": {
"expo": ">=43.0",
"expo-asset": ">=8.4",
"expo-file-system": ">=11.0",
"expo-gl": ">=11.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-native": ">=0.78",
"three": ">=0.156"
},
"peerDependenciesMeta": {
"expo": {
"optional": true
},
"expo-asset": {
"optional": true
},
"expo-file-system": {
"optional": true
},
"expo-gl": {
"optional": true
},
"react-dom": {
"optional": true
},
"react-native": {
"optional": true
}
}
},
"node_modules/@react-three/fiber/node_modules/scheduler": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz",
"integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==",
"license": "MIT"
},
"node_modules/@rollup/pluginutils": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz",
@@ -2027,6 +2085,32 @@
"storybook": "^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0"
}
},
"node_modules/@tabler/icons": {
"version": "3.31.0",
"resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-3.31.0.tgz",
"integrity": "sha512-dblAdeKY3+GA1U+Q9eziZ0ooVlZMHsE8dqP0RkwvRtEsAULoKOYaCUOcJ4oW1DjWegdxk++UAt2SlQVnmeHv+g==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/codecalm"
}
},
"node_modules/@tabler/icons-react": {
"version": "3.31.0",
"resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-3.31.0.tgz",
"integrity": "sha512-2rrCM5y/VnaVKnORpDdAua9SEGuJKVqPtWxeQ/vUVsgaUx30LDgBZph7/lterXxDY1IKR6NO//HDhWiifXTi3w==",
"license": "MIT",
"dependencies": {
"@tabler/icons": "3.31.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/codecalm"
},
"peerDependencies": {
"react": ">= 16"
}
},
"node_modules/@testing-library/dom": {
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz",
@@ -2218,7 +2302,6 @@
"version": "19.1.2",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz",
"integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.0.2"
@@ -2234,6 +2317,15 @@
"@types/react": "^19.0.0"
}
},
"node_modules/@types/react-reconciler": {
"version": "0.28.9",
"resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.9.tgz",
"integrity": "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*"
}
},
"node_modules/@types/resolve": {
"version": "1.20.6",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.6.tgz",
@@ -2241,6 +2333,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/webxr": {
"version": "0.5.22",
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.22.tgz",
"integrity": "sha512-Vr6Stjv5jPRqH690f5I5GLjVk8GSsoQSYJ2FVd/3jJF7KaqfwPi3ehfBS96mlQ2kPCwZaX6U0rG2+NGHBKkA/A==",
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.32.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.32.0.tgz",
@@ -2940,7 +3038,6 @@
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"dev": true,
"funding": [
{
"type": "github",
@@ -3058,7 +3155,6 @@
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"dev": true,
"funding": [
{
"type": "github",
@@ -3466,7 +3562,6 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"devOptional": true,
"license": "MIT"
},
"node_modules/damerau-levenshtein": {
@@ -5199,7 +5294,6 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"dev": true,
"funding": [
{
"type": "github",
@@ -5799,6 +5893,18 @@
"node": ">= 0.4"
}
},
"node_modules/its-fine": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/its-fine/-/its-fine-2.0.0.tgz",
"integrity": "sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng==",
"license": "MIT",
"dependencies": {
"@types/react-reconciler": "^0.28.9"
},
"peerDependencies": {
"react": "^19.0.0"
}
},
"node_modules/jackspeak": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
@@ -7239,6 +7345,27 @@
"react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/react-reconciler": {
"version": "0.31.0",
"resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.31.0.tgz",
"integrity": "sha512-7Ob7Z+URmesIsIVRjnLoDGwBEG/tVitidU0nMsqX/eeJaLY89RISO/10ERe0MqmzuKUUB1rmY+h1itMbUHg9BQ==",
"license": "MIT",
"dependencies": {
"scheduler": "^0.25.0"
},
"engines": {
"node": ">=0.10.0"
},
"peerDependencies": {
"react": "^19.0.0"
}
},
"node_modules/react-reconciler/node_modules/scheduler": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz",
"integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==",
"license": "MIT"
},
"node_modules/react-refresh": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
@@ -7383,6 +7510,21 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/react-use-measure": {
"version": "2.1.7",
"resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz",
"integrity": "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==",
"license": "MIT",
"peerDependencies": {
"react": ">=16.13",
"react-dom": ">=16.13"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
}
}
},
"node_modules/recast": {
"version": "0.23.11",
"resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz",
@@ -8695,6 +8837,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/suspend-react": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.1.3.tgz",
"integrity": "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==",
"license": "MIT",
"peerDependencies": {
"react": ">=17.0"
}
},
"node_modules/svg-tags": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz",
@@ -8790,6 +8941,13 @@
"node": ">=8"
}
},
"node_modules/three": {
"version": "0.176.0",
"resolved": "https://registry.npmjs.org/three/-/three-0.176.0.tgz",
"integrity": "sha512-PWRKYWQo23ojf9oZSlRGH8K09q7nRSWx6LY/HF/UUrMdYgN9i1e2OwJYHoQjwc6HF/4lvvYLC5YC1X8UJL2ZpA==",
"license": "MIT",
"peer": true
},
"node_modules/tiny-invariant": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
@@ -9332,6 +9490,15 @@
}
}
},
"node_modules/use-sync-external-store": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
"integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/util": {
"version": "0.12.5",
"resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
@@ -9935,6 +10102,35 @@
"peerDependencies": {
"zod": "^3.24.1"
}
},
"node_modules/zustand": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.4.tgz",
"integrity": "sha512-39VFTN5InDtMd28ZhjLyuTnlytDr9HfwO512Ai4I8ZABCoyAj4F1+sr7sD1jP/+p7k77Iko0Pb5NhgBFDCX0kQ==",
"license": "MIT",
"engines": {
"node": ">=12.20.0"
},
"peerDependencies": {
"@types/react": ">=18.0.0",
"immer": ">=9.0.6",
"react": ">=18.0.0",
"use-sync-external-store": ">=1.2.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"immer": {
"optional": true
},
"react": {
"optional": true
},
"use-sync-external-store": {
"optional": true
}
}
}
}
}
+2
View File
@@ -22,6 +22,8 @@
"dependencies": {
"@mantine/core": "8.0.0",
"@mantine/hooks": "8.0.0",
"@react-three/fiber": "^9.1.2",
"@tabler/icons-react": "^3.31.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-router-dom": "^7.4.0"
+10
View File
@@ -1,11 +1,21 @@
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import { HomePage } from './pages/Home.page';
import { Consulta_API } from './pages/Consulta_api';
import { Error_404 } from './pages/404'; // Ajusta si está en otra carpeta
const router = createBrowserRouter([
{
path: '/',
element: <HomePage />,
},
{
path: '/Consulta_API',
element: <Consulta_API />,
},
{
path: '*',
element: <Error_404 />,
},
]);
export function Router() {
@@ -0,0 +1,114 @@
.navbar {
height: 100vh; /* ← Ocupa todo el alto de la ventana */
display: flex;
flex-direction: column;
background-color: light-dark(var(--mantine-color-white), var(--mantine-color-dark-6));
width: 300px;
border-right: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
position: sticky; /* ← Opcional, si quieres que se quede "pegado" */
top: 0; /* ← Ancla arriba */
}
.collapsed {
width: 70px; /* Ancho reducido cuando colapsa */
}
.wrapper {
display: flex;
flex: 1;
}
.collapseButton {
margin-top: auto; /* Hace que el botón se empuje hacia abajo */
margin-bottom: 16px; /* ← Agrega separación del borde inferior */
}
.aside {
flex: 0 0 60px;
background-color: var(--mantine-color-body);
display: flex;
flex-direction: column;
align-items: center;
border-right: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-7));
}
.main {
flex: 1;
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6));
}
.mainLink {
width: 44px;
height: 44px;
border-radius: var(--mantine-radius-md);
display: flex;
align-items: center;
justify-content: center;
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-0));
&:hover {
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-5));
}
&[data-active] {
&,
&:hover {
background-color: var(--mantine-color-blue-light);
color: var(--mantine-color-blue-light-color);
}
}
}
.title {
font-family:
Greycliff CF,
var(--mantine-font-family);
margin-bottom: var(--mantine-spacing-xl);
background-color: var(--mantine-color-body);
padding: var(--mantine-spacing-md);
padding-top: 18px;
height: 60px;
border-bottom: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-7));
}
.logo {
width: 100%;
display: flex;
justify-content: center;
height: 60px;
padding-top: var(--mantine-spacing-md);
border-bottom: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-7));
margin-bottom: var(--mantine-spacing-xl);
}
.link {
display: block;
text-decoration: none;
border-top-right-radius: var(--mantine-radius-md);
border-bottom-right-radius: var(--mantine-radius-md);
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-0));
padding: 0 var(--mantine-spacing-md);
font-size: var(--mantine-font-size-sm);
margin-right: var(--mantine-spacing-md);
font-weight: 500;
height: 44px;
line-height: 44px;
&:hover {
background-color: light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-5));
color: light-dark(var(--mantine-color-dark), var(--mantine-color-light));
}
&[data-active] {
&,
&:hover {
border-left-color: var(--mantine-color-blue-filled);
background-color: var(--mantine-color-blue-filled);
color: var(--mantine-color-white);
}
}
}
+155
View File
@@ -0,0 +1,155 @@
import {
IconCalendarStats,
IconDeviceDesktopAnalytics,
IconFingerprint,
IconGauge,
IconHome2,
IconSettings,
IconUser,
IconArrowBarLeft,
IconArrowBarRight,
} from '@tabler/icons-react';
import {
Title,
Tooltip,
UnstyledButton,
ActionIcon,
} from '@mantine/core';
import { Link, useLocation } from 'react-router-dom';
import { useEffect, useState } from 'react';
import classes from './DoubleNavbar.module.css';
const mainLinksdata = [
{ icon: IconHome2, label: 'Home' },
{ icon: IconGauge, label: 'Dashboard' },
{ icon: IconDeviceDesktopAnalytics, label: 'Analytics' },
{ icon: IconCalendarStats, label: 'Releases' },
{ icon: IconUser, label: 'Account' },
{ icon: IconFingerprint, label: 'Security' },
{ icon: IconSettings, label: 'Settings' },
];
const submenuLinks: Record<string, { label: string; to: string }[]> = {
Home: [
{ label: 'Inicio', to: '/' },
{ label: 'Consulta Api', to: '/Consulta_API' },
],
Dashboard: [
{ label: 'Resumen', to: '/dashboard/resumen' },
{ label: 'Estadísticas', to: '/dashboard/estadisticas' },
{ label: 'Usuarios', to: '/dashboard/usuarios' },
],
Analytics: [
{ label: 'Conversiones', to: '/analytics/conversiones' },
{ label: 'Tráfico', to: '/analytics/trafico' },
{ label: 'Tendencias', to: '/analytics/tendencias' },
],
Releases: [
{ label: 'Notas de versión', to: '/releases/notas-de-version' },
{ label: 'Historial', to: '/releases/historial' },
],
Account: [
{ label: 'Perfil', to: '/account/perfil' },
{ label: 'Suscripciones', to: '/account/suscripciones' },
],
Security: [
{ label: 'Contraseña', to: '/security/contraseña' },
{ label: '2FA', to: '/security/2fa' },
],
Settings: [
{ label: 'Preferencias', to: '/settings/preferencias' },
{ label: 'Notificaciones', to: '/settings/notificaciones' },
],
};
export function DoubleNavbar() {
const location = useLocation();
const [collapsed, setCollapsed] = useState(false);
const [manualActiveTab, setManualActiveTab] = useState<string | null>(null);
// Detectar cuál pestaña es activa por la ruta actual
const matchedMain = Object.entries(submenuLinks).find(([mainKey, items]) =>
items.some((item) => location.pathname.startsWith(item.to))
);
const routeBasedActive = matchedMain?.[0] ?? 'Home';
const active = manualActiveTab ?? routeBasedActive;
const activeLink =
submenuLinks[active]?.find((item) => location.pathname === item.to)?.label ?? '';
const mainLinks = mainLinksdata.map((link) => (
<Tooltip
label={link.label}
position="right"
withArrow
transitionProps={{ duration: 0 }}
key={link.label}
>
<UnstyledButton
onClick={() => setManualActiveTab(link.label)}
className={classes.mainLink}
data-active={link.label === active || undefined}
>
<link.icon size={22} stroke={1.5} />
</UnstyledButton>
</Tooltip>
));
const links = (submenuLinks[active] || []).map((item) => (
<Link
className={classes.link}
data-active={activeLink === item.label || undefined}
to={item.to}
key={item.label}
style={{ display: collapsed ? 'none' : 'block' }}
>
{item.label}
</Link>
));
// Resetea pestaña seleccionada si la ruta cambia (opcional)
useEffect(() => {
setManualActiveTab(null);
}, [location.pathname]);
return (
<nav className={`${classes.navbar} ${collapsed ? classes.collapsed : ''}`}>
<div className={classes.wrapper}>
<div className={classes.aside}>
{/* Sección superior: logo + mainLinks */}
<div className={classes.topSection}>
<div className={classes.logo}>
<img
src="/src/favicon.svg"
alt="Logo"
style={{ width: 30, height: 30 }}
/>
</div>
{mainLinks}
</div>
{/* Botón de colapsar separado abajo */}
<ActionIcon
variant="subtle"
onClick={() => setCollapsed((c) => !c)}
className={classes.collapseButton}
size="lg"
>
{collapsed ? <IconArrowBarRight size={20} /> : <IconArrowBarLeft size={20} />}
</ActionIcon>
</div>
<div className={classes.main}>
{!collapsed && (
<Title order={4} className={classes.title}>
{active}
</Title>
)}
{links}
</div>
</div>
</nav>
);
}
+140
View File
@@ -0,0 +1,140 @@
import { Card, Title, Box, useMantineTheme } from '@mantine/core';
import { Canvas, extend, useFrame, useThree } from '@react-three/fiber';
import { useRef, useMemo } from 'react';
import * as THREE from 'three';
// 🎨 Utilidad para convertir hex a RGB [01]
function hexToRGBArray(hex: string): [number, number, number] {
const bigint = parseInt(hex.replace('#', ''), 16);
return [
((bigint >> 16) & 255) / 255,
((bigint >> 8) & 255) / 255,
(bigint & 255) / 255,
];
}
// ✨ Shader personalizado estilo holográfico, con color dinámico
class HoloShaderMaterial extends THREE.ShaderMaterial {
constructor(color: [number, number, number]) {
super({
uniforms: {
u_time: { value: 0 },
u_resolution: { value: new THREE.Vector2() },
u_color: { value: new THREE.Vector3(...color) },
},
vertexShader: `
void main() {
gl_Position = vec4(position, 1.0);
}
`,
fragmentShader: `
precision mediump float;
uniform float u_time;
uniform vec2 u_resolution;
uniform vec3 u_color;
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution;
vec2 pos = uv * 10.0;
pos.x += u_time * 0.3;
pos.y += sin(u_time * 0.2) * 2.0;
float color = sin(pos.x + sin(pos.y + sin(pos.x))) * 0.5 + 0.5;
vec3 c = vec3(
u_color.r + 0.2 * sin(u_time + pos.x),
u_color.g + 0.2 * cos(u_time + pos.y),
u_color.b + 0.2 * sin(pos.x + pos.y + u_time)
);
gl_FragColor = vec4(c * color, 1.0);
}
`,
});
}
}
extend({ HoloShaderMaterial });
// 🎥 Plano con el shader
function HoloPlane({ color }: { color: [number, number, number] }) {
const mat = useRef<any>();
const { size } = useThree();
useFrame(({ clock }) => {
if (mat.current) {
mat.current.uniforms.u_time.value = clock.getElapsedTime();
mat.current.uniforms.u_resolution.value.set(size.width, size.height);
}
});
const material = useMemo(() => new HoloShaderMaterial(color), [color]);
return (
<mesh>
<planeGeometry args={[2, 2]} />
<primitive object={material} ref={mat} attach="material" />
</mesh>
);
}
// 🎨 Fondo que ocupa todo el contenedor
function HolographicBackground({ color }: { color: [number, number, number] }) {
return (
<Box
style={{
position: 'absolute',
inset: 0,
zIndex: 0,
pointerEvents: 'none',
}}
>
<Canvas orthographic camera={{ zoom: 1, position: [0, 0, 1] }}>
<HoloPlane color={color} />
</Canvas>
</Box>
);
}
// 🧩 Componente final con fondo shader y texto 404
export function MantineCardWithShader() {
const theme = useMantineTheme();
const hex = theme.colors[theme.primaryColor][6];
const rgb = hexToRGBArray(hex);
return (
<Card
withBorder
radius="lg"
shadow="xl"
style={{
position: 'relative',
overflow: 'hidden',
minHeight: 300,
minWidth: 400,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<HolographicBackground color={rgb} />
<Box style={{ position: 'relative', zIndex: 1, textAlign: 'center' }}>
<Title
order={1}
style={{
fontSize: '15rem',
fontWeight: 900,
backgroundImage: 'linear-gradient(to bottom, rgba(255,255,255,1), rgba(255,255,255,0))',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
textAlign: 'center',
lineHeight: 1,
}}
>
404
</Title>
</Box>
</Card>
);
}
+52
View File
@@ -0,0 +1,52 @@
import { Select, Group } from '@mantine/core';
import { IconCheck } from '@tabler/icons-react';
interface MetodoSelectProps {
metodo: string;
setMetodo: (value: string) => void;
}
const metodoData = [
{ value: 'GET', label: 'GET' },
{ value: 'POST', label: 'POST' },
{ value: 'PUT', label: 'PUT' },
{ value: 'DELETE', label: 'DELETE' },
];
const colorMap: Record<string, string> = {
GET: '#10b981', // verde
POST: '#3b82f6', // azul
PUT: '#f59e0b', // naranja
DELETE: '#ef4444', // rojo
};
const backgroundMap: Record<string, string> = {
GET: '#d1fae5',
POST: '#e0f2fe',
PUT: '#fef3c7',
DELETE: '#fee2e2',
};
export function MetodoSelect({ metodo, setMetodo }: MetodoSelectProps) {
return (
<Select
label="Método"
value={metodo}
onChange={(value) => setMetodo(value!)}
data={metodoData}
renderOption={({ option, checked }) => (
<Group style={{ color: colorMap[option.value], fontWeight: 600 }}>
{option.label}
{checked && <IconCheck size={14} />}
</Group>
)}
styles={{
input: {
backgroundColor: backgroundMap[metodo],
color: '#111',
fontWeight: 600,
},
}}
/>
);
}
+5 -10
View File
@@ -5,18 +5,13 @@ export function Welcome() {
return (
<>
<Title className={classes.title} ta="center" mt={100}>
Welcome to{' '}
<Text inherit variant="gradient" component="span" gradient={{ from: 'pink', to: 'yellow' }}>
Mantine
Hola! {' '}
<Text inherit variant="gradient" component="span" gradient={{ from: 'blue', to: 'green' }} style={{ letterSpacing: '1px' }}>
Egutierrez
</Text>
</Title>
<Text c="dimmed" ta="center" size="lg" maw={580} mx="auto" mt="xl">
This starter Vite project includes a minimal setup, if you want to learn more on Mantine +
Vite integration follow{' '}
<Anchor href="https://mantine.dev/guides/vite/" size="lg">
this guide
</Anchor>
. To get started edit pages/Home.page.tsx file.
<Text c="dimmed" ta="left" size="lg" maw={580} mx="auto" mt="xl">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Suscipit modi officia porro natus ullam vero mollitia provident, fuga quidem! Perspiciatis explicabo, vel non illum libero suscipit esse animi a voluptatem!
</Text>
</>
);
+16
View File
@@ -0,0 +1,16 @@
// src/components/MiBoton.tsx
import { Button } from '@mantine/core';
import { MouseEventHandler } from 'react';
type MiBotonProps = {
onClick: MouseEventHandler<HTMLButtonElement>;
label?: string;
color?: string;
};
function MiBoton({ onClick, label = 'Hola que tal!', color = 'teal' }: MiBotonProps) {
return <Button color={color} onClick={onClick}>{label}</Button>;
}
export default MiBoton;
+47
View File
@@ -0,0 +1,47 @@
import { Box, Title, Text, Button, Group, Stack, Image, Center } from '@mantine/core';
import { IconArrowLeft } from '@tabler/icons-react';
import { Link } from 'react-router-dom';
import { DoubleNavbar } from '../components/DoubleNavbar'; // Ajusta ruta si es necesario
import { MantineCardWithShader } from '../components/HoloShader'; // Ajusta ruta si es necesario
export function Error_404() {
return (
<Box style={{ display: 'flex', height: '100vh' }}>
<DoubleNavbar />
<Box style={{ flex: 1, display: 'flex', justifyContent: 'center', alignItems: 'center', padding: '2rem' }}>
<Stack align="center" maw={500} mx="auto">
<MantineCardWithShader />
<Title order={1}>
Página no encontrada
</Title>
<Text size="lg">
Parece que la página que estás buscando no existe o fue removida. Pero no te preocupes, puedes volver al inicio fácilmente.
</Text>
<Group mt="md">
<Button
component={Link}
to="/"
size="md"
variant="gradient"
gradient={{ from: 'blue', to: 'cyan' }}
leftSection={<IconArrowLeft size={18} />}
>
Volver al inicio
</Button>
</Group>
</Stack>
</Box>
</Box>
);
}
+20
View File
@@ -0,0 +1,20 @@
import { ColorSchemeToggle } from '../components/ColorSchemeToggle/ColorSchemeToggle';
import { Welcome } from '../components/Welcome/Welcome';
import MiBoton from '../components/botoncito';
import { Center, Box } from '@mantine/core';
import { DoubleNavbar } from '../components/DoubleNavbar';
export function Prueba_1() {
return (
<Box style={{ display: 'flex', height: '100vh' }}>
<DoubleNavbar /> {/* Sidebar fijo a la izquierda */}
{/* Contenido principal */}
</Box>
);
}
+139
View File
@@ -0,0 +1,139 @@
import { useState } from 'react';
import {
TextInput,
Textarea,
Button,
Box,
Center,
Stack,
Badge,
Group,
} from '@mantine/core';
import { DoubleNavbar } from '../components/DoubleNavbar';
import { MetodoSelect } from '../components/MetodoSelect'; // 👈 Importación del nuevo componente
import { useEffect } from 'react';
export function Consulta_API() {
const [direccion, setDireccion] = useState('http://localhost:8000/api/saludo');
const [metodo, setMetodo] = useState('GET');
const [contenido, setContenido] = useState('');
const [respuesta, setRespuesta] = useState('');
const [codigoRespuesta, setCodigoRespuesta] = useState<number | null>(null);
const colorCodigo = (status: number): string => {
if (status >= 200 && status < 300) return 'green';
if (status >= 300 && status < 400) return 'yellow';
if (status >= 400 && status < 500) return 'orange';
return 'red';
};
const llamarAPI = async () => {
try {
const options: RequestInit = {
method: metodo,
mode: 'cors',
headers: {},
};
if (metodo !== 'GET') {
options.headers = {
'Content-Type': 'application/json',
};
try {
JSON.parse(contenido);
options.body = contenido;
} catch (err) {
setRespuesta('Error: El contenido no es un JSON válido');
setCodigoRespuesta(null);
return;
}
}
const res = await fetch(direccion, options);
setCodigoRespuesta(res.status);
const contentType = res.headers.get('content-type');
if (contentType?.includes('application/json')) {
const data = await res.json();
const formatted = JSON.stringify(data, null, 2);
setRespuesta(formatted);
} else {
const text = await res.text();
setRespuesta(text);
}
} catch (error: any) {
console.error('Error en la API:', error);
setRespuesta(`Error: ${error.message || error}`);
setCodigoRespuesta(null);
}
};
useEffect(() => {
const handler = (e: KeyboardEvent) => {
if (e.ctrlKey && e.key === 'Enter') {
e.preventDefault();
llamarAPI();
}
};
window.addEventListener('keydown', handler);
return () => window.removeEventListener('keydown', handler);
}, [metodo, direccion, contenido]);
return (
<Box style={{ display: 'flex', height: '100vh' }}>
<DoubleNavbar />
<Box style={{ flex: 1, padding: 40 }}>
<Center style={{ height: '100%' }}>
<Stack style={{ width: 600 }}>
<TextInput
label="Dirección"
placeholder="http://localhost:8000/api/..."
value={direccion}
onChange={(e) => setDireccion(e.currentTarget.value)}
/>
<MetodoSelect metodo={metodo} setMetodo={setMetodo} />
<Textarea
label="Contenido (JSON)"
placeholder='{"contenido": "Hola"}'
value={contenido}
onChange={(e) => setContenido(e.currentTarget.value)}
autosize
minRows={3}
/>
<Button onClick={llamarAPI} color="blue">
Enviar solicitud
</Button>
{codigoRespuesta !== null && (
<Group>
<Badge color={colorCodigo(codigoRespuesta)} size="lg">
Código: {codigoRespuesta}
</Badge>
</Group>
)}
<Textarea
label="Respuesta de la API"
value={respuesta}
readOnly
autosize
minRows={6}
/>
</Stack>
</Center>
</Box>
</Box>
);
}
+22 -5
View File
@@ -1,11 +1,28 @@
import { ColorSchemeToggle } from '../components/ColorSchemeToggle/ColorSchemeToggle';
import { Welcome } from '../components/Welcome/Welcome';
import MiBoton from '../components/botoncito';
import { Center, Box } from '@mantine/core';
import { DoubleNavbar } from '../components/DoubleNavbar';
import { MantineCardWithShader } from '../components/HoloShader';
export function HomePage() {
return (
<>
<Welcome />
<ColorSchemeToggle />
</>
<Box style={{ display: 'flex', height: '100vh' }}>
<DoubleNavbar /> {/* Sidebar fijo a la izquierda */}
{/* Contenido principal */}
<Box style={{ flex: 1, padding: '24px' }}>
<Welcome />
<ColorSchemeToggle />
<Center mt="md">
<MiBoton />
</Center>
</Box>
</Box>
);
}
}
+89 -5
View File
@@ -149,7 +149,7 @@
dependencies:
"@babel/helper-plugin-utils" "^7.27.1"
"@babel/runtime@^7.12.5", "@babel/runtime@^7.20.13":
"@babel/runtime@^7.12.5", "@babel/runtime@^7.17.8", "@babel/runtime@^7.20.13":
version "7.27.1"
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz"
integrity sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==
@@ -492,6 +492,24 @@
resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz"
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
"@react-three/fiber@^9.1.2":
version "9.1.2"
resolved "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.1.2.tgz"
integrity sha512-k8FR9yVHV9kIF3iuOD0ds5hVymXYXfgdKklqziBVod9ZEJ8uk05Zjw29J/omU3IKeUfLNAIHfxneN3TUYM4I2w==
dependencies:
"@babel/runtime" "^7.17.8"
"@types/react-reconciler" "^0.28.9"
"@types/webxr" "*"
base64-js "^1.5.1"
buffer "^6.0.3"
its-fine "^2.0.0"
react-reconciler "^0.31.0"
react-use-measure "^2.1.7"
scheduler "^0.25.0"
suspend-react "^0.1.3"
use-sync-external-store "^1.4.0"
zustand "^5.0.3"
"@rollup/pluginutils@^5.0.2":
version "5.1.4"
resolved "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz"
@@ -606,6 +624,18 @@
resolved "https://registry.npmjs.org/@storybook/theming/-/theming-8.6.12.tgz"
integrity sha512-6VjZg8HJ2Op7+KV7ihJpYrDnFtd9D1jrQnUS8LckcpuBXrIEbaut5+34ObY8ssQnSqkk2GwIZBBBQYQBCVvkOw==
"@tabler/icons-react@^3.31.0":
version "3.31.0"
resolved "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-3.31.0.tgz"
integrity sha512-2rrCM5y/VnaVKnORpDdAua9SEGuJKVqPtWxeQ/vUVsgaUx30LDgBZph7/lterXxDY1IKR6NO//HDhWiifXTi3w==
dependencies:
"@tabler/icons" "3.31.0"
"@tabler/icons@3.31.0":
version "3.31.0"
resolved "https://registry.npmjs.org/@tabler/icons/-/icons-3.31.0.tgz"
integrity sha512-dblAdeKY3+GA1U+Q9eziZ0ooVlZMHsE8dqP0RkwvRtEsAULoKOYaCUOcJ4oW1DjWegdxk++UAt2SlQVnmeHv+g==
"@testing-library/dom@^10.0.0", "@testing-library/dom@^10.4.0", "@testing-library/dom@>=7.21.4":
version "10.4.0"
resolved "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz"
@@ -710,7 +740,12 @@
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.3.tgz"
integrity sha512-rJXC08OG0h3W6wDMFxQrZF00Kq6qQvw0djHRdzl3U5DnIERz0MRce3WVc7IS6JYBwtaP/DwYtRRjVlvivNveKg==
"@types/react@*", "@types/react@^18.0.0 || ^19.0.0", "@types/react@^19.0.0", "@types/react@^19.0.12":
"@types/react-reconciler@^0.28.9":
version "0.28.9"
resolved "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.9.tgz"
integrity sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==
"@types/react@*", "@types/react@^18.0.0 || ^19.0.0", "@types/react@^19.0.0", "@types/react@^19.0.12", "@types/react@>=18.0.0":
version "19.1.2"
resolved "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz"
integrity sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==
@@ -722,6 +757,11 @@
resolved "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.6.tgz"
integrity sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==
"@types/webxr@*":
version "0.5.22"
resolved "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.22.tgz"
integrity sha512-Vr6Stjv5jPRqH690f5I5GLjVk8GSsoQSYJ2FVd/3jJF7KaqfwPi3ehfBS96mlQ2kPCwZaX6U0rG2+NGHBKkA/A==
"@typescript-eslint/eslint-plugin@8.32.0":
version "8.32.0"
resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.32.0.tgz"
@@ -1095,7 +1135,7 @@ balanced-match@^2.0.0:
resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz"
integrity sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==
base64-js@^1.3.1:
base64-js@^1.3.1, base64-js@^1.5.1:
version "1.5.1"
resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
@@ -2668,6 +2708,13 @@ iterator.prototype@^1.1.4:
has-symbols "^1.1.0"
set-function-name "^2.0.2"
its-fine@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/its-fine/-/its-fine-2.0.0.tgz"
integrity sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng==
dependencies:
"@types/react-reconciler" "^0.28.9"
jackspeak@^3.1.2:
version "3.4.3"
resolved "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz"
@@ -3418,7 +3465,7 @@ react-docgen@^7.0.0:
resolve "^1.22.1"
strip-indent "^4.0.0"
"react-dom@^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom@^18.0.0 || ^19.0.0", "react-dom@^18.x || ^19.x", react-dom@^19.1.0, react-dom@>=16.8.0, react-dom@>=18:
"react-dom@^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom@^18.0.0 || ^19.0.0", "react-dom@^18.x || ^19.x", react-dom@^19.0.0, react-dom@^19.1.0, react-dom@>=16.13, react-dom@>=16.8.0, react-dom@>=18:
version "19.1.0"
resolved "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz"
integrity sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==
@@ -3440,6 +3487,13 @@ react-number-format@^5.4.3:
resolved "https://registry.npmjs.org/react-number-format/-/react-number-format-5.4.4.tgz"
integrity sha512-wOmoNZoOpvMminhifQYiYSTCLUDOiUbBunrMrMjA+dV52sY+vck1S4UhR6PkgnoCquvvMSeJjErXZ4qSaWCliA==
react-reconciler@^0.31.0:
version "0.31.0"
resolved "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.31.0.tgz"
integrity sha512-7Ob7Z+URmesIsIVRjnLoDGwBEG/tVitidU0nMsqX/eeJaLY89RISO/10ERe0MqmzuKUUB1rmY+h1itMbUHg9BQ==
dependencies:
scheduler "^0.25.0"
react-refresh@^0.17.0:
version "0.17.0"
resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz"
@@ -3497,7 +3551,12 @@ react-textarea-autosize@8.5.9:
use-composed-ref "^1.3.0"
use-latest "^1.2.1"
"react@^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react@^18.0.0 || ^19.0.0", "react@^18.x || ^19.x", react@^19.1.0, react@>=16.8.0, react@>=18:
react-use-measure@^2.1.7:
version "2.1.7"
resolved "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz"
integrity sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==
"react@^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react@^18.0.0 || ^19.0.0", "react@^18.x || ^19.x", react@^19.0.0, react@^19.1.0, "react@>= 16", react@>=16.13, react@>=16.8.0, react@>=17.0, react@>=18, react@>=18.0.0:
version "19.1.0"
resolved "https://registry.npmjs.org/react/-/react-19.1.0.tgz"
integrity sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==
@@ -3682,6 +3741,11 @@ saxes@^6.0.0:
dependencies:
xmlchars "^2.2.0"
scheduler@^0.25.0:
version "0.25.0"
resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz"
integrity sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==
scheduler@^0.26.0:
version "0.26.0"
resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz"
@@ -4148,6 +4212,11 @@ supports-preserve-symlinks-flag@^1.0.0:
resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
suspend-react@^0.1.3:
version "0.1.3"
resolved "https://registry.npmjs.org/suspend-react/-/suspend-react-0.1.3.tgz"
integrity sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==
svg-tags@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz"
@@ -4174,6 +4243,11 @@ table@^6.9.0:
string-width "^4.2.3"
strip-ansi "^6.0.1"
three@>=0.156:
version "0.176.0"
resolved "https://registry.npmjs.org/three/-/three-0.176.0.tgz"
integrity sha512-PWRKYWQo23ojf9oZSlRGH8K09q7nRSWx6LY/HF/UUrMdYgN9i1e2OwJYHoQjwc6HF/4lvvYLC5YC1X8UJL2ZpA==
tiny-invariant@^1.3.3:
version "1.3.3"
resolved "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz"
@@ -4439,6 +4513,11 @@ use-sidecar@^1.1.3:
detect-node-es "^1.1.0"
tslib "^2.0.0"
use-sync-external-store@^1.4.0, use-sync-external-store@>=1.2.0:
version "1.5.0"
resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz"
integrity sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==
util-deprecate@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
@@ -4703,3 +4782,8 @@ zod@^3.23.8, zod@^3.24.1, zod@^3.24.2:
version "3.24.4"
resolved "https://registry.npmjs.org/zod/-/zod-3.24.4.tgz"
integrity sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==
zustand@^5.0.3:
version "5.0.4"
resolved "https://registry.npmjs.org/zustand/-/zustand-5.0.4.tgz"
integrity sha512-39VFTN5InDtMd28ZhjLyuTnlytDr9HfwO512Ai4I8ZABCoyAj4F1+sr7sD1jP/+p7k77Iko0Pb5NhgBFDCX0kQ==