Primer vistaso
This commit is contained in:
370
src/components/Hero/Hero.module.css
Normal file
370
src/components/Hero/Hero.module.css
Normal file
@@ -0,0 +1,370 @@
|
||||
.hero {
|
||||
padding-top: 72px; /* navbar height */
|
||||
background: var(--color-white);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
padding-top: var(--space-20);
|
||||
padding-bottom: var(--space-16);
|
||||
gap: var(--space-6);
|
||||
}
|
||||
|
||||
/* Reveal animation */
|
||||
.reveal {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
transition: opacity 0.7s ease, transform 0.7s ease;
|
||||
}
|
||||
|
||||
.reveal.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.reveal:nth-child(2) { transition-delay: 0.1s; }
|
||||
.reveal:nth-child(3) { transition-delay: 0.2s; }
|
||||
.reveal:nth-child(4) { transition-delay: 0.3s; }
|
||||
.reveal:nth-child(5) { transition-delay: 0.4s; }
|
||||
.reveal:nth-child(6) { transition-delay: 0.5s; }
|
||||
.reveal:nth-child(7) { transition-delay: 0.6s; }
|
||||
.reveal:nth-child(8) { transition-delay: 0.7s; }
|
||||
|
||||
/* Badge */
|
||||
.badgeWrap {
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
/* Heading */
|
||||
.heading {
|
||||
font-size: clamp(2.5rem, 7vw, 5rem);
|
||||
font-weight: 900;
|
||||
letter-spacing: -0.04em;
|
||||
line-height: 1.05;
|
||||
color: var(--color-black);
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.emphasis {
|
||||
font-style: italic;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
/* Subheading */
|
||||
.subheading {
|
||||
font-size: clamp(var(--text-base), 2vw, var(--text-xl));
|
||||
color: var(--color-gray-600);
|
||||
max-width: 520px;
|
||||
line-height: 1.6;
|
||||
margin-top: var(--space-2);
|
||||
}
|
||||
|
||||
/* CTAs */
|
||||
.ctas {
|
||||
display: flex;
|
||||
gap: var(--space-3);
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
margin-top: var(--space-2);
|
||||
}
|
||||
|
||||
/* Trust note */
|
||||
.trustNote {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--color-gray-400);
|
||||
margin-top: var(--space-1);
|
||||
}
|
||||
|
||||
/* Dashboard Mockup */
|
||||
.mockupWrap {
|
||||
width: 100%;
|
||||
max-width: 900px;
|
||||
margin-top: var(--space-8);
|
||||
perspective: 1200px;
|
||||
}
|
||||
|
||||
.mockup {
|
||||
background: var(--color-white);
|
||||
border: 1px solid var(--color-gray-200);
|
||||
border-radius: var(--radius-xl);
|
||||
box-shadow: var(--shadow-xl), 0 0 0 1px rgba(0, 0, 0, 0.03);
|
||||
overflow: hidden;
|
||||
transform: rotateX(4deg);
|
||||
transition: transform 0.4s ease;
|
||||
}
|
||||
|
||||
.mockup:hover {
|
||||
transform: rotateX(0deg);
|
||||
}
|
||||
|
||||
/* Window bar */
|
||||
.mockupBar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
background: var(--color-gray-50);
|
||||
border-bottom: 1px solid var(--color-gray-200);
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.mockupUrl {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: var(--text-xs);
|
||||
color: var(--color-gray-400);
|
||||
background: var(--color-white);
|
||||
border: 1px solid var(--color-gray-200);
|
||||
border-radius: var(--radius-full);
|
||||
padding: 2px var(--space-3);
|
||||
max-width: 280px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Mockup body */
|
||||
.mockupBody {
|
||||
display: flex;
|
||||
height: 380px;
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
.mockupSidebar {
|
||||
width: 56px;
|
||||
background: var(--color-gray-50);
|
||||
border-right: 1px solid var(--color-gray-200);
|
||||
padding: var(--space-4) var(--space-3);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sidebarLogo {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: var(--color-black);
|
||||
border-radius: var(--radius-md);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.sidebarItem {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: var(--color-gray-200);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.sidebarActive {
|
||||
background: var(--color-black) !important;
|
||||
}
|
||||
|
||||
/* Main dashboard */
|
||||
.mockupMain {
|
||||
flex: 1;
|
||||
padding: var(--space-6);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-4);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mockupHeader {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mockupTitle {
|
||||
height: 20px;
|
||||
width: 160px;
|
||||
background: var(--color-gray-200);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.mockupBtn {
|
||||
height: 32px;
|
||||
width: 100px;
|
||||
background: var(--color-black);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
/* Metric cards */
|
||||
.metricsRow {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.metricCard {
|
||||
background: var(--color-gray-50);
|
||||
border: 1px solid var(--color-gray-200);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--space-3);
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.metricIcon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: var(--radius-md);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.metricLines {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.metricValue {
|
||||
height: 14px;
|
||||
background: var(--color-gray-300);
|
||||
border-radius: var(--radius-sm);
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.metricLabel {
|
||||
height: 10px;
|
||||
background: var(--color-gray-200);
|
||||
border-radius: var(--radius-sm);
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
/* Chart */
|
||||
.chartArea {
|
||||
flex: 1;
|
||||
background: var(--color-gray-50);
|
||||
border: 1px solid var(--color-gray-200);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--space-4);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chartBars {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: var(--space-2);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@keyframes growBar {
|
||||
from { height: 0; }
|
||||
to { height: var(--target-height); }
|
||||
}
|
||||
|
||||
.bar {
|
||||
flex: 1;
|
||||
background: var(--color-black);
|
||||
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
|
||||
opacity: 0.12;
|
||||
animation: fadeIn 0.5s ease forwards;
|
||||
}
|
||||
|
||||
.bar:nth-child(odd) {
|
||||
opacity: 0.08;
|
||||
}
|
||||
|
||||
.bar:nth-child(4),
|
||||
.bar:nth-child(8) {
|
||||
opacity: 0.9;
|
||||
background: var(--color-black);
|
||||
}
|
||||
|
||||
/* Task list */
|
||||
.taskList {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.taskRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.taskCheck {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 2px solid var(--color-gray-300);
|
||||
border-radius: var(--radius-sm);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.taskLine {
|
||||
height: 10px;
|
||||
background: var(--color-gray-200);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
/* Stats */
|
||||
.stats {
|
||||
display: flex;
|
||||
gap: var(--space-12);
|
||||
margin-top: var(--space-8);
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.statItem {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
.statValue {
|
||||
font-size: var(--text-3xl);
|
||||
font-weight: 900;
|
||||
letter-spacing: -0.04em;
|
||||
color: var(--color-black);
|
||||
}
|
||||
|
||||
.statLabel {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--color-gray-500, #666);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.mockupBody {
|
||||
height: 260px;
|
||||
}
|
||||
|
||||
.mockupSidebar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.metricsRow {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.stats {
|
||||
gap: var(--space-8);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.ctas {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
max-width: 320px;
|
||||
}
|
||||
|
||||
.ctas .btn {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
177
src/components/Hero/Hero.tsx
Normal file
177
src/components/Hero/Hero.tsx
Normal file
@@ -0,0 +1,177 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useRef } from 'react';
|
||||
import styles from './Hero.module.css';
|
||||
|
||||
const stats = [
|
||||
{ value: '10K+', label: 'Equipos activos' },
|
||||
{ value: '99.9%', label: 'Uptime garantizado' },
|
||||
{ value: '3x', label: 'Más productividad' },
|
||||
{ value: '140+', label: 'Países' },
|
||||
];
|
||||
|
||||
export default function Hero() {
|
||||
const heroRef = useRef<HTMLElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add(styles.visible);
|
||||
}
|
||||
});
|
||||
},
|
||||
{ threshold: 0.1 }
|
||||
);
|
||||
|
||||
const elements = heroRef.current?.querySelectorAll(`.${styles.reveal}`);
|
||||
elements?.forEach((el) => observer.observe(el));
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
const handleScrollToContact = () => {
|
||||
document.querySelector('#contact')?.scrollIntoView({ behavior: 'smooth' });
|
||||
};
|
||||
|
||||
const handleScrollToFeatures = () => {
|
||||
document.querySelector('#features')?.scrollIntoView({ behavior: 'smooth' });
|
||||
};
|
||||
|
||||
return (
|
||||
<section className={styles.hero} id="hero" ref={heroRef} aria-label="Sección principal">
|
||||
<div className={`container ${styles.inner}`}>
|
||||
{/* Badge */}
|
||||
<div className={`${styles.reveal} ${styles.badgeWrap}`}>
|
||||
<span className="badge badge-dark">
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" aria-hidden="true">
|
||||
<circle cx="4" cy="4" r="4" fill="#00c853" />
|
||||
</svg>
|
||||
Nuevo — Ahora con IA integrada
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Heading */}
|
||||
<h1 className={`${styles.reveal} ${styles.heading}`}>
|
||||
El flujo de trabajo
|
||||
<br />
|
||||
<em className={styles.emphasis}>que tu equipo</em>
|
||||
<br />
|
||||
siempre necesitó
|
||||
</h1>
|
||||
|
||||
{/* Subheading */}
|
||||
<p className={`${styles.reveal} ${styles.subheading}`}>
|
||||
FlowSync conecta a tu equipo, automatiza tareas repetitivas y te da
|
||||
visibilidad total de cada proyecto — todo en un solo lugar.
|
||||
</p>
|
||||
|
||||
{/* CTA Buttons */}
|
||||
<div className={`${styles.reveal} ${styles.ctas}`}>
|
||||
<button
|
||||
className="btn btn-primary btn-lg"
|
||||
id="hero-cta-primary"
|
||||
onClick={handleScrollToContact}
|
||||
>
|
||||
Empieza gratis
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<path d="M3 8h10M9 4l4 4-4 4" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-secondary btn-lg"
|
||||
id="hero-cta-secondary"
|
||||
onClick={handleScrollToFeatures}
|
||||
>
|
||||
Ver características
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Trust note */}
|
||||
<p className={`${styles.reveal} ${styles.trustNote}`}>
|
||||
Sin tarjeta de crédito · 14 días gratis · Cancela cuando quieras
|
||||
</p>
|
||||
|
||||
{/* Dashboard mockup */}
|
||||
<div className={`${styles.reveal} ${styles.mockupWrap}`}>
|
||||
<div className={styles.mockup} role="img" aria-label="Vista previa del dashboard de FlowSync">
|
||||
{/* Window chrome */}
|
||||
<div className={styles.mockupBar}>
|
||||
<span className={styles.dot} style={{ background: '#ff5f57' }} />
|
||||
<span className={styles.dot} style={{ background: '#febc2e' }} />
|
||||
<span className={styles.dot} style={{ background: '#28c840' }} />
|
||||
<span className={styles.mockupUrl}>app.flowsync.io/dashboard</span>
|
||||
</div>
|
||||
|
||||
{/* Mock dashboard content */}
|
||||
<div className={styles.mockupBody}>
|
||||
{/* Sidebar */}
|
||||
<nav className={styles.mockupSidebar} aria-hidden="true">
|
||||
<div className={styles.sidebarLogo} />
|
||||
{[...Array(5)].map((_, i) => (
|
||||
<div key={i} className={`${styles.sidebarItem} ${i === 0 ? styles.sidebarActive : ''}`} />
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{/* Main content */}
|
||||
<main className={styles.mockupMain} aria-hidden="true">
|
||||
{/* Header row */}
|
||||
<div className={styles.mockupHeader}>
|
||||
<div className={styles.mockupTitle} />
|
||||
<div className={styles.mockupBtn} />
|
||||
</div>
|
||||
|
||||
{/* Metric cards */}
|
||||
<div className={styles.metricsRow}>
|
||||
{['#0066ff', '#00c853', '#ff9500', '#6c5ce7'].map((color, i) => (
|
||||
<div key={i} className={styles.metricCard}>
|
||||
<div className={styles.metricIcon} style={{ background: `${color}18`, color }} />
|
||||
<div className={styles.metricLines}>
|
||||
<div className={styles.metricValue} />
|
||||
<div className={styles.metricLabel} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Chart area */}
|
||||
<div className={styles.chartArea}>
|
||||
<div className={styles.chartBars}>
|
||||
{[60, 80, 50, 90, 70, 85, 65, 95, 75, 88].map((h, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={styles.bar}
|
||||
style={{ height: `${h}%`, animationDelay: `${i * 0.05}s` }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Task list */}
|
||||
<div className={styles.taskList}>
|
||||
{[85, 60, 95, 70].map((w, i) => (
|
||||
<div key={i} className={styles.taskRow}>
|
||||
<div className={styles.taskCheck} />
|
||||
<div className={styles.taskLine} style={{ width: `${w}%` }} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats */}
|
||||
<div className={`${styles.reveal} ${styles.stats}`}>
|
||||
{stats.map((stat) => (
|
||||
<div key={stat.label} className={styles.statItem}>
|
||||
<span className={styles.statValue}>{stat.value}</span>
|
||||
<span className={styles.statLabel}>{stat.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user