feat: add canvas-confetti integration and AuroraButton component; enhance UI with animations and improve button interactions
This commit is contained in:
6
bun.lock
6
bun.lock
@@ -22,6 +22,8 @@
|
|||||||
"@tanstack/react-router": "^1.130.2",
|
"@tanstack/react-router": "^1.130.2",
|
||||||
"@tanstack/react-router-devtools": "^1.131.5",
|
"@tanstack/react-router-devtools": "^1.131.5",
|
||||||
"@tanstack/router-plugin": "^1.121.2",
|
"@tanstack/router-plugin": "^1.121.2",
|
||||||
|
"@types/canvas-confetti": "^1.9.0",
|
||||||
|
"canvas-confetti": "^1.9.3",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.1.1",
|
"cmdk": "^1.1.1",
|
||||||
@@ -426,6 +428,8 @@
|
|||||||
|
|
||||||
"@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
|
"@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
|
||||||
|
|
||||||
|
"@types/canvas-confetti": ["@types/canvas-confetti@1.9.0", "", {}, "sha512-aBGj/dULrimR1XDZLtG9JwxX1b4HPRF6CX9Yfwh3NvstZEm1ZL7RBnel4keCPSqs1ANRu1u2Aoz9R+VmtjYuTg=="],
|
||||||
|
|
||||||
"@types/chai": ["@types/chai@5.2.2", "", { "dependencies": { "@types/deep-eql": "*" } }, "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg=="],
|
"@types/chai": ["@types/chai@5.2.2", "", { "dependencies": { "@types/deep-eql": "*" } }, "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg=="],
|
||||||
|
|
||||||
"@types/d3-array": ["@types/d3-array@3.2.1", "", {}, "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg=="],
|
"@types/d3-array": ["@types/d3-array@3.2.1", "", {}, "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg=="],
|
||||||
@@ -510,6 +514,8 @@
|
|||||||
|
|
||||||
"caniuse-lite": ["caniuse-lite@1.0.30001735", "", {}, "sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w=="],
|
"caniuse-lite": ["caniuse-lite@1.0.30001735", "", {}, "sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w=="],
|
||||||
|
|
||||||
|
"canvas-confetti": ["canvas-confetti@1.9.3", "", {}, "sha512-rFfTURMvmVEX1gyXFgn5QMn81bYk70qa0HLzcIOSVEyl57n6o9ItHeBtUSWdvKAPY0xlvBHno4/v3QPrT83q9g=="],
|
||||||
|
|
||||||
"chai": ["chai@5.3.1", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-48af6xm9gQK8rhIcOxWwdGzIervm8BVTin+yRp9HEvU20BtVZ2lBywlIJBzwaDtvo0FvjeL7QdCADoUoqIbV3A=="],
|
"chai": ["chai@5.3.1", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-48af6xm9gQK8rhIcOxWwdGzIervm8BVTin+yRp9HEvU20BtVZ2lBywlIJBzwaDtvo0FvjeL7QdCADoUoqIbV3A=="],
|
||||||
|
|
||||||
"check-error": ["check-error@2.1.1", "", {}, "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw=="],
|
"check-error": ["check-error@2.1.1", "", {}, "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw=="],
|
||||||
|
|||||||
@@ -28,6 +28,8 @@
|
|||||||
"@tanstack/react-router": "^1.130.2",
|
"@tanstack/react-router": "^1.130.2",
|
||||||
"@tanstack/react-router-devtools": "^1.131.5",
|
"@tanstack/react-router-devtools": "^1.131.5",
|
||||||
"@tanstack/router-plugin": "^1.121.2",
|
"@tanstack/router-plugin": "^1.121.2",
|
||||||
|
"@types/canvas-confetti": "^1.9.0",
|
||||||
|
"canvas-confetti": "^1.9.3",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.1.1",
|
"cmdk": "^1.1.1",
|
||||||
|
|||||||
23
src/components/effect/aurora-button.tsx
Normal file
23
src/components/effect/aurora-button.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
// components/ui/aurora-button.tsx
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
|
||||||
|
export function AuroraButton({
|
||||||
|
loading,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof Button> & { loading?: boolean }) {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
{...props}
|
||||||
|
className={`${props.className ?? ""} relative overflow-hidden`}
|
||||||
|
disabled={props.disabled || loading}
|
||||||
|
>
|
||||||
|
{loading && (
|
||||||
|
<span className="absolute inset-0 animate-aurora" />
|
||||||
|
)}
|
||||||
|
<span className="relative z-10">
|
||||||
|
{loading ? "Pensando…" : children}
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -6,6 +6,8 @@ import { supabase } from "@/auth/supabase"
|
|||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Badge } from "@/components/ui/badge"
|
import { Badge } from "@/components/ui/badge"
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
|
import confetti from "canvas-confetti"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Accordion, AccordionContent, AccordionItem, AccordionTrigger,
|
Accordion, AccordionContent, AccordionItem, AccordionTrigger,
|
||||||
} from "@/components/ui/accordion"
|
} from "@/components/ui/accordion"
|
||||||
@@ -514,6 +516,11 @@ function MejorarAIButton({ asignaturaId, onApply }: {
|
|||||||
}
|
}
|
||||||
const nuevo = await res.json()
|
const nuevo = await res.json()
|
||||||
onApply(nuevo as Asignatura)
|
onApply(nuevo as Asignatura)
|
||||||
|
confetti({
|
||||||
|
particleCount: 120,
|
||||||
|
spread: 80,
|
||||||
|
origin: { y: 0.6 },
|
||||||
|
})
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
alert(e?.message ?? "Error al mejorar la asignatura")
|
alert(e?.message ?? "Error al mejorar la asignatura")
|
||||||
@@ -547,7 +554,24 @@ function MejorarAIButton({ asignaturaId, onApply }: {
|
|||||||
</label>
|
</label>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button variant="outline" onClick={() => setOpen(false)}>Cancelar</Button>
|
<Button variant="outline" onClick={() => setOpen(false)}>Cancelar</Button>
|
||||||
<Button onClick={apply} disabled={!prompt.trim() || loading}>{loading ? "Aplicando…" : "Aplicar ajuste"}</Button>
|
<Button
|
||||||
|
onClick={apply}
|
||||||
|
disabled={!prompt.trim() || loading}
|
||||||
|
className={
|
||||||
|
loading
|
||||||
|
? "relative overflow-hidden text-white shadow-md"
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<span className="relative z-10">Pensando…</span>
|
||||||
|
) : (
|
||||||
|
"Aplicar ajuste"
|
||||||
|
)}
|
||||||
|
{loading && (
|
||||||
|
<span className="absolute inset-0 animate-aurora" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -291,7 +291,7 @@ function RouteComponent() {
|
|||||||
// NEW: acciones carrito
|
// NEW: acciones carrito
|
||||||
function addToCart(a: Asignatura) {
|
function addToCart(a: Asignatura) {
|
||||||
setCart(prev => prev.find(x => x.id === a.id) ? prev : [...prev, a])
|
setCart(prev => prev.find(x => x.id === a.id) ? prev : [...prev, a])
|
||||||
toast.success('Asignatura añadida al carrito')
|
toast.success('Asignatura añadida al carrito de asignaturas')
|
||||||
}
|
}
|
||||||
function removeFromCart(id: string) {
|
function removeFromCart(id: string) {
|
||||||
setCart(prev => prev.filter(x => x.id !== id))
|
setCart(prev => prev.filter(x => x.id !== id))
|
||||||
@@ -355,7 +355,7 @@ function RouteComponent() {
|
|||||||
className="relative"
|
className="relative"
|
||||||
>
|
>
|
||||||
<Icons.ShoppingCart className="w-4 h-4 mr-2" />
|
<Icons.ShoppingCart className="w-4 h-4 mr-2" />
|
||||||
Carrito
|
Carrito de asignaturas
|
||||||
{cart.length > 0 && (
|
{cart.length > 0 && (
|
||||||
<span className="ml-2 inline-flex h-5 min-w-[1.25rem] items-center justify-center rounded-full bg-white/90 text-[11px] text-neutral-900 px-1">
|
<span className="ml-2 inline-flex h-5 min-w-[1.25rem] items-center justify-center rounded-full bg-white/90 text-[11px] text-neutral-900 px-1">
|
||||||
{cart.length}
|
{cart.length}
|
||||||
@@ -369,36 +369,51 @@ function RouteComponent() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Filtros */}
|
{/* Filtros */}
|
||||||
<div className="grid gap-2 sm:grid-cols-[1fr,140px,180px,150px]">
|
<div className="grid gap-4 sm:grid-cols-4">
|
||||||
<Input
|
<div>
|
||||||
value={q}
|
<Label>Búsqueda</Label>
|
||||||
onChange={(e) => setQ(e.target.value)}
|
<Input
|
||||||
placeholder="Buscar por nombre, clave, plan, carrera, facultad…"
|
value={q}
|
||||||
className="w-full"
|
onChange={(e) => setQ(e.target.value)}
|
||||||
/>
|
placeholder="Nombre, clave, plan, carrera, facultad…"
|
||||||
<Select value={sem} onValueChange={setSem}>
|
/>
|
||||||
<SelectTrigger><SelectValue placeholder="Semestre" /></SelectTrigger>
|
</div>
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="todos">Todos</SelectItem>
|
<div>
|
||||||
{semestres.map(s => <SelectItem key={s} value={s}>Semestre {s}</SelectItem>)}
|
<Label>Semestre</Label>
|
||||||
</SelectContent>
|
<Select value={sem} onValueChange={setSem}>
|
||||||
</Select>
|
<SelectTrigger><SelectValue placeholder="Todos" /></SelectTrigger>
|
||||||
<Select value={tipo} onValueChange={setTipo}>
|
<SelectContent>
|
||||||
<SelectTrigger><SelectValue placeholder="Tipo" /></SelectTrigger>
|
<SelectItem value="todos">Todos</SelectItem>
|
||||||
<SelectContent className="max-h-64">
|
{semestres.map(s => <SelectItem key={s} value={s}>Semestre {s}</SelectItem>)}
|
||||||
<SelectItem value="todos">Todos</SelectItem>
|
</SelectContent>
|
||||||
{tipos.map(t => <SelectItem key={t} value={t}>{t}</SelectItem>)}
|
</Select>
|
||||||
</SelectContent>
|
</div>
|
||||||
</Select>
|
|
||||||
<Select value={groupBy} onValueChange={(v) => setGroupBy(v as any)}>
|
<div>
|
||||||
<SelectTrigger><SelectValue placeholder="Agrupar por" /></SelectTrigger>
|
<Label>Tipo</Label>
|
||||||
<SelectContent>
|
<Select value={tipo} onValueChange={setTipo}>
|
||||||
<SelectItem value="semestre">Agrupar por semestre</SelectItem>
|
<SelectTrigger><SelectValue placeholder="Todos" /></SelectTrigger>
|
||||||
<SelectItem value="ninguno">Sin agrupación</SelectItem>
|
<SelectContent className="max-h-64">
|
||||||
</SelectContent>
|
<SelectItem value="todos">Todos</SelectItem>
|
||||||
</Select>
|
{tipos.map(t => <SelectItem key={t} value={t}>{t}</SelectItem>)}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label>Agrupación</Label>
|
||||||
|
<Select value={groupBy} onValueChange={(v) => setGroupBy(v as any)}>
|
||||||
|
<SelectTrigger><SelectValue /></SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="semestre">Por semestre</SelectItem>
|
||||||
|
<SelectItem value="ninguno">Sin agrupación</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{/* Chips de salud */}
|
{/* Chips de salud */}
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
<HealthChip
|
<HealthChip
|
||||||
@@ -621,7 +636,7 @@ function RouteComponent() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<Button variant="ghost" onClick={clearCart}><Icons.Trash2 className="w-4 h-4 mr-1" /> Vaciar carrito</Button>
|
<Button variant="ghost" onClick={clearCart}><Icons.Trash2 className="w-4 h-4 mr-1" /> Vaciar carrito de Asignaturas</Button>
|
||||||
<div className="space-x-2">
|
<div className="space-x-2">
|
||||||
<Button variant="outline" onClick={() => setBulkOpen(false)}>Cerrar</Button>
|
<Button variant="outline" onClick={() => setBulkOpen(false)}>Cerrar</Button>
|
||||||
<Button onClick={cloneBulk}><Icons.CopyPlus className="w-4 h-4 mr-1" /> Clonar en lote</Button>
|
<Button onClick={cloneBulk}><Icons.CopyPlus className="w-4 h-4 mr-1" /> Clonar en lote</Button>
|
||||||
@@ -686,7 +701,7 @@ function AsignaturaCard({ a, onClone, onAddToCart }: { a: Asignatura; onClone: (
|
|||||||
<Icons.Copy className="w-4 h-4" /> Clonar…
|
<Icons.Copy className="w-4 h-4" /> Clonar…
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem className="gap-2" onClick={onAddToCart}>
|
<DropdownMenuItem className="gap-2" onClick={onAddToCart}>
|
||||||
<Icons.ShoppingCart className="w-4 h-4" /> Añadir al carrito
|
<Icons.ShoppingCart className="w-4 h-4" /> Añadir al carrito de asignaturas
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
|||||||
@@ -171,10 +171,10 @@ function RouteComponent() {
|
|||||||
|
|
||||||
{/* Métricas principales */}
|
{/* Métricas principales */}
|
||||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||||
<Metric to={`/_authenticated/carreras?facultadId=${facultad.id}`} label="Carreras" value={counts.carreras} Icon={Icons.GraduationCap} />
|
<Metric to={`/carreras?facultadId=${facultad.id}`} label="Carreras" value={counts.carreras} Icon={Icons.GraduationCap} />
|
||||||
<Metric to={`/_authenticated/planes?facultadId=${facultad.id}`} label="Planes de estudio" value={counts.planes} Icon={Icons.ScrollText} />
|
<Metric to={`/planes?facultadId=${facultad.id}`} label="Planes de estudio" value={counts.planes} Icon={Icons.ScrollText} />
|
||||||
<Metric to={`/_authenticated/asignaturas?facultadId=${facultad.id}`} label="Asignaturas" value={counts.asignaturas} Icon={Icons.BookOpen} />
|
<Metric to={`/asignaturas?facultadId=${facultad.id}`} label="Asignaturas" value={counts.asignaturas} Icon={Icons.BookOpen} />
|
||||||
<Metric to={`/_authenticated/criterios?facultadId=${facultad.id}`} label="Criterios de carrera" value={counts.criterios} Icon={Icons.CheckCircle2} />
|
<Metric to={`/criterios?facultadId=${facultad.id}`} label="Criterios de carrera" value={counts.criterios} Icon={Icons.CheckCircle2} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Calidad + Salud */}
|
{/* Calidad + Salud */}
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import gsap from 'gsap'
|
|||||||
import { ScrollTrigger } from 'gsap/ScrollTrigger'
|
import { ScrollTrigger } from 'gsap/ScrollTrigger'
|
||||||
import { AcademicSections } from '@/components/planes/academic-sections'
|
import { AcademicSections } from '@/components/planes/academic-sections'
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@radix-ui/react-tabs'
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@radix-ui/react-tabs'
|
||||||
|
import confetti from 'canvas-confetti'
|
||||||
|
import { AuroraButton } from '@/components/effect/aurora-button'
|
||||||
|
|
||||||
gsap.registerPlugin(ScrollTrigger)
|
gsap.registerPlugin(ScrollTrigger)
|
||||||
|
|
||||||
@@ -407,6 +409,8 @@ function AdjustAIButton({ plan }: { plan: PlanFull }) {
|
|||||||
body: JSON.stringify({ prompt, plan_id: plan.id }),
|
body: JSON.stringify({ prompt, plan_id: plan.id }),
|
||||||
}).catch(() => { })
|
}).catch(() => { })
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
|
confetti({ particleCount: 120, spread: 80, origin: { y: 0.6 } })
|
||||||
|
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -423,9 +427,9 @@ function AdjustAIButton({ plan }: { plan: PlanFull }) {
|
|||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<Textarea value={prompt} onChange={(e) => setPrompt(e.target.value)} placeholder="Ej.: Enfatiza ciberseguridad y proyectos prácticos…" className="min-h-[120px]" />
|
<Textarea value={prompt} onChange={(e) => setPrompt(e.target.value)} placeholder="Ej.: Enfatiza ciberseguridad y proyectos prácticos…" className="min-h-[120px]" />
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button onClick={apply} disabled={!prompt.trim() || loading}>
|
<AuroraButton onClick={apply} disabled={!prompt.trim() || loading}>
|
||||||
{loading ? 'Aplicando…' : 'Aplicar ajuste'}
|
{loading ? 'Aplicando…' : 'Aplicar ajuste'}
|
||||||
</Button>
|
</AuroraButton>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
@@ -519,6 +523,8 @@ function AddAsignaturaButton({
|
|||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
if (!res.ok) throw new Error(await res.text())
|
if (!res.ok) throw new Error(await res.text())
|
||||||
|
confetti({ particleCount: 120, spread: 80, origin: { y: 0.6 } })
|
||||||
|
|
||||||
setOpen(false); onAdded?.()
|
setOpen(false); onAdded?.()
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
alert(e?.message ?? "Error al generar la asignatura")
|
alert(e?.message ?? "Error al generar la asignatura")
|
||||||
@@ -616,11 +622,13 @@ function AddAsignaturaButton({
|
|||||||
|
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button variant="outline" onClick={() => setOpen(false)}>Cancelar</Button>
|
<Button variant="outline" onClick={() => setOpen(false)}>Cancelar</Button>
|
||||||
<Button onClick={submit} disabled={saving || !canSubmit}>
|
<Button >
|
||||||
|
</Button>
|
||||||
|
<AuroraButton onClick={submit} disabled={saving || !canSubmit}>
|
||||||
{saving
|
{saving
|
||||||
? (mode === "manual" ? "Guardando…" : "Generando…")
|
? (mode === "manual" ? "Guardando…" : "Generando…")
|
||||||
: (mode === "manual" ? "Crear" : "Generar e insertar")}
|
: (mode === "manual" ? "Crear" : "Generar e insertar")}
|
||||||
</Button>
|
</AuroraButton>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -118,7 +118,31 @@
|
|||||||
* {
|
* {
|
||||||
@apply border-border outline-ring/50;
|
@apply border-border outline-ring/50;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes aurora {
|
||||||
|
0% {
|
||||||
|
background-position: 0% 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
background-position: 100% 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
background-position: 0% 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-aurora {
|
||||||
|
background: radial-gradient(at 20% 30%, rgba(59, 130, 246, .5), transparent 50%),
|
||||||
|
radial-gradient(at 80% 70%, rgba(236, 72, 153, .5), transparent 50%),
|
||||||
|
radial-gradient(at 50% 100%, rgba(34, 197, 94, .5), transparent 50%);
|
||||||
|
background-size: 200% 200%;
|
||||||
|
animation: aurora 6s ease infinite;
|
||||||
|
filter: blur(12px) opacity(0.8);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user