first commit
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
'use client';
|
||||
|
||||
import { motion } from 'framer-motion';
|
||||
import { useLocale } from '@/lib/i18n/locale-context';
|
||||
import { SectionHeader } from '@/components/ui/SectionHeader';
|
||||
|
||||
/**
|
||||
* Animated RAG pipeline: ingest → embed → retrieve → rerank → generate.
|
||||
*
|
||||
* The diagram itself is always laid out left-to-right (dir="ltr") regardless of
|
||||
* page locale — a data pipeline reads forward in both languages — while the
|
||||
* labels/descriptions come from the localized dictionary. The flowing dashes
|
||||
* are pure SVG (animated stroke-dashoffset), so there is no per-frame JS.
|
||||
*/
|
||||
|
||||
type Accent = 'electric' | 'violet' | 'cyan' | 'magenta' | 'emerald';
|
||||
|
||||
const ACCENT_HEX: Record<Accent, string> = {
|
||||
electric: '#38bdf8',
|
||||
violet: '#818cf8',
|
||||
cyan: '#22d3ee',
|
||||
magenta: '#e879f9',
|
||||
emerald: '#34d399',
|
||||
};
|
||||
|
||||
// Literal class maps so Tailwind's JIT scanner can see every variant.
|
||||
const ACCENT_TEXT: Record<Accent, string> = {
|
||||
electric: 'text-electric',
|
||||
violet: 'text-violet',
|
||||
cyan: 'text-cyan',
|
||||
magenta: 'text-magenta',
|
||||
emerald: 'text-emerald',
|
||||
};
|
||||
const ACCENT_BORDER: Record<Accent, string> = {
|
||||
electric: 'border-electric/40',
|
||||
violet: 'border-violet/40',
|
||||
cyan: 'border-cyan/40',
|
||||
magenta: 'border-magenta/40',
|
||||
emerald: 'border-emerald/40',
|
||||
};
|
||||
const ACCENT_HOVER_SHADOW: Record<Accent, string> = {
|
||||
electric: 'hover:shadow-[0_0_30px_-12px_#38bdf8]',
|
||||
violet: 'hover:shadow-[0_0_30px_-12px_#818cf8]',
|
||||
cyan: 'hover:shadow-[0_0_30px_-12px_#22d3ee]',
|
||||
magenta: 'hover:shadow-[0_0_30px_-12px_#e879f9]',
|
||||
emerald: 'hover:shadow-[0_0_30px_-12px_#34d399]',
|
||||
};
|
||||
|
||||
function asAccent(value: string | undefined): Accent {
|
||||
return value === 'violet' ||
|
||||
value === 'cyan' ||
|
||||
value === 'magenta' ||
|
||||
value === 'emerald' ||
|
||||
value === 'electric'
|
||||
? value
|
||||
: 'electric';
|
||||
}
|
||||
|
||||
export function DataFlow() {
|
||||
const { t } = useLocale();
|
||||
const data = t.dataflow;
|
||||
const nodes = data.nodes;
|
||||
|
||||
return (
|
||||
<section id="dataflow" className="relative px-5 py-28 sm:px-8">
|
||||
<div className="mx-auto max-w-7xl">
|
||||
<SectionHeader eyebrow={data.eyebrow} title={data.title} sub={data.sub} />
|
||||
|
||||
{/* Diagram canvas — fixed LTR reading order. */}
|
||||
<div dir="ltr" className="relative mt-14">
|
||||
{/* SVG connectors sit behind the cards on md+ (horizontal flow). */}
|
||||
<svg
|
||||
aria-hidden
|
||||
viewBox="0 0 1000 120"
|
||||
preserveAspectRatio="none"
|
||||
className="pointer-events-none absolute inset-x-0 top-1/2 hidden h-28 -translate-y-1/2 md:block"
|
||||
>
|
||||
<defs>
|
||||
<linearGradient id="flow-line" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0%" stopColor="#38bdf8" />
|
||||
<stop offset="25%" stopColor="#818cf8" />
|
||||
<stop offset="50%" stopColor="#22d3ee" />
|
||||
<stop offset="75%" stopColor="#e879f9" />
|
||||
<stop offset="100%" stopColor="#34d399" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
{/* Static base rail */}
|
||||
<line
|
||||
x1="40"
|
||||
y1="60"
|
||||
x2="960"
|
||||
y2="60"
|
||||
stroke="url(#flow-line)"
|
||||
strokeWidth="1.5"
|
||||
strokeOpacity="0.28"
|
||||
/>
|
||||
{/* Animated travelling packets */}
|
||||
<line
|
||||
x1="40"
|
||||
y1="60"
|
||||
x2="960"
|
||||
y2="60"
|
||||
stroke="url(#flow-line)"
|
||||
strokeWidth="2.5"
|
||||
strokeLinecap="round"
|
||||
strokeDasharray="6 60"
|
||||
className="animate-flow-dash"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<ol className="relative grid grid-cols-1 gap-5 sm:grid-cols-2 md:grid-cols-5 md:gap-3">
|
||||
{nodes.map((node, i) => {
|
||||
const accent = asAccent(node.accent);
|
||||
return (
|
||||
<motion.li
|
||||
key={node.id}
|
||||
initial={{ opacity: 0, y: 22 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-60px' }}
|
||||
transition={{
|
||||
duration: 0.5,
|
||||
ease: [0.22, 1, 0.36, 1],
|
||||
delay: 0.08 * i,
|
||||
}}
|
||||
className="relative"
|
||||
>
|
||||
<div
|
||||
className={`glass group relative flex h-full flex-col gap-3 rounded-2xl border ${ACCENT_BORDER[accent]} bg-white/[0.02] p-5 transition-shadow duration-500 ${ACCENT_HOVER_SHADOW[accent]}`}
|
||||
>
|
||||
{/* Step index + pulsing node dot */}
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="font-mono text-[0.7rem] text-slate-500">
|
||||
{String(i + 1).padStart(2, '0')}
|
||||
</span>
|
||||
<span className="relative flex h-2.5 w-2.5">
|
||||
<span
|
||||
className="absolute inline-flex h-full w-full animate-ping rounded-full opacity-60"
|
||||
style={{ backgroundColor: ACCENT_HEX[accent] }}
|
||||
/>
|
||||
<span
|
||||
className="relative inline-flex h-2.5 w-2.5 rounded-full"
|
||||
style={{ backgroundColor: ACCENT_HEX[accent] }}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h3
|
||||
className={`font-display text-lg font-semibold ${ACCENT_TEXT[accent]}`}
|
||||
>
|
||||
{node.label}
|
||||
</h3>
|
||||
<p className="text-sm leading-relaxed text-slate-400">
|
||||
{node.desc}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Arrow connector for stacked (mobile / sm) layouts */}
|
||||
{i < nodes.length - 1 && (
|
||||
<span
|
||||
aria-hidden
|
||||
className="absolute left-1/2 top-full z-10 -translate-x-1/2 text-slate-600 sm:hidden"
|
||||
>
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none">
|
||||
<path
|
||||
d="M12 4v16m0 0l6-6m-6 6l-6-6"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
)}
|
||||
</motion.li>
|
||||
);
|
||||
})}
|
||||
</ol>
|
||||
|
||||
{data.caption && (
|
||||
<motion.p
|
||||
initial={{ opacity: 0 }}
|
||||
whileInView={{ opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6, delay: 0.4 }}
|
||||
className="mt-10 text-center font-mono text-[0.72rem] uppercase tracking-[0.18em] text-slate-500"
|
||||
>
|
||||
{data.caption}
|
||||
</motion.p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user