Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 36 additions & 31 deletions web/src/components/PipelineDiagram.jsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,53 @@
import styles from "./PipelineDiagram.module.css";

const steps = [
{ label: "Machine", desc: "Fault code emitted" },
{ label: "TORQ Agent", desc: "AI diagnoses" },
{ label: "Work Order", desc: "Trilingual PDF" },
{ label: "Supervisor", desc: "Approval queue" },
{ label: "Technician", desc: "Dispatched" },
{ label: "1. Machine", desc: "Fault code emitted" },
{ label: "2. TORQ Agent", desc: "AI diagnoses" },
{ label: "3. Work Order", desc: "Trilingual PDF" },
{ label: "4. Supervisor", desc: "Approval queue" },
{ label: "5. Technician", desc: "Dispatched" },
];

function ArrowRight() {
return (
<div className={styles.arrow}>
<svg className={styles.arrowSvg} width="28" height="24" viewBox="0 0 28 24" fill="none">
<path d="M2 12H24M24 12L16 4M24 12L16 20" stroke="#E4E7EB" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</div>
);
}

export default function PipelineDiagram() {
return (
<div className={styles.wrapper}>
<div className={styles.pipeline}>
{steps.map((s, i) => (
<div key={i} className={styles.step}>
<div className={styles.box}>
{/* Desktop / Tablet View */}
<div className={styles.diagramDesktop}>
<div className={styles.svgContainer}>
<svg viewBox="0 0 1000 600" preserveAspectRatio="xMidYMid meet" className={styles.linesSvg}>
{/* The base faint line */}
<path
d="M 100 250 L 500 50 L 900 250 L 750 500 L 250 500 Z"
className={styles.basePath}
/>
{/* The animated data pulse line */}
<path
d="M 100 250 L 500 50 L 900 250 L 750 500 L 250 500 Z"
className={styles.animatedPath}
/>
</svg>
</div>
<div className={styles.nodesContainer}>
{steps.map((s, i) => (
<div key={i} className={`${styles.node} ${styles[`node${i + 1}`]}`}>
<span className={styles.label}>{s.label}</span>
<span className={styles.desc}>{s.desc}</span>
</div>
{i < steps.length - 1 && <ArrowRight />}
))}
</div>
</div>

{/* Mobile Fallback View */}
<div className={styles.diagramMobile}>
<div className={styles.mobileLine} />
<div className={styles.mobileAnimatedLine} />
{steps.map((s, i) => (
<div key={i} className={`${styles.node} ${styles.nodeMobile}`}>
<span className={styles.label}>{s.label}</span>
<span className={styles.desc}>{s.desc}</span>
</div>
))}
</div>
<div className={styles.feedback}>
<svg viewBox="0 0 640 72" className={styles.feedbackSvg}>
<path
d="M 40 28 Q 320 68 600 28"
className={styles.feedbackPath}
/>
<text x="320" y="62" textAnchor="middle" className={styles.feedbackText}>
feedback loop — model improves with every fix
</text>
</svg>
</div>
</div>
);
}
172 changes: 115 additions & 57 deletions web/src/components/PipelineDiagram.module.css
Original file line number Diff line number Diff line change
@@ -1,99 +1,157 @@
.wrapper {
padding: 48px 0 8px;
padding: 48px 0;
width: 100%;
max-width: 1000px;
margin: 0 auto;
}

.pipeline {
display: flex;
align-items: center;
justify-content: center;
gap: 0;
flex-wrap: wrap;
.diagramDesktop {
position: relative;
width: 100%;
aspect-ratio: 10 / 6; /* 1000x600 */
}

.step {
display: flex;
align-items: center;
/* ── SVG Lines ── */
.svgContainer {
position: absolute;
inset: 0;
pointer-events: none;
z-index: 1;
}

.linesSvg {
width: 100%;
height: 100%;
overflow: visible;
}

.basePath {
stroke: var(--border-color);
stroke-width: 2;
fill: none;
}

.animatedPath {
stroke: var(--text-primary);
stroke-width: 3;
fill: none;
stroke-linecap: round;
stroke-dasharray: 20 60;
animation: flowAnimation 3s linear infinite;
}

@keyframes flowAnimation {
0% {
stroke-dashoffset: 80;
}
100% {
stroke-dashoffset: 0;
}
}

.box {
/* ── Nodes ── */
.nodesContainer {
position: absolute;
inset: 0;
z-index: 2;
pointer-events: none;
}

.node {
position: absolute;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
align-items: center;
background: var(--surface);
justify-content: center;
background: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 20px 24px;
min-width: 130px;
transition: border-color 0.3s, box-shadow 0.3s, background 0.35s;
border-radius: 50%;
padding: 16px;
width: 160px;
height: 160px;
box-shadow: var(--shadow-sm);
pointer-events: auto;
transition: transform 0.3s, box-shadow 0.3s, border-color 0.35s;
text-align: center;
}

.box:hover {
border-color: var(--text-primary);
.node:hover {
transform: translate(-50%, -54%);
box-shadow: var(--shadow-md);
border-color: var(--text-primary);
}

.label {
font-family: "ALTGumbo", system-ui, sans-serif;
font-weight: 700;
font-size: 15px;
font-size: 16px;
color: var(--text-primary);
transition: color 0.35s;
}

.desc {
font-family: "ALTGumbo", system-ui, sans-serif;
font-weight: 400;
font-size: 11px;
font-size: 12px;
color: var(--text-muted);
margin-top: 6px;
white-space: nowrap;
transition: color 0.35s;
}

.arrow {
display: flex;
align-items: center;
padding: 0 4px;
user-select: none;
}

.arrowSvg {
display: block;
color: var(--border-color);
transition: color 0.35s;
}
/* Positions matching viewBox="0 0 1000 600"
Path: M 100 250 L 500 50 L 900 250 L 750 500 L 250 500 Z
*/
.node1 { left: 10%; top: 41.66%; } /* 100, 250 */
.node2 { left: 50%; top: 8.33%; } /* 500, 50 */
.node3 { left: 90%; top: 41.66%; } /* 900, 250 */
.node4 { left: 75%; top: 83.33%; } /* 750, 500 */
.node5 { left: 25%; top: 83.33%; } /* 250, 500 */

.arrowSvg path {
stroke: currentColor;
/* ── Mobile Fallback ── */
.diagramMobile {
display: none;
flex-direction: column;
align-items: center;
gap: 32px;
position: relative;
}

.feedback {
display: flex;
justify-content: center;
margin-top: 20px;
.mobileLine {
position: absolute;
top: 0;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 2px;
background: var(--border-color);
z-index: 1;
}

.feedbackSvg {
width: 100%;
max-width: 640px;
height: 72px;
.mobileAnimatedLine {
position: absolute;
top: 0;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 2px;
background: repeating-linear-gradient(to bottom, var(--text-primary) 0, var(--text-primary) 20px, transparent 20px, transparent 80px);
z-index: 2;
background-size: 100% 80px;
animation: scrollDown 2s linear infinite;
}

.feedbackPath {
stroke: var(--border-color);
stroke-width: 2;
stroke-dasharray: 6 4;
fill: none;
transition: stroke 0.35s;
@keyframes scrollDown {
0% { background-position: 0 -80px; }
100% { background-position: 0 0; }
}

.wrapper:hover .feedbackPath {
stroke: var(--text-muted);
.nodeMobile {
position: relative;
z-index: 3;
transform: none !important;
}

.feedbackText {
font-family: "ALTGumbo", system-ui, sans-serif;
font-size: 11px;
fill: var(--text-muted);
transition: fill 0.35s;
@media (max-width: 768px) {
.diagramDesktop { display: none; }
.diagramMobile { display: flex; }
}
10 changes: 5 additions & 5 deletions web/src/pages/LandingPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const features = [
},
{
icon: <IconRefresh />,
title: "Self-Learning",
title: "Self Learning",
description: "Every fix enriches the knowledge base. The model gets smarter with each resolved fault.",
},
];
Expand Down Expand Up @@ -111,13 +111,13 @@ export default function LandingPage() {
<div className={styles.heroBg} />
<div className={styles.heroGrid} />
<div className={styles.heroContent}>
<span className={styles.heroBadge}>Fault-to-Fix Engine</span>
<span className={styles.heroBadge}>Fault to Fix Engine</span>
<h1 className={styles.heroTitle}>
From fault code<br />
<span className={styles.heroTitleAccent}>to fixed.</span>
</h1>
<p className={styles.heroDesc}>
TORQ autonomously diagnoses machine faults, generates trilingual work orders, and dispatches the right technician &mdash; all in real time.
TORQ autonomously diagnoses machine faults, generates trilingual work orders, and dispatches the right technician all in real time.
</p>
<div className={styles.heroCta}>
<Link to="/dashboard" className={styles.btnPrimary}>
Expand Down Expand Up @@ -166,8 +166,8 @@ export default function LandingPage() {
{/* ── Pipeline ── */}
<section className={styles.pipelineSection} id="pipeline" ref={pipelineRef}>
<div className={styles.sectionInner}>
<SectionTitle subtitle="From fault event to resolved work order entirely automated.">
Pipeline
<SectionTitle subtitle="From fault event to resolved work order entirely automated.">
How TORQ drives the repair
</SectionTitle>
<div className={`${styles.fadeUp} ${pipelineVisible ? styles.fadeUpVisible : ""}`}>
<PipelineDiagram />
Expand Down
Loading