Yacine Jernite commited on
Commit
803ec5a
·
1 Parent(s): 8ed13f7

components

Browse files
js/components/Card.js ADDED
@@ -0,0 +1,246 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Card.js - Unified card component
2
+ // Uses template strings for HTML generation (simple, clear, and efficient for our use case)
3
+ // All card types are defined here for consistency and maintainability
4
+
5
+ import { renderTagSet } from '../utils/tags.js';
6
+
7
+ // Use global areasData (loaded in index.html <head>)
8
+ const areasData = window.areasData;
9
+
10
+ /**
11
+ * Main card creation function
12
+ * @param {string} type - Card type: 'artifact', 'area'
13
+ * @param {object} data - Card data
14
+ * @param {object} options - Additional options
15
+ * @returns {string} HTML string for the card
16
+ */
17
+ export function createCard(type, data, options = {}) {
18
+ const renderers = {
19
+ artifact: renderArtifactCard,
20
+ area: renderAreaCard
21
+ };
22
+
23
+ const renderer = renderers[type];
24
+ if (!renderer) {
25
+ console.warn(`Unknown card type: ${type}`);
26
+ return '';
27
+ }
28
+
29
+ return renderer(data, options);
30
+ }
31
+
32
+ /**
33
+ * Renders an artifact card (for carousels)
34
+ */
35
+ function renderArtifactCard(artifact, options = {}) {
36
+ const { index = 0 } = options;
37
+
38
+ // Handle both old and new field names
39
+ const title = artifact.title;
40
+ const date = artifact.date;
41
+ const type = artifact.type;
42
+ const description = artifact.description;
43
+ const areaTags = artifact.areas || artifact.areaTags || [];
44
+ const subAreaTags = artifact.topics || artifact.subAreaTags || [];
45
+ const sourceUrl = artifact.url || artifact.sourceUrl || '';
46
+
47
+ // Type-based styling
48
+ const typeStyles = {
49
+ 'blog': { icon: '📝', textColor: 'text-blue-700' },
50
+ 'paper': { icon: '📄', textColor: 'text-green-700' },
51
+ 'dataset': { icon: '📊', textColor: 'text-purple-700' },
52
+ 'space': { icon: '🚀', textColor: 'text-orange-700' },
53
+ 'external': { icon: '🔗', textColor: 'text-gray-700' }
54
+ };
55
+
56
+ const style = typeStyles[type] || typeStyles['external'];
57
+
58
+ // Get area data for background
59
+ const primaryArea = areaTags[0];
60
+ const primaryAreaData = areasData[primaryArea];
61
+ const backgroundImage = primaryAreaData?.image || '';
62
+ const imageCredit = primaryAreaData?.imageAttribution || '';
63
+
64
+ // Render tags
65
+ const { areaTagsHtml, topicTagsHtml } = renderTagSet(areaTags, subAreaTags);
66
+
67
+ const cardId = `artifact-card-${index}`;
68
+
69
+ return `
70
+ <div class="flex-none w-80 h-60 border border-gray-200 rounded-lg overflow-hidden bg-white relative group hover:shadow-lg transition-shadow duration-200">
71
+ <!-- Background image -->
72
+ ${backgroundImage ? `
73
+ <div class="absolute inset-0 opacity-10">
74
+ <img src="/images/${backgroundImage}" alt="" class="w-full h-full object-cover">
75
+ </div>
76
+ ` : ''}
77
+
78
+ <!-- Toggle indicator -->
79
+ <div class="absolute top-2 right-2 z-10">
80
+ <button class="toggle-btn w-6 h-6 bg-white bg-opacity-80 hover:bg-opacity-100 rounded-full flex items-center justify-center text-gray-600 hover:text-gray-800 transition-all shadow-sm" onclick="toggleCardView('${cardId}')">
81
+ <svg class="w-3 h-3 expand-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
82
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
83
+ </svg>
84
+ <svg class="w-3 h-3 collapse-icon hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
85
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7"></path>
86
+ </svg>
87
+ </button>
88
+ </div>
89
+
90
+ <!-- Content -->
91
+ <div class="relative p-4 h-full flex flex-col overflow-hidden" id="${cardId}">
92
+ <!-- Default view -->
93
+ <div class="default-view">
94
+ <!-- Header: Type and Date -->
95
+ <div class="flex justify-between items-start mb-3 pr-8">
96
+ <div class="flex items-center space-x-2">
97
+ <span class="text-lg">${style.icon}</span>
98
+ <span class="text-xs font-medium font-bold uppercase tracking-wide">${type}</span>
99
+ </div>
100
+ <span class="text-xs text-gray-600">${date}</span>
101
+ </div>
102
+
103
+ <!-- Title -->
104
+ <div class="mb-4 flex-grow min-h-0">
105
+ <h3 class="font-semibold text-gray-900 text-sm leading-tight line-clamp-3">${title}</h3>
106
+ </div>
107
+
108
+ <!-- Bottom section with tags and image -->
109
+ <div class="flex justify-between items-end">
110
+ <!-- Left: Tags -->
111
+ <div class="flex-1 mr-4">
112
+ <!-- Area Tags -->
113
+ <div class="flex flex-wrap gap-1 mb-2">
114
+ ${areaTagsHtml}
115
+ </div>
116
+
117
+ <!-- Sub-area Tags -->
118
+ ${subAreaTags.length > 0 ? `
119
+ <div class="flex flex-wrap gap-1">
120
+ ${topicTagsHtml}
121
+ </div>
122
+ ` : ''}
123
+ </div>
124
+
125
+ <!-- Right: Area image with credit on hover -->
126
+ ${backgroundImage ? `
127
+ <div class="relative group/image">
128
+ <div class="w-12 h-12 rounded-lg overflow-hidden bg-gray-100 flex items-center justify-center cursor-help" title="${imageCredit}">
129
+ <img src="/images/${backgroundImage}" alt="${primaryAreaData.name}" class="w-full h-full object-cover opacity-80">
130
+ </div>
131
+ </div>
132
+ ` : ''}
133
+ </div>
134
+ </div>
135
+
136
+ <!-- Description view (hidden by default) -->
137
+ <div class="description-view hidden h-full flex flex-col min-h-0">
138
+ <!-- Title (single line with overflow) -->
139
+ <div class="mb-3 flex-shrink-0">
140
+ <h3 class="font-semibold text-gray-900 text-sm leading-tight truncate" title="${title}">${title}</h3>
141
+ </div>
142
+
143
+ <!-- Description (scrollable, takes remaining space) -->
144
+ <div class="flex-grow overflow-y-auto min-h-0">
145
+ <p class="text-xs text-gray-700 leading-relaxed">${description}</p>
146
+ </div>
147
+ </div>
148
+
149
+ <!-- URL link (always visible) -->
150
+ ${sourceUrl ? `
151
+ <div class="absolute bottom-2 right-2">
152
+ <a href="${sourceUrl}" target="_blank" rel="noopener noreferrer" class="text-gray-400 hover:text-blue-600 transition-colors">
153
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
154
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"></path>
155
+ </svg>
156
+ </a>
157
+ </div>
158
+ ` : ''}
159
+ </div>
160
+ </div>
161
+ `;
162
+ }
163
+
164
+ /**
165
+ * Renders an area card (for homepage)
166
+ */
167
+ function renderAreaCard(area, options = {}) {
168
+ // Get short description
169
+ const shortDesc = area.description?.short || area.description.split('.')[0] + '.';
170
+
171
+ // Get topic names and colors from the topics
172
+ const topics = Object.values(area.topics).map(topic => {
173
+ const topicName = topic.navName || topic.name;
174
+ let bgColor = 'bg-gray-200';
175
+ let textColor = 'text-gray-700';
176
+
177
+ // Extract background and text color from the topic color class
178
+ if (topic.color) {
179
+ const bgMatch = topic.color.match(/bg-(\w+)-(\d+)/);
180
+ const textMatch = topic.color.match(/text-(\w+)-(\d+)/);
181
+
182
+ if (bgMatch) {
183
+ bgColor = `bg-${bgMatch[1]}-${bgMatch[2]}`;
184
+ }
185
+ if (textMatch) {
186
+ textColor = `text-${textMatch[1]}-700`;
187
+ }
188
+ }
189
+
190
+ return {
191
+ name: topicName,
192
+ bgColor,
193
+ textColor
194
+ };
195
+ });
196
+
197
+ return `
198
+ <a href="/${area.id}"
199
+ class="group relative block border border-gray-200 rounded-lg overflow-hidden bg-white hover:shadow-lg transition-all duration-200 h-64">
200
+
201
+ <!-- Background image with low opacity -->
202
+ ${area.image ? `
203
+ <div class="absolute inset-0 opacity-15 group-hover:opacity-30 transition-opacity">
204
+ <img src="/images/${area.image}" alt="" class="w-full h-full object-cover">
205
+ </div>
206
+ ` : ''}
207
+
208
+ <!-- Content -->
209
+ <div class="relative p-5 h-full flex flex-col">
210
+ <!-- Header -->
211
+ <div class="flex justify-between items-start mb-3 flex-shrink-0">
212
+ <h3 class="text-lg font-bold text-gray-900 leading-tight">${area.navTitle}</h3>
213
+ <svg class="w-5 h-5 text-gray-400 group-hover:text-blue-600 transition-colors flex-shrink-0 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
214
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
215
+ </svg>
216
+ </div>
217
+
218
+ <!-- Description - scrollable -->
219
+ <div class="text-md text-gray-700 mb-4 flex-grow overflow-y-auto pr-2">
220
+ <p class="leading-relaxed">${shortDesc}</p>
221
+ </div>
222
+
223
+ <!-- Topics with colors from subAreas -->
224
+ <div class="flex-shrink-0">
225
+ <p class="text-xs font-semibold text-gray-600 uppercase tracking-wide mb-2">Topics:</p>
226
+ <div class="flex flex-wrap gap-2">
227
+ ${topics.map(topic => `
228
+ <span class="inline-block px-2 py-0.5 text-sm ${topic.bgColor} ${topic.textColor} rounded">
229
+ ${topic.name}
230
+ </span>
231
+ `).join('')}
232
+ </div>
233
+ </div>
234
+
235
+ <!-- Image Attribution -->
236
+ ${area.imageAttribution ? `
237
+ <p class="text-xs text-gray-500 mt-3 pt-2 border-t border-gray-100 flex-shrink-0">${area.imageAttribution}</p>
238
+ ` : ''}
239
+ </div>
240
+ </a>
241
+ `;
242
+ }
243
+
244
+ // Export individual renderers for backward compatibility
245
+ export { renderArtifactCard, renderAreaCard };
246
+
js/components/ContentSection.js ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // ContentSection.js - Reusable content section container
2
+ // Provides consistent frosted glass styling across all content sections
3
+
4
+ /**
5
+ * Wraps content in a frosted glass container
6
+ * @param {string} id - Section ID
7
+ * @param {string} content - Inner HTML content
8
+ * @param {object} options - Additional options
9
+ * @returns {string} HTML string for the section
10
+ */
11
+ export function renderContentSection(id, content, options = {}) {
12
+ const {
13
+ className = 'mb-16',
14
+ opacity = '40', // Default 40% opacity like About section
15
+ blur = 'sm', // sm, md, lg
16
+ padding = 'p-6 md:p-10'
17
+ } = options;
18
+
19
+ return `
20
+ <section id="${id}" class="${className} relative z-20 px-4 sm:px-6 lg:px-8">
21
+ <div class="bg-white/${opacity} backdrop-blur-${blur} shadow-sm rounded-lg ${padding} w-full" style="max-width: min(90%, 1400px); margin: 0 auto;">
22
+ ${content}
23
+ </div>
24
+ </section>
25
+ `;
26
+ }
27
+
28
+ /**
29
+ * Creates the standard "Role of Openness" callout box
30
+ * @param {string} text - The openness text content
31
+ * @returns {string} HTML string for the callout
32
+ */
33
+ export function renderOpennessCallout(text) {
34
+ if (!text) return '';
35
+
36
+ return `
37
+ <div class="px-4 sm:px-6 py-5 bg-gradient-to-r from-orange-50/95 to-yellow-50/95 backdrop-blur-sm border-l-4 border-orange-400 rounded-r-lg mb-8">
38
+ <p class="font-bold text-orange-900 mb-3 text-lg">🤗 The Role of Openness</p>
39
+ <p class="text-orange-800 italic leading-relaxed">${text}</p>
40
+ </div>
41
+ `;
42
+ }
43
+
js/components/PageNavigation.js ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // PageNavigation.js - Reusable page navigation component
2
+ // Creates the "On this page" / breadcrumb navigation card
3
+
4
+ /**
5
+ * Renders a page navigation card
6
+ * @param {string} label - Label for the navigation (e.g., "On this page:", "Area Name:")
7
+ * @param {Array} items - Array of navigation items: { text, href, active }
8
+ * @returns {string} HTML string for the navigation card
9
+ */
10
+ export function renderPageNavigation(label, items) {
11
+ if (!items || items.length === 0) {
12
+ return '';
13
+ }
14
+
15
+ return `
16
+ <section class="mb-8 relative z-20 px-4 sm:px-6 lg:px-8">
17
+ <div class="bg-white/70 backdrop-blur-sm shadow-sm rounded-lg px-6 py-4 w-full" style="max-width: min(90%, 1400px); margin: 0 auto;">
18
+ <div class="flex items-center gap-3 overflow-x-auto">
19
+ <span class="text-gray-600 font-semibold whitespace-nowrap">${label}</span>
20
+ ${items.map((item, index) => {
21
+ const activeClasses = item.active
22
+ ? 'text-blue-600 font-semibold underline decoration-2 underline-offset-2'
23
+ : 'text-gray-700 hover:text-blue-600';
24
+
25
+ return `
26
+ ${index > 0 ? '<span class="text-gray-300">•</span>' : ''}
27
+ <a href="${item.href}" class="px-3 py-1.5 text-sm font-medium ${activeClasses} hover:bg-blue-50 rounded transition-colors whitespace-nowrap">${item.text}</a>
28
+ `;
29
+ }).join('')}
30
+ </div>
31
+ </div>
32
+ </section>
33
+ `;
34
+ }
35
+
36
+ /**
37
+ * Convenience function for home page navigation
38
+ */
39
+ export function renderHomeNavigation() {
40
+ return renderPageNavigation('On this page:', [
41
+ { text: 'About', href: '/#about' },
42
+ { text: 'Recent Works', href: '/#recent-works' },
43
+ { text: 'Research Areas', href: '/#research-areas' },
44
+ { text: 'Team Members', href: '/#team' }
45
+ ]);
46
+ }
47
+
48
+ /**
49
+ * Convenience function for area page navigation
50
+ * @param {string} areaId - Area identifier
51
+ * @param {string} areaTitle - Area display title
52
+ * @param {Array} topics - Array of topics: { id, navName }
53
+ * @param {string} currentTopicId - Current topic ID (if any)
54
+ */
55
+ export function renderAreaNavigation(areaId, areaTitle, topics, currentTopicId = null) {
56
+ const items = [
57
+ {
58
+ text: 'Overview',
59
+ href: `/${areaId}`,
60
+ active: currentTopicId === null
61
+ },
62
+ ...topics.map(topic => ({
63
+ text: topic.navName,
64
+ href: `/${areaId}/${topic.id}`,
65
+ active: topic.id === currentTopicId
66
+ }))
67
+ ];
68
+
69
+ return renderPageNavigation(`${areaTitle}:`, items);
70
+ }
71
+