From a9d1e30b5a5c3fee878dfa757678fa717eb38759 Mon Sep 17 00:00:00 2001
From: Devadakene
Date: Fri, 21 Aug 2026 15:41:01 +0100
Subject: [PATCH 1/2] feat: implement CSS animation library and apply utilities
---
frontend/src/components/dashboard/KpiCard.tsx | 2 +-
frontend/src/components/landing/Hero.tsx | 69 +++++++-----------
frontend/src/styles/animations.css | 70 +++++++++++++++++++
frontend/src/styles/global.css | 3 +
frontend/src/styles/micro-interactions.css | 62 ++++++++++++++++
5 files changed, 162 insertions(+), 44 deletions(-)
create mode 100644 frontend/src/styles/animations.css
create mode 100644 frontend/src/styles/micro-interactions.css
diff --git a/frontend/src/components/dashboard/KpiCard.tsx b/frontend/src/components/dashboard/KpiCard.tsx
index 53ec57e..be0ffdd 100644
--- a/frontend/src/components/dashboard/KpiCard.tsx
+++ b/frontend/src/components/dashboard/KpiCard.tsx
@@ -13,7 +13,7 @@ interface KpiCardProps {
export const KpiCard: React.FC = ({ title, value, sparklineData, loading }) => {
const data = sparklineData.map((v, i) => ({ x: i, y: v }));
return (
-
+
{loading ? (
) : (
diff --git a/frontend/src/components/landing/Hero.tsx b/frontend/src/components/landing/Hero.tsx
index ba0de1f..5bc2287 100644
--- a/frontend/src/components/landing/Hero.tsx
+++ b/frontend/src/components/landing/Hero.tsx
@@ -1,78 +1,61 @@
import React from 'react'
import { useNavigate } from 'react-router-dom'
-import { motion } from 'framer-motion'
import { Sparkles, ArrowRight } from 'lucide-react'
-const containerVariants = {
- hidden: { opacity: 0 },
- visible: {
- opacity: 1,
- transition: { staggerChildren: 0.12, delayChildren: 0.2 },
- },
-} as const
-
-const itemVariants = {
- hidden: { opacity: 0, y: 20 },
- visible: { opacity: 1, y: 0, transition: { duration: 0.5, ease: 'easeOut' as const } },
-} as const
-
const Hero: React.FC = () => {
const navigate = useNavigate()
return (
-
{/* Centered Logo Mark */}
-
a
-
+
{/* Status Pill */}
-
-
+
Live on Stellar Testnet
-
+
{/* Headline */}
-
AI agents that
hire & pay each other
-
+
{/* Subtext */}
-
Submit one task. Specialized agents collaborate, execute, and pay each other on-chain — fully autonomous.
-
+
{/* CTAs */}
-
navigate('/tasks/new')}
- className="group w-full sm:w-auto flex items-center justify-center gap-2 px-7 py-3.5 rounded-xl bg-gradient-primary text-white font-semibold shadow-[0_0_20px_rgba(139,92,246,0.3)] hover:shadow-[0_0_35px_rgba(139,92,246,0.5)] hover:scale-[1.02] active:scale-[0.98] transition-all"
+ className="group w-full sm:w-auto flex items-center justify-center gap-2 px-7 py-3.5 rounded-xl bg-gradient-primary text-white font-semibold shadow-[0_0_20px_rgba(139,92,246,0.3)] hover:shadow-[0_0_35px_rgba(139,92,246,0.5)] transition-all hover-scale focus-ring"
>
Start a Task
@@ -80,13 +63,13 @@ const Hero: React.FC = () => {
navigate('/agents')}
- className="group w-full sm:w-auto flex items-center justify-center gap-2 px-7 py-3.5 rounded-xl bg-background-surface-alt border border-border-subtle text-text-secondary font-medium hover:text-text-primary hover:bg-background-surface hover:border-border-subtle/50 active:scale-[0.98] transition-all"
+ className="group w-full sm:w-auto flex items-center justify-center gap-2 px-7 py-3.5 rounded-xl bg-background-surface-alt border border-border-subtle text-text-secondary font-medium hover:text-text-primary hover:bg-background-surface hover:border-border-subtle/50 transition-all hover-scale focus-ring"
>
Browse Agents
-
-
+
+
)
}
diff --git a/frontend/src/styles/animations.css b/frontend/src/styles/animations.css
new file mode 100644
index 0000000..ac315e7
--- /dev/null
+++ b/frontend/src/styles/animations.css
@@ -0,0 +1,70 @@
+.fade-in {
+ animation: fadeIn 0.3s ease forwards;
+}
+
+.slide-up {
+ animation: slideUp 0.3s ease forwards;
+}
+
+.scale-in {
+ animation: scaleIn 0.3s ease forwards;
+}
+
+.pulse {
+ animation: pulse 2s infinite ease-in-out;
+}
+
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+
+@keyframes slideUp {
+ from {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+@keyframes scaleIn {
+ from {
+ opacity: 0;
+ transform: scale(0.95);
+ }
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
+
+@keyframes pulse {
+ 0% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 0.6;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .fade-in,
+ .slide-up,
+ .scale-in,
+ .pulse {
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.01ms !important;
+ animation-delay: 0.01ms !important;
+ }
+}
diff --git a/frontend/src/styles/global.css b/frontend/src/styles/global.css
index de1ad35..e016a1b 100644
--- a/frontend/src/styles/global.css
+++ b/frontend/src/styles/global.css
@@ -2,6 +2,9 @@
@tailwind components;
@tailwind utilities;
+@import './animations.css';
+@import './micro-interactions.css';
+
:root {
--font-sans: 'Outfit', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
--bg-primary: #0A0E14;
diff --git a/frontend/src/styles/micro-interactions.css b/frontend/src/styles/micro-interactions.css
new file mode 100644
index 0000000..14a7aa7
--- /dev/null
+++ b/frontend/src/styles/micro-interactions.css
@@ -0,0 +1,62 @@
+.hover-lift {
+ transition-property: transform, box-shadow;
+ transition-duration: 300ms;
+ transition-timing-function: ease;
+}
+
+.hover-lift:hover {
+ transform: translateY(-4px);
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15);
+}
+
+.hover-glow {
+ transition-property: box-shadow;
+ transition-duration: 300ms;
+ transition-timing-function: ease;
+}
+
+.hover-glow:hover {
+ box-shadow: 0 0 15px var(--accent-cyan);
+}
+
+.hover-scale {
+ transition-property: transform;
+ transition-duration: 300ms;
+ transition-timing-function: ease;
+}
+
+.hover-scale:hover {
+ transform: scale(1.05);
+}
+
+.focus-ring {
+ outline: none;
+}
+
+.focus-ring:focus-visible {
+ outline: 2px solid var(--accent-cyan);
+ outline-offset: 2px;
+}
+
+.transition-fast {
+ transition-duration: 150ms;
+}
+
+.transition-normal {
+ transition-duration: 300ms;
+}
+
+.transition-slow {
+ transition-duration: 500ms;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .hover-lift,
+ .hover-glow,
+ .hover-scale,
+ .transition-fast,
+ .transition-normal,
+ .transition-slow {
+ transition-duration: 0.01ms !important;
+ }
+}
From 63d8c5ba6d472eef960ed996b8b8fe4019850914 Mon Sep 17 00:00:00 2001
From: Devadakene
Date: Fri, 21 Aug 2026 15:48:48 +0100
Subject: [PATCH 2/2] feat: add agent reputation radar and trend charts
---
.../agents/AgentDetailModal.module.css | 7 ++++
.../components/agents/AgentDetailModal.tsx | 21 ++++++++++
.../agents/AgentReputationRadar.module.css | 6 +++
.../agents/AgentReputationRadar.test.tsx | 34 +++++++++++++++
.../agents/AgentReputationRadar.tsx | 31 ++++++++++++++
.../agents/AgentReputationTrend.module.css | 4 ++
.../agents/AgentReputationTrend.tsx | 34 +++++++++++++++
frontend/src/hooks/useAgentReputation.ts | 41 +++++++++++++++++++
frontend/src/services/api.ts | 4 ++
frontend/src/types/agent.ts | 18 ++++++++
10 files changed, 200 insertions(+)
create mode 100644 frontend/src/components/agents/AgentReputationRadar.module.css
create mode 100644 frontend/src/components/agents/AgentReputationRadar.test.tsx
create mode 100644 frontend/src/components/agents/AgentReputationRadar.tsx
create mode 100644 frontend/src/components/agents/AgentReputationTrend.module.css
create mode 100644 frontend/src/components/agents/AgentReputationTrend.tsx
create mode 100644 frontend/src/hooks/useAgentReputation.ts
diff --git a/frontend/src/components/agents/AgentDetailModal.module.css b/frontend/src/components/agents/AgentDetailModal.module.css
index 4e0cf9a..b54e872 100644
--- a/frontend/src/components/agents/AgentDetailModal.module.css
+++ b/frontend/src/components/agents/AgentDetailModal.module.css
@@ -161,3 +161,10 @@
grid-template-columns: 1fr;
}
}
+
+.chartsContainer {
+ display: flex;
+ flex-direction: column;
+ gap: 1.5rem;
+ margin-top: 0.5rem;
+}
diff --git a/frontend/src/components/agents/AgentDetailModal.tsx b/frontend/src/components/agents/AgentDetailModal.tsx
index f659dce..fabc935 100644
--- a/frontend/src/components/agents/AgentDetailModal.tsx
+++ b/frontend/src/components/agents/AgentDetailModal.tsx
@@ -2,6 +2,9 @@ import { useEffect } from 'react'
import { ExternalLink, X } from 'lucide-react'
import type { AgentRecord } from '../../types/api'
import { ReputationStars } from './ReputationStars'
+import { useAgentReputation } from '../../hooks/useAgentReputation'
+import { AgentReputationRadar } from './AgentReputationRadar'
+import { AgentReputationTrend } from './AgentReputationTrend'
import styles from './AgentDetailModal.module.css'
const STELLAR_EXPLORER = 'https://stellar.expert/explorer/testnet'
@@ -12,6 +15,8 @@ interface AgentDetailModalProps {
}
export function AgentDetailModal({ agent, onClose }: AgentDetailModalProps) {
+ const { data: reputationData, loading: reputationLoading } = useAgentReputation(agent?.id || '')
+
useEffect(() => {
if (!agent) return
const onKey = (e: KeyboardEvent) => {
@@ -120,6 +125,22 @@ export function AgentDetailModal({ agent, onClose }: AgentDetailModalProps) {
)}
+
+
+
Reputation Details
+
+ {reputationLoading ? (
+ Loading charts...
+ ) : reputationData ? (
+
+ ) : (
+ No detailed reputation data available
+ )}
+
+
diff --git a/frontend/src/components/agents/AgentReputationRadar.module.css b/frontend/src/components/agents/AgentReputationRadar.module.css
new file mode 100644
index 0000000..cdb7320
--- /dev/null
+++ b/frontend/src/components/agents/AgentReputationRadar.module.css
@@ -0,0 +1,6 @@
+.container {
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
diff --git a/frontend/src/components/agents/AgentReputationRadar.test.tsx b/frontend/src/components/agents/AgentReputationRadar.test.tsx
new file mode 100644
index 0000000..f269682
--- /dev/null
+++ b/frontend/src/components/agents/AgentReputationRadar.test.tsx
@@ -0,0 +1,34 @@
+import { render } from '@testing-library/react';
+import { AgentReputationRadar } from './AgentReputationRadar';
+import { vi, describe, it, expect } from 'vitest';
+
+// Mock recharts because ResponsiveContainer doesn't work well in JSDOM
+vi.mock('recharts', async () => {
+ const OriginalRecharts = await vi.importActual('recharts');
+ return {
+ ...OriginalRecharts,
+ ResponsiveContainer: ({ children }: any) => (
+ {children}
+ ),
+ };
+});
+
+describe('AgentReputationRadar', () => {
+ it('renders the radar chart with given dimensions', () => {
+ const dimensions = {
+ quality: 90,
+ speed: 85,
+ reliability: 95,
+ cost: 80,
+ };
+
+ const { container } = render( );
+
+ // Check if the container is rendered
+ expect(container.firstChild).toBeInTheDocument();
+
+ // In a real JSDOM environment with SVG, we could check for specific SVG elements.
+ // For this test, we ensure it renders without crashing and contains the ResponsiveContainer div.
+ expect(container.querySelector('.recharts-wrapper')).toBeInTheDocument();
+ });
+});
diff --git a/frontend/src/components/agents/AgentReputationRadar.tsx b/frontend/src/components/agents/AgentReputationRadar.tsx
new file mode 100644
index 0000000..00cd788
--- /dev/null
+++ b/frontend/src/components/agents/AgentReputationRadar.tsx
@@ -0,0 +1,31 @@
+import React from 'react';
+import { Radar, RadarChart, PolarGrid, PolarAngleAxis, PolarRadiusAxis, ResponsiveContainer, Tooltip } from 'recharts';
+import type { ReputationDimensions } from '../../types/agent';
+import styles from './AgentReputationRadar.module.css';
+
+interface AgentReputationRadarProps {
+ dimensions: ReputationDimensions;
+}
+
+export const AgentReputationRadar: React.FC = ({ dimensions }) => {
+ const data = [
+ { subject: 'Quality', A: dimensions.quality, fullMark: 100 },
+ { subject: 'Speed', A: dimensions.speed, fullMark: 100 },
+ { subject: 'Reliability', A: dimensions.reliability, fullMark: 100 },
+ { subject: 'Cost', A: dimensions.cost, fullMark: 100 },
+ ];
+
+ return (
+
+ );
+};
diff --git a/frontend/src/components/agents/AgentReputationTrend.module.css b/frontend/src/components/agents/AgentReputationTrend.module.css
new file mode 100644
index 0000000..97e43ea
--- /dev/null
+++ b/frontend/src/components/agents/AgentReputationTrend.module.css
@@ -0,0 +1,4 @@
+.container {
+ width: 100%;
+ margin-top: 1rem;
+}
diff --git a/frontend/src/components/agents/AgentReputationTrend.tsx b/frontend/src/components/agents/AgentReputationTrend.tsx
new file mode 100644
index 0000000..a44af59
--- /dev/null
+++ b/frontend/src/components/agents/AgentReputationTrend.tsx
@@ -0,0 +1,34 @@
+import React from 'react';
+import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Brush } from 'recharts';
+import type { ReputationHistory } from '../../types/agent';
+import styles from './AgentReputationTrend.module.css';
+
+interface AgentReputationTrendProps {
+ history: ReputationHistory[];
+}
+
+export const AgentReputationTrend: React.FC = ({ history }) => {
+ // Format dates for display
+ const data = history.map(item => {
+ const d = new Date(item.date);
+ return {
+ ...item,
+ displayDate: `${d.getMonth() + 1}/${d.getDate()}`
+ };
+ });
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/frontend/src/hooks/useAgentReputation.ts b/frontend/src/hooks/useAgentReputation.ts
new file mode 100644
index 0000000..2bc804d
--- /dev/null
+++ b/frontend/src/hooks/useAgentReputation.ts
@@ -0,0 +1,41 @@
+import { useState, useEffect } from 'react';
+import { getAgentReputation } from '../services/api';
+import type { AgentReputation } from '../types/agent';
+
+export function useAgentReputation(agentId: string) {
+ const [data, setData] = useState(null);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ if (!agentId) return;
+
+ let mounted = true;
+ const fetchReputation = async () => {
+ setLoading(true);
+ setError(null);
+ try {
+ const result = await getAgentReputation(agentId);
+ if (mounted) {
+ setData(result);
+ }
+ } catch (err) {
+ if (mounted) {
+ setError(err instanceof Error ? err.message : 'Failed to fetch reputation data');
+ }
+ } finally {
+ if (mounted) {
+ setLoading(false);
+ }
+ }
+ };
+
+ fetchReputation();
+
+ return () => {
+ mounted = false;
+ };
+ }, [agentId]);
+
+ return { data, loading, error };
+}
diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts
index b2479ea..37b95d4 100644
--- a/frontend/src/services/api.ts
+++ b/frontend/src/services/api.ts
@@ -114,3 +114,7 @@ export const getRecentTasks = async (walletAddress: string): Promise => {
return apiClient.get('/api/agents');
};
+
+export const getAgentReputation = async (id: string): Promise => {
+ return apiClient.get(`/api/agents/${id}/reputation`);
+};
diff --git a/frontend/src/types/agent.ts b/frontend/src/types/agent.ts
index 8622000..ca31d08 100644
--- a/frontend/src/types/agent.ts
+++ b/frontend/src/types/agent.ts
@@ -66,3 +66,21 @@ export type AgentResult =
| DesignResult
| null
| undefined;
+
+export interface ReputationDimensions {
+ quality: number;
+ speed: number;
+ reliability: number;
+ cost: number;
+}
+
+export interface ReputationHistory {
+ date: string;
+ score: number;
+}
+
+export interface AgentReputation {
+ id: string;
+ dimensions: ReputationDimensions;
+ history: ReputationHistory[];
+}