File size: 13,990 Bytes
6fe3275
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264

import React, { useState } from 'react';
import { BentoCardData } from '../types';
import { Maximize2, Minimize2, BookOpen, MessageSquare, Activity, Quote, Zap, ThumbsUp, ThumbsDown, ArrowRight, FileText, MoveHorizontal, MoveVertical, Plus, Minus } from 'lucide-react';
import ReactMarkdown from 'react-markdown';
import MermaidDiagram from './MermaidDiagram';

interface Props {
  card: BentoCardData;
  onExpand: (card: BentoCardData) => void;
  onRate: (id: string, rating: number) => void;
  onResize: (id: string, deltaCol: number, deltaRow: number) => void;
  layoutMode: 'auto' | 'grid' | 'list';
}

const BentoCard: React.FC<Props> = ({ card, onExpand, onRate, onResize, layoutMode }) => {
  const [isExpanded, setIsExpanded] = useState(false);

  // Visual Configurations based on Card Type
  const getTypeConfig = () => {
    switch (card.type) {
      case 'stat': 
        return {
          icon: <Activity className="w-5 h-5" />,
          color: 'text-emerald-600 dark:text-emerald-400',
          bg: 'bg-emerald-100 dark:bg-emerald-900/30',
          border: 'border-emerald-200 dark:border-emerald-800',
          gradient: 'from-emerald-500/10 to-teal-500/5',
          graphic: (
            <svg viewBox="0 0 100 40" className="w-full h-full opacity-20 text-emerald-500" fill="none" stroke="currentColor" strokeWidth="2">
               <path d="M0 35 C 20 35, 20 15, 40 15 S 60 25, 80 5 L 100 0" />
               <path d="M0 40 L 100 40 L 100 0 L 80 5 C 60 25, 60 15, 40 15 C 20 15, 20 35, 0 35 Z" fill="currentColor" stroke="none" opacity="0.2" />
            </svg>
          )
        };
      case 'quote': 
        return {
          icon: <Quote className="w-5 h-5" />,
          color: 'text-amber-600 dark:text-amber-400',
          bg: 'bg-amber-100 dark:bg-amber-900/30',
          border: 'border-amber-200 dark:border-amber-800',
          gradient: 'from-amber-500/10 to-orange-500/5',
          graphic: (
             <svg width="120" height="120" viewBox="0 0 24 24" className="absolute -right-2 -bottom-6 text-amber-500 opacity-10 transform rotate-12" fill="currentColor">
                <path d="M14.017 21L14.017 18C14.017 16.8954 14.9124 16 16.017 16H19.017C19.5693 16 20.017 15.5523 20.017 15V9C20.017 8.44772 19.5693 8 19.017 8H15.017C14.4647 8 14.017 7.55228 14.017 7V3H19.017C20.6739 3 22.017 4.34315 22.017 6V15C22.017 16.6569 20.6739 18 19.017 18H16.017V21H14.017ZM5.0166 21L5.0166 18C5.0166 16.8954 5.91203 16 7.0166 16H10.0166C10.5689 16 11.0166 15.5523 11.0166 15V9C11.0166 8.44772 10.5689 8 10.0166 8H6.0166C5.46432 8 5.0166 7.55228 5.0166 7V3H10.0166C11.6735 3 13.0166 4.34315 13.0166 6V15C13.0166 16.6569 11.6735 18 10.0166 18H7.0166V21H5.0166Z" />
             </svg>
          )
        };
      case 'process': 
        return {
          icon: <Zap className="w-5 h-5" />,
          color: 'text-purple-600 dark:text-purple-400',
          bg: 'bg-purple-100 dark:bg-purple-900/30',
          border: 'border-purple-200 dark:border-purple-800',
          gradient: 'from-purple-500/10 to-indigo-500/5',
          graphic: (
            <svg viewBox="0 0 100 100" className="w-full h-full opacity-10 text-purple-500 absolute right-0 top-0">
              <circle cx="80" cy="20" r="15" stroke="currentColor" strokeWidth="2" fill="none" />
              <circle cx="60" cy="50" r="10" stroke="currentColor" strokeWidth="2" fill="none" />
              <circle cx="80" cy="80" r="20" stroke="currentColor" strokeWidth="2" fill="none" />
              <line x1="75" y1="33" x2="65" y2="42" stroke="currentColor" strokeWidth="2" />
              <line x1="65" y1="58" x2="75" y2="65" stroke="currentColor" strokeWidth="2" />
            </svg>
          )
        };
      case 'insight': 
        return {
          icon: <MessageSquare className="w-5 h-5" />,
          color: 'text-blue-600 dark:text-blue-400',
          bg: 'bg-blue-100 dark:bg-blue-900/30',
          border: 'border-blue-200 dark:border-blue-800',
          gradient: 'from-blue-500/10 to-cyan-500/5',
          graphic: (
             <div className="absolute top-0 right-0 w-32 h-32 bg-blue-500/10 rounded-bl-full pointer-events-none blur-xl" />
          )
        };
      default: 
        return {
          icon: <BookOpen className="w-5 h-5" />,
          color: 'text-gray-600 dark:text-gray-400',
          bg: 'bg-gray-100 dark:bg-gray-800',
          border: 'border-gray-200 dark:border-gray-700',
          gradient: 'from-gray-500/5 to-slate-500/5',
          graphic: null
        };
    }
  };

  const config = getTypeConfig();

  const displayRow = card.rowSpan > 3 ? 3 : card.rowSpan < 1 ? 1 : card.rowSpan;
  
  const getGridClass = () => {
    if (layoutMode === 'list') return 'col-span-4 row-span-1';
    if (isExpanded) return 'col-span-4 row-span-2 md:col-span-4 md:row-span-3 z-30'; 
    
    const displayCol = card.colSpan;
    
    // Clamping to grid limits
    const cols = displayCol > 4 ? 4 : displayCol < 1 ? 1 : displayCol;
    
    const colClass = cols === 4 ? 'md:col-span-4' : cols === 3 ? 'md:col-span-3' : cols === 2 ? 'md:col-span-2' : 'md:col-span-1';
    const rowClass = displayRow === 3 ? 'md:row-span-3' : displayRow === 2 ? 'md:row-span-2' : 'md:row-span-1';
    
    return `col-span-4 ${colClass} ${rowClass}`;
  };

  const getLineClampClass = () => {
    if (isExpanded) return '';
    switch (displayRow) {
      case 1: return 'line-clamp-4';
      case 2: return 'line-clamp-[12]';
      case 3: return 'line-clamp-[20]';
      default: return 'line-clamp-4';
    }
  };

  const handleExpandClick = (e: React.MouseEvent) => {
    if ((e.target as HTMLElement).closest('.control-btn')) return;
    e.stopPropagation();
    if (!isExpanded && !card.expandedContent) {
      onExpand(card);
    }
    setIsExpanded(!isExpanded);
  };

  return (
    <div 
      className={`
        relative group transition-all duration-500 ease-[cubic-bezier(0.25,0.1,0.25,1.0)]
        ${getGridClass()}
        ${isExpanded 
          ? 'scale-[1.02] z-40 shadow-2xl -translate-y-2 h-auto min-h-[400px]' 
          : 'hover:scale-[1.02] hover:-translate-y-2 hover:shadow-xl hover:shadow-brand-500/10 hover:z-20 h-full'
        }
      `}
      onClick={handleExpandClick}
    >
      <div className={`
        h-full w-full rounded-2xl overflow-hidden flex flex-col 
        bg-white dark:bg-slate-900 
        border 
        ${isExpanded ? 'border-brand-500 ring-1 ring-brand-500' : `${config.border} group-hover:border-opacity-0`}
        shadow-sm transition-all duration-300
        relative
        group-hover:ring-1 group-hover:ring-black/5 dark:group-hover:ring-white/10
      `}>
        
        {/* Mermaid Diagram (Takes priority over default graphics if present) */}
        {card.mermaid && (
           <div className={`absolute inset-0 z-0 opacity-20 dark:opacity-30 ${isExpanded ? 'opacity-100 dark:opacity-100 z-10 bg-white/90 dark:bg-slate-900/90' : ''} transition-all duration-500`}>
              <MermaidDiagram chart={card.mermaid} theme={document.documentElement.classList.contains('dark') ? 'dark' : 'light'} />
           </div>
        )}

        {/* Default Graphic / Fallback */}
        {!card.mermaid && (
           <div className="absolute inset-0 overflow-hidden pointer-events-none z-0">
             <div className={`absolute inset-0 bg-gradient-to-br ${config.gradient} opacity-100`} />
             {config.graphic}
           </div>
        )}

        {/* Layout & Action Controls (Visible on Hover) */}
        <div className="absolute top-3 right-12 flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity z-20 control-btn">
             {/* Resize Width */}
             <div className="flex items-center bg-white/90 dark:bg-black/60 rounded-lg backdrop-blur-sm border border-gray-200 dark:border-gray-700 p-1 shadow-sm">
                <button onClick={(e) => { e.stopPropagation(); onResize(card.id, -1, 0); }} className="p-1 hover:bg-black/10 dark:hover:bg-white/20 rounded" title="Decrease Width">
                   <Minus size={12} />
                </button>
                <span className="px-1.5 text-[10px] font-mono text-gray-500">W</span>
                <button onClick={(e) => { e.stopPropagation(); onResize(card.id, 1, 0); }} className="p-1 hover:bg-black/10 dark:hover:bg-white/20 rounded" title="Increase Width">
                   <Plus size={12} />
                </button>
             </div>
             {/* Resize Height */}
             <div className="flex items-center bg-white/90 dark:bg-black/60 rounded-lg backdrop-blur-sm border border-gray-200 dark:border-gray-700 p-1 shadow-sm">
                <button onClick={(e) => { e.stopPropagation(); onResize(card.id, 0, -1); }} className="p-1 hover:bg-black/10 dark:hover:bg-white/20 rounded" title="Decrease Height">
                   <Minus size={12} />
                </button>
                <span className="px-1.5 text-[10px] font-mono text-gray-500">H</span>
                <button onClick={(e) => { e.stopPropagation(); onResize(card.id, 0, 1); }} className="p-1 hover:bg-black/10 dark:hover:bg-white/20 rounded" title="Increase Height">
                   <Plus size={12} />
                </button>
             </div>
        </div>

        <div className={`relative p-6 flex flex-col h-full z-10 ${isExpanded && card.mermaid ? 'bg-white/80 dark:bg-slate-900/80 backdrop-blur-sm' : ''}`}>
            {/* Header */}
            <div className="flex items-start justify-between mb-4">
              <div className="flex items-center gap-3">
                <div className={`p-2 rounded-lg ${config.bg} ${config.color} backdrop-blur-md shadow-sm transition-transform duration-300 group-hover:scale-110`}>
                  {config.icon}
                </div>
                <h3 className="font-display font-bold text-lg leading-tight text-gray-900 dark:text-gray-50 pr-8 drop-shadow-sm">{card.title}</h3>
              </div>
              <button 
                onClick={handleExpandClick}
                className="control-btn p-2 rounded-full bg-white/50 dark:bg-black/20 hover:bg-white dark:hover:bg-black/40 transition-all opacity-0 group-hover:opacity-100 absolute right-4 top-4 backdrop-blur-md border border-white/20 shadow-sm"
              >
                {isExpanded ? <Minimize2 size={16} className="text-gray-700 dark:text-gray-200" /> : <Maximize2 size={16} className="text-gray-700 dark:text-gray-200" />}
              </button>
            </div>

            {/* Main Content (Summary) */}
            <div className="flex-grow overflow-y-auto custom-scrollbar relative">
              {/* Use ReactMarkdown for summary to handle bolding/italics if model sends it, but style as simple text */}
              <div className={`text-base leading-relaxed text-gray-700 dark:text-gray-200 font-medium ${getLineClampClass()} drop-shadow-sm prose prose-sm dark:prose-invert max-w-none prose-p:my-0`}>
                  <ReactMarkdown>{card.summary}</ReactMarkdown>
              </div>
              
              {!isExpanded && (
                  <div className="mt-4 flex items-center gap-2 text-xs font-medium uppercase tracking-wider opacity-60 text-gray-600 dark:text-gray-300 group-hover:translate-x-1 transition-transform">
                    Click to explore <ArrowRight size={12} />
                  </div>
              )}
            </div>

            {/* Expanded Content Area */}
            {isExpanded && (
              <div 
                className="mt-6 pt-6 border-t border-gray-200 dark:border-gray-700 animate-in fade-in slide-in-from-bottom-4 duration-500 overflow-y-auto custom-scrollbar max-h-[400px]"
                onClick={(e) => e.stopPropagation()} 
              >
                <div className="flex justify-between items-center mb-4">
                  <div className="flex items-center gap-2 text-brand-600 dark:text-brand-400">
                    <FileText size={16} />
                    <h4 className="text-sm uppercase tracking-wider font-bold">First Principles Deep Dive</h4>
                  </div>
                </div>
                
                {card.isLoadingDetails ? (
                  <div className="space-y-3 py-2">
                    <div className="h-2 bg-gray-200 dark:bg-gray-700 rounded w-full animate-pulse"></div>
                    <div className="h-2 bg-gray-200 dark:bg-gray-700 rounded w-5/6 animate-pulse delay-75"></div>
                    <div className="h-2 bg-gray-200 dark:bg-gray-700 rounded w-4/6 animate-pulse delay-150"></div>
                    <div className="h-2 bg-gray-200 dark:bg-gray-700 rounded w-full animate-pulse delay-100"></div>
                  </div>
                ) : (
                  <div className="prose prose-sm dark:prose-invert max-w-none text-gray-700 dark:text-gray-300 bg-gray-50/50 dark:bg-black/20 p-4 rounded-xl">
                     <ReactMarkdown>{card.expandedContent || "No detailed content available."}</ReactMarkdown>
                  </div>
                )}

                {/* Feedback */}
                <div className="flex items-center justify-end gap-2 mt-4 pt-2">
                  <span className="text-xs font-medium text-gray-500">Helpful?</span>
                  <button onClick={() => onRate(card.id, 1)} className={`p-1.5 rounded hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors ${card.rating === 1 ? 'text-green-600 bg-green-50 dark:bg-green-900/20' : 'text-gray-400'}`}>
                    <ThumbsUp size={14} />
                  </button>
                  <button onClick={() => onRate(card.id, -1)} className={`p-1.5 rounded hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors ${card.rating === -1 ? 'text-red-600 bg-red-50 dark:bg-red-900/20' : 'text-gray-400'}`}>
                    <ThumbsDown size={14} />
                  </button>
                </div>
              </div>
            )}
        </div>
      </div>
    </div>
  );
};

export default BentoCard;