diff --git a/frontend/src/components/agents/AgentDetailModal.module.css b/frontend/src/components/agents/AgentDetailModal.module.css
index 4e0cf9af..b54e872b 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 f319f4cb..952679c6 100644
--- a/frontend/src/components/agents/AgentDetailModal.tsx
+++ b/frontend/src/components/agents/AgentDetailModal.tsx
@@ -3,6 +3,9 @@ import { useTranslation } from 'react-i18next'
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'
@@ -123,6 +126,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 00000000..cdb73207
--- /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 00000000..f2696827
--- /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 00000000..00cd7882
--- /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 00000000..97e43ea5
--- /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 00000000..a44af598
--- /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/components/landing/Hero.tsx b/frontend/src/components/landing/Hero.tsx
index ad506a2a..a1b9e2e3 100644
--- a/frontend/src/components/landing/Hero.tsx
+++ b/frontend/src/components/landing/Hero.tsx
@@ -1,25 +1,11 @@
import React from 'react'
import { useTranslation, Trans } from 'react-i18next'
import { useNavigate } from 'react-router-dom'
-import { motion } from 'framer-motion'
import { Sparkles, ArrowRight } from 'lucide-react'
import { useParticles } from '../../hooks/useParticles'
import { useTypingAnimation } from '../../hooks/useTypingAnimation'
import styles from './Hero.module.css'
-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 { t } = useTranslation()
const navigate = useNavigate()
@@ -45,29 +31,29 @@ const Hero: React.FC = () => {
{/* Centered Logo Mark */}
-
a
-
+
{/* Status Pill */}
-
{t('landing.hero.badge')}
{/* Headline */}
-
{
{/* Subtext */}
-
{t('landing.hero.subtitle')}
{/* CTAs */}
-
-
+
+
)
}
diff --git a/frontend/src/hooks/useAgentReputation.ts b/frontend/src/hooks/useAgentReputation.ts
new file mode 100644
index 00000000..2bc804de
--- /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 b2479ea6..37b95d4c 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/styles/animations.css b/frontend/src/styles/animations.css
new file mode 100644
index 00000000..ac315e77
--- /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 73998584..32cbeea1 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 00000000..14a7aa7a
--- /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;
+ }
+}
diff --git a/frontend/src/types/agent.ts b/frontend/src/types/agent.ts
index c9758f36..2e614d8c 100644
--- a/frontend/src/types/agent.ts
+++ b/frontend/src/types/agent.ts
@@ -80,3 +80,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[];
+}