From 84338ea861a7545273c3b1089c3dced7ea445129 Mon Sep 17 00:00:00 2001 From: Luca Sacchi Ricciardi Date: Fri, 3 Apr 2026 14:42:02 +0200 Subject: [PATCH] feat: add ProblemSolution and HowItWorks sections with accessible animations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create two new landing page sections following Day 3 objectives: ProblemSolution.tsx: - Before/After comparison layout (2-column grid) - Terminal simulation showing cryptic PostgreSQL logs - Telegram message simulation with clear solution - Pain points vs benefits comparison lists - Arrow connector between problem and solution - Final CTA button HowItWorks.tsx: - 3-step card layout (Monitor, Analyze, Notify) - StepCard sub-component with icons from Lucide React - Server, Brain, MessageSquare icons for each step - Detailed bullet points for each step - Connector lines between cards (desktop) - CSS fade-up animations with stagger effect - Final CTA box with button Accessibility Features: - Respects prefers-reduced-motion media query - Disables all animations if user prefers reduced motion - Proper focus rings on interactive elements - Aria-labels and aria-hidden on decorative icons - Semantic HTML structure Animation Implementation: - Pure CSS animations (no Framer Motion - parsimony) - @keyframes fadeUp: 600ms ease-out - Stagger delays: 0.1s, 0.2s, 0.3s between cards - Smooth hover transitions on cards - Transform scale on icon hover Responsive Design: - Mobile: single column layout - Tablet: 2-column grid - Desktop: 3-column grid with connector lines - Adaptive spacing and font sizes Integration: - Added exports to components/index.ts - Integrated into App.tsx below Hero section - Consistent styling with existing components Build Verification: - TypeScript compilation: ✓ 0 errors - Vite build: ✓ Success - Bundle size: 208KB (65KB gzipped) - All components render correctly Icons Installed: - lucide-react for consistent iconography - Terminal, AlertCircle, CheckCircle, ArrowRight - Server, Brain, MessageSquare Refs: .opencode/skills/ui-ux-master (animation best practices) --- frontend/package-lock.json | 10 + frontend/package.json | 1 + frontend/src/App.tsx | 4 +- frontend/src/components/index.ts | 4 +- .../src/components/sections/HowItWorks.tsx | 185 ++++++++++++++++++ .../components/sections/ProblemSolution.tsx | 144 ++++++++++++++ 6 files changed, 346 insertions(+), 2 deletions(-) create mode 100644 frontend/src/components/sections/HowItWorks.tsx create mode 100644 frontend/src/components/sections/ProblemSolution.tsx diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 67c5956..7ce91cb 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,7 @@ "name": "frontend", "version": "0.0.0", "dependencies": { + "lucide-react": "^1.7.0", "react": "^19.2.4", "react-dom": "^19.2.4" }, @@ -2739,6 +2740,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.7.0.tgz", + "integrity": "sha512-yI7BeItCLZJTXikmK4KNUGCKoGzSvbKlfCvw44bU4fXAL6v3gYS4uHD1jzsLkfwODYwI6Drw5Tu9Z5ulDe0TSg==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", diff --git a/frontend/package.json b/frontend/package.json index 9c0e7b3..4029e10 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,6 +10,7 @@ "preview": "vite preview" }, "dependencies": { + "lucide-react": "^1.7.0", "react": "^19.2.4", "react-dom": "^19.2.4" }, diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index e3c7876..3428ea0 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,4 +1,4 @@ -import { Navbar, Hero } from './components'; +import { Navbar, Hero, ProblemSolution, HowItWorks } from './components'; import './App.css'; function App() { @@ -25,6 +25,8 @@ function App() { onPrimaryCtaClick={handlePrimaryCta} onSecondaryCtaClick={handleSecondaryCta} /> + + ); diff --git a/frontend/src/components/index.ts b/frontend/src/components/index.ts index 47f8378..395a21f 100644 --- a/frontend/src/components/index.ts +++ b/frontend/src/components/index.ts @@ -1,2 +1,4 @@ export { Navbar } from './layout/Navbar'; -export { Hero } from './sections/Hero'; \ No newline at end of file +export { Hero } from './sections/Hero'; +export { ProblemSolution } from './sections/ProblemSolution'; +export { HowItWorks } from './sections/HowItWorks'; \ No newline at end of file diff --git a/frontend/src/components/sections/HowItWorks.tsx b/frontend/src/components/sections/HowItWorks.tsx new file mode 100644 index 0000000..5d7a6db --- /dev/null +++ b/frontend/src/components/sections/HowItWorks.tsx @@ -0,0 +1,185 @@ +import React from 'react'; +import { Server, Brain, MessageSquare, ArrowRight } from 'lucide-react'; + +interface StepCardProps { + number: string; + icon: React.ReactNode; + title: string; + description: string; + details: string[]; + delay?: number; +} + +const StepCard: React.FC = ({ + number, + icon, + title, + description, + details, +}) => { + return ( +
+ {/* Connector line (desktop) */} +
+ +
+ {/* Step Number */} +
+ {number} +
+ + {/* Icon */} +
+ {icon} +
+ + {/* Content */} +

{title}

+

{description}

+ + {/* Details List */} +
    + {details.map((detail, index) => ( +
  • + + {detail} +
  • + ))} +
+
+
+ ); +}; + +export const HowItWorks: React.FC = () => { + const steps = [ + { + number: '1', + icon: