yjernite HF Staff commited on
Commit
930894d
·
verified ·
1 Parent(s): eb8130c

Upload 3 files

Browse files
js/pages/AreaPage.js ADDED
@@ -0,0 +1,260 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // pages/AreaPage.js - Reusable area page component
2
+ import { areasData } from '../data/areas.js';
3
+ import { featuredArtifacts } from '../data/artifacts.js';
4
+ import { createArtifactCarousel } from '../cards/ArtifactSummaryCard.js';
5
+ import { sampleResources } from '../data/resources.js';
6
+ import { createResourceCard, initializeResourceCards } from '../cards/ResourceCard.js';
7
+
8
+ export function renderAreaPage(areaId) {
9
+ const area = areasData[areaId];
10
+ if (!area) {
11
+ return {
12
+ content: `<div class="bg-white rounded-lg shadow-sm p-8"><h1 class="text-2xl font-bold text-red-600">Area not found</h1></div>`,
13
+ init: () => {}
14
+ };
15
+ }
16
+
17
+ // Filter resources by area
18
+ const areaResources = sampleResources.filter(resource =>
19
+ resource.areaTags.includes(areaId)
20
+ );
21
+
22
+ // Get sub-areas as array for easier processing
23
+ const subAreas = Object.entries(area.subAreas).map(([key, value]) => ({
24
+ id: key,
25
+ name: typeof value === 'string' ? value : value.name,
26
+ description: typeof value === 'string' ? '' : (value.description || ''),
27
+ openness: typeof value === 'string' ? '' : (value.openness || ''),
28
+ gradient: typeof value === 'object' ? value.gradient : null
29
+ }));
30
+
31
+ const content = `
32
+ <!-- Page Background Image for Main Content -->
33
+ <div class="fixed opacity-40 z-0" style="top: 104px; left: 256px; right: 0; bottom: 0;" id="area-background-container">
34
+ <img src="images/${area.image}" alt="" class="w-full h-full object-cover pointer-events-none">
35
+ <!-- Attribution Tooltip -->
36
+ <div id="area-bg-attribution" class="absolute bottom-4 right-4 bg-black bg-opacity-75 text-white text-xs px-2 py-1 rounded opacity-0 transition-opacity duration-200 max-w-xs z-50">
37
+ <a href="${area.imageSourceUrl}" target="_blank" class="text-blue-300 hover:text-blue-100">
38
+ ${area.imageAttribution}
39
+ </a>
40
+ </div>
41
+ </div>
42
+
43
+ <!-- Overview Section -->
44
+ <section id="overview" class="mb-12 relative z-20">
45
+ <!-- Overview Background Image -->
46
+ <div class="absolute inset-0 opacity-10 rounded-lg overflow-hidden">
47
+ <img src="images/${area.image}" alt="" class="w-full h-full object-cover">
48
+ </div>
49
+ <div class="bg-white bg-opacity-90 backdrop-blur-sm rounded-lg shadow-sm p-8 mb-6 relative z-10">
50
+ <h1 class="text-3xl font-bold text-gray-900 mb-6">${area.title}</h1>
51
+
52
+ <!-- Description -->
53
+ <div class="mb-8">
54
+ <p class="text-gray-700 leading-relaxed mb-6">${area.description}</p>
55
+ </div>
56
+
57
+ <!-- Role of Openness -->
58
+ <div class="mb-8 px-4 py-4 bg-gradient-to-r from-orange-50 to-yellow-50 border-l-4 border-orange-300 rounded-r-lg">
59
+ <p class="font-bold text-orange-900 mb-2">🤗 The Role of Openness</p>
60
+ <p class="text-orange-800 italic leading-relaxed">${area.openness}</p>
61
+ </div>
62
+
63
+ <!-- Sub-areas Navigation -->
64
+ <div class="mb-8">
65
+ <h3 class="text-lg font-semibold text-gray-900 mb-4">Explore Sub-areas</h3>
66
+ <div class="grid grid-cols-1 md:grid-cols-${Math.min(subAreas.length, 3)} gap-4">
67
+ ${subAreas.map(subArea => `
68
+ <a
69
+ href="#${subArea.id}"
70
+ class="group p-4 bg-gradient-to-br ${subArea.gradient || 'from-gray-50 to-gray-100 hover:from-gray-100 hover:to-gray-200 border-gray-200 hover:border-gray-300 text-gray-900'} rounded-lg border transition-all duration-200 text-left block no-underline"
71
+ >
72
+ <h4 class="font-semibold text-sm group-hover:scale-105 transition-transform">${subArea.name}</h4>
73
+ </a>
74
+ `).join('')}
75
+ </div>
76
+ </div>
77
+ </div>
78
+ </section>
79
+
80
+ <!-- Sub-areas Sections -->
81
+ ${subAreas.map(subArea => `
82
+ <section id="${subArea.id}" class="mb-12 relative z-20">
83
+ <div class="bg-white bg-opacity-90 backdrop-blur-sm rounded-lg shadow-sm p-8">
84
+ <h2 class="text-2xl font-bold text-gray-900 mb-6">${subArea.name}</h2>
85
+
86
+ ${subArea.description ? `
87
+ <div class="mb-6">
88
+ <p class="text-gray-700 leading-relaxed">${subArea.description}</p>
89
+ </div>
90
+ ` : ''}
91
+
92
+ ${subArea.openness ? `
93
+ <div class="mb-6 px-4 py-4 bg-gradient-to-r from-orange-50 to-yellow-50 border-l-4 border-orange-300 rounded-r-lg">
94
+ <p class="font-bold text-orange-900 mb-2">🤗 The Role of Openness</p>
95
+ <p class="text-orange-800 italic leading-relaxed">${subArea.openness}</p>
96
+ </div>
97
+ ` : ''}
98
+
99
+ <!-- Artifacts Carousel -->
100
+ <div class="mb-6">
101
+ <h3 class="text-lg font-semibold text-gray-900 mb-4">Related Research & Resources</h3>
102
+ <div id="${subArea.id}-artifacts-carousel">
103
+ <!-- Carousel will be inserted here -->
104
+ </div>
105
+ </div>
106
+ </div>
107
+ </section>
108
+ `).join('')}
109
+
110
+ <!-- Resources Section -->
111
+ <section id="resources" class="mb-12 relative z-10">
112
+ <div class="bg-white bg-opacity-90 backdrop-blur-sm rounded-lg shadow-sm p-8">
113
+ <h2 class="text-2xl font-bold text-gray-900 mb-6">Technical Resources</h2>
114
+
115
+ <!-- Filter resources by area -->
116
+ <div class="mb-6">
117
+ <p class="text-gray-700 leading-relaxed">
118
+ Technical resources and tools related to ${area.name.toLowerCase()} research and development.
119
+ </p>
120
+ </div>
121
+
122
+ <!-- Resource Cards Carousel -->
123
+ <div class="relative">
124
+ <div class="resource-carousel-container overflow-hidden" style="width: calc(100% - 2rem); margin: 0 auto;">
125
+ <div class="resource-carousel flex gap-6 transition-transform duration-300 ease-in-out" style="width: ${areaResources.length * 624}px;">
126
+ ${areaResources.map(resource => createResourceCard(resource)).join('')}
127
+ </div>
128
+ </div>
129
+
130
+ <!-- Carousel Navigation -->
131
+ <div class="flex justify-center mt-4 space-x-2">
132
+ <button class="resource-carousel-prev bg-gray-200 hover:bg-gray-300 text-gray-700 px-3 py-2 rounded-md transition-colors">
133
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
134
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
135
+ </svg>
136
+ </button>
137
+ <button class="resource-carousel-next bg-gray-200 hover:bg-gray-300 text-gray-700 px-3 py-2 rounded-md transition-colors">
138
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
139
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
140
+ </svg>
141
+ </button>
142
+ </div>
143
+ </div>
144
+ </div>
145
+ </section>
146
+ `;
147
+
148
+ return {
149
+ content,
150
+ init: () => {
151
+ // Initialize artifact carousels for each sub-area with a small delay
152
+ // to ensure DOM elements are fully rendered
153
+ setTimeout(() => {
154
+ subAreas.forEach(subArea => {
155
+ // For now, show all artifacts in each carousel
156
+ // Later this will be filtered by the backend
157
+ const carouselId = `${subArea.id}-artifacts-carousel`;
158
+ createArtifactCarousel(featuredArtifacts, carouselId);
159
+ });
160
+ }, 50);
161
+
162
+ // Initialize resource cards
163
+ initializeResourceCards();
164
+
165
+ // Initialize resource carousel if there are resources
166
+ if (areaResources.length > 0) {
167
+ initializeAreaResourceCarousel(areaResources);
168
+ }
169
+
170
+ // Initialize background attribution
171
+ initializeAreaBackgroundAttribution();
172
+ }
173
+ };
174
+ }
175
+
176
+ export function getAreaPageSidebar(areaId) {
177
+ const area = areasData[areaId];
178
+ if (!area) {
179
+ return '<div class="p-4">Area not found</div>';
180
+ }
181
+
182
+ const subAreas = Object.entries(area.subAreas).map(([key, value]) => ({
183
+ id: key,
184
+ name: typeof value === 'string' ? value : value.name
185
+ }));
186
+
187
+ return `
188
+ <nav class="p-4">
189
+ <h3 class="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-3">On This Page</h3>
190
+ <ul class="space-y-1">
191
+ <li><a href="#overview" class="page-nav-link block px-3 py-2 text-sm text-blue-600 bg-blue-50 rounded-md">Overview</a></li>
192
+ <li class="ml-4">
193
+ <ul class="space-y-1">
194
+ ${subAreas.map(subArea => `
195
+ <li><a href="#${subArea.id}" class="page-nav-link block px-3 py-2 text-xs text-gray-600 hover:text-gray-800 hover:bg-gray-50 rounded-md transition-colors">${subArea.name}</a></li>
196
+ `).join('')}
197
+ </ul>
198
+ </li>
199
+ <li><a href="#resources" class="page-nav-link block px-3 py-2 text-sm text-gray-700 hover:text-gray-900 hover:bg-gray-50 rounded-md transition-colors">Resources</a></li>
200
+ </ul>
201
+ </nav>
202
+ `;
203
+ }
204
+
205
+ function initializeAreaResourceCarousel(resources) {
206
+ const carousel = document.querySelector('.resource-carousel');
207
+ const prevButton = document.querySelector('.resource-carousel-prev');
208
+ const nextButton = document.querySelector('.resource-carousel-next');
209
+
210
+ if (!carousel || !prevButton || !nextButton) {
211
+ return;
212
+ }
213
+
214
+ let currentIndex = 0;
215
+ const cardWidth = 624; // 600px card + 24px gap
216
+ const visibleCards = 1.5; // Show 1.5 cards at a time (larger cards)
217
+ const maxIndex = resources.length - 1; // Allow going to the last item
218
+
219
+ function updateCarousel() {
220
+ const translateX = -currentIndex * cardWidth;
221
+ carousel.style.transform = `translateX(${translateX}px)`;
222
+
223
+ // Update button states - no disabled states for looping
224
+ prevButton.classList.remove('opacity-50', 'cursor-not-allowed');
225
+ nextButton.classList.remove('opacity-50', 'cursor-not-allowed');
226
+ }
227
+
228
+ prevButton.addEventListener('click', () => {
229
+ currentIndex = currentIndex > 0 ? currentIndex - 1 : maxIndex;
230
+ updateCarousel();
231
+ });
232
+
233
+ nextButton.addEventListener('click', () => {
234
+ currentIndex = currentIndex < maxIndex ? currentIndex + 1 : 0;
235
+ updateCarousel();
236
+ });
237
+
238
+ // Initialize carousel state
239
+ updateCarousel();
240
+ }
241
+
242
+ function initializeAreaBackgroundAttribution() {
243
+ const backgroundContainer = document.getElementById('area-background-container');
244
+ const attribution = document.getElementById('area-bg-attribution');
245
+
246
+ if (!backgroundContainer || !attribution) {
247
+ return;
248
+ }
249
+
250
+ // Show attribution on hover over the background container
251
+ backgroundContainer.addEventListener('mouseenter', () => {
252
+ attribution.style.opacity = '1';
253
+ });
254
+
255
+ backgroundContainer.addEventListener('mouseleave', () => {
256
+ attribution.style.opacity = '0';
257
+ });
258
+ }
259
+
260
+ // Colors are now defined in the areas data and accessed via subArea.gradient
js/pages/HomePage.js ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // pages/HomePage.js - Home page functionality for SPA
2
+ import { createTeamMember } from '../main.js';
3
+ import { createHomeAreaCard } from '../cards/HomeAreaCard.js';
4
+ import { createArtifactCarousel } from '../cards/ArtifactSummaryCard.js';
5
+ import { areasData, homeBackgroundImage } from '../data/areas.js';
6
+ import { featuredArtifacts } from '../data/artifacts.js';
7
+ import { teamMembers } from '../data/team.js';
8
+
9
+ export function renderHomePage() {
10
+ const content = `
11
+ <!-- Page Background Image for Main Content -->
12
+ <div class="fixed opacity-40 z-0" style="top: 104px; left: 256px; right: 0; bottom: 0;" id="home-background-container">
13
+ <img src="images/${homeBackgroundImage.image}" alt="" class="w-full h-full object-cover pointer-events-none">
14
+ <!-- Attribution Tooltip -->
15
+ <div id="home-bg-attribution" class="absolute bottom-4 right-4 bg-black bg-opacity-75 text-white text-xs px-2 py-1 rounded opacity-0 transition-opacity duration-200 max-w-xs z-50">
16
+ <a href="${homeBackgroundImage.sourceUrl}" target="_blank" class="text-blue-300 hover:text-blue-100">
17
+ ${homeBackgroundImage.attribution}
18
+ </a>
19
+ </div>
20
+ </div>
21
+
22
+ <!-- Team Introduction Section -->
23
+ <section id="intro" class="mb-12 relative z-20">
24
+ <!-- Part 1: Team Introduction and Research Areas -->
25
+ <div class="bg-white rounded-lg shadow-sm p-8 mb-6">
26
+ <h3 class="text-3xl font-bold text-gray-900 mb-6">Team Introduction</h3>
27
+
28
+ <!-- Research Areas Introduction -->
29
+ <div class="mb-8">
30
+ <p class="text-gray-700 leading-relaxed mb-6">We're a multidisciplinary team working on research and regulatory questions related to AI systems &mdash; their (open) development, governance, and impact on society at large. Our work spans four key inter-connected areas where AI technology intersects with society:</p>
31
+
32
+ <!-- Research Areas Navigation -->
33
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
34
+ ${Object.values(areasData).map(area => `
35
+ <a
36
+ href="#${area.id}"
37
+ class="group p-4 bg-gradient-to-br ${area.colors.gradient} rounded-lg border transition-all duration-200 text-left block no-underline"
38
+ >
39
+ <h4 class="font-semibold text-sm mb-1 group-hover:scale-105 transition-transform">${area.title}</h4>
40
+ <p class="text-xs opacity-80">${getAreaShortDescription(area.id)}</p>
41
+ </a>
42
+ `).join('')}
43
+ </div>
44
+
45
+ <!-- Perspective and Positionality -->
46
+ <div class="mt-8 px-4 py-4 bg-gradient-to-r from-orange-50 to-yellow-50 border-l-4 border-orange-300 rounded-r-lg">
47
+ <p class="font-bold text-center text-orange-900 mb-2">🤗 Perspective and Positionality</p>
48
+ <p class="text-orange-800 italic leading-relaxed">
49
+ Our work on these topics is shaped by the context of our work at Hugging Face, which is the main platform for open and collaborative development and sharing of Artificial Intelligence artifacts. As a result, much of our work discusses the specific roles of openness and transparency in shaping AI technology into a more equitable and better-governed category of technology.
50
+ </p>
51
+ </div>
52
+ </div>
53
+
54
+
55
+
56
+ <!-- Recent & Featured Works -->
57
+ <div id="recent-featured-works" class="mb-12 border-t border-gray-200 pt-6">
58
+ <h4 class="text-lg font-semibold text-gray-900 mb-4">Recent & Featured Works</h4>
59
+ <div class="mb-8">
60
+ <div id="featured-artifacts-carousel">
61
+ <!-- Carousel will be inserted here -->
62
+ </div>
63
+ </div>
64
+ </div>
65
+
66
+
67
+ <!-- Team and Collaborators -->
68
+ <div id="team-members" class="border-t border-gray-200 pt-6">
69
+ <h4 class="text-lg font-semibold text-gray-900 mb-4">Team Members</h4>
70
+ <div class="mb-4">
71
+ <div id="team-grid" class="grid grid-cols-1 md:grid-cols-2 gap-4">
72
+ <!-- Team members will be inserted here by JavaScript -->
73
+ </div>
74
+ </div>
75
+ <div class="prose text-gray-700">
76
+ <p>We also work closely with
77
+ <a href="https://huggingface.co/irenesolaiman" class="text-blue-600 hover:text-blue-800 transition-colors" target="_blank">Irene Solaiman</a> (Chief Policy Officer),
78
+ <a href="https://huggingface.co/evijit" class="text-blue-600 hover:text-blue-800 transition-colors" target="_blank">Avijit Ghosh</a> (Applied Policy Researcher) in the policy team,
79
+ and with <a href="https://huggingface.co/meg" class="text-blue-600 hover:text-blue-800 transition-colors" target="_blank">Meg Mitchell</a> (Chief Ethics Scientist),
80
+ and with <a href="https://huggingface.co/brunatrevelin" class="text-blue-600 hover:text-blue-800 transition-colors" target="_blank">Bruna Trevelin</a> (Legal Counsel)!
81
+ </p>
82
+ </div>
83
+ </div>
84
+ </div>
85
+ </section>
86
+
87
+ <!-- Research Areas Header -->
88
+ <div id="research-areas-header" class="text-center mb-12 relative z-20">
89
+ <h2 class="text-3xl font-bold text-gray-900">Research Areas</h2>
90
+ </div>
91
+
92
+ <!-- Research Areas -->
93
+ <section id="research-areas" class="space-y-8 relative z-20">
94
+ <!-- Area cards will be inserted here by JavaScript -->
95
+ </section>
96
+ `;
97
+
98
+ return {
99
+ content,
100
+ init: () => {
101
+ initializeTeamMembers();
102
+ initializeHomeAreaCards();
103
+ initializeArtifactCarousels();
104
+ initializeBackgroundAttribution();
105
+ }
106
+ };
107
+ }
108
+
109
+ export function getHomePageSidebar() {
110
+ return `
111
+ <nav class="p-4">
112
+ <h3 class="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-3">On This Page</h3>
113
+ <ul class="space-y-1">
114
+ <li><a href="#intro" class="page-nav-link block px-3 py-2 text-sm text-blue-600 bg-blue-50 rounded-md">Team Introduction</a></li>
115
+ <li class="ml-4">
116
+ <ul class="space-y-1">
117
+ <li><a href="#recent-featured-works" class="page-nav-link block px-3 py-2 text-xs text-blue-600 bg-blue-50 rounded-md">Recent & Featured Works</a></li>
118
+ <li><a href="#team-members" class="page-nav-link block px-3 py-2 text-xs text-blue-600 bg-blue-50 rounded-md">Team Members</a></li>
119
+ </ul>
120
+ </li>
121
+ <li><a href="#research-areas-header" class="page-nav-link block px-3 py-2 text-sm text-gray-700 hover:text-gray-900 hover:bg-gray-50 rounded-md transition-colors">Research Areas</a></li>
122
+ <li class="ml-4">
123
+ <ul class="space-y-1">
124
+ <li><a href="#efficiency" class="page-nav-link block px-3 py-2 text-xs text-gray-600 hover:text-gray-800 hover:bg-gray-50 rounded-md transition-colors">Efficiency & Environment</a></li>
125
+ <li><a href="#personal" class="page-nav-link block px-3 py-2 text-xs text-gray-600 hover:text-gray-800 hover:bg-gray-50 rounded-md transition-colors">Consent & Personal Interactions</a></li>
126
+ <li><a href="#rights" class="page-nav-link block px-3 py-2 text-xs text-gray-600 hover:text-gray-800 hover:bg-gray-50 rounded-md transition-colors">Rights & Regulation</a></li>
127
+ <li><a href="#ecosystems" class="page-nav-link block px-3 py-2 text-xs text-gray-600 hover:text-gray-800 hover:bg-gray-50 rounded-md transition-colors">Socio-Economic & Technical Ecosystems</a></li>
128
+ </ul>
129
+ </li>
130
+ </ul>
131
+ </nav>
132
+ `;
133
+ }
134
+
135
+ function initializeTeamMembers() {
136
+ const teamContainer = document.getElementById('team-grid');
137
+ if (!teamContainer) {
138
+ return;
139
+ }
140
+
141
+ teamContainer.innerHTML = teamMembers.map(member =>
142
+ createTeamMember(member.name, member.role, member.username, member.tags)
143
+ ).join('');
144
+ }
145
+
146
+ function getAreaShortDescription(areaId) {
147
+ const descriptions = {
148
+ efficiency: 'Costs, energy, sustainability',
149
+ personal: 'Personal interactions, agency',
150
+ rights: 'Legal frameworks, compliance',
151
+ ecosystems: 'Markets, labor, power dynamics'
152
+ };
153
+ return descriptions[areaId] || '';
154
+ }
155
+
156
+ function initializeHomeAreaCards() {
157
+ const areasContainer = document.getElementById('research-areas');
158
+ if (!areasContainer) return;
159
+
160
+ const areas = Object.values(areasData);
161
+ areasContainer.innerHTML = areas.map(area =>
162
+ createHomeAreaCard(area.id, area.title, area.description, area.openness, Object.values(area.subAreas).map(sub => typeof sub === 'string' ? sub : sub.name), area.imagePosition, area.imageAttribution, area.imageAltText)
163
+ ).join('');
164
+ }
165
+
166
+ function initializeArtifactCarousels() {
167
+ const carouselContainer = document.getElementById('featured-artifacts-carousel');
168
+ if (!carouselContainer) return;
169
+
170
+ createArtifactCarousel(featuredArtifacts, 'featured-artifacts-carousel');
171
+ }
172
+
173
+ function initializeBackgroundAttribution() {
174
+ const backgroundContainer = document.getElementById('home-background-container');
175
+ const attribution = document.getElementById('home-bg-attribution');
176
+
177
+ if (!backgroundContainer || !attribution) {
178
+ return;
179
+ }
180
+
181
+ // Show attribution on hover over the background container
182
+ backgroundContainer.addEventListener('mouseenter', () => {
183
+ attribution.style.opacity = '1';
184
+ });
185
+
186
+ backgroundContainer.addEventListener('mouseleave', () => {
187
+ attribution.style.opacity = '0';
188
+ });
189
+ }
js/pages/ResourcesPage.js ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // pages/ResourcesPage.js - Resources page functionality for SPA
2
+ import { homeBackgroundImage, areasData } from '../data/areas.js';
3
+ import { pressMentions } from '../data/press.js';
4
+ import { sampleResources } from '../data/resources.js';
5
+ import { createResourceCard, initializeResourceCards } from '../cards/ResourceCard.js';
6
+
7
+ export function renderResourcesPage() {
8
+ const content = `
9
+ <!-- Page Background Image for Main Content -->
10
+ <div class="fixed opacity-40 z-0" style="top: 104px; left: 256px; right: 0; bottom: 0;" id="resources-background-container">
11
+ <img src="images/${homeBackgroundImage.image}" alt="" class="w-full h-full object-cover pointer-events-none">
12
+ <!-- Attribution Tooltip -->
13
+ <div id="resources-bg-attribution" class="absolute bottom-4 right-4 bg-black bg-opacity-75 text-white text-xs px-2 py-1 rounded opacity-0 transition-opacity duration-200 max-w-xs z-50">
14
+ <a href="${homeBackgroundImage.sourceUrl}" target="_blank" class="text-blue-300 hover:text-blue-100">
15
+ ${homeBackgroundImage.attribution}
16
+ </a>
17
+ </div>
18
+ </div>
19
+
20
+ <!-- Technical Resources Section -->
21
+ <section id="technical-resources" class="mb-12 relative z-20">
22
+ <div class="bg-white bg-opacity-90 backdrop-blur-sm rounded-lg shadow-sm p-8">
23
+ <h2 class="text-3xl font-bold text-gray-900 mb-6">Technical Resources</h2>
24
+
25
+ <div class="mb-8">
26
+ <p class="text-gray-700 leading-relaxed mb-6">
27
+ Our team develops technical artifacts in the course of their research and policy work.
28
+ These resources include datasets, tools, benchmarks, and interactive applications that
29
+ support transparency, reproducibility, and accessibility in AI research and governance.
30
+ </p>
31
+ </div>
32
+
33
+ <!-- Resource Cards Carousel -->
34
+ <div class="relative">
35
+ <div class="resource-carousel-container overflow-hidden" style="width: calc(100% - 2rem); margin: 0 auto;">
36
+ <div class="resource-carousel flex gap-6 transition-transform duration-300 ease-in-out" style="width: ${sampleResources.length * 624}px;">
37
+ ${sampleResources.map(resource => createResourceCard(resource)).join('')}
38
+ </div>
39
+ </div>
40
+
41
+ <!-- Carousel Navigation -->
42
+ <div class="flex justify-center mt-4 space-x-2">
43
+ <button class="resource-carousel-prev bg-gray-200 hover:bg-gray-300 text-gray-700 px-3 py-2 rounded-md transition-colors">
44
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
45
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
46
+ </svg>
47
+ </button>
48
+ <button class="resource-carousel-next bg-gray-200 hover:bg-gray-300 text-gray-700 px-3 py-2 rounded-md transition-colors">
49
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
50
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
51
+ </svg>
52
+ </button>
53
+ </div>
54
+ </div>
55
+ </div>
56
+ </section>
57
+
58
+ <!-- Press Mentions Section -->
59
+ <section id="press-mentions" class="mb-12 relative z-20">
60
+ <div class="bg-white bg-opacity-90 backdrop-blur-sm rounded-lg shadow-sm p-8">
61
+ <h2 class="text-3xl font-bold text-gray-900 mb-6">Press Mentions</h2>
62
+
63
+ <div class="space-y-3">
64
+ ${pressMentions.map((article, index) => {
65
+ // Get the primary area for this article
66
+ const primaryArea = areasData[article.areaTags[0]];
67
+ const primaryColor = primaryArea?.primaryColor || 'gray';
68
+
69
+ // Determine alignment (alternating)
70
+ const isLeftAligned = index % 2 === 0;
71
+ const alignment = isLeftAligned ? 'justify-start' : 'justify-end';
72
+ const borderSide = isLeftAligned ? 'border-l-2' : 'border-r-2';
73
+ const borderColor = `border-${primaryColor}-200`;
74
+
75
+ // Format date
76
+ const formattedDate = new Date(article.date).toLocaleDateString('en-US', {
77
+ year: 'numeric',
78
+ month: 'long',
79
+ day: 'numeric'
80
+ });
81
+
82
+ // Generate area tags
83
+ const areaTagsHtml = article.areaTags.map(areaId => {
84
+ const area = areasData[areaId];
85
+ if (!area) return '';
86
+ return `<span class="px-2 py-1 rounded-full text-xs font-medium ${area.colors.medium}">${area.name}</span>`;
87
+ }).join('');
88
+
89
+ // Generate sub-area tags
90
+ const subAreaTagsHtml = article.subAreaTags.map(subAreaKey => {
91
+ // Find the sub-area name and color by looking through all areas
92
+ let subAreaName = subAreaKey; // fallback to key if not found
93
+ let textColor = 'text-gray-700'; // fallback color
94
+
95
+ for (const areaKey in areasData) {
96
+ const area = areasData[areaKey];
97
+ if (area.subAreas && area.subAreas[subAreaKey]) {
98
+ const subArea = area.subAreas[subAreaKey];
99
+ subAreaName = typeof subArea === 'string' ? subArea : subArea.name;
100
+ // Extract just the text color from the sub-area color
101
+ if (typeof subArea === 'object' && subArea.color) {
102
+ const colorMatch = subArea.color.match(/text-(\w+)-(\d+)/);
103
+ if (colorMatch) {
104
+ textColor = `text-${colorMatch[1]}-700`; // Use consistent 700 shade
105
+ }
106
+ }
107
+ break;
108
+ }
109
+ }
110
+ return `<span class="inline-block px-2 py-0.5 text-xs bg-gray-200 ${textColor} rounded">${subAreaName}</span>`;
111
+ }).join('');
112
+
113
+ return `
114
+ <!-- Press Article ${index + 1} - ${isLeftAligned ? 'Left' : 'Right'} aligned -->
115
+ <div class="flex items-center ${alignment}">
116
+ <div class="flex-1 max-w-2xl">
117
+ <div class="bg-gray-50 rounded-lg p-4 ${borderSide} ${borderColor} relative overflow-hidden">
118
+ <!-- Background image for this article -->
119
+ <div class="absolute inset-0 opacity-5 pointer-events-none">
120
+ <img src="images/${primaryArea?.image || 'ai.png'}" alt="" class="w-full h-full object-cover">
121
+ </div>
122
+
123
+ <div class="relative z-10">
124
+ <h3 class="text-base font-medium text-gray-900 mb-2">
125
+ <a href="${article.sourceUrl}" target="_blank" class="text-gray-900 hover:text-blue-600 transition-colors">
126
+ "${article.title}"
127
+ </a>
128
+ </h3>
129
+
130
+ <div class="flex items-center text-sm text-gray-600 mb-2">
131
+ <span class="bg-gray-600 text-white px-3 py-1 rounded-md text-xs font-semibold mr-3 shadow-sm">${article.source}</span>
132
+ <span class="mr-3">${formattedDate}</span>
133
+ <a href="${article.sourceUrl}" target="_blank" class="text-gray-400 hover:text-blue-600 transition-colors">
134
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
135
+ <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>
136
+ </svg>
137
+ </a>
138
+ </div>
139
+
140
+ <!-- Tags -->
141
+ <div class="flex flex-wrap gap-1">
142
+ ${areaTagsHtml}
143
+ ${subAreaTagsHtml}
144
+ </div>
145
+ </div>
146
+ </div>
147
+ </div>
148
+ </div>
149
+ `;
150
+ }).join('')}
151
+ </div>
152
+ </div>
153
+ </section>
154
+ `;
155
+
156
+ return {
157
+ content,
158
+ init: () => {
159
+ // Initialize background attribution
160
+ initializeResourcesBackgroundAttribution();
161
+ // Initialize resource cards
162
+ initializeResourceCards();
163
+ // Initialize resource carousel
164
+ initializeResourceCarousel();
165
+ }
166
+ };
167
+ }
168
+
169
+ export function getResourcesPageSidebar() {
170
+ return `
171
+ <nav class="p-4">
172
+ <h3 class="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-3">Resources</h3>
173
+ <ul class="space-y-1">
174
+ <li><a href="#technical-resources" class="page-nav-link block px-3 py-2 text-sm text-blue-600 bg-blue-50 rounded-md">Technical Resources</a></li>
175
+ <li><a href="#press-mentions" class="page-nav-link block px-3 py-2 text-sm text-gray-700 hover:text-gray-900 hover:bg-gray-50 rounded-md transition-colors">Press Mentions</a></li>
176
+ </ul>
177
+ </nav>
178
+ `;
179
+ }
180
+
181
+ function initializeResourcesBackgroundAttribution() {
182
+ const backgroundContainer = document.getElementById('resources-background-container');
183
+ const attribution = document.getElementById('resources-bg-attribution');
184
+
185
+ if (!backgroundContainer || !attribution) {
186
+ return;
187
+ }
188
+
189
+ // Show attribution on hover over the background container
190
+ backgroundContainer.addEventListener('mouseenter', () => {
191
+ attribution.style.opacity = '1';
192
+ });
193
+
194
+ backgroundContainer.addEventListener('mouseleave', () => {
195
+ attribution.style.opacity = '0';
196
+ });
197
+ }
198
+
199
+ function initializeResourceCarousel() {
200
+ const carousel = document.querySelector('.resource-carousel');
201
+ const prevButton = document.querySelector('.resource-carousel-prev');
202
+ const nextButton = document.querySelector('.resource-carousel-next');
203
+
204
+ if (!carousel || !prevButton || !nextButton) {
205
+ return;
206
+ }
207
+
208
+ let currentIndex = 0;
209
+ const cardWidth = 624; // 600px card + 24px gap
210
+ const visibleCards = 1.5; // Show 1.5 cards at a time (larger cards)
211
+ const maxIndex = sampleResources.length - 1; // Allow going to the last item
212
+
213
+ function updateCarousel() {
214
+ const translateX = -currentIndex * cardWidth;
215
+ carousel.style.transform = `translateX(${translateX}px)`;
216
+
217
+ // Update button states - no disabled states for looping
218
+ prevButton.classList.remove('opacity-50', 'cursor-not-allowed');
219
+ nextButton.classList.remove('opacity-50', 'cursor-not-allowed');
220
+ }
221
+
222
+ prevButton.addEventListener('click', () => {
223
+ currentIndex = currentIndex > 0 ? currentIndex - 1 : maxIndex;
224
+ updateCarousel();
225
+ });
226
+
227
+ nextButton.addEventListener('click', () => {
228
+ currentIndex = currentIndex < maxIndex ? currentIndex + 1 : 0;
229
+ updateCarousel();
230
+ });
231
+
232
+ // Initialize carousel state
233
+ updateCarousel();
234
+ }