1 Commits
main ... rls

Author SHA1 Message Date
cf85e609a5 mock index and testing rsl 2026-03-28 16:49:01 +01:00
11 changed files with 864 additions and 341 deletions

View File

@@ -0,0 +1,29 @@
import { createServerFn } from "@tanstack/react-start"
import z from "zod"
import { getSupabaseServerClient } from "@/integrations/supabase/supabase"
const getAllInstruments = createServerFn().handler(async () => {
const { data } = await getSupabaseServerClient().from("instruments").select()
return data
})
const insertAInstrument = createServerFn()
.inputValidator(z.object({ name: z.string() }))
.handler(async ({ data }) => {
const { data: resp } = await getSupabaseServerClient()
.from("instruments")
.insert(data)
return resp
})
const deleteAInstrument = createServerFn()
.inputValidator(z.object({ id: z.number() }))
.handler(async ({ data }) => {
const { data: resp } = await getSupabaseServerClient()
.from("instruments")
.delete()
.eq("id", data.id)
return resp
})
export { getAllInstruments, insertAInstrument, deleteAInstrument }

View File

@@ -4,7 +4,7 @@ import { getSupabaseServerClient } from "@/integrations/supabase/supabase"
import { import {
loginFormSchema, loginFormSchema,
signupFormSchema, signupFormSchema,
userListParamsSchema, userListParamsSchema
} from "../validation/user" } from "../validation/user"
const login = createServerFn({ method: "POST" }) const login = createServerFn({ method: "POST" })
@@ -14,18 +14,18 @@ const login = createServerFn({ method: "POST" })
const login = await supabase.auth.signInWithPassword({ const login = await supabase.auth.signInWithPassword({
email: data.email, email: data.email,
password: data.password, password: data.password
}) })
if (login.error) { if (login.error) {
return { return {
error: true, error: true,
message: login.error.message, message: login.error.message
} }
} }
return { return {
error: false, error: false
} }
}) })
@@ -36,14 +36,14 @@ const logout = createServerFn().handler(async () => {
if (error) { if (error) {
return { return {
error: true, error: true,
message: error.message, message: error.message
} }
} }
throw redirect({ throw redirect({
to: "/", to: "/",
viewTransition: true, viewTransition: true,
replace: true, replace: true
}) })
}) })
@@ -58,18 +58,19 @@ const signup = createServerFn({ method: "POST" })
data: { data: {
name: data.name, name: data.name,
location: data.location, location: data.location,
}, role: data.role
}, }
}
}) })
if (error) { if (error) {
return { return {
error: true, error: true,
message: error.message, message: error.message
} }
} }
throw redirect({ throw redirect({
href: data.redirectUrl || "/", href: data.redirectUrl || "/"
}) })
}) })
@@ -79,7 +80,7 @@ const userData = createServerFn().handler(async () => {
if (error || !data.user) { if (error || !data.user) {
return { return {
error: true, error: true,
message: error?.message ?? "Unknown error", message: error?.message ?? "Unknown error"
} }
} }
return { return {
@@ -87,9 +88,9 @@ const userData = createServerFn().handler(async () => {
id: data.user.id, id: data.user.id,
email: data.user.email, email: data.user.email,
name: data.user.user_metadata.name || "", name: data.user.user_metadata.name || "",
location: data.user.user_metadata.location || "", location: data.user.user_metadata.location || ""
}, },
error: false, error: false
} }
}) })
@@ -102,12 +103,12 @@ const resendConfirmationEmail = createServerFn({ method: "POST" })
if (error) { if (error) {
return { return {
error: true, error: true,
message: error.message, message: error.message
} }
} }
return { return {
error: false, error: false
} }
}) })
@@ -117,18 +118,18 @@ const userList = createServerFn()
const supabase = getSupabaseServerClient() const supabase = getSupabaseServerClient()
const users = await supabase.auth.admin.listUsers({ const users = await supabase.auth.admin.listUsers({
page: data.page, page: data.page,
perPage: data.limit, perPage: data.limit
}) })
if (users.error) { if (users.error) {
return { return {
error: true, error: true,
message: users.error.message, message: users.error.message
} }
} }
return { return {
users: users.data, users: users.data,
error: false, error: false
} }
}) })
@@ -138,5 +139,5 @@ export const user = {
signup, signup,
userData, userData,
resendConfirmationEmail, resendConfirmationEmail,
userList, userList
} }

View File

@@ -4,3 +4,24 @@ import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)) return twMerge(clsx(inputs))
} }
export async function searchLocation(query: string) {
const response = await fetch(
`https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(query)}&limit=1`
)
const data = await response.json()
if (data.length > 0) {
const place = data[0]
return {
coords: `(${place.lat}, ${place.lon})`, // Formato para el tipo POINT de Postgres
details: place,
city:
place.address?.city ||
place.address?.town ||
place.display_name.split(",")[0],
country: place.address?.country
}
}
return null
}

View File

@@ -10,7 +10,8 @@ export const signupFormSchema = z.object({
password: z.string().min(6, "La contraseña debe tener al menos 6 caracteres"), password: z.string().min(6, "La contraseña debe tener al menos 6 caracteres"),
name: z.string().min(1, "El nombre es obligatorio"), name: z.string().min(1, "El nombre es obligatorio"),
location: z.string().min(1, "La ubicación es obligatoria"), location: z.string().min(1, "La ubicación es obligatoria"),
redirectUrl: z.string().optional() redirectUrl: z.string().optional(),
role: z.enum(["pilot", "employer"])
}) })
// Schema extendido para el formulario cliente (incluye confirmación de contraseña) // Schema extendido para el formulario cliente (incluye confirmación de contraseña)

View File

@@ -51,7 +51,7 @@ export const Route = createRootRouteWithContext<MyRouterContext>()({
function RootDocument({ children }: { children: React.ReactNode }) { function RootDocument({ children }: { children: React.ReactNode }) {
return ( return (
<html lang={getLocale()}> <html lang={getLocale()} className="dark">
<head> <head>
<HeadContent /> <HeadContent />
</head> </head>

View File

@@ -1,4 +1,13 @@
import { Card } from "@heroui/react" import {
Button,
Card,
CardContent,
CardHeader,
Chip,
ScrollShadow,
Spinner
} from "@heroui/react"
import { useMutation, useQuery } from "@tanstack/react-query"
import { createFileRoute } from "@tanstack/react-router" import { createFileRoute } from "@tanstack/react-router"
import { Drone } from "lucide-react" import { Drone } from "lucide-react"
import { useState } from "react" import { useState } from "react"
@@ -10,6 +19,11 @@ import {
MarkerPopup, MarkerPopup,
MarkerTooltip MarkerTooltip
} from "@/components/maps/map" } from "@/components/maps/map"
import {
deleteAInstrument,
getAllInstruments,
insertAInstrument
} from "@/lib/server/instruments"
export const Route = createFileRoute("/_auth/dashboard")({ export const Route = createFileRoute("/_auth/dashboard")({
component: RouteComponent component: RouteComponent
@@ -22,57 +36,134 @@ function RouteComponent() {
name: "Location 1", name: "Location 1",
lat: 40.76, lat: 40.76,
lng: -73.98 lng: -73.98
},
{
id: 2,
name: "Location 2",
lat: 40.77,
lng: -73.99
},
{
id: 3,
name: "Location 3",
lat: 40.5874827,
lng: -1.7925343
} }
] ]
const { data: instruments, isPending } = useQuery({
queryKey: ["instruments"],
queryFn: async () => {
return await getAllInstruments()
}
})
const insertInstrument = useMutation({
mutationKey: ["instruments"],
mutationFn: async (data: { name: string }) => {
return await insertAInstrument({
data: {
name: data.name
}
})
}
})
const deleteInstrument = useMutation({
mutationKey: ["instruments"],
mutationFn: async (data: { id: number }) => {
return await deleteAInstrument({
data: {
id: data.id
}
})
}
})
console.log(instruments)
const [viewport, setViewport] = useState<MapViewport>({ const [viewport, setViewport] = useState<MapViewport>({
center: [40.5874827, -1.7925343], center: [40.5874827, -1.7925343],
zoom: 8, zoom: 8,
bearing: 0, bearing: 0,
pitch: 0 pitch: 0
}) })
return ( return (
<div> <div className="w-full justify-center flex items-center min-h-screen">
<Card className="h-200 p-0 overflow-hidden"> <Card className="max-w-5xl w-full rounded-2xl" variant="secondary">
<MapComponent <CardHeader>
center={[40.5874827, -1.7925343]} <h1 className="text-2xl font-bold">Dashboard</h1>
zoom={10} </CardHeader>
viewport={viewport} <CardContent>
onViewportChange={setViewport} <div className="flex border-2 gap-2 justify-between">
> <div className="block gap-4">
{locations.map((location) => ( <Chip size="lg" variant="primary" color="accent">
<MapMarker Instrumentos
key={location.id} </Chip>
longitude={location.lng} <form
latitude={location.lat} onSubmit={(e) => {
> e.preventDefault()
{/* Prueba para ssl */} const formData = Object.fromEntries(
<MarkerContent> new FormData(e.currentTarget)
<Drone size={24} color="green" className="text-green-200" /> )
</MarkerContent>
<MarkerTooltip>{location.name}</MarkerTooltip> console.log(formData)
<MarkerPopup> insertInstrument.mutate(formData as { name: string })
<div className="space-y-1"> }}
<p className="font-medium text-foreground">{location.name}</p> >
<p className="text-xs text-muted-foreground"> <input name="name" />
{location.lat.toFixed(4)}, {location.lng.toFixed(4)} <button type="submit">Agregar</button>
</p> </form>
</div> <div className="flex items-center">
</MarkerPopup> {isPending && <Spinner size="sm" />}
</MapMarker> </div>
))} <ScrollShadow
</MapComponent> hideScrollBar
className="max-w-xl h-[400px] min-w-[400px]"
>
{instruments?.map((instrument) => (
<div key={instrument.id}>
<p>{instrument.name}</p>
<Button
onPress={() =>
deleteInstrument.mutate({ id: instrument.id })
}
size="sm"
>
Eliminar
</Button>
</div>
))}
</ScrollShadow>
</div>
<div className="">
<MapComponent
center={[40.5874827, -1.7925343]}
zoom={10}
className="min-w-xl w-full border-2"
viewport={viewport}
onViewportChange={setViewport}
>
{locations.map((location) => (
<MapMarker
key={location.id}
longitude={location.lng}
latitude={location.lat}
>
{/* Prueba para ssl */}
<MarkerContent>
<Drone
size={24}
color="green"
className="text-green-200"
/>
</MarkerContent>
<MarkerTooltip>{location.name}</MarkerTooltip>
<MarkerPopup>
<div className="space-y-1">
<p className="font-medium text-foreground">
{location.name}
</p>
<p className="text-xs text-muted-foreground">
{location.lat.toFixed(4)}, {location.lng.toFixed(4)}
</p>
</div>
</MarkerPopup>
</MapMarker>
))}
</MapComponent>
</div>
</div>
</CardContent>
</Card> </Card>
</div> </div>
) )

View File

@@ -5,11 +5,10 @@ import {
Form, Form,
Input, Input,
Label, Label,
Spinner,
TextField TextField
} from "@heroui/react" } from "@heroui/react"
import { createFileRoute } from "@tanstack/react-router" import { createFileRoute } from "@tanstack/react-router"
import { LogIn } from "lucide-react" import { ArrowRight, Drone } from "lucide-react"
import { useLogin } from "@/lib/hooks/useLogin" import { useLogin } from "@/lib/hooks/useLogin"
export const Route = createFileRoute("/access/login")({ export const Route = createFileRoute("/access/login")({
@@ -37,13 +36,11 @@ function RouteComponent() {
type="email" type="email"
name="email" name="email"
variant="secondary" variant="secondary"
className="py-4 text-lg" className=" text-lg"
isRequired isRequired
defaultValue={import.meta.env.VITE_LOGIN_USER} defaultValue={import.meta.env.VITE_LOGIN_USER}
> >
<Label isRequired className="ml-4 text-lg"> <Label isRequired>Correo</Label>
Correo
</Label>
<Input placeholder="Introduce tu correo" /> <Input placeholder="Introduce tu correo" />
<FieldError /> <FieldError />
</TextField> </TextField>
@@ -51,13 +48,11 @@ function RouteComponent() {
type="password" type="password"
name="password" name="password"
variant="secondary" variant="secondary"
className="py-4 text-lg" className=" text-lg"
isRequired isRequired
defaultValue={import.meta.env.VITE_PASSWORD_USER} defaultValue={import.meta.env.VITE_PASSWORD_USER}
> >
<Label isRequired className="ml-4 text-lg"> <Label isRequired>Contraseña</Label>
Contraseña
</Label>
<Input placeholder="Introduce tu contraseña" /> <Input placeholder="Introduce tu contraseña" />
<FieldError /> <FieldError />
</TextField> </TextField>
@@ -70,12 +65,16 @@ function RouteComponent() {
size="lg" size="lg"
isPending={isPending} isPending={isPending}
> >
{isPending ? <Spinner /> : <LogIn size={18} />}
Entrar Entrar
{isPending ? (
<Drone className="animate-spin" />
) : (
<ArrowRight size={18} />
)}
</Button> </Button>
</Form> </Form>
<div className="flex justify-evenly w-full gap-4 mt-2"> <div className="flex justify-evenly w-full gap-4 mt-2">
<Button size="lg" className="w-full" variant="secondary"> <Button size="lg" className="w-full" variant="tertiary">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="16" width="16"
@@ -108,7 +107,7 @@ function RouteComponent() {
</svg> </svg>
Google Google
</Button> </Button>
<Button size="lg" className="w-full" variant="secondary"> <Button size="lg" className="w-full" variant="tertiary">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="16" width="16"

View File

@@ -5,12 +5,14 @@ import {
Form, Form,
Input, Input,
Label, Label,
Spinner, ListBox,
Select,
TextField TextField
} from "@heroui/react" } from "@heroui/react"
import { createFileRoute } from "@tanstack/react-router" import { createFileRoute } from "@tanstack/react-router"
import { UserPlus } from "lucide-react" import { ArrowRight, Drone } from "lucide-react"
import { useSignup } from "@/lib/hooks/useSignup" import { useSignup } from "@/lib/hooks/useSignup"
import { searchLocation } from "@/lib/utils"
export const Route = createFileRoute("/access/register")({ export const Route = createFileRoute("/access/register")({
component: RouteComponent component: RouteComponent
@@ -19,15 +21,18 @@ export const Route = createFileRoute("/access/register")({
function RouteComponent() { function RouteComponent() {
const { signup, isPending } = useSignup() const { signup, isPending } = useSignup()
const handleFormSubmit = (e: React.FormEvent<HTMLFormElement>) => { const handleFormSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault() e.preventDefault()
const formData = new FormData(e.currentTarget) const formData = new FormData(e.currentTarget)
const ubication = await searchLocation(formData.get("location") as string)
console.log(ubication)
signup({ signup({
email: formData.get("email") as string, email: formData.get("email") as string,
password: formData.get("password") as string, password: formData.get("password") as string,
confirmPassword: formData.get("confirmPassword") as string, confirmPassword: formData.get("confirmPassword") as string,
location: formData.get("location") as string, location: formData.get("location") as string,
name: formData.get("name") as string name: formData.get("name") as string,
role: (formData.get("role") as "pilot" | "employer") || "pilot"
}) })
} }
@@ -36,29 +41,45 @@ function RouteComponent() {
<Form onSubmit={handleFormSubmit} className="flex flex-col gap-4"> <Form onSubmit={handleFormSubmit} className="flex flex-col gap-4">
<Fieldset> <Fieldset>
<Fieldset.Group> <Fieldset.Group>
<TextField <div className="grid grid-cols-2 gap-4 items-baseline">
type="text" <TextField
name="name" type="text"
variant="secondary" name="name"
className="py-2 text-lg" variant="secondary"
isRequired className=" text-lg"
> isRequired
<Label isRequired className="ml-4 text-lg"> >
Nombre completo <Label isRequired>Nombre completo</Label>
</Label> <Input placeholder="Tu nombre y apellidos" />
<Input placeholder="Tu nombre y apellidos" /> <FieldError />
<FieldError /> </TextField>
</TextField> <Select
<TextField placeholder="Selecciona una opción"
type="email" isRequired
name="email" defaultValue="pilot"
variant="secondary" variant="secondary"
className="py-2 text-lg" >
isRequired <Label isRequired>Tipo de usuario</Label>
> <Select.Trigger>
<Label isRequired className="ml-4 text-lg"> <Select.Value />
Correo electrónico <Select.Indicator />
</Label> </Select.Trigger>
<Select.Popover>
<ListBox>
<ListBox.Item id="pilot" textValue="Piloto">
Piloto
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="employer" textValue="Reclutador">
Reclutador
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</Select.Popover>
</Select>
</div>
<TextField type="email" name="email" variant="secondary" isRequired>
<Label isRequired>Correo electrónico</Label>
<Input placeholder="tu@correo.com" /> <Input placeholder="tu@correo.com" />
<FieldError /> <FieldError />
</TextField> </TextField>
@@ -66,41 +87,36 @@ function RouteComponent() {
type="text" type="text"
name="location" name="location"
variant="secondary" variant="secondary"
className="py-2 text-lg"
isRequired isRequired
> >
<Label isRequired className="ml-4 text-lg"> <Label isRequired>Ubicación</Label>
Ubicación
</Label>
<Input placeholder="Ciudad, País" /> <Input placeholder="Ciudad, País" />
<FieldError /> <FieldError />
</TextField> </TextField>
<TextField <div className="grid grid-cols-2 gap-4 items-baseline">
type="password" <TextField
name="password" type="password"
variant="secondary" name="password"
className="py-2 text-lg" variant="secondary"
isRequired className=" text-lg"
> isRequired
<Label isRequired className="ml-4 text-lg"> >
Contraseña <Label isRequired>Contraseña</Label>
</Label> <Input placeholder="Mínimo 6 caracteres" />
<Input placeholder="Mínimo 6 caracteres" /> <FieldError />
<FieldError /> </TextField>
</TextField> <TextField
<TextField type="password"
type="password" name="confirmPassword"
name="confirmPassword" variant="secondary"
variant="secondary" className=" text-lg"
className="py-2 text-lg" isRequired
isRequired >
> <Label isRequired>Confirmar contraseña</Label>
<Label isRequired className="ml-4 text-lg"> <Input placeholder="Repite tu contraseña" />
Confirmar contraseña <FieldError />
</Label> </TextField>
<Input placeholder="Repite tu contraseña" /> </div>
<FieldError />
</TextField>
</Fieldset.Group> </Fieldset.Group>
</Fieldset> </Fieldset>
<Button <Button
@@ -109,12 +125,16 @@ function RouteComponent() {
size="lg" size="lg"
isPending={isPending} isPending={isPending}
> >
{isPending ? <Spinner /> : <UserPlus size={18} />}
Crear cuenta Crear cuenta
{isPending ? (
<Drone className="animate-spin" />
) : (
<ArrowRight size={18} />
)}
</Button> </Button>
</Form> </Form>
<div className="flex justify-evenly w-full gap-4 mt-2"> <div className="flex justify-evenly w-full gap-4 mt-2">
<Button size="lg" className="w-full" variant="secondary"> <Button size="lg" className="w-full" variant="tertiary">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="16" width="16"
@@ -147,7 +167,7 @@ function RouteComponent() {
</svg> </svg>
Google Google
</Button> </Button>
<Button size="lg" className="w-full" variant="secondary"> <Button size="lg" className="w-full" variant="tertiary">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="16" width="16"

View File

@@ -4,10 +4,9 @@ import { createFileRoute, Outlet, redirect } from "@tanstack/react-router"
export const Route = createFileRoute("/access")({ export const Route = createFileRoute("/access")({
beforeLoad: ({ location }) => { beforeLoad: ({ location }) => {
if (location.pathname === "/access") { if (location.pathname === "/access") {
redirect({ throw redirect({
to: "/access/login", to: "/access/login",
replace: true, replace: true
throw: true
}) })
} }
}, },
@@ -25,37 +24,91 @@ function RouteComponent() {
} }
return ( return (
<div className="grid-cols-2 grid min-h-screen"> <div className="grid grid-cols-1 md:grid-cols-2 min-h-screen bg-background text-foreground font-sans">
<div className=" p-5 bg-default"> <div className="relative z-10 flex flex-col p-6 md:p-12 justify-between border-r border-border bg-surface/30 backdrop-blur-sm">
<h1 className="text-4xl font-bold text-end "> <header className="flex items-center justify-between">
Find<span className="text-accent">your</span>Pilot <div className="flex items-center gap-2">
</h1> <div className="w-2 h-2 bg-accent animate-pulse" />
<div className="flex items-center justify-center min-h-[90vh]"> <span className="font-mono text-xs tracking-[0.3em] uppercase text-muted">
<Card className="w-full max-w-md bg-white/90 backdrop-blur-2xl border-3 border-accent-soft"> <h1 className="text-lg font-light tracking-tighter">
<Card.Header> FIND<span className="font-bold text-accent">YOUR</span>PILOT
</h1>
</span>
</div>
<div className="font-mono text-[9px] tracking-[0.3em] uppercase text-muted">
<p>
Connection: <span className="text-success">Secured</span>
</p>
<p>
Node: <span className="text-foreground">E-210_MAD</span>
</p>
</div>
</header>
{/* Contenedor Central */}
<div className="flex flex-col items-center justify-center">
<Card className="w-full max-w-md shadow-none border border-border bg-surface/50 ">
<Card.Header className="p-0">
<Tabs className="w-full max-w-md" onSelectionChange={onSelectTab}> <Tabs className="w-full max-w-md" onSelectionChange={onSelectTab}>
<Tabs.ListContainer> <Tabs.ListContainer>
<Tabs.List aria-label="Options"> <Tabs.List aria-label="Options">
<Tabs.Tab id="login" className="text-lg"> <Tabs.Tab id="login">
Acceso Entrar
<Tabs.Indicator className="bg-accent" /> <Tabs.Indicator />
</Tabs.Tab> </Tabs.Tab>
<Tabs.Tab id="register" className="text-lg "> <Tabs.Tab id="register">
<Tabs.Separator /> <Tabs.Separator />
Registro Registrarse
<Tabs.Indicator className="bg-accent" /> <Tabs.Indicator />
</Tabs.Tab> </Tabs.Tab>
</Tabs.List> </Tabs.List>
</Tabs.ListContainer> </Tabs.ListContainer>
</Tabs> </Tabs>
</Card.Header> </Card.Header>
<Card.Content> <Card.Content className="p-4">
<Outlet /> <Outlet />
</Card.Content> </Card.Content>
</Card> </Card>
<div className="mt-8 flex gap-4 overflow-hidden">
<div className="h-[1px] w-12 bg-border self-center" />
<span className="text-[10px] font-mono text-muted uppercase">
Ready for Takeoff
</span>
<div className="h-[1px] w-12 bg-border self-center" />
</div>
</div>
<footer className="grid grid-cols-2 gap-4 border-t border-border pt-6 font-mono text-[9px] text-muted uppercase tracking-wider">
<div className="text-right">
<p>© 2026 / FYP_CORP</p>
</div>
</footer>
</div>
<div className="relative hidden md:block overflow-hidden bg-accent">
<div className="absolute inset-0 bg-[url('https://cdn.pixabay.com/photo/2023/03/22/22/37/mavic-2-7870679_1280.jpg')] bg-cover bg-center opacity-90 " />
<div className="absolute inset-0 bg-gradient-to-b from-transparent to-background/50" />
<div className="absolute inset-0 z-20 flex flex-col justify-between p-10 pointer-events-none">
<div className="absolute top-10 left-10 w-20 h-20 border-l border-t" />
<div className="absolute top-10 right-10 w-20 h-20 border-r border-t" />
<div className="absolute bottom-10 left-10 w-20 h-20 border-l border-b" />
<div className="absolute bottom-10 right-10 w-20 h-20 border-r border-b" />
{/* Telemetría Dinámica */}
<div className="flex justify-between items-start font-mono ">
<div className="bg-black/40 backdrop-blur-md border border-white/10 p-4 rounded-sm invisible">
<div className="text-accent text-xs mb-1">TELEMETRY_STREAM</div>
<div className="text-white text-lg leading-none">40.4168° N</div>
<div className="text-white text-lg leading-none">3.7038° W</div>
</div>
<div className="flex flex-col items-end gap-2 text-[10px] text-white/50">
<div className="bg-accent-soft-hover px-2 py-1 border text-accent">
REC
</div>
<span>4K / 60FPS</span>
</div>
</div>
</div> </div>
</div> </div>
<div className="bg-accent bg-[url('https://cdn.pixabay.com/photo/2023/03/22/22/37/mavic-2-7870679_1280.jpg')] bg-cover"></div>
</div> </div>
) )
} }

View File

@@ -1,132 +1,396 @@
import { Button } from "@heroui/react" import { Avatar, Button, Card, Chip, ScrollShadow } from "@heroui/react"
import { createFileRoute } from "@tanstack/react-router" import { createFileRoute } from "@tanstack/react-router"
import { import {
Route as RouteIcon, ChevronRight,
Server, Cpu,
Database,
Euro,
Globe,
Layers,
LogIn,
Map as MapIcon,
Radio,
Rocket,
Shield, Shield,
Sparkles,
Waves,
Zap Zap
} from "lucide-react" } from "lucide-react"
import {
Map as MapComponent,
MapMarker,
MarkerContent,
MarkerPopup,
MarkerTooltip
} from "@/components/maps/map"
export const Route = createFileRoute("/")({ component: App }) export const Route = createFileRoute("/")({ component: App })
function App() { const WebMockHeader = () => {
const navigate = Route.useNavigate() const locations = [
{
id: 5,
name: "EEUU",
lat: 40.76,
lng: -73.98
},
const features = [
{ {
icon: <Zap className="w-12 h-12 text-cyan-400" />, id: 1,
title: "Powerful Server Functions", name: "Madrid",
description: lat: 40.4168,
"Write server-side code that seamlessly integrates with your client components. Type-safe, secure, and simple." lng: -3.7038
}, },
{ {
icon: <Server className="w-12 h-12 text-cyan-400" />, id: 2,
title: "Flexible Server Side Rendering", name: "Barcelona",
description: lat: 41.3874,
"Full-document SSR, streaming, and progressive enhancement out of the box. Control exactly what renders where." lng: 2.1686
}, },
{ {
icon: <RouteIcon className="w-12 h-12 text-cyan-400" />, id: 3,
title: "API Routes", name: "Sevilla",
description: lat: 37.3891,
"Build type-safe API endpoints alongside your application. No separate backend needed." lng: -5.9845
}, },
{ {
icon: <Shield className="w-12 h-12 text-cyan-400" />, id: 4,
title: "Strongly Typed Everything", name: "Valencia",
description: lat: 39.4699,
"End-to-end type safety from server to client. Catch errors before they reach production." lng: -0.3763
},
{
icon: <Waves className="w-12 h-12 text-cyan-400" />,
title: "Full Streaming Support",
description:
"Stream data from server to client progressively. Perfect for AI applications and real-time updates."
},
{
icon: <Sparkles className="w-12 h-12 text-cyan-400" />,
title: "Next Generation Ready",
description:
"Built from the ground up for modern web applications. Deploy anywhere JavaScript runs."
} }
] ]
return ( return (
<div className="min-h-screen bg-linear-to-b from-slate-900 via-slate-800 to-slate-900"> <div className="space-y-6 animate-in fade-in zoom-in-95 duration-500">
<Button <div className="flex justify-between items-center">
onPress={() => { <div>
navigate({ <h3 className="text-xl font-black text-white tracking-tight">
to: "/login", Panel de Control
viewTransition: true </h3>
}) <p className="text-[10px] text-cyan-500 font-mono hidden">
}} ID: 550e8400-e29b-41d4-a716-446655440000
>
{" "}
Hola
</Button>
<section className="relative py-20 px-6 text-center overflow-hidden">
<div className="absolute inset-0 bg-linear-to-r from-cyan-500/10 via-blue-500/10 to-purple-500/10"></div>
<div className="relative max-w-5xl mx-auto">
<div className="flex items-center justify-center gap-6 mb-6">
<img
src="/tanstack-circle-logo.png"
alt="TanStack Logo"
className="w-24 h-24 md:w-32 md:h-32"
/>
<h1 className="text-6xl md:text-7xl font-black text-white tracking-[-0.08em]">
<span className="text-gray-300">TANSTACK</span>{" "}
<span className="bg-linear-to-r from-cyan-400 to-blue-400 bg-clip-text text-transparent">
START
</span>
</h1>
</div>
<p className="text-2xl md:text-3xl text-gray-300 mb-4 font-light">
The framework for next generation AI applications
</p> </p>
<p className="text-lg text-gray-400 max-w-3xl mx-auto mb-8"> </div>
Full-stack framework powered by TanStack Router for React and Solid. <div className="flex gap-2">
Build modern applications with server functions, streaming, and type <Chip size="sm" className="px-2" variant="soft">
safety. <div className="w-2 h-2 bg-emerald-500 rounded-full animate-pulse mr-2" />{" "}
</p> ACTIVO
<div className="flex flex-col items-center gap-4"> </Chip>
<a </div>
href="https://tanstack.com/start" </div>
target="_blank"
rel="noopener noreferrer" <div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
className="px-8 py-3 bg-cyan-500 hover:bg-cyan-600 text-white font-semibold rounded-lg transition-colors shadow-lg shadow-cyan-500/50" {[
> { label: "OFERTAS", val: "+300", unit: "", color: "text-white" },
Documentation {
</a> label: "PILOTOS",
<p className="text-gray-400 text-sm mt-2"> val: "+400",
Begin your TanStack Start journey by editing{" "} unit: "",
<code className="px-2 py-1 bg-slate-700 rounded text-cyan-400"> color: "text-gray-200/60"
/src/routes/index.tsx },
</code> {
label: "TRAYECTOS",
val: "+400",
unit: "",
color: "text-white"
},
{
label: "PUESTOS",
val: "+300",
unit: "",
color: "text-gray-200/60"
}
].map((s, i) => (
<div key={i} className="glass p-4 rounded-2xl">
<p className="text-[9px] text-slate-500 font-bold mb-1 tracking-widest">
{s.label}
</p>
<p className={`text-xl font-black ${s.color}`}>
{s.val}
<span className="text-xs ml-1 opacity-50">{s.unit}</span>
</p> </p>
</div> </div>
</div> ))}
</section> </div>
<section className="py-16 px-6 max-w-7xl mx-auto"> <div className="relative aspect-video rounded-3xl overflow-hidden group">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> <div className="absolute inset-0 bg-transparent opacity-60">
{features.map((feature, index) => ( <MapComponent
<div zoom={1}
key={index} className="min-w-xl w-full"
className="bg-slate-800/50 backdrop-blur-sm border border-slate-700 rounded-xl p-6 hover:border-cyan-500/50 transition-all duration-300 hover:shadow-lg hover:shadow-cyan-500/10" center={[2.3522, 48.8566]}
> >
<div className="mb-4">{feature.icon}</div> {(locations || [])?.map((location) => (
<h3 className="text-xl font-semibold text-white mb-3"> <MapMarker
{feature.title} key={location.id}
</h3> longitude={location.lng}
<p className="text-gray-400 leading-relaxed"> latitude={location.lat}
{feature.description} >
</p> {/* Prueba para ssl */}
</div> <MarkerContent>
))} {/* <Drone size={24} color="green" className="text-green-200" /> */}
<div className="w-3 h-3 bg-accent animate-pulse rounded-full" />
</MarkerContent>
<MarkerTooltip>{location.name}</MarkerTooltip>
<MarkerPopup>
<div className="space-y-1">
<p className="font-medium text-foreground">
{location.name}
</p>
<p className="text-xs text-muted-foreground">
{location.lat.toFixed(4)}, {location.lng.toFixed(4)}
</p>
</div>
</MarkerPopup>
</MapMarker>
))}
</MapComponent>
</div> </div>
</section> <div className="absolute bottom-6 left-6 z-10">
<Button>
<MapIcon className="size-6" />
¿Quieres ser parte de mapa?
</Button>
</div>
<div className="absolute inset-0 bg-linear-to-t from-brand-dark via-transparent to-transparent"></div>
<div className="absolute top-6 left-6 flex flex-col gap-2">
<div className="glass p-3 rounded-xl"></div>
</div>
</div>
</div>
)
}
function App() {
const navigate = Route.useNavigate()
return (
<div className="min-h-screen bg-brand-dark text-slate-400 ">
{/* NAVBAR */}
<nav className="flex items-center justify-between px-10 py-6 border-b border-white/5 backdrop-blur-xl sticky top-0 z-50">
<div className="flex items-center gap-2">
<div className="w-2 h-2 bg-accent animate-pulse" />
<span className="font-mono text-xs tracking-[0.3em] uppercase text-muted">
<h1 className="text-lg font-light tracking-tighter">
FIND<span className="font-bold text-accent">YOUR</span>PILOT
</h1>
</span>
</div>
<div className="hidden md:flex gap-8 text-[11px] font-black uppercase tracking-[0.2em]"></div>
<Button
onPress={() => {
navigate({ to: "/access/login" })
}}
>
Acceso <LogIn />
</Button>
</nav>
{/* HERO / LANDING */}
<header className="relative pt-20 px-6 text-center lg:flex gap-4 pb-10">
<div className="lg:w-2/5">
<div className="absolute inset-0 z-0 overflow-hidden pointer-events-none">
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-[1000px] h-[600px] bg-cyan-500/10 blur-[120px] rounded-full"></div>
</div>
<div className="relative z-10 max-w-5xl mx-auto">
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/5 border border-white/10 text-[10px] font-black uppercase tracking-[0.3em] mb-8 text-accent">
<Radio size={12} className="animate-pulse" />
<span className="text-accent">v4.0.2</span>
</div>
<h1 className="text-6xl md:text-6xl font-black text-white tracking-tighter mb-8 leading-[0.9]">
ENCUENTRA A <br />
<span className="text-transparent bg-clip-text bg-linear-to-r from-accent-400 via-accent to-stone-400 px-4">
TU PILOTO
</span>
</h1>
<p className="text-lg md:text-lg text-slate-300 max-w-2xl mx-auto mb-12 font-medium">
La plataforma definitiva para la gestión de pilotos de drones.
Conecta con los mejores profesionales y lleva tus proyectos al
siguiente <span className="italic">nivel</span>.
</p>
<div className="flex flex-wrap justify-center gap-4">
<Button size="lg">
Iniciar despliegue <Rocket size={20} />
</Button>
<Button size="lg">
Más información <ChevronRight size={20} />
</Button>
</div>
<div className="relative flex overflow-hidden group mt-20">
<div className="flex animate-marquee whitespace-nowrap gap-16 items-center hover:paused py-2">
{[
{ name: "PostgreSQL", icon: <Database size={32} /> },
{ name: "React", icon: <Layers size={32} /> },
{ name: "Tailwind", icon: <Zap size={32} /> },
{ name: "TanStack", icon: <Cpu size={32} /> },
{ name: "TypeScript", icon: <Shield size={32} /> },
{ name: "Vite", icon: <Globe size={32} /> }
].map((logo, index) => (
<div
key={index}
className="flex items-center gap-4 grayscale opacity-40 hover:grayscale-0 hover:opacity-100 transition-all duration-500 cursor-pointer"
>
<div className="text-white">{logo.icon}</div>
<span className="text-xl font-black italic tracking-tighter text-white uppercase">
{logo.name}
</span>
</div>
))}
</div>
{/* Gradientes laterales para suavizar la entrada/salida (Fading effect) */}
<div className="pointer-events-none absolute inset-y-0 left-0 w-20 bg-linear-to-r from-transparent to-transparent z-10"></div>
<div className="pointer-events-none absolute inset-y-0 right-0 w-20 bg-linear-to-l from-transparent to-transparent z-10"></div>
</div>
</div>
</div>
{/* DASHBOARD DEMO SECTION */}
<section className="w-3/5 px-6 relative items-start flex ">
<div className="max-w-5xl mx-auto w-full">
{/* Cabecera del simulador */}
<div className="flex items-center justify-between mb-4 px-4 hidden">
<div className="flex gap-2">
<div className="w-2 h-2 rounded-full bg-red-500/40" />
<div className="w-2 h-2 rounded-full bg-orange-500/40" />
<div className="w-2 h-2 rounded-full bg-emerald-500/40" />
</div>
<div className="flex items-center gap-4 text-[9px] font-mono tracking-widest text-slate-600">
<span>BUFFER: 0ms</span>
<span className="flex items-center gap-1 text-cyan-500/60">
<Database size={10} /> PG_CONNECTED
</span>
</div>
</div>
<div className="border bg-surface border-white/10 rounded shadow-[0_0_100px_rgba(6,182,212,0.05)] overflow-hidden">
<div className="flex flex-col lg:flex-row min-h-[600px]">
<aside className="w-full lg:max-w-82 border-r border-white/5 bg-black/40 p-8 flex flex-col gap-3">
<Chip size="lg" variant="tertiary">
<h3 className="text-xl font-black text-white tracking-tight">
Ofertas
</h3>
</Chip>
<ScrollShadow className="max-h-[500px]" hideScrollBar>
<div className="flex gap-3 flex-col">
{[
{
id: "J-001",
name: "Inspección de Aerogeneradores",
price: 1200,
location: "Parque Eólico Tarifa",
description:
"Se requiere vuelo de proximidad para detección de microfisuras en palas. Obligatorio sensor térmico.",
employer: "IberEnergía S.A.",
tags: ["Térmico", "STS-01", "Alta Prioridad"],
avatar: "https://i.pravatar.cc/150?u=corp1"
},
{
id: "J-002",
name: "Ortomosaico Agrícola 50Ha",
price: 450,
location: "Valladolid, ES",
description:
"Mapeo multiespectral para análisis de estrés hídrico en viñedos. Entrega en GeoJSON.",
employer: "AgroTech Solutions",
tags: ["Multiespectral", "Mapeo"],
avatar: "https://i.pravatar.cc/150?u=corp2"
},
{
id: "J-003",
name: "Grabación FPVCinematic",
price: 800,
location: "Madrid (Circuito)",
description:
"Seguimiento de vehículos a alta velocidad para spot publicitario. Se requiere dron de 5 pulgadas.",
employer: "RedMedia",
tags: ["FPV", "ProRes"],
avatar: "https://i.pravatar.cc/150?u=corp3"
}
].map((offer) => (
<Card key={offer.id}>
<Card.Header className="flex flex-col gap-2">
<div className="flex gap-2">
<Avatar size="sm">
<Avatar.Image
alt="John Doe"
src={offer.avatar}
/>
<Avatar.Fallback>JD</Avatar.Fallback>
</Avatar>
<h1 className="text-md font-semibold text-start">
{offer.name}
</h1>
</div>
<ScrollShadow
orientation="horizontal"
className="w-[200px]"
hideScrollBar
>
<div className="flex gap-2">
{offer.tags.map((tag) => (
<Chip variant="soft" key={tag}>
{tag}
</Chip>
))}
</div>
</ScrollShadow>
</Card.Header>
<Card.Content className=" text-slate-50">
<div className="text-xs text-start">
{offer.description}
</div>
</Card.Content>
<Card.Footer className="items-center justify-between">
<Chip>
{offer.price} <Euro className="size-4" />
</Chip>
<Button size="sm" variant="secondary">
Detalles
</Button>
</Card.Footer>
</Card>
))}
<div className="flex justify-end pb-10">
<Button>Ver más</Button>
</div>
</div>
</ScrollShadow>
</aside>
{/* Main Content Area */}
<main className="flex-1 p-10 bg-linear-to-br from-transparent to-cyan-950/5">
<WebMockHeader />
</main>
</div>
</div>
</div>
</section>
</header>
{/* FOOTER TÉCNICO */}
<footer className="border-t border-white/5 py-20 px-10">
<div className="max-w-6xl mx-auto grid grid-cols-2 md:grid-cols-4 gap-12">
<div>
<p className="text-white font-black italic mb-4">FYP.OS</p>
<p className="text-xs leading-loose">
Gestiona perfil profesional, tus misiones y tus drones.
</p>
</div>
{["Schema", "Security", "Endpoints"].map((title) => (
<div key={title}>
<p className="text-[10px] font-black text-white uppercase tracking-widest mb-4">
{title}
</p>
<ul className="text-xs space-y-2 opacity-50 font-mono">
<li>{`fetch_${title.toLowerCase()}`}</li>
<li>{`push_logs_v4`}</li>
<li>{`auth_verify`}</li>
</ul>
</div>
))}
</div>
</footer>
</div> </div>
) )
} }

View File

@@ -1,22 +1,60 @@
@import 'tailwindcss'; @import "tailwindcss";
@import "@heroui/styles"; @import "@heroui/styles";
@import 'tw-animate-css'; @import "tw-animate-css";
@custom-variant dark (&:is(.dark *)); @custom-variant dark (&:is(.dark *));
@theme {
/* Animación de balanceo suave (vuelo) */
@keyframes float {
0%,
100% {
transform: translateY(0) rotate(0deg);
}
50% {
transform: translateY(-15px) rotate(1deg);
}
}
/* Animación de zoom de cámara de dron */
@keyframes drone-zoom {
from {
transform: scale(1);
}
to {
transform: scale(1.1);
}
}
@keyframes marquee {
from {
transform: translateX(0);
}
to {
transform: translateX(-50%);
}
}
--animate-marquee: marquee 30s linear infinite;
/* Registro de las animaciones para usarlas como clases */
--animate-float: float 6s ease-in-out infinite;
--animate-drone-zoom: drone-zoom 20s linear infinite alternate;
}
body { body {
@apply m-0; @apply m-0;
font-family: font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu",
'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
code { code {
font-family: font-family:
source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
} }
/* /*
* HeroUI Theme Customization * HeroUI Theme Customization
@@ -24,85 +62,91 @@ code {
* Only includes base variables from variables.css * Only includes base variables from variables.css
* @see https://v3.heroui.com/docs/react/getting-started/theming * @see https://v3.heroui.com/docs/react/getting-started/theming
*/ */
/*
* HeroUI Theme Customization
* Add this to your global.css after importing @heroui/styles
* Only includes base variables from variables.css
* @see https://heroui.com/docs/react/getting-started/theming
*/
:root, :root,
.light, .light,
.default, .default,
[data-theme="light"], [data-theme="light"],
[data-theme="default"] { [data-theme="default"] {
/* Theme Colors (Light Mode) */ /* Theme Colors (Light Mode) */
--accent: oklch(66.78% 0.2232 43.72); --accent: oklch(76.97% 0.2124 148.67);
--accent-foreground: oklch(15% 0.0300 43.72); --accent-foreground: oklch(15% 0.03 148.67);
--background: oklch(97.02% 0.0064 43.72); --background: oklch(97.02% 0.0069 148.67);
--border: oklch(90.00% 0.0064 43.72); --border: oklch(90.00% 0.0069 148.67);
--danger: oklch(65.32% 0.2358 0.53); --danger: oklch(65.32% 0.236 13.12);
--danger-foreground: oklch(99.11% 0 0); --danger-foreground: oklch(99.11% 0 0);
--default: oklch(94.00% 0.0064 43.72); --default: oklch(94.00% 0.0069 148.67);
--default-foreground: oklch(21.03% 0.0059 43.72); --default-foreground: oklch(21.03% 0.0059 148.67);
--field-background: oklch(100.00% 0.0032 43.72); --field-background: oklch(100.00% 0.0034 148.67);
--field-foreground: oklch(21.03% 0.0064 43.72); --field-foreground: oklch(21.03% 0.0069 148.67);
--field-placeholder: oklch(55.17% 0.0128 43.72); --field-placeholder: oklch(55.17% 0.0138 148.67);
--focus: oklch(66.78% 0.2232 43.72); --focus: oklch(76.97% 0.2124 148.67);
--foreground: oklch(21.03% 0.0064 43.72); --foreground: oklch(21.03% 0.0069 148.67);
--muted: oklch(55.17% 0.0128 43.72); --muted: oklch(55.17% 0.0138 148.67);
--overlay: oklch(100.00% 0.0019 43.72); --overlay: oklch(100.00% 0.0021 148.67);
--overlay-foreground: oklch(21.03% 0.0064 43.72); --overlay-foreground: oklch(21.03% 0.0069 148.67);
--scrollbar: oklch(87.10% 0.0064 43.72); --scrollbar: oklch(87.10% 0.0069 148.67);
--segment: oklch(100.00% 0.0064 43.72); --segment: oklch(100.00% 0.0069 148.67);
--segment-foreground: oklch(21.03% 0.0064 43.72); --segment-foreground: oklch(21.03% 0.0069 148.67);
--separator: oklch(92.00% 0.0064 43.72); --separator: oklch(92.00% 0.0069 148.67);
--success: oklch(73.29% 0.1960 125.60); --success: oklch(73.29% 0.1962 138.19);
--success-foreground: oklch(21.03% 0.0059 125.60); --success-foreground: oklch(21.03% 0.0059 138.19);
--surface: oklch(100.00% 0.0032 43.72); --surface: oklch(100.00% 0.0034 148.67);
--surface-foreground: oklch(21.03% 0.0064 43.72); --surface-foreground: oklch(21.03% 0.0069 148.67);
--surface-secondary: oklch(95.24% 0.0051 43.72); --surface-secondary: oklch(95.24% 0.0055 148.67);
--surface-secondary-foreground: oklch(21.03% 0.0064 43.72); --surface-secondary-foreground: oklch(21.03% 0.0069 148.67);
--surface-tertiary: oklch(93.73% 0.0051 43.72); --surface-tertiary: oklch(93.73% 0.0055 148.67);
--surface-tertiary-foreground: oklch(21.03% 0.0064 43.72); --surface-tertiary-foreground: oklch(21.03% 0.0069 148.67);
--warning: oklch(78.19% 0.1605 47.12); --warning: oklch(78.19% 0.1607 59.71);
--warning-foreground: oklch(21.03% 0.0059 47.12); --warning-foreground: oklch(21.03% 0.0059 59.71);
/* Border Radius */ /* Border Radius */
--radius: 0.125rem; --radius: 0.5rem;
--field-radius: 0.125rem; --field-radius: 0.125rem;
/* Font Family */ /* Font Family */
/* Make sure to load Inter font in your app */ /* Make sure to load Hanken Grotesk font in your app */
--font-sans: var(--font-inter); --font-sans: var(--font-hanken-grotesk);
} }
.dark, .dark,
[data-theme="dark"] { [data-theme="dark"] {
color-scheme: dark; color-scheme: dark;
/* Theme Colors (Dark Mode) */ /* Theme Colors (Dark Mode) */
--accent: oklch(66.78% 0.2232 43.72); --accent: oklch(76.97% 0.2124 148.67);
--accent-foreground: oklch(15% 0.0300 43.72); --accent-foreground: oklch(15% 0.03 148.67);
--background: oklch(12.00% 0.0064 43.72); --background: oklch(12.00% 0.0069 148.67);
--border: oklch(28.00% 0.0064 43.72); --border: oklch(28.00% 0.0069 148.67);
--danger: oklch(59.40% 0.1992 359.42); --danger: oklch(59.40% 0.1994 12.01);
--danger-foreground: oklch(99.11% 0 0); --danger-foreground: oklch(99.11% 0 0);
--default: oklch(27.40% 0.0064 43.72); --default: oklch(27.40% 0.0069 148.67);
--default-foreground: oklch(99.11% 0 0); --default-foreground: oklch(99.11% 0 0);
--field-background: oklch(21.03% 0.0128 43.72); --field-background: oklch(21.03% 0.0138 148.67);
--field-foreground: oklch(99.11% 0.0064 43.72); --field-foreground: oklch(99.11% 0.0069 148.67);
--field-placeholder: oklch(70.50% 0.0128 43.72); --field-placeholder: oklch(70.50% 0.0138 148.67);
--focus: oklch(66.78% 0.2232 43.72); --focus: oklch(76.97% 0.2124 148.67);
--foreground: oklch(99.11% 0.0064 43.72); --foreground: oklch(99.11% 0.0069 148.67);
--muted: oklch(70.50% 0.0128 43.72); --muted: oklch(70.50% 0.0138 148.67);
--overlay: oklch(21.03% 0.0128 43.72); --overlay: oklch(21.03% 0.0138 148.67);
--overlay-foreground: oklch(99.11% 0.0064 43.72); --overlay-foreground: oklch(99.11% 0.0069 148.67);
--scrollbar: oklch(70.50% 0.0064 43.72); --scrollbar: oklch(70.50% 0.0069 148.67);
--segment: oklch(39.64% 0.0064 43.72); --segment: oklch(39.64% 0.0069 148.67);
--segment-foreground: oklch(99.11% 0.0064 43.72); --segment-foreground: oklch(99.11% 0.0069 148.67);
--separator: oklch(25.00% 0.0064 43.72); --separator: oklch(25.00% 0.0069 148.67);
--success: oklch(73.29% 0.1960 125.60); --success: oklch(73.29% 0.1962 138.19);
--success-foreground: oklch(21.03% 0.0059 125.60); --success-foreground: oklch(21.03% 0.0059 138.19);
--surface: oklch(21.03% 0.0128 43.72); --surface: oklch(21.03% 0.0138 148.67);
--surface-foreground: oklch(99.11% 0.0064 43.72); --surface-foreground: oklch(99.11% 0.0069 148.67);
--surface-secondary: oklch(25.70% 0.0096 43.72); --surface-secondary: oklch(25.70% 0.0103 148.67);
--surface-secondary-foreground: oklch(99.11% 0.0064 43.72); --surface-secondary-foreground: oklch(99.11% 0.0069 148.67);
--surface-tertiary: oklch(27.21% 0.0096 43.72); --surface-tertiary: oklch(27.21% 0.0103 148.67);
--surface-tertiary-foreground: oklch(99.11% 0.0064 43.72); --surface-tertiary-foreground: oklch(99.11% 0.0069 148.67);
--warning: oklch(82.03% 0.1406 51.13); --warning: oklch(82.03% 0.1407 63.72);
--warning-foreground: oklch(21.03% 0.0059 51.13); --warning-foreground: oklch(21.03% 0.0059 63.72);
} }