all changes
This commit is contained in:
1 parent
cba8d4055b
commit
9dfced04f1
103 files changed
+7378
-31250
No files matched your search
@@ -0,0 +1,44 @@
|
||||
'use client';
|
||||
import { Suspense } from 'react';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { Hero } from '../sections/Hero';
|
||||
import { Problem } from '../sections/Problem';
|
||||
import { Machinery } from '../sections/Machinery';
|
||||
import { Showcase } from '../sections/Showcase';
|
||||
import { PlatformAdvantage } from '../sections/PlatformAdvantage';
|
||||
import { Services } from '../sections/Services';
|
||||
import { Testimonial } from '../sections/Testimonial';
|
||||
import { Anatomy } from '../sections/Anatomy';
|
||||
import { Process } from '../sections/Process';
|
||||
import { Principles } from '../sections/Principles';
|
||||
import { FAQ } from '../sections/FAQ';
|
||||
import { CtaBanner } from '../sections/CtaBanner';
|
||||
import { ContactSection } from '../sections/ContactSection';
|
||||
|
||||
const Clients = dynamic(
|
||||
() => import('../sections/Clients').then(m => ({ default: m.Clients })),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
export function HomePage() {
|
||||
return (
|
||||
<main id="main-content">
|
||||
<Hero isNavyBg={false} />
|
||||
<Suspense fallback={null}>
|
||||
<Clients isNavyBg={false} />
|
||||
</Suspense>
|
||||
<Problem isNavyBg={false} />
|
||||
<Machinery isNavyBg={false} />
|
||||
<Showcase isNavyBg={false} />
|
||||
<PlatformAdvantage isNavyBg={false} />
|
||||
<Services isNavyBg={false} />
|
||||
<Testimonial isNavyBg={false} />
|
||||
<Anatomy isNavyBg={false} />
|
||||
<Process isNavyBg={false} />
|
||||
<Principles isNavyBg={false} />
|
||||
<FAQ isNavyBg={false} />
|
||||
<CtaBanner isNavyBg={false} />
|
||||
<ContactSection isNavyBg={false} />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
'use client';
|
||||
import { useEffect } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { ArrowLeft } from 'lucide-react';
|
||||
|
||||
export function NotFoundPage() {
|
||||
useEffect(() => {
|
||||
document.title = '404 — Página no encontrada | Rynext Technologies';
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<main
|
||||
id="main-content"
|
||||
className="flex min-h-screen flex-col items-center justify-center px-8 text-center"
|
||||
style={{ background: '#f9f9f7' }}
|
||||
>
|
||||
<p
|
||||
className="font-display font-black mb-6"
|
||||
style={{ fontSize: 'clamp(6rem, 20vw, 12rem)', lineHeight: 1, color: '#3b82f6', letterSpacing: '-0.06em', opacity: 0.15 }}
|
||||
aria-hidden="true"
|
||||
>
|
||||
404
|
||||
</p>
|
||||
|
||||
<div style={{ marginTop: '-2rem' }}>
|
||||
<h1
|
||||
className="font-display font-black mb-4"
|
||||
style={{ fontSize: 'clamp(1.5rem, 4vw, 2.5rem)', color: '#0a0a0a', letterSpacing: '-0.03em' }}
|
||||
>
|
||||
Página no encontrada.
|
||||
</h1>
|
||||
<p className="text-base mb-10 max-w-sm mx-auto" style={{ color: '#6a6866', lineHeight: 1.7 }}>
|
||||
La página que buscas no existe o fue movida. Regresa al inicio para continuar.
|
||||
</p>
|
||||
|
||||
<Link
|
||||
href="/"
|
||||
className="inline-flex items-center gap-2 px-6 py-3 text-sm font-bold text-white transition hover:opacity-90"
|
||||
style={{ background: '#3b82f6', borderRadius: 6 }}
|
||||
>
|
||||
<ArrowLeft size={15} />
|
||||
Volver al inicio
|
||||
</Link>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
'use client';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import Loader from '../../components/Loader';
|
||||
import { useBlogPosts } from '../../services/blog';
|
||||
|
||||
const fadeUp = {
|
||||
hidden: { opacity: 0, y: 28 },
|
||||
visible: { opacity: 1, y: 0, transition: { duration: 0.6, ease: [0.22, 1, 0.36, 1] as const } },
|
||||
};
|
||||
|
||||
function BlogContent() {
|
||||
const { data, isLoading } = useBlogPosts();
|
||||
useEffect(() => { document.title = 'Blog | Rynext Technologies'; }, []);
|
||||
|
||||
const posts = (data ?? []).map(p => ({
|
||||
id: p.id,
|
||||
category: p.category.toUpperCase(),
|
||||
title: p.title,
|
||||
summary: p.summary,
|
||||
publishedAt: p.publishedAt,
|
||||
imageUrl: p.imageUrl,
|
||||
slug: p.slug,
|
||||
}));
|
||||
|
||||
return (
|
||||
<main id="main-content">
|
||||
<section
|
||||
style={{ background: '#ffffff', borderBottom: '1px solid #dddbd5', paddingTop: '160px', paddingBottom: '100px' }}
|
||||
className="px-8 md:px-14"
|
||||
>
|
||||
<div className="mx-auto max-w-7xl">
|
||||
<motion.p className="text-mono-label mb-6" style={{ color: '#3b82f6', letterSpacing: '0.18em' }}
|
||||
initial="hidden" whileInView="visible" viewport={{ once: true }} variants={fadeUp}>
|
||||
RECURSOS
|
||||
</motion.p>
|
||||
<motion.h1 className="text-display" style={{ color: '#0a0a0a', maxWidth: '16ch' }}
|
||||
initial="hidden" whileInView="visible" viewport={{ once: true }}
|
||||
variants={{ hidden: { opacity: 0, y: 32 }, visible: { opacity: 1, y: 0, transition: { duration: 0.7, delay: 0.1, ease: [0.22, 1, 0.36, 1] } } }}>
|
||||
Pensamiento de ingeniería.
|
||||
</motion.h1>
|
||||
<motion.p className="mt-8 text-lg leading-relaxed" style={{ color: '#6a6866', maxWidth: '52ch' }}
|
||||
initial="hidden" whileInView="visible" viewport={{ once: true }}
|
||||
variants={{ hidden: { opacity: 0, y: 24 }, visible: { opacity: 1, y: 0, transition: { duration: 0.6, delay: 0.2, ease: [0.22, 1, 0.36, 1] } } }}>
|
||||
Perspectivas sobre arquitectura de software, sistemas empresariales y decisiones técnicas que impactan la operación real de los negocios.
|
||||
</motion.p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
style={{ background: '#f5f5f3', borderBottom: '1px solid #dddbd5', padding: '100px 0', minHeight: '60vh' }}
|
||||
className="px-8 md:px-14"
|
||||
>
|
||||
<div className="mx-auto max-w-7xl">
|
||||
<motion.p className="text-mono-label mb-12" style={{ color: '#3b82f6', letterSpacing: '0.18em' }}
|
||||
initial="hidden" whileInView="visible" viewport={{ once: true }} variants={fadeUp}>
|
||||
ARTÍCULOS
|
||||
</motion.p>
|
||||
|
||||
{isLoading ? (
|
||||
<Loader className="py-20" />
|
||||
) : posts.length > 0 ? (
|
||||
<div className="grid gap-6 md:grid-cols-3">
|
||||
{posts.map((art, i) => (
|
||||
<motion.article key={art.id}
|
||||
initial="hidden" whileInView="visible" viewport={{ once: true }}
|
||||
variants={{ hidden: { opacity: 0, y: 24 }, visible: { opacity: 1, y: 0, transition: { duration: 0.5, delay: i * 0.1, ease: [0.22, 1, 0.36, 1] } } }}
|
||||
style={{ background: '#ffffff', border: '1px solid #dddbd5', borderRadius: 4, overflow: 'hidden', display: 'flex', flexDirection: 'column', opacity: art.slug ? 1 : 0.82 }}
|
||||
>
|
||||
<div style={{ aspectRatio: '16/9', background: '#f0efec', overflow: 'hidden', position: 'relative' }}>
|
||||
{art.imageUrl && (
|
||||
<img src={art.imageUrl} alt={art.title}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
onError={(e) => { (e.currentTarget as HTMLImageElement).style.display = 'none'; }}
|
||||
/>
|
||||
)}
|
||||
<span style={{ position: 'absolute', top: 14, left: 14, background: '#3b82f6', color: '#fff', fontFamily: 'monospace', fontSize: 10, letterSpacing: '0.12em', padding: '3px 8px', borderRadius: 2 }}>
|
||||
{art.category}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ padding: '28px 28px 24px', display: 'flex', flexDirection: 'column', gap: 12, flexGrow: 1 }}>
|
||||
<h2 className="font-display font-bold text-lg leading-snug" style={{ color: '#0a0a0a' }}>{art.title}</h2>
|
||||
<p className="text-sm leading-relaxed" style={{ color: '#6a6866', flexGrow: 1 }}>{art.summary}</p>
|
||||
<p className="text-mono-label" style={{ color: '#aaa8a4', letterSpacing: '0.12em', marginTop: 4 }}>
|
||||
{art.publishedAt
|
||||
? new Date(art.publishedAt).toLocaleDateString('es-MX', { day: '2-digit', month: 'long', year: 'numeric' })
|
||||
: 'Próximamente'}
|
||||
</p>
|
||||
</div>
|
||||
</motion.article>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm" style={{ color: '#aaa8a4' }}>Sin artículos publicados.</p>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
export function BlogPage() {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
useEffect(() => setMounted(true), []);
|
||||
if (!mounted) return null;
|
||||
return <BlogContent />;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import Loader from '../../components/Loader';
|
||||
|
||||
export default function Loading() {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center" style={{ background: '#f9f9f7' }}>
|
||||
<Loader />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { BlogPage } from './BlogPage';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Blog | Rynext Technologies',
|
||||
description: 'Artículos sobre arquitectura de software, sistemas empresariales y tecnología. Perspectivas del equipo de Rynext Technologies.',
|
||||
alternates: { canonical: 'https://rynext.com.mx/blog' },
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
return <BlogPage />;
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Quicksand:wght@300;400;500;600;700&display=swap');
|
||||
@import "tailwindcss";
|
||||
|
||||
@theme {
|
||||
--color-brand-blue: #0077c2;
|
||||
--color-brand-navy: #001f5b;
|
||||
--color-brand-black: #0a0a0a;
|
||||
--color-brand-red: #3b82f6;
|
||||
--color-bg-light: #f9f9f7;
|
||||
--color-bg-alt: #f0efec;
|
||||
--color-border-light: #dddbd5;
|
||||
--color-text-secondary: #6a6866;
|
||||
--color-dark-bg: #080c14;
|
||||
--color-dark-surface: #0f1520;
|
||||
--color-dark-border: #1c2535;
|
||||
|
||||
--font-sans: 'Quicksand', system-ui, sans-serif;
|
||||
--font-display: 'Quicksand', system-ui, sans-serif;
|
||||
--font-mono: 'SF Mono', 'Cascadia Code', 'Fira Code', Consolas, monospace;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
|
||||
body {
|
||||
@apply font-sans antialiased m-0 p-0 overflow-x-hidden;
|
||||
background-color: #f9f9f7;
|
||||
color: #0a0a0a;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, .font-display {
|
||||
font-family: var(--font-display);
|
||||
}
|
||||
|
||||
*:focus-visible {
|
||||
outline: 2px solid #3b82f6;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Typography scale */
|
||||
.text-display {
|
||||
font-family: var(--font-display);
|
||||
font-size: clamp(2.6rem, 6vw, 5.5rem);
|
||||
line-height: 1.02;
|
||||
font-weight: 900;
|
||||
letter-spacing: -0.04em;
|
||||
}
|
||||
|
||||
.text-headline {
|
||||
font-family: var(--font-display);
|
||||
font-size: clamp(1.8rem, 4vw, 3.2rem);
|
||||
line-height: 1.1;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.03em;
|
||||
}
|
||||
|
||||
.text-section-title {
|
||||
font-family: var(--font-display);
|
||||
font-size: clamp(1.5rem, 3vw, 2.4rem);
|
||||
line-height: 1.15;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.025em;
|
||||
}
|
||||
|
||||
.text-mono-label {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.18em;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Section wave divider */
|
||||
.section-wave {
|
||||
position: relative;
|
||||
}
|
||||
.section-wave::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, transparent, currentColor, transparent);
|
||||
opacity: 0.12;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Bg patterns */
|
||||
.bg-dot-grid {
|
||||
background-image: radial-gradient(circle, currentColor 1px, transparent 1px);
|
||||
background-size: 24px 24px;
|
||||
}
|
||||
|
||||
.bg-grid-lines {
|
||||
background-image:
|
||||
linear-gradient(currentColor 1px, transparent 1px),
|
||||
linear-gradient(to right, currentColor 1px, transparent 1px);
|
||||
background-size: 48px 48px;
|
||||
}
|
||||
|
||||
/* Ticker */
|
||||
@keyframes ticker {
|
||||
from { transform: translateX(0); }
|
||||
to { transform: translateX(-50%); }
|
||||
}
|
||||
.animate-ticker {
|
||||
animation: ticker 32s linear infinite;
|
||||
}
|
||||
|
||||
/* Anatomy accordion */
|
||||
.anatomy-body {
|
||||
display: grid;
|
||||
grid-template-rows: 0fr;
|
||||
transition: grid-template-rows 320ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
.anatomy-body.open {
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
.anatomy-body-inner {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Principles hover */
|
||||
.principle-desc {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition: max-height 320ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
.principle-item:hover .principle-desc,
|
||||
.principle-item:focus-within .principle-desc {
|
||||
max-height: 120px;
|
||||
}
|
||||
|
||||
/* Modal fade */
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
.animate-fadeIn {
|
||||
animation: fadeIn 200ms ease;
|
||||
}
|
||||
|
||||
/* Aviso de privacidad — contenido editorial */
|
||||
.prose-rynext h2 {
|
||||
font-family: var(--font-display);
|
||||
font-size: 1.15rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.02em;
|
||||
color: #0a0a0a;
|
||||
margin-top: 2.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
.prose-rynext p,
|
||||
.prose-rynext ul {
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.75;
|
||||
color: #4a4846;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.prose-rynext ul {
|
||||
padding-left: 1.25rem;
|
||||
list-style: disc;
|
||||
}
|
||||
.prose-rynext li {
|
||||
margin-bottom: 0.35rem;
|
||||
}
|
||||
.prose-rynext a {
|
||||
color: #3b82f6;
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
.prose-rynext strong {
|
||||
color: #0a0a0a;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Reduced motion */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*, *::before, *::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
.animate-ticker {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import type { Metadata, Viewport } from 'next';
|
||||
import './globals.css';
|
||||
import { Providers } from './providers';
|
||||
import { AppShell } from '../components/AppShell';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL('https://rynext.com.mx'),
|
||||
title: 'Rynext Technologies | Software Empresarial 100% Mexicano',
|
||||
description:
|
||||
'Diseñamos y construimos sistemas empresariales a medida. Arquitectura real, sin atajos técnicos.',
|
||||
openGraph: {
|
||||
title: 'Rynext Technologies | Software Empresarial',
|
||||
description: 'Ingeniería de software empresarial desde la arquitectura. Cancún, México.',
|
||||
locale: 'es_MX',
|
||||
type: 'website',
|
||||
images: [
|
||||
{
|
||||
url: '/og-image.png',
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: 'Rynext Technologies',
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Rynext Technologies | Software Empresarial',
|
||||
description: 'Ingeniería de software empresarial desde la arquitectura. Cancún, México.',
|
||||
images: ['/og-image.png'],
|
||||
},
|
||||
icons: { icon: '/favicon.svg' },
|
||||
};
|
||||
|
||||
export const viewport: Viewport = {
|
||||
themeColor: '#3b82f6',
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="es">
|
||||
<body>
|
||||
<Providers>
|
||||
<AppShell>{children}</AppShell>
|
||||
</Providers>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import Loader from '../components/Loader';
|
||||
|
||||
export default function Loading() {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center" style={{ background: '#f9f9f7' }}>
|
||||
<Loader />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { NotFoundPage } from './NotFoundPage';
|
||||
|
||||
export default function NotFound() {
|
||||
return <NotFoundPage />;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { HomePage } from './HomePage';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Rynext Technologies | Software Empresarial 100% Mexicano',
|
||||
description: 'Diseñamos y construimos sistemas empresariales a medida. Arquitectura real, sin atajos técnicos. Cancún, México.',
|
||||
alternates: { canonical: 'https://rynext.com.mx' },
|
||||
};
|
||||
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'SoftwareCompany',
|
||||
name: 'Rynext Technologies',
|
||||
url: 'https://rynext.com.mx',
|
||||
logo: 'https://rynext.com.mx/icons/Logo_negro.svg',
|
||||
description: 'Diseñamos y construimos sistemas empresariales a medida. Arquitectura real, sin atajos técnicos.',
|
||||
address: {
|
||||
'@type': 'PostalAddress',
|
||||
addressLocality: 'Cancún',
|
||||
addressRegion: 'Quintana Roo',
|
||||
addressCountry: 'MX',
|
||||
},
|
||||
contactPoint: {
|
||||
'@type': 'ContactPoint',
|
||||
email: '[email protected]',
|
||||
contactType: 'customer support',
|
||||
availableLanguage: 'Spanish',
|
||||
},
|
||||
sameAs: [
|
||||
'https://www.facebook.com/RynextTechnologies/',
|
||||
'https://linkedin.com/company/rynext',
|
||||
],
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
<HomePage />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
"use client";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export function PrivacidadPage() {
|
||||
useEffect(() => {
|
||||
document.title = "Aviso de Privacidad | Rynext Technologies";
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<main
|
||||
id="main-content"
|
||||
style={{ background: "#ffffff", minHeight: "100vh" }}
|
||||
>
|
||||
<section
|
||||
style={{
|
||||
paddingTop: "140px",
|
||||
paddingBottom: "100px",
|
||||
borderBottom: "1px solid #dddbd5",
|
||||
}}
|
||||
className="px-8 md:px-14"
|
||||
>
|
||||
<div className="mx-auto max-w-3xl">
|
||||
<h1 className="text-headline mb-4" style={{ color: "#0a0a0a" }}>
|
||||
Aviso de Privacidad Integral
|
||||
</h1>
|
||||
<p className="text-sm mb-16" style={{ color: "#aaa8a4" }}>
|
||||
Última actualización: agosto de 2026
|
||||
</p>
|
||||
|
||||
<div className="prose-rynext">
|
||||
<h2>1. Responsable del tratamiento de datos personales</h2>
|
||||
<p>
|
||||
<strong>Rynext Technologies</strong> (en adelante "Rynext"), con
|
||||
domicilio en Cancún, Quintana Roo, México, es responsable del
|
||||
tratamiento de sus datos personales conforme a lo establecido en
|
||||
la Ley Federal de Protección de Datos Personales en Posesión de
|
||||
los Particulares (LFPDPPP) y su Reglamento.
|
||||
</p>
|
||||
<p>
|
||||
Para cualquier asunto relacionado con el presente Aviso de
|
||||
Privacidad puede contactarnos en:{" "}
|
||||
<a href="mailto:[email protected]">contacto@rynext.com.mx</a>
|
||||
</p>
|
||||
|
||||
<h2>2. Datos personales que recabamos</h2>
|
||||
<p>
|
||||
Rynext puede recabar las siguientes categorías de datos
|
||||
personales:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Datos de identificación:</strong> nombre completo,
|
||||
empresa o razón social.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Datos de contacto:</strong> correo electrónico, número
|
||||
de teléfono.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Datos de navegación:</strong> dirección IP, tipo de
|
||||
navegador, páginas visitadas y duración de la sesión (mediante
|
||||
cookies técnicas).
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
Rynext <strong>no recaba datos personales sensibles</strong> tales
|
||||
como origen racial, estado de salud, creencias religiosas, datos
|
||||
biométricos ni información financiera a través de este sitio web.
|
||||
</p>
|
||||
|
||||
<h2>3. Finalidades del tratamiento</h2>
|
||||
<p>
|
||||
Sus datos personales son utilizados para las siguientes{" "}
|
||||
<strong>finalidades primarias</strong>:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
Atender solicitudes de contacto, cotizaciones y propuestas
|
||||
comerciales.
|
||||
</li>
|
||||
<li>
|
||||
Dar seguimiento a proyectos de desarrollo de software y
|
||||
consultoría.
|
||||
</li>
|
||||
<li>Cumplir con obligaciones legales y contractuales.</li>
|
||||
</ul>
|
||||
<p>
|
||||
De manera secundaria, y solo con su consentimiento expreso,
|
||||
podemos utilizar sus datos para:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
Envío de comunicaciones sobre nuevos servicios, actualizaciones
|
||||
o contenido de interés.
|
||||
</li>
|
||||
<li>
|
||||
Estadísticas internas y mejora de la experiencia en el sitio.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
Si no desea que sus datos sean tratados para las finalidades
|
||||
secundarias, puede manifestarlo en cualquier momento
|
||||
escribiéndonos a{" "}
|
||||
<a href="mailto:[email protected]">contacto@rynext.com.mx</a>
|
||||
.
|
||||
</p>
|
||||
|
||||
<h2>4. Transferencia de datos personales</h2>
|
||||
<p>
|
||||
Rynext no vende, renta ni comercializa sus datos personales a
|
||||
terceros. Únicamente podremos transferirlos en los siguientes
|
||||
casos:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
A proveedores de servicios tecnológicos que actúen como
|
||||
encargados bajo instrucción de Rynext y con las mismas
|
||||
obligaciones de confidencialidad.
|
||||
</li>
|
||||
<li>
|
||||
Cuando sea requerido por autoridades competentes en cumplimiento
|
||||
de disposiciones legales.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
En ningún caso sus datos serán transferidos a terceros con fines
|
||||
distintos a los aquí establecidos sin su consentimiento previo.
|
||||
</p>
|
||||
|
||||
<h2>5. Derechos ARCO</h2>
|
||||
<p>
|
||||
Usted tiene derecho a{" "}
|
||||
<strong>Acceder, Rectificar, Cancelar u Oponerse</strong>{" "}
|
||||
(derechos ARCO) al tratamiento de sus datos personales, así como a
|
||||
revocar el consentimiento que haya otorgado para dicho
|
||||
tratamiento.
|
||||
</p>
|
||||
<p>Para ejercer sus derechos ARCO debe enviar una solicitud a:</p>
|
||||
<p>
|
||||
<strong>Correo:</strong>{" "}
|
||||
<a href="mailto:[email protected]">contacto@rynext.com.mx</a>
|
||||
<br />
|
||||
<strong>Asunto:</strong> Ejercicio de Derechos ARCO
|
||||
</p>
|
||||
<p>Su solicitud deberá contener:</p>
|
||||
<ul>
|
||||
<li>
|
||||
Nombre completo y correo electrónico con el que se registró o
|
||||
contactó.
|
||||
</li>
|
||||
<li>
|
||||
Descripción clara del derecho que desea ejercer y los datos a
|
||||
los que se refiere.
|
||||
</li>
|
||||
<li>
|
||||
Cualquier documento que facilite la localización de sus datos.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
Rynext responderá a su solicitud en un plazo máximo de{" "}
|
||||
<strong>20 días hábiles{" "}</strong>a partir de su recepción.
|
||||
</p>
|
||||
|
||||
<h2>6. Uso de cookies</h2>
|
||||
<p>
|
||||
Este sitio utiliza cookies técnicas estrictamente necesarias para
|
||||
su funcionamiento. No utilizamos cookies de rastreo ni publicidad
|
||||
comportamental sin su consentimiento. Puede configurar su
|
||||
navegador para bloquear o eliminar las cookies; sin embargo,
|
||||
algunas funcionalidades del sitio podrían verse afectadas.
|
||||
</p>
|
||||
|
||||
<h2>7. Cambios al aviso de privacidad</h2>
|
||||
<p>
|
||||
Rynext se reserva el derecho de modificar este Aviso de Privacidad
|
||||
en cualquier momento para reflejar cambios legislativos,
|
||||
jurisprudenciales o en nuestras prácticas de privacidad. Las
|
||||
modificaciones serán publicadas en esta página con la fecha de
|
||||
actualización correspondiente. Se recomienda revisar
|
||||
periódicamente este aviso.
|
||||
</p>
|
||||
|
||||
<h2>8. Autoridad competente</h2>
|
||||
<p>
|
||||
Si considera que sus derechos han sido vulnerados, tiene derecho a
|
||||
acudir ante el Instituto Nacional de Transparencia, Acceso a la
|
||||
Información y Protección de Datos Personales (INAI) en{" "}
|
||||
<a
|
||||
href="https://www.inai.org.mx"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
www.inai.org.mx
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import Loader from '../../components/Loader';
|
||||
|
||||
export default function Loading() {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center" style={{ background: '#ffffff' }}>
|
||||
<Loader />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { PrivacidadPage } from './PrivacidadPage';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Aviso de Privacidad | Rynext Technologies',
|
||||
description: 'Aviso de privacidad de Rynext Technologies. Conoce cómo tratamos y protegemos tus datos personales.',
|
||||
alternates: { canonical: 'https://rynext.com.mx/privacidad' },
|
||||
robots: { index: false },
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
return <PrivacidadPage />;
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
'use client';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import Loader from '../../components/Loader';
|
||||
import { useProducts } from '../../services/products';
|
||||
import { assetUrl } from '../../services/api';
|
||||
|
||||
const fadeUp = {
|
||||
hidden: { opacity: 0, y: 28 },
|
||||
visible: { opacity: 1, y: 0, transition: { duration: 0.6, ease: [0.22, 1, 0.36, 1] as const } },
|
||||
};
|
||||
|
||||
function ProductosContent() {
|
||||
const { data: products, isLoading } = useProducts();
|
||||
useEffect(() => { document.title = 'Productos | Rynext Technologies'; }, []);
|
||||
|
||||
const active = (products ?? []).filter(p => p.isActive);
|
||||
|
||||
return (
|
||||
<main id="main-content">
|
||||
<section
|
||||
style={{ background: '#ffffff', borderBottom: '1px solid #dddbd5', paddingTop: '160px', paddingBottom: '100px' }}
|
||||
className="px-8 md:px-14"
|
||||
>
|
||||
<div className="mx-auto max-w-7xl">
|
||||
<motion.p className="text-mono-label mb-6" style={{ color: '#3b82f6', letterSpacing: '0.18em' }}
|
||||
initial="hidden" whileInView="visible" viewport={{ once: true }} variants={fadeUp}>
|
||||
PRODUCTOS
|
||||
</motion.p>
|
||||
<motion.h1 className="text-display" style={{ color: '#0a0a0a', maxWidth: '18ch' }}
|
||||
initial="hidden" whileInView="visible" viewport={{ once: true }}
|
||||
variants={{ hidden: { opacity: 0, y: 32 }, visible: { opacity: 1, y: 0, transition: { duration: 0.7, delay: 0.1, ease: [0.22, 1, 0.36, 1] } } }}>
|
||||
Software diseñado para operar.
|
||||
</motion.h1>
|
||||
<motion.p className="mt-8 text-lg leading-relaxed" style={{ color: '#6a6866', maxWidth: '52ch' }}
|
||||
initial="hidden" whileInView="visible" viewport={{ once: true }}
|
||||
variants={{ hidden: { opacity: 0, y: 24 }, visible: { opacity: 1, y: 0, transition: { duration: 0.6, delay: 0.2, ease: [0.22, 1, 0.36, 1] } } }}>
|
||||
Módulos y licencias listos para implementar, respaldados por la misma ingeniería con la que construimos sistemas a medida.
|
||||
</motion.p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
style={{ background: '#f5f5f3', borderBottom: '1px solid #dddbd5', padding: '100px 0', minHeight: '40vh' }}
|
||||
className="px-8 md:px-14"
|
||||
>
|
||||
<div className="mx-auto max-w-7xl">
|
||||
{isLoading ? (
|
||||
<Loader className="py-20" />
|
||||
) : active.length === 0 ? (
|
||||
<p className="text-sm" style={{ color: '#aaa8a4' }}>Sin productos disponibles.</p>
|
||||
) : (
|
||||
<div className="flex flex-col gap-8">
|
||||
{active.map((product, i) => (
|
||||
<motion.div key={product.id}
|
||||
initial="hidden" whileInView="visible" viewport={{ once: true }}
|
||||
variants={{ hidden: { opacity: 0, y: 24 }, visible: { opacity: 1, y: 0, transition: { duration: 0.6, delay: i * 0.08, ease: [0.22, 1, 0.36, 1] as const } } }}
|
||||
style={{ background: '#ffffff', border: '1px solid #dddbd5', borderRadius: 4, padding: '60px 56px', display: 'flex', flexDirection: 'column', gap: 24, maxWidth: 720 }}>
|
||||
{product.imageUrl && (
|
||||
<img src={assetUrl(product.imageUrl)} alt={product.name} style={{ width: '100%', maxWidth: 480, borderRadius: 4, objectFit: 'cover', aspectRatio: '16/9' }} />
|
||||
)}
|
||||
<div>
|
||||
<p className="text-mono-label mb-2" style={{ color: '#aaa8a4', letterSpacing: '0.14em' }}>PRODUCTO</p>
|
||||
<h2 className="text-headline" style={{ color: '#0a0a0a' }}>{product.name}</h2>
|
||||
</div>
|
||||
{product.description && (
|
||||
<p className="text-base leading-relaxed" style={{ color: '#6a6866', maxWidth: '48ch' }}>{product.description}</p>
|
||||
)}
|
||||
{product.badges && product.badges.length > 0 && (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{product.badges.map(badge => (
|
||||
<span key={badge} style={{ fontSize: 11, fontWeight: 600, padding: '4px 10px', borderRadius: 99, background: 'rgba(59,130,246,0.08)', color: '#3b82f6' }}>
|
||||
{badge}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<a href="mailto:[email protected]?subject=Solicitud%20de%20demo"
|
||||
className="inline-flex items-center gap-2 px-7 py-3.5 text-sm font-bold text-white transition hover:opacity-90"
|
||||
style={{ background: '#3b82f6', borderRadius: 3 }}>
|
||||
Solicitar demo
|
||||
</a>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
export function ProductosPage() {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
useEffect(() => setMounted(true), []);
|
||||
if (!mounted) return null;
|
||||
return <ProductosContent />;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import Loader from '../../components/Loader';
|
||||
|
||||
export default function Loading() {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center" style={{ background: '#f9f9f7' }}>
|
||||
<Loader />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { ProductosPage } from './ProductosPage';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Productos | Rynext Technologies',
|
||||
description: 'Explora los productos y soluciones empresariales de Rynext Technologies. Sistemas a medida para la realidad operativa de los negocios mexicanos.',
|
||||
alternates: { canonical: 'https://rynext.com.mx/productos' },
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
return <ProductosPage />;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
'use client';
|
||||
import { useState } from 'react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
|
||||
export function Providers({ children }: { children: React.ReactNode }) {
|
||||
const [queryClient] = useState(() => new QueryClient({
|
||||
defaultOptions: { queries: { retry: 1, staleTime: 60_000 } },
|
||||
}));
|
||||
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export const dynamic = 'force-static';
|
||||
import type { MetadataRoute } from 'next';
|
||||
|
||||
export default function robots(): MetadataRoute.Robots {
|
||||
return {
|
||||
rules: { userAgent: '*', allow: '/' },
|
||||
sitemap: 'https://rynext.com.mx/sitemap.xml',
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
export const dynamic = 'force-static';
|
||||
import type { MetadataRoute } from 'next';
|
||||
|
||||
export default function sitemap(): MetadataRoute.Sitemap {
|
||||
return [
|
||||
{ url: 'https://rynext.com.mx', changeFrequency: 'monthly', priority: 1 },
|
||||
{ url: 'https://rynext.com.mx/sobre-nosotros', changeFrequency: 'monthly', priority: 0.8 },
|
||||
{ url: 'https://rynext.com.mx/productos', changeFrequency: 'monthly', priority: 0.8 },
|
||||
{ url: 'https://rynext.com.mx/blog', changeFrequency: 'weekly', priority: 0.6 },
|
||||
{ url: 'https://rynext.com.mx/privacidad', changeFrequency: 'yearly', priority: 0.3 },
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
'use client';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import Loader from '../../components/Loader';
|
||||
import { useTeam, groupByDepartment } from '../../services/team';
|
||||
import { valores, porQueRynext } from '../../data/sobreNosotrosData';
|
||||
|
||||
const TEAM_IMAGE = 'https://images.unsplash.com/photo-1522071820081-009f0129c71c?w=1200&q=80';
|
||||
|
||||
function SobreNosotrosContent() {
|
||||
const { data: members, isLoading: loadingTeam } = useTeam();
|
||||
const departments = groupByDepartment(members ?? []);
|
||||
|
||||
useEffect(() => {
|
||||
document.title = 'Sobre Nosotros | Rynext Technologies';
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<main id="main-content">
|
||||
|
||||
{/* Hero */}
|
||||
<section
|
||||
style={{ background: '#ffffff', borderBottom: '1px solid #dddbd5', paddingTop: '140px', paddingBottom: '100px' }}
|
||||
className="px-8 md:px-14"
|
||||
>
|
||||
<div className="mx-auto max-w-7xl grid gap-16 lg:grid-cols-2 lg:items-center">
|
||||
<div>
|
||||
<motion.h1
|
||||
className="text-display mb-8"
|
||||
style={{ color: '#0a0a0a' }}
|
||||
initial="hidden" whileInView="visible" viewport={{ once: true }}
|
||||
variants={{ hidden: { opacity: 0, y: 32 }, visible: { opacity: 1, y: 0, transition: { duration: 0.7, delay: 0.1, ease: [0.22, 1, 0.36, 1] } } }}
|
||||
>
|
||||
Software 100%{' '}
|
||||
<span style={{ position: 'relative', display: 'inline-block' }}>
|
||||
<span style={{ color: '#3b82f6' }}>Mexicano</span>
|
||||
<svg viewBox="0 0 200 12" preserveAspectRatio="none" aria-hidden="true"
|
||||
style={{ position: 'absolute', bottom: -4, left: 0, width: '100%', height: 12 }}>
|
||||
<path d="M2 8 C50 2, 150 2, 198 8" stroke="#3b82f6" strokeWidth="3" strokeLinecap="round" fill="none" />
|
||||
</svg>
|
||||
</span>{' '}
|
||||
de clase mundial.
|
||||
</motion.h1>
|
||||
<motion.p className="text-lg leading-relaxed" style={{ color: '#6a6866' }}
|
||||
initial="hidden" whileInView="visible" viewport={{ once: true }}
|
||||
variants={{ hidden: { opacity: 0, y: 24 }, visible: { opacity: 1, y: 0, transition: { duration: 0.6, delay: 0.2, ease: [0.22, 1, 0.36, 1] } } }}>
|
||||
Rynext Technologies nació de una convicción: México tiene el talento para construir
|
||||
software empresarial que compita con cualquier firma en el mundo. No adaptamos
|
||||
herramientas extranjeras diseñamos sistemas propios, con las tecnologías más
|
||||
actuales, para la realidad operativa de los negocios mexicanos.
|
||||
</motion.p>
|
||||
</div>
|
||||
|
||||
<motion.div initial="hidden" whileInView="visible" viewport={{ once: true }}
|
||||
variants={{ hidden: { opacity: 0, x: 24 }, visible: { opacity: 1, x: 0, transition: { duration: 0.7, delay: 0.15, ease: [0.22, 1, 0.36, 1] } } }}
|
||||
style={{ position: 'relative' }}>
|
||||
<div style={{ position: 'absolute', top: 16, left: -16, width: '100%', height: '100%', border: '3px solid #3b82f6', borderRadius: 2, zIndex: 0 }} />
|
||||
<img src={TEAM_IMAGE} alt="Equipo Rynext Technologies"
|
||||
style={{ position: 'relative', zIndex: 1, width: '100%', aspectRatio: '4/3', objectFit: 'cover', display: 'block', clipPath: 'polygon(0 0, 100% 0, 100% 88%, 94% 100%, 0 100%)' }}
|
||||
/>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Por qué Rynext */}
|
||||
<section style={{ background: '#f5f5f3', borderBottom: '1px solid #dddbd5', padding: '100px 0' }} className="px-8 md:px-14">
|
||||
<div className="mx-auto max-w-7xl">
|
||||
<motion.h2 className="text-display mb-20" style={{ color: '#0a0a0a', maxWidth: '22ch' }}
|
||||
initial="hidden" whileInView="visible" viewport={{ once: true }}
|
||||
variants={{ hidden: { opacity: 0, y: 28 }, visible: { opacity: 1, y: 0, transition: { duration: 0.7, ease: [0.22, 1, 0.36, 1] } } }}>
|
||||
¿Por qué <span style={{ color: '#3b82f6' }}>Rynext Technologies?</span>
|
||||
</motion.h2>
|
||||
<div className="flex flex-col divide-y" style={{ borderTop: '1px solid #dddbd5', borderBottom: '1px solid #dddbd5' }}>
|
||||
{porQueRynext.map((item, i) => (
|
||||
<motion.div key={i}
|
||||
initial="hidden" whileInView="visible" viewport={{ once: true }}
|
||||
variants={{ hidden: { opacity: 0, y: 16 }, visible: { opacity: 1, y: 0, transition: { duration: 0.5, delay: i * 0.08, ease: [0.22, 1, 0.36, 1] } } }}
|
||||
className="grid py-10 md:grid-cols-[80px_1fr_2fr] gap-6 items-start"
|
||||
style={{ borderColor: '#dddbd5' }}>
|
||||
<span className="font-display font-black" style={{ fontSize: 48, lineHeight: 1, color: '#3b82f6', letterSpacing: '-0.04em' }}>{item.num}</span>
|
||||
<h3 className="font-display font-bold text-xl" style={{ color: '#0a0a0a' }}>{item.titulo}</h3>
|
||||
<p className="text-base leading-relaxed" style={{ color: '#6a6866', maxWidth: '52ch' }}>{item.desc}</p>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Valores */}
|
||||
<section style={{ background: '#ffffff', borderBottom: '1px solid #dddbd5', padding: '100px 0' }} className="px-8 md:px-14">
|
||||
<div className="mx-auto max-w-7xl">
|
||||
<motion.h2 className="text-headline mb-16" style={{ color: '#0a0a0a' }}
|
||||
initial="hidden" whileInView="visible" viewport={{ once: true }}
|
||||
variants={{ hidden: { opacity: 0, y: 28 }, visible: { opacity: 1, y: 0, transition: { duration: 0.6, delay: 0.1, ease: [0.22, 1, 0.36, 1] } } }}>
|
||||
Los principios que guían cada proyecto.
|
||||
</motion.h2>
|
||||
<div className="grid gap-8 md:grid-cols-5">
|
||||
{valores.map((v, i) => (
|
||||
<motion.div key={i}
|
||||
initial="hidden" whileInView="visible" viewport={{ once: true }}
|
||||
variants={{ hidden: { opacity: 0, y: 20 }, visible: { opacity: 1, y: 0, transition: { duration: 0.5, delay: i * 0.08, ease: [0.22, 1, 0.36, 1] } } }}
|
||||
style={{ borderTop: '3px solid #3b82f6', paddingTop: 24 }}>
|
||||
<span className="font-display font-black block mb-4" style={{ fontSize: 48, lineHeight: 1, color: '#3b82f6', letterSpacing: '-0.04em' }}>{v.num}</span>
|
||||
<h3 className="font-display font-bold text-base mb-3" style={{ color: '#0a0a0a' }}>{v.titulo}</h3>
|
||||
<p className="text-sm leading-relaxed" style={{ color: '#6a6866' }}>{v.desc}</p>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Equipo */}
|
||||
<section style={{ background: '#f5f5f3', borderBottom: '1px solid #dddbd5', padding: '100px 0' }} className="px-8 md:px-14">
|
||||
<div className="mx-auto max-w-7xl">
|
||||
<motion.h2 className="text-headline mb-16" style={{ color: '#0a0a0a' }}
|
||||
initial={{ opacity: 0, y: 24 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }}
|
||||
transition={{ duration: 0.6, ease: [0.22, 1, 0.36, 1] }}>
|
||||
El equipo detrás del código.
|
||||
</motion.h2>
|
||||
|
||||
{loadingTeam ? (
|
||||
<Loader className="py-16" />
|
||||
) : departments.length === 0 ? (
|
||||
<p className="text-sm" style={{ color: '#aaa8a4' }}>Sin miembros registrados.</p>
|
||||
) : (
|
||||
<div className="flex flex-col gap-14">
|
||||
{departments.map((dept, di) => (
|
||||
<motion.div key={dept.name}
|
||||
initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: di * 0.08, ease: [0.22, 1, 0.36, 1] }}>
|
||||
{/* Dept header */}
|
||||
<div className="flex items-center gap-3 mb-0">
|
||||
<span style={{ width: 3, height: 16, background: '#3b82f6', borderRadius: 99, flexShrink: 0, display: 'block' }} />
|
||||
<p className="text-xs font-semibold uppercase tracking-widest" style={{ color: '#6a6866' }}>
|
||||
{dept.name}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Hierarchy connector */}
|
||||
<div style={{ position: 'relative', paddingLeft: 20, marginLeft: 1 }}>
|
||||
{/* Vertical line */}
|
||||
<span style={{ position: 'absolute', left: 0, top: 0, bottom: 24, width: 1, background: '#dddbd5' }} />
|
||||
{/* Horizontal branch */}
|
||||
<span style={{ position: 'absolute', left: 0, top: 24, width: 16, height: 1, background: '#dddbd5' }} />
|
||||
|
||||
<div className="grid gap-3 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 pt-6">
|
||||
{dept.members.map((m, mi) => (
|
||||
<motion.div key={m.id}
|
||||
initial={{ opacity: 0, y: 12 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }}
|
||||
transition={{ duration: 0.4, delay: mi * 0.05, ease: [0.22, 1, 0.36, 1] }}
|
||||
style={{ background: '#ffffff', borderRadius: 6, padding: '20px', display: 'flex', alignItems: 'center', gap: 14, boxShadow: '0 1px 3px rgba(0,0,0,0.07)' }}>
|
||||
<div style={{ width: 44, height: 44, borderRadius: '50%', background: 'linear-gradient(135deg,#3b82f6,#1d4ed8)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
|
||||
<span style={{ color: '#fff', fontWeight: 700, fontSize: 14, letterSpacing: '0.02em' }}>{m.firstName[0]}{m.lastName[0]}</span>
|
||||
</div>
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<p className="font-display font-bold text-sm" style={{ color: '#0a0a0a', lineHeight: 1.3, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{m.firstName} {m.lastName}</p>
|
||||
{m.positionName && <p className="text-xs mt-0.5" style={{ color: '#9ca3af' }}>{m.positionName}</p>}
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
export function SobreNosotrosPage() {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
useEffect(() => setMounted(true), []);
|
||||
if (!mounted) return null;
|
||||
return <SobreNosotrosContent />;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import Loader from '../../components/Loader';
|
||||
|
||||
export default function Loading() {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center" style={{ background: '#f9f9f7' }}>
|
||||
<Loader />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { SobreNosotrosPage } from './SobreNosotrosPage';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Sobre Nosotros | Rynext Technologies',
|
||||
description: 'Conoce al equipo detrás de Rynext Technologies. Software empresarial 100% mexicano de clase mundial, construido con arquitectura real.',
|
||||
alternates: { canonical: 'https://rynext.com.mx/sobre-nosotros' },
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
return <SobreNosotrosPage />;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
'use client';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
export default function Template({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0, transition: { duration: 0.35, ease: [0.22, 1, 0.36, 1] } }}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user