landing page y login listos en estilos

This commit is contained in:
Your Name
2025-06-20 21:29:52 -06:00
parent 2b22368d5e
commit 1628ab796e
11 changed files with 543 additions and 17 deletions
+43
View File
@@ -11,6 +11,7 @@
"axios": "^1.9.0",
"bootstrap": "^5.3.6",
"bootstrap-icons": "^1.13.1",
"framer-motion": "^12.18.1",
"next": "15.3.3",
"react": "^19.0.0",
"react-dom": "^19.0.0"
@@ -987,6 +988,33 @@
"node": ">= 6"
}
},
"node_modules/framer-motion": {
"version": "12.18.1",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.18.1.tgz",
"integrity": "sha512-6o4EDuRPLk4LSZ1kRnnEOurbQ86MklVk+Y1rFBUKiF+d2pCdvMjWVu0ZkyMVCTwl5UyTH2n/zJEJx+jvTYuxow==",
"license": "MIT",
"dependencies": {
"motion-dom": "^12.18.1",
"motion-utils": "^12.18.1",
"tslib": "^2.4.0"
},
"peerDependencies": {
"@emotion/is-prop-valid": "*",
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/is-prop-valid": {
"optional": true
},
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -1195,6 +1223,21 @@
"node": ">= 0.6"
}
},
"node_modules/motion-dom": {
"version": "12.18.1",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.18.1.tgz",
"integrity": "sha512-dR/4EYT23Snd+eUSLrde63Ws3oXQtJNw/krgautvTfwrN/2cHfCZMdu6CeTxVfRRWREW3Fy1f5vobRDiBb/q+w==",
"license": "MIT",
"dependencies": {
"motion-utils": "^12.18.1"
}
},
"node_modules/motion-utils": {
"version": "12.18.1",
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.18.1.tgz",
"integrity": "sha512-az26YDU4WoDP0ueAkUtABLk2BIxe28d8NH1qWT8jPGhPyf44XTdDUh8pDk9OPphaSrR9McgpcJlgwSOIw/sfkA==",
"license": "MIT"
},
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+1
View File
@@ -12,6 +12,7 @@
"axios": "^1.9.0",
"bootstrap": "^5.3.6",
"bootstrap-icons": "^1.13.1",
"framer-motion": "^12.18.1",
"next": "15.3.3",
"react": "^19.0.0",
"react-dom": "^19.0.0"
+16 -2
View File
@@ -3,9 +3,22 @@ import SimpleInput from "@/components/input";
import PasswordInput from "@/components/password";
import React from "react";
import { motion } from "framer-motion";
export default function Page() {
return (
<div className="flex-grow-1 d-flex flex-column justify-content-center align-items-center">
<motion.main
className="flex-fill flex-grow-1 d-flex flex-column justify-content-center align-items-center"
initial={{ y: 50, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ delay: 0, duration: .5 }}
>
<h2 className="text-dorado">Bienvenido al sistema de</h2>
<h1 className="text-azul">Carga Masiva</h1>
<SimpleInput
@@ -21,6 +34,7 @@ export default function Page() {
}}
/>
<Button>Iniciar Sesión</Button>
</div>
</motion.main>
);
}
+45
View File
@@ -0,0 +1,45 @@
* {
padding: none;
margin: none;
}
.icon-circle {
position: relative;
width: 300px;
height: 300px;
margin: 2rem auto;
border-radius: 50%;
}
.central-icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 2rem;
background: white;
padding: 1rem;
border-radius: 50%;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
z-index: 1;
}
.floating-icon {
position: absolute;
width: 40px;
height: 40px;
background: white;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
top: 50%;
left: 50%;
transform: rotate(calc(45deg * var(--i))) translate(130px) rotate(calc(-45deg * var(--i)));
box-shadow: 0 0 8px rgba(0, 0, 0, 0.1);
}
.floating-icon i {
font-size: 1.25rem;
color: #7c3aed; /* Morado como el diseño */
}
+60
View File
@@ -0,0 +1,60 @@
// IconCircle.tsx
'use client';
import React from 'react';
import { motion } from 'framer-motion';
import './IconCircle.css';
const iconClasses = [
'bi bi-graph-up',
'bi bi-lightning-fill',
'bi bi-currency-exchange',
'bi bi-globe2',
'bi bi-bar-chart-line-fill',
'bi bi-arrow-left-right',
'bi bi-bank',
'bi bi-boxes',
];
interface IconCircleProps {
delay?: number;
}
const IconCircle: React.FC<IconCircleProps> = ({ delay = 0 }) => {
return (
<div className="icon-circle">
<motion.div
className="central-icon"
initial={{ scale: 0 }}
animate={{ scale: 1, rotate: 1080 }}
transition={{ duration: 1, delay }}
>
<i className="bi bi-stars"></i>
</motion.div>
{iconClasses.map((iconClass, i) => (
<motion.div
key={i}
className="floating-icon"
initial={{
transform: 'translate(0, 0) scale(0)',
opacity: 0,
}}
animate={{
transform: `rotate(${i * 45}deg) translate(130px) rotate(-${i * 45}deg)`,
scale: 1,
opacity: 1,
}}
transition={{
delay: delay + 0.5 + i * 0.1, // empieza después del centro
duration: 0.5,
type: 'spring',
}}
>
<i className={iconClass}></i>
</motion.div>
))}
</div>
);
};
export default IconCircle;
+152
View File
@@ -0,0 +1,152 @@
* {
padding: none;
margin: none;
}
/* Fondo general y tipografía */
body {
margin: 0;
font-family: 'Segoe UI', sans-serif;
background: radial-gradient(circle at center, #f8f4ff, hsl(218, 45%, 37%));
/* background: radial-gradient(circle at center, #f8f4ff, hsl(217, 100%, 89%)); */
color: #1a1a1a;
text-align: center;
}
/* Header */
header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.5rem 2rem;
background: transparent;
font-weight: bold;
}
header div:first-child {
font-size: 1.5rem;
color: #5b2edd;
}
.nav-links {
background-color: white;
padding: 10px;
border-radius: 50px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); /* sombra suave */
display: flex;
gap: 1rem; /* espacio entre los <a> */
}
header a {
margin: 0 0.75rem;
text-decoration: none;
color: #333;
font-weight: 500;
}
header button {
margin-left: 0.5rem;
padding: 0.4rem 1rem;
border-radius: 20px;
border: none;
font-weight: 600;
cursor: pointer;
}
header button:first-of-type {
background-color: white;
border: 1px solid #ccc;
color: #555;
}
header button:last-of-type {
background-color: #0033ff;
color: white;
}
/* Texto principal */
strong {
display: block;
font-size: 2.5rem;
margin: 2rem 0 1rem;
}
p {
font-size: 1rem;
color: #555;
/* max-width: 600px; */
margin: 0 auto 1rem;
}
/* Botón principal */
div > button {
margin-top: 1rem;
background-color: #5b2edd;
color: white;
padding: 0.7rem 2rem;
border: none;
border-radius: 25px;
font-size: 1rem;
cursor: pointer;
}
/* Texto de "years of reliability" */
div > div {
/* display: flex; */
justify-content: center;
align-items: center;
gap: 0.5rem;
margin-top: 0.5rem;
}
div > div p {
margin: 0;
font-size: 0.9rem;
color: #888;
}
/* Scroll hint */
body > p:last-of-type {
margin-top: 2rem;
font-size: 0.85rem;
color: #aaa;
letter-spacing: 1px;
}
.circle-background {
margin-top: 0;
padding-top: 0;
position: fixed;
top: none;
left: none;
width: 100vw;
height: 100vh;
z-index: -1; /* para que esté detrás de todo */
pointer-events: none; /* no interfiere con el contenido */
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}
.circle {
position: absolute;
background: radial-gradient(circle, rgb(255, 255, 255), transparent);
border-radius: 50%;
pointer-events: none;
backdrop-filter: blur(2px);
}
.invert {
filter: invert(1);
}
+31
View File
@@ -0,0 +1,31 @@
// components/LandingBody.tsx
"use client";
import { motion } from "framer-motion";
import IconCircle from "./IconCircle";
export default function LandingBody() {
return (
<motion.main
className="flex-fill"
initial={{ y: 50, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ delay: 1, duration: 1 }}
>
<IconCircle delay={2} />
<strong>Explore markets ready for trading</strong>
<p>
Explore 300+ trading instruments across 10+ asset classes with Alchemy
Markets. Trade Forex, Stocks, Indices, and more!
</p>
<div>
<button>Start Trading</button>
<div>
<i></i>
</div>
</div>
</motion.main>
);
}
+80
View File
@@ -0,0 +1,80 @@
"use client";
import { motion } from "framer-motion";
import Image from "next/image";
import Link from "next/link";
interface HeaderProps {
onNavClick: (page: string) => void;
}
export default function Header({ onNavClick }: HeaderProps) {
return (
<motion.header
initial={{ x: -100, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
transition={{ duration: 1.5, ease: "easeOut" }}
>
<div className="first-child">
<Link
href={"https://www.unam.mx/"}
target="_blank"
className="d-none d-sm-block"
>
<Image
src="/logo-unam.png"
alt="Logo de la UNAM"
width={280}
height={84}
className="invert"
/>
</Link>
</div>
{/* <div className="nav-links">
<a href="">Trading</a>
<a href="">Platforms</a>
<a href="">Tools & Education</a>
<a href="">About Us</a>
<a href="">Partners</a>
<a href="">Login</a>
</div> */}
<nav className="nav-links">
{[
{ label: "Landing", page: "landing" },
{ label: "Login", page: "login" },
{ label: "Admin", page: "admin" },
{ label: "Worker", page: "worker" },
{ label: "Público", page: "publico" },
].map((item) => (
<a
key={item.page}
onClick={() => onNavClick(item.page)}
>
{item.label}
</a>
))}
</nav>
<div className="first-child">
<Link href={"https://acatlan.unam.mx/"} target="_blank">
<Image
src="/logo-fes.png"
alt="Logo de la FES Acatlán"
width={250}
height={60}
className="invert"
/>
</Link>
</div>
</motion.header>
);
}
+91
View File
@@ -0,0 +1,91 @@
"use client";
import "./globals.css";
import "bootstrap-icons/font/bootstrap-icons.css";
import IconCircle from "./IconCircle";
import { motion } from "framer-motion";
import Image from "next/image";
import Link from "next/link";
import Header from "./new_header"; // ajusta la ruta si estás en otra carpeta
import Footer from "@/components/layout/footer";
import LandingBody from "./landing_body";
import { useState } from "react";
import Page from "../(auth)/page";
type PageKey = "landing" | "login" | "admin" | "worker" | "publico";
const Home = () => {
const [page, setPage] = useState<PageKey>("landing");
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 1 }}
className="page-wrapper d-flex flex-column min-vh-100"
>
<div className="circle-background">
{[150, 300, 450, 600, 900].map((size, i) => (
<motion.div
key={i}
className="circle"
initial={{ scale: 0 }}
animate={{ scale: 3 }}
transition={{
delay: 1 + i * 0.3, // empieza después de la animación del texto
duration: 2,
ease: "easeOut",
}}
style={{
width: size,
height: size,
}}
/>
))}
</div>
{/* ⬇️ Tu contenido principal */}
<Header onNavClick={setPage} />
{/* <LandingBody />*/}
{/* renderiza el body según el estado `page` */}
{page === "landing" && <LandingBody />}
{page === "login" && <Page />}
{/* {page === "admin" && <ContenidoAdmin1 />}
{page === "worker" && <ContenidoWorker />}
{page === "publico" && <ContenidoPublico />}
*/}
{/* <Login />
<contenidoAdmin1 />
<contenidoWorker />
<contenidoPublico />
*/}
<Footer></Footer>
</motion.div>
);
};
export default Home;
+3 -7
View File
@@ -4,8 +4,7 @@ import { Roboto } from "next/font/google";
import 'bootstrap-icons/font/bootstrap-icons.css';
import '@/styles/sass/bootstrap.scss';
import BootstrapClient from "@/components/bootstrap-client";
import Footer from "@/components/layout/footer";
import Header from "@/components/layout/header";
const roboto = Roboto({
weight: ["100", "300", "400", "500", "700", "900"],
@@ -20,11 +19,8 @@ export default function RootLayout({
return (
<html lang="en">
<body >
<div className="d-flex flex-column min-vh-100">
<Header />
{children}
<Footer />
</div>
{children}
</body>
</html>
);
+21 -8
View File
@@ -1,20 +1,33 @@
import React from "react";
import { motion } from "framer-motion";
export default function Footer() {
const date = new Date();
const year = date.getFullYear();
return (
<footer className="bg-azul p-3 text-center text-white small">
<p className="m-0">
Hecho en México. Todos los derechos reservados {year}.
return (
<motion.footer
// Empieza 50px abajo y totalmente transparente
initial={{ y: 50, opacity: 0 }}
// Termina en su posición natural y opacidad 1
animate={{ y: 0, opacity: 1 }}
// Retraso para que aparezca justo después del main
transition={{ delay: 2, duration: 1, ease: "easeOut" }}
className="p-3 text-center small text-muted opacity-50"
>
<p className="mb-1">
Hecho en México. Todos los derechos reservados 2025.
</p>
<p className="m-0">
<p className="mb-0">
Esta página puede ser reproducida con fines no lucrativos, siempre y
cuando no se mutile, se cite la fuente completa y su dirección
electrónica. De otra forma, requiere permiso previo por escrito de la
institución.
</p>
</footer>
);
}
</motion.footer>
)
}