-
-
0{service.id}
-
-
-
+ {/* Top accent line */}
+
+
+ {/* Header row */}
+
+
+
-
+
+ 0{service.id}
+
-
{service.title}
-
{service.description}
+
{service.title}
+
+ {service.description}
+
+
+ {/* Feature list */}
+
+ {service.features.map((f: string) => (
+ -
+
+
+ {f}
+
+
+ ))}
+
);
})}
@@ -61,4 +93,4 @@ export const Services = ({ isNavyBg }: ThemeProps) => {
);
-};
\ No newline at end of file
+};
diff --git a/src/components/Stack.tsx b/src/components/Stack.tsx
new file mode 100644
index 0000000..945458d
--- /dev/null
+++ b/src/components/Stack.tsx
@@ -0,0 +1,116 @@
+import { motion } from 'framer-motion';
+import type { ThemeProps } from '../types';
+
+const stackGroups = [
+ {
+ label: 'FRONTEND',
+ items: ['React', 'TypeScript', 'Next.js', 'Tailwind CSS', 'Vite'],
+ },
+ {
+ label: 'BACKEND',
+ items: ['Node.js', 'Python', 'Express', 'FastAPI', 'NestJS'],
+ },
+ {
+ label: 'DATOS',
+ items: ['PostgreSQL', 'MongoDB', 'Redis', 'Prisma', 'Elasticsearch'],
+ },
+ {
+ label: 'CLOUD & OPS',
+ items: ['AWS', 'Docker', 'GitHub Actions', 'Terraform', 'Linux'],
+ },
+ {
+ label: 'INTEGRACIONES',
+ items: ['REST', 'GraphQL', 'Stripe', 'OAuth 2.0', 'Webhooks'],
+ },
+];
+
+export const Stack = ({ isNavyBg }: ThemeProps) => {
+ const bg = isNavyBg ? '#0f1520' : '#f9f9f7';
+ const text = isNavyBg ? '#eef0f4' : '#0a0a0a';
+ const sub = isNavyBg ? 'rgba(255,255,255,0.35)' : '#6a6866';
+ const border = isNavyBg ? 'rgba(255,255,255,0.07)' : '#dddbd5';
+ const tagBg = isNavyBg ? 'rgba(255,255,255,0.04)' : '#f0efec';
+
+ return (
+
+
+
+ {/* Header */}
+
+
+
+
+ Las herramientas son el medio
+
+
+ Las herramientas son el medio.
El sistema es el fin.
+
+
+
+ Elegimos la tecnología correcta para cada problema. No hay un stack universal.
+
+
+
+ {/* Stack groups */}
+
+ {stackGroups.map((group, i) => (
+
+
+ {group.label}
+
+
+ {group.items.map(item => (
+
+ {item}
+
+ ))}
+
+
+ ))}
+
+
+ {/* Footnote */}
+
+ Estas son las herramientas con las que trabajamos actualmente. La elección depende siempre del problema, no de la preferencia.
+
+
+
+ );
+};
diff --git a/src/components/SystemMap.tsx b/src/components/SystemMap.tsx
new file mode 100644
index 0000000..b221473
--- /dev/null
+++ b/src/components/SystemMap.tsx
@@ -0,0 +1,226 @@
+import { useState } from 'react';
+
+interface Node {
+ id: string;
+ label: string;
+ x: number;
+ y: number;
+ w: number;
+ h: number;
+ type: 'main' | 'side';
+ desc: string;
+}
+
+interface Edge {
+ from: string;
+ to: string;
+ type: 'main' | 'side';
+}
+
+const NODES: Node[] = [
+ { id: 'frontend', label: 'FRONTEND', x: 80, y: 16, w: 160, h: 38, type: 'main', desc: 'Portales, apps y paneles:interfaces donde el usuario opera' },
+ { id: 'api', label: 'API GATEWAY', x: 80, y: 108, w: 160, h: 38, type: 'main', desc: 'Seguridad, ruteo, rate limiting y versionado unificados' },
+ { id: 'logic', label: 'LÓGICA DE NEGOCIO', x: 80, y: 200, w: 160, h: 38, type: 'main', desc: 'Reglas de negocio, validaciones, procesamiento y orquestación' },
+ { id: 'db', label: 'BASE DE DATOS', x: 80, y: 292, w: 160, h: 38, type: 'main', desc: 'Persistencia diseñada para tu modelo:SQL, NoSQL, caché' },
+ { id: 'infra', label: 'INFRAESTRUCTURA', x: 80, y: 384, w: 160, h: 36, type: 'main', desc: 'Nube, contenedores, CI/CD y observabilidad operacional' },
+ { id: 'payments', label: 'PAGOS', x: 8, y: 172, w: 62, h: 28, type: 'side', desc: 'Stripe, Conekta:flujo de cobro integrado' },
+ { id: 'erp', label: 'ERP / CRM', x: 8, y: 222, w: 62, h: 28, type: 'side', desc: 'SAP, Odoo:sincronización bidireccional' },
+ { id: 'auth', label: 'ACCESO', x: 250, y: 84, w: 62, h: 28, type: 'side', desc: 'SSO, JWT, OAuth 2.0:identidad y permisos por rol' },
+ { id: 'external', label: 'EXTERNOS', x: 250, y: 134, w: 62, h: 28, type: 'side', desc: 'APIs de terceros:logística, notificaciones, reportes' },
+];
+
+const EDGES: Edge[] = [
+ { from: 'frontend', to: 'api', type: 'main' },
+ { from: 'api', to: 'logic', type: 'main' },
+ { from: 'logic', to: 'db', type: 'main' },
+ { from: 'db', to: 'infra', type: 'main' },
+ { from: 'payments', to: 'logic', type: 'side' },
+ { from: 'erp', to: 'logic', type: 'side' },
+ { from: 'auth', to: 'api', type: 'side' },
+ { from: 'external', to: 'api', type: 'side' },
+];
+
+function nodeCenter(n: Node) {
+ return { cx: n.x + n.w / 2, cy: n.y + n.h / 2 };
+}
+
+function edgeLine(edge: Edge): { x1: number; y1: number; x2: number; y2: number } {
+ const from = NODES.find(n => n.id === edge.from)!;
+ const to = NODES.find(n => n.id === edge.to)!;
+ const fc = nodeCenter(from);
+ const tc = nodeCenter(to);
+ if (edge.type === 'main') {
+ return { x1: fc.cx, y1: from.y + from.h, x2: tc.cx, y2: to.y };
+ }
+ if (from.x < to.x) {
+ return { x1: from.x + from.w, y1: fc.cy, x2: to.x, y2: tc.cy };
+ }
+ return { x1: from.x, y1: fc.cy, x2: to.x + to.w, y2: tc.cy };
+}
+
+function isConnected(nodeId: string, activeId: string | null) {
+ if (!activeId || nodeId === activeId) return false;
+ return EDGES.some(e =>
+ (e.from === activeId && e.to === nodeId) ||
+ (e.to === activeId && e.from === nodeId),
+ );
+}
+
+function isEdgeActive(edge: Edge, activeId: string | null) {
+ if (!activeId) return false;
+ return edge.from === activeId || edge.to === activeId;
+}
+
+const TOOLTIP_W = 172;
+const TAIL_H = 9;
+const TAIL_W = 14;
+
+function tooltipHeight(desc: string): number {
+ const charsPerLine = Math.floor((TOOLTIP_W - 26) / 6.3); // ~aprox chars por línea a 10.5px
+ const lines = Math.ceil(desc.length / charsPerLine);
+ return 14 + 16 + lines * 17 + 14; // paddingTop + label + líneas + paddingBottom
+}
+
+export function SystemMap({ isNavyBg = true }: { isNavyBg?: boolean }) {
+ const [active, setActive] = useState
(null);
+
+ const RED = '#e50914';
+ const dimStroke = isNavyBg ? 'rgba(255,255,255,0.12)' : 'rgba(0,0,0,0.12)';
+ const defStroke = isNavyBg ? 'rgba(255,255,255,0.25)' : 'rgba(0,31,91,0.3)';
+ const connStroke = 'rgba(229,9,20,0.45)';
+ const descText = isNavyBg ? 'rgba(255,255,255,0.7)' : 'rgba(0,0,0,0.65)';
+ const tooltipBg = isNavyBg ? '#0a1628' : '#ffffff';
+ const tooltipBorder = isNavyBg ? 'rgba(255,255,255,0.12)' : 'rgba(0,31,91,0.15)';
+ void tooltipBorder;
+ const activeDesc = NODES.find(n => n.id === active);
+
+ return (
+
+
+
+ );
+}
diff --git a/src/data/machineryData.ts b/src/data/machineryData.ts
new file mode 100644
index 0000000..e8890ca
--- /dev/null
+++ b/src/data/machineryData.ts
@@ -0,0 +1,37 @@
+export const machineryData = [
+ {
+ id: 1,
+ num: '01',
+ title: 'OPERACIONES',
+ description: 'Plataformas internas, automatización de flujos y control de procesos críticos. Sistemas que eliminan la fricción operativa y dan visibilidad en tiempo real.',
+ tags: ['Portales de gestión', 'Automatización de flujos', 'Reportes operacionales', 'Control de acceso por rol'],
+ },
+ {
+ id: 2,
+ num: '02',
+ title: 'VENTAS',
+ description: 'Sistemas de venta, cobro y facturación integrados al núcleo del negocio. Desde el punto de contacto con el cliente hasta el comprobante fiscal.',
+ tags: ['E-commerce a medida', 'Integración de pagos', 'Facturación y CFDI', 'Recuperación de carritos'],
+ },
+ {
+ id: 3,
+ num: '03',
+ title: 'INTEGRACIONES',
+ description: 'Conectamos sistemas heterogéneos en un ecosistema coherente: ERPs, CRMs, proveedores y APIs de terceros. Sin silos, sin duplicidad de datos.',
+ tags: ['ERP / SAP / Odoo', 'APIs de terceros', 'Sincronización bidireccional', 'Webhooks y eventos'],
+ },
+ {
+ id: 4,
+ num: '04',
+ title: 'AUTOMATIZACIÓN',
+ description: 'Eliminamos intervención humana donde no aporta valor: aprobaciones, notificaciones, sincronizaciones y pipelines de datos que corren solos.',
+ tags: ['Bots y pipelines', 'Procesamiento de datos', 'Notificaciones inteligentes', 'Triggers y colas'],
+ },
+ {
+ id: 5,
+ num: '05',
+ title: 'DATOS',
+ description: 'Instrumentación del negocio para tomar decisiones con evidencia. Dashboards ejecutivos, modelos analíticos y alertas que activan antes de que el problema escale.',
+ tags: ['Dashboards ejecutivos', 'Pipelines de datos', 'KPIs e instrumentación', 'Alertas y umbrales'],
+ },
+];
diff --git a/src/data/servicesData.ts b/src/data/servicesData.ts
index be54649..9f37bac 100644
--- a/src/data/servicesData.ts
+++ b/src/data/servicesData.ts
@@ -5,29 +5,37 @@ export const servicesData: ServiceItem[] = [
{
id: 1,
title: 'Desarrollo de software',
- description: 'Arquitecturas a medida, código mantenible y desarrollo de plataformas de alto rendimiento.',
+ description: 'Plataformas web y APIs construidas con arquitectura limpia, código mantenible y enfoque en rendimiento real.',
+ features: ['APIs REST & GraphQL', 'Microservicios y monolitos modulares', 'Code review y auditorías técnicas'],
icon: Code,
gridCols: 'md:col-span-7',
+ accent: 'blue',
},
{
id: 2,
- title: 'Soluciones en la nube',
- description: 'Infraestructura cloud escalable, migración y alta disponibilidad 24/7 sin interrupciones.',
+ title: 'Infraestructura en la nube',
+ description: 'Diseño, migración y operación de infraestructura cloud escalable con alta disponibilidad.',
+ features: ['AWS, GCP y Azure', 'CI/CD y DevOps', 'Monitoreo y alertas 24/7'],
icon: Cloud,
gridCols: 'md:col-span-5',
+ accent: 'blue',
},
{
id: 3,
- title: 'Tecnología segura',
- description: 'Protección de datos corporativos, cifrado avanzado y protocolos de ciberseguridad activa.',
+ title: 'Ciberseguridad aplicada',
+ description: 'Protección de datos, auditorías de seguridad y protocolos que cumplen estándares empresariales.',
+ features: ['Pruebas de penetración', 'Cifrado end-to-end', 'Cumplimiento GDPR / ISO'],
icon: ShieldCheck,
gridCols: 'md:col-span-5',
+ accent: 'red',
},
{
id: 4,
- title: 'Innovación que impulsa negocios',
- description: 'Estrategias tecnológicas orientadas a optimizar operaciones y acelerar el crecimiento comercial.',
+ title: 'Producto y crecimiento',
+ description: 'Estrategia tecnológica alineada al negocio para lanzar, medir y escalar con datos reales.',
+ features: ['Roadmap técnico y de producto', 'Analytics e instrumentación', 'Optimización de conversión'],
icon: TrendingUp,
gridCols: 'md:col-span-7',
+ accent: 'red',
},
-];
\ No newline at end of file
+];
diff --git a/src/hooks/useScrollSnap.ts b/src/hooks/useScrollSnap.ts
new file mode 100644
index 0000000..1d41388
--- /dev/null
+++ b/src/hooks/useScrollSnap.ts
@@ -0,0 +1,60 @@
+import { useEffect, useRef } from 'react';
+
+export function useScrollSnap() {
+ const locked = useRef(false);
+ const current = useRef(0);
+
+ useEffect(() => {
+ const getSections = () =>
+ Array.from(document.querySelectorAll('section, footer')) as HTMLElement[];
+
+ const goTo = (index: number) => {
+ const sections = getSections();
+ if (index < 0 || index >= sections.length) return;
+ current.current = index;
+ locked.current = true;
+ sections[index].scrollIntoView({ behavior: 'smooth', block: 'start' });
+ setTimeout(() => { locked.current = false; }, 900);
+ };
+
+ const updateCurrent = () => {
+ const sections = getSections();
+ const mid = window.innerHeight / 2;
+ sections.forEach((s, i) => {
+ const rect = s.getBoundingClientRect();
+ if (rect.top <= mid && rect.bottom > mid) current.current = i;
+ });
+ };
+
+ const onWheel = (e: WheelEvent) => {
+ if (locked.current) { e.preventDefault(); return; }
+ updateCurrent();
+ const dir = e.deltaY > 0 ? 1 : -1;
+ const next = current.current + dir;
+ const sections = getSections();
+ if (next >= 0 && next < sections.length) {
+ e.preventDefault();
+ goTo(next);
+ }
+ };
+
+ const onKeyDown = (e: KeyboardEvent) => {
+ if (locked.current) return;
+ updateCurrent();
+ if (e.key === 'ArrowDown' || e.key === 'PageDown') {
+ e.preventDefault();
+ goTo(current.current + 1);
+ } else if (e.key === 'ArrowUp' || e.key === 'PageUp') {
+ e.preventDefault();
+ goTo(current.current - 1);
+ }
+ };
+
+ window.addEventListener('wheel', onWheel, { passive: false });
+ window.addEventListener('keydown', onKeyDown);
+ return () => {
+ window.removeEventListener('wheel', onWheel);
+ window.removeEventListener('keydown', onKeyDown);
+ };
+ }, []);
+}
diff --git a/src/index.css b/src/index.css
index c5f50ba..fca2ca1 100644
--- a/src/index.css
+++ b/src/index.css
@@ -1,34 +1,89 @@
-@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@700;800;900&family=Inter:wght@300;400;500;600&display=swap');
+@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,700;0,800;0,900;1,800&family=Inter:wght@300;400;500;600&display=swap');
@import "tailwindcss";
@theme {
--color-brand-blue: #0077c2;
--color-brand-navy: #001f5b;
- --color-brand-black: #000000;
+ --color-brand-black: #0a0a0a;
--color-brand-red: #e50914;
+ --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: 'Inter', sans-serif;
- --font-display: 'Plus Jakarta Sans', sans-serif;
+ --font-sans: 'Inter', system-ui, sans-serif;
+ --font-display: 'Plus Jakarta Sans', system-ui, sans-serif;
+ --font-mono: 'SF Mono', 'Cascadia Code', 'Fira Code', Consolas, monospace;
+}
+
+::selection {
+ background: rgba(229, 9, 20, 0.18);
+ color: inherit;
}
@layer base {
+
body {
- @apply font-sans antialiased m-0 p-0 overflow-x-hidden selection:bg-brand-blue selection:text-white;
+ @apply font-sans antialiased m-0 p-0 overflow-x-hidden;
+ background-color: #f9f9f7;
+ color: #0a0a0a;
}
- h1,
- h2,
- h3,
- h4,
- .font-display {
+ h1, h2, h3, h4, .font-display {
font-family: var(--font-display);
}
+
+ *:focus-visible {
+ outline: 2px solid #e50914;
+ outline-offset: 2px;
+ }
+
+ ::selection {
+ background: #001f5b;
+ color: #fff;
+ }
}
+/* 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;
@@ -36,51 +91,74 @@
left: 0;
right: 0;
height: 1px;
- background: linear-gradient(90deg, transparent, rgba(0, 31, 91, 0.24), transparent);
- opacity: 0.55;
+ background: linear-gradient(90deg, transparent, currentColor, transparent);
+ opacity: 0.12;
pointer-events: none;
}
-.visual-shell {
- position: relative;
+/* 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;
}
-.image-tile {
- border-radius: 18px;
+/* 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;
}
-.visual-shell::before {
- content: '';
- position: absolute;
- inset: 0;
- background:
- radial-gradient(circle at top right, rgba(229, 9, 20, 0.18), transparent 24%),
- radial-gradient(circle at bottom left, rgba(0, 119, 194, 0.24), transparent 26%);
- pointer-events: none;
+/* Modal fade */
+@keyframes fadeIn {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+.animate-fadeIn {
+ animation: fadeIn 200ms ease;
}
-.visual-grid {
- position: relative;
- z-index: 1;
+/* 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;
+ }
}
-
-.placeholder-tile {
- backdrop-filter: blur(8px);
-}
-
-.text-hero-dacodes {
- font-family: var(--font-display);
- font-size: clamp(2.5rem, 6vw, 5rem);
- line-height: 1.05;
- font-weight: 900;
- letter-spacing: -0.035em;
-}
-
-.text-section-title {
- font-family: var(--font-display);
- font-size: clamp(2rem, 4vw, 3.5rem);
- line-height: 1.1;
- font-weight: 800;
- letter-spacing: -0.03em;
-}
\ No newline at end of file
diff --git a/src/main.tsx b/src/main.tsx
index bef5202..ae3d161 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,10 +1,13 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
+import { BrowserRouter } from 'react-router'
import './index.css'
import App from './App.tsx'
createRoot(document.getElementById('root')!).render(
-
+
+
+
,
)
diff --git a/src/pages/Blog.tsx b/src/pages/Blog.tsx
new file mode 100644
index 0000000..fba4cca
--- /dev/null
+++ b/src/pages/Blog.tsx
@@ -0,0 +1,151 @@
+import { useEffect } from 'react';
+import { motion } from 'framer-motion';
+
+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 } },
+};
+
+const articulos = [
+ {
+ categoria: 'ARQUITECTURA',
+ titulo: 'Por qué el software empresarial fracasa antes de llegar a producción',
+ desc: 'La mayoría de los proyectos de software empresarial no fallan en el código, fallan en el diseño. Exploramos las causas estructurales más comunes y cómo evitarlas desde el inicio.',
+ fecha: 'Próximamente',
+ img: 'https://images.unsplash.com/photo-1558494949-ef010cbdcc31?w=800&q=80',
+ },
+ {
+ categoria: 'INTEGRACIONES',
+ titulo: 'ERP vs. sistema a medida: cuándo tiene sentido cada uno',
+ desc: 'La respuesta no es simple. Depende del tamaño de la operación, la madurez del proceso y la capacidad técnica interna. Aquí el marco de decisión que usamos con nuestros clientes.',
+ fecha: 'Próximamente',
+ img: 'https://images.unsplash.com/photo-1451187580459-43490279c0fa?w=800&q=80',
+ },
+ {
+ categoria: 'DATOS',
+ titulo: 'Instrumentación operacional: medir lo que importa',
+ desc: 'Tener datos no es lo mismo que tener visibilidad. La diferencia está en qué se mide, cómo se almacena y cuándo se activa una alerta. Una guía práctica para equipos de operaciones.',
+ fecha: 'Próximamente',
+ img: 'https://images.unsplash.com/photo-1460925895917-afdab827c52f?w=800&q=80',
+ },
+];
+
+export const Blog = () => {
+ useEffect(() => {
+ document.title = 'Blog | Rynext Technologies';
+ }, []);
+
+ return (
+
+
+ {/* Hero */}
+
+
+
+ RECURSOS
+
+
+ Pensamiento de ingeniería.
+
+
+ Perspectivas sobre arquitectura de software, sistemas empresariales y decisiones técnicas
+ que impactan la operación real de los negocios.
+
+
+
+
+ {/* Artículos */}
+
+
+
+ PRÓXIMOS ARTÍCULOS
+
+
+ {articulos.map((art, i) => (
+
+ {/* Thumbnail */}
+
+

{ (e.currentTarget as HTMLImageElement).style.display = 'none'; }}
+ />
+ {/* Categoria badge encima */}
+
{art.categoria}
+
+ {/* Contenido */}
+
+
+ {art.titulo}
+
+
+ {art.desc}
+
+
+ {art.fecha}
+
+
+
+ ))}
+
+
+
+
+ El blog está en preparación. Los artículos llegarán pronto.
+
+
+
+
+
+
+ );
+};
diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx
new file mode 100644
index 0000000..777ad58
--- /dev/null
+++ b/src/pages/Home.tsx
@@ -0,0 +1,24 @@
+import { Hero } from '../components/Hero';
+import { Problem } from '../components/Problem';
+import { Machinery } from '../components/Machinery';
+import { Anatomy } from '../components/Anatomy';
+import { Process } from '../components/Process';
+import { Principles } from '../components/Principles';
+import { CtaBanner } from '../components/CtaBanner';
+import type { ThemeProps } from '../types';
+
+interface HomeProps extends ThemeProps {
+ onOpenContact: () => void;
+}
+
+export const Home = ({ isNavyBg, onOpenContact }: HomeProps) => (
+
+
+
+
+
+
+
+
+
+);
diff --git a/src/pages/Productos.tsx b/src/pages/Productos.tsx
new file mode 100644
index 0000000..032c59f
--- /dev/null
+++ b/src/pages/Productos.tsx
@@ -0,0 +1,110 @@
+import { useEffect } from 'react';
+import { motion } from 'framer-motion';
+
+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 } },
+};
+
+export const Productos = () => {
+ useEffect(() => {
+ document.title = 'Productos | Rynext Technologies';
+ }, []);
+
+ return (
+
+
+ {/* Hero */}
+
+
+
+ PRODUCTOS
+
+
+ Software diseñado para operar.
+
+
+ Módulos y licencias listos para implementar, respaldados por la misma ingeniería
+ con la que construimos sistemas a medida.
+
+
+
+
+ {/* Producto — comentado hasta tener contenido real */}
+ {/*
+
+
+
+
+
NOMBRE DEL PRODUCTO
+
Tu producto aquí
+
+
+ Descripción del producto. Qué resuelve, para quién está diseñado y qué lo hace diferente.
+ Agrega aquí los detalles que quieres que el cliente vea antes de solicitar una demo.
+
+
+
+
+ */}
+
+
+ );
+};
diff --git a/src/pages/SobreNosotros.tsx b/src/pages/SobreNosotros.tsx
new file mode 100644
index 0000000..2485e8e
--- /dev/null
+++ b/src/pages/SobreNosotros.tsx
@@ -0,0 +1,173 @@
+import { useEffect } from 'react';
+import { motion } from 'framer-motion';
+
+const valores = [
+ { num: '01', titulo: 'Arquitectura primero', desc: 'Cada sistema empieza con un diseño estructural claro antes de escribir la primera línea de código.' },
+ { num: '02', titulo: 'Sin atajos técnicos', desc: 'No usamos soluciones genéricas donde la operación exige algo específico. La deuda técnica cuesta más a largo plazo.' },
+ { num: '03', titulo: 'Código como contrato', desc: 'El software que entregamos es la documentación viva de la operación del cliente. Tiene que ser legible y mantenible.' },
+ { num: '04', titulo: 'Iteración con propósito', desc: 'Construimos en ciclos cortos y medibles. Cada entrega aporta valor real y permite ajustar la dirección.' },
+ { num: '05', titulo: 'Responsabilidad completa', desc: 'Desde el análisis hasta la producción, somos dueños del resultado. No transferimos riesgo al cliente.' },
+];
+
+const TEAM_IMAGE = 'https://images.unsplash.com/photo-1522071820081-009f0129c71c?w=1200&q=80';
+
+export const SobreNosotros = () => {
+ useEffect(() => {
+ document.title = 'Sobre Nosotros | Rynext Technologies';
+ }, []);
+
+ return (
+
+
+ {/* Hero — texto + imagen lado a lado */}
+
+
+ {/* Texto */}
+
+
+ Software 100%{' '}
+
+ Mexicano
+
+ {' '}
+ de clase mundial.
+
+
+ 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.
+
+
+
+ {/* Imagen del equipo */}
+
+ {/* Acento rojo desplazado atrás */}
+
+ {/* Imagen con corte diagonal */}
+
+
+
+
+
+ {/* Por qué Rynext */}
+
+
+
+ ¿Por qué Rynext Technologies?
+
+
+ {[
+ { num: '01', titulo: 'Diseño desde la raíz', desc: 'Cada sistema empieza con un análisis profundo de la operación. No adaptamos plantillas, diseñamos desde cero para el contexto específico del negocio.' },
+ { num: '02', titulo: 'Equipo técnico propio', desc: 'Sin intermediarios. El equipo que analiza tu operación es el mismo que diseña la arquitectura y escribe el código.' },
+ { num: '03', titulo: 'Enfoque en producción', desc: 'No entregamos prototipos. Entregamos sistemas listos para operar con carga real, datos reales y usuarios reales.' },
+ ].map((item, i) => (
+
+ {item.num}
+ {item.titulo}
+ {item.desc}
+
+ ))}
+
+
+
+
+ {/* Valores */}
+
+
+
+ Los principios que guían cada proyecto.
+
+
+ {valores.map((v, i) => (
+
+
+ {v.num}
+
+ {v.titulo}
+ {v.desc}
+
+ ))}
+
+
+
+
+
+ );
+};
diff --git a/src/types/index.ts b/src/types/index.ts
index 492f459..e39f2b0 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -1,27 +1,31 @@
-import { type ComponentType, type Dispatch, type SetStateAction } from 'react';
-
-export interface ServiceItem {
- id: number;
- title: string;
- description: string;
- icon: ComponentType<{ size?: number; className?: string }>;
- gridCols: string;
-}
+import type React from 'react';
+import { type Dispatch, type SetStateAction } from 'react';
export interface ThemeProps {
isNavyBg: boolean;
}
+export interface NavbarProps {
+ onOpenContact: () => void;
+}
+
+export interface HeroProps extends ThemeProps {
+ onOpenContact: () => void;
+}
+
export interface ModalProps extends ThemeProps {
isOpen: boolean;
onClose: () => void;
}
-export interface NavbarProps extends ThemeProps {
- setIsNavyBg: Dispatch>;
- onOpenContact: () => void;
+export interface ServiceItem {
+ id: number;
+ title: string;
+ description: string;
+ features: string[];
+ icon: React.ElementType;
+ accent: 'blue' | 'red';
+ gridCols: string;
}
-export interface HeroProps extends ThemeProps {
- onOpenContact: () => void;
-}
\ No newline at end of file
+export type { Dispatch, SetStateAction };
diff --git a/vite.config.ts b/vite.config.ts
index c4069b7..9269c31 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,8 +1,35 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
+import { VitePWA } from 'vite-plugin-pwa'
-// https://vite.dev/config/
export default defineConfig({
- plugins: [react(), tailwindcss()],
+ plugins: [
+ react(),
+ tailwindcss(),
+ VitePWA({
+ registerType: 'autoUpdate',
+ workbox: {
+ globPatterns: ['**/*.{js,css,html,svg,png,ico,woff2}'],
+ runtimeCaching: [
+ {
+ urlPattern: /^https:\/\/fonts\./i,
+ handler: 'CacheFirst',
+ options: { cacheName: 'google-fonts', expiration: { maxEntries: 10, maxAgeSeconds: 60 * 60 * 24 * 365 } },
+ },
+ ],
+ },
+ manifest: {
+ name: 'Rynext Technologies',
+ short_name: 'Rynext',
+ description: 'Ingeniería de software empresarial desde la arquitectura.',
+ theme_color: '#001f5b',
+ background_color: '#ffffff',
+ display: 'standalone',
+ icons: [
+ { src: '/icons/Logo_blanco.svg', sizes: 'any', type: 'image/svg+xml' },
+ ],
+ },
+ }),
+ ],
})