- Add React.lazy code splitting for all 15 tool pages - Fix WCAG AA contrast issues (304 text color fixes) - Add ARIA labels and aria-expanded to navigation buttons - Add aria-live for error announcements in tools - Implement responsive ad layout: - Desktop (≥1280px): Right sidebar with 3 ad units - Tablet (1024-1279px): Bottom section with 3 horizontal units - Mobile (<1024px): Fixed bottom banner - Add TabletAdSection component for tablet ad placement - Integrate Onidel affiliate partnership - Update all Adsterra domains to solutionbiologyisle.com - Add release notes for 2026-02-18 updates
109 lines
5.2 KiB
JavaScript
109 lines
5.2 KiB
JavaScript
import React from 'react';
|
|
import { Link } from 'react-router-dom';
|
|
import { ArrowRight } from 'lucide-react';
|
|
import { getCategoryConfig } from '../config/tools';
|
|
|
|
const ToolCard = ({ icon: Icon, title, description, path, tags, category }) => {
|
|
const categoryConfig = getCategoryConfig(category);
|
|
|
|
// Define explicit hover classes for Tailwind CSS purging
|
|
const getHoverClasses = (category) => {
|
|
switch (category) {
|
|
case 'editor':
|
|
return {
|
|
border: 'hover:border-blue-300 dark:hover:border-blue-500',
|
|
shadow: 'hover:shadow-blue-500/20',
|
|
titleColor: 'group-hover:text-blue-600 dark:group-hover:text-blue-400',
|
|
arrowColor: 'group-hover:text-blue-600',
|
|
badgeColor: 'group-hover:bg-blue-100 dark:group-hover:bg-blue-900/30 group-hover:text-blue-700 dark:group-hover:text-blue-300'
|
|
};
|
|
case 'encoder':
|
|
return {
|
|
border: 'hover:border-purple-300 dark:hover:border-purple-500',
|
|
shadow: 'hover:shadow-purple-500/20',
|
|
titleColor: 'group-hover:text-purple-600 dark:group-hover:text-purple-400',
|
|
arrowColor: 'group-hover:text-purple-600',
|
|
badgeColor: 'group-hover:bg-purple-100 dark:group-hover:bg-purple-900/30 group-hover:text-purple-700 dark:group-hover:text-purple-300'
|
|
};
|
|
case 'formatter':
|
|
return {
|
|
border: 'hover:border-green-300 dark:hover:border-green-500',
|
|
shadow: 'hover:shadow-green-500/20',
|
|
titleColor: 'group-hover:text-green-600 dark:group-hover:text-green-400',
|
|
arrowColor: 'group-hover:text-green-600',
|
|
badgeColor: 'group-hover:bg-green-100 dark:group-hover:bg-green-900/30 group-hover:text-green-700 dark:group-hover:text-green-300'
|
|
};
|
|
case 'analyzer':
|
|
return {
|
|
border: 'hover:border-orange-300 dark:hover:border-orange-500',
|
|
shadow: 'hover:shadow-orange-500/20',
|
|
titleColor: 'group-hover:text-orange-600 dark:group-hover:text-orange-400',
|
|
arrowColor: 'group-hover:text-orange-600',
|
|
badgeColor: 'group-hover:bg-orange-100 dark:group-hover:bg-orange-900/30 group-hover:text-orange-700 dark:group-hover:text-orange-300'
|
|
};
|
|
default:
|
|
return {
|
|
border: 'hover:border-slate-300 dark:hover:border-slate-500',
|
|
shadow: 'hover:shadow-slate-500/20',
|
|
titleColor: 'group-hover:text-slate-600 dark:group-hover:text-slate-600',
|
|
arrowColor: 'group-hover:text-slate-600',
|
|
badgeColor: 'group-hover:bg-slate-100 dark:group-hover:bg-slate-700 group-hover:text-slate-700 dark:group-hover:text-slate-300'
|
|
};
|
|
}
|
|
};
|
|
|
|
const hoverClasses = getHoverClasses(category);
|
|
|
|
return (
|
|
<Link to={path} className="block group">
|
|
<div className={`relative overflow-hidden rounded-2xl bg-white/70 dark:bg-slate-800/70 backdrop-blur-sm border border-slate-200 dark:border-slate-700 ${hoverClasses.border} transition-all duration-300 hover:shadow-2xl ${hoverClasses.shadow} hover:-translate-y-1`}>
|
|
{/* Gradient overlay on hover */}
|
|
<div className={`absolute inset-0 bg-gradient-to-br ${categoryConfig.color} opacity-0 group-hover:opacity-5 transition-opacity duration-300`}></div>
|
|
|
|
<div className="relative p-6">
|
|
{/* Header */}
|
|
<div className="flex items-start justify-between mb-4">
|
|
<div className={`flex-shrink-0 p-3 rounded-xl bg-gradient-to-br ${categoryConfig.color} shadow-lg group-hover:scale-110 transition-transform duration-300`}>
|
|
<Icon className="h-6 w-6 text-white" />
|
|
</div>
|
|
<div className="flex-shrink-0 ml-4">
|
|
<ArrowRight className={`h-5 w-5 text-slate-600 ${hoverClasses.arrowColor} group-hover:translate-x-1 transition-all duration-300`} />
|
|
</div>
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<div className="space-y-3">
|
|
<div className="flex items-center gap-2">
|
|
<h3 className={`text-xl font-bold text-slate-800 dark:text-white ${hoverClasses.titleColor} transition-colors`}>
|
|
{title}
|
|
</h3>
|
|
<span className={`px-2 py-1 text-xs font-medium bg-slate-100 dark:bg-slate-700 text-slate-600 dark:text-slate-300 rounded-full ${hoverClasses.badgeColor} transition-colors`}>
|
|
{categoryConfig.name}
|
|
</span>
|
|
</div>
|
|
|
|
<p className="text-slate-600 dark:text-slate-300 leading-relaxed group-hover:text-slate-700 dark:group-hover:text-slate-200 transition-colors">
|
|
{description}
|
|
</p>
|
|
|
|
{tags && tags.length > 0 && (
|
|
<div className="flex flex-wrap gap-2 pt-2">
|
|
{tags.map((tag, index) => (
|
|
<span
|
|
key={index}
|
|
className="px-3 py-1 text-xs font-medium bg-slate-50 dark:bg-slate-700/50 text-slate-600 dark:text-slate-600 rounded-full border border-slate-200 dark:border-slate-600 group-hover:border-slate-300 dark:group-hover:border-slate-500 transition-colors"
|
|
>
|
|
{tag}
|
|
</span>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Link>
|
|
);
|
|
};
|
|
|
|
export default ToolCard;
|