···
81
81
transition: color 0.3s ease;
82
82
}
83
83
84
84
+
.navbar-links ul li {
85
85
+
margin-left: 20px; /* Consistent spacing between all links */
86
86
+
position: relative;
87
87
+
display: flex;
88
88
+
align-items: center;
89
89
+
}
90
90
+
84
91
.navbar-links ul li a:hover {
85
92
color: #3B9AF8; /* Change color on hover */
86
93
}
···
237
244
238
245
.dropdown-trigger {
239
246
position: relative;
240
240
-
display: inline-block;
247
247
+
display: inline-flex;
248
248
+
align-items: center;
241
249
white-space: nowrap;
250
250
+
padding-right: 1.2em; /* Add padding to accommodate the triangle */
242
251
}
243
252
244
253
.dropdown-trigger::after {
245
254
content: '▼';
246
255
font-size: 0.6em;
256
256
+
display: inline-block;
257
257
+
margin-left: 0.4em;
247
258
position: absolute;
248
259
right: 0;
249
260
top: 50%;
250
261
transform: translateY(-50%);
251
262
transition: transform 0.2s ease;
252
263
}
264
264
+
253
265
254
266
.dropdown-container:hover .dropdown-trigger::after {
255
267
transform: translateY(-50%) rotate(180deg);
···
302
314
color: #3B9AF8;
303
315
}
304
316
317
317
+
.navbar-links ul li a:not(.dropdown-trigger) {
318
318
+
padding-right: 0;
319
319
+
display: inline-block;
320
320
+
}
321
321
+
322
322
+
305
323
/* Dark mode adjustments for dropdown */
306
324
.dark-mode .dropdown-menu {
307
325
background-color: var(--navbar-bg);
···
467
485
.dropdown-container {
468
486
margin-bottom: 4px;
469
487
min-height: 32px;
488
488
+
}
489
489
+
}
490
490
+
491
491
+
@media (max-width: 940px) {
492
492
+
.dropdown-trigger {
493
493
+
padding-right: 1.5em;
494
494
+
}
495
495
+
496
496
+
.dropdown-trigger::after {
497
497
+
right: 0;
498
498
+
}
499
499
+
500
500
+
/* Ensure all links in mobile have consistent appearance */
501
501
+
.navbar-links ul li a {
502
502
+
text-align: center;
503
503
+
display: inline-flex;
504
504
+
align-items: center;
505
505
+
justify-content: center;
470
506
}
471
507
}
472
508
···
4
4
import './Navbar.css';
5
5
6
6
// Dropdown Menu Component
7
7
-
const DropdownMenu = ({ title, path, items, position }) => {
7
7
+
const DropdownMenu = ({ title, path, items }) => {
8
8
const [isOpen, setIsOpen] = useState(false);
9
9
const dropdownRef = useRef(null);
10
10
···
97
97
);
98
98
};
99
99
100
100
+
// Regular menu item component to ensure consistent styling
101
101
+
const MenuItem = ({ title, path }) => {
102
102
+
return (
103
103
+
<li>
104
104
+
<Link to={path}>{title}</Link>
105
105
+
</li>
106
106
+
);
107
107
+
};
108
108
+
100
109
const Navbar = () => {
101
110
const { isDarkMode, toggleDarkMode } = useContext(ThemeContext);
102
111
const navigate = useNavigate();
···
140
149
</div>
141
150
<nav className="navbar-links">
142
151
<ul>
143
143
-
<DropdownMenu {...scoreDropdown} position="left" />
144
144
-
<li><Link to="/resources">resources</Link></li>
145
145
-
<DropdownMenu {...aboutDropdown} position="right" />
152
152
+
<DropdownMenu {...scoreDropdown} />
153
153
+
<MenuItem title="resources" path="/resources" />
154
154
+
<DropdownMenu {...aboutDropdown} />
146
155
</ul>
147
156
</nav>
148
157
</div>
···
15
15
transition: background-color 0.3s ease, border-color 0.3s ease;
16
16
}
17
17
18
18
-
/* Improved header structure */
18
18
+
/* ======= Redesigned Header Section ======= */
19
19
.resources-header {
20
20
margin-bottom: 2rem;
21
21
+
display: flex;
22
22
+
flex-direction: column;
23
23
+
gap: 1.5rem;
24
24
+
}
25
25
+
26
26
+
/* Header main section with title and tagline */
27
27
+
.header-main {
28
28
+
text-align: center;
29
29
+
margin-bottom: 0.5rem;
21
30
}
22
31
23
32
.resources-header h1 {
24
24
-
font-size: 2rem;
25
25
-
font-weight: bold;
26
26
-
margin-bottom: 1rem;
33
33
+
font-size: 2.2rem;
34
34
+
font-weight: 700;
35
35
+
margin-bottom: 0.5rem;
27
36
color: var(--button-bg);
28
28
-
text-align: center;
37
37
+
letter-spacing: -0.01em;
29
38
}
30
39
31
31
-
.resources-intro {
32
32
-
max-width: 800px;
33
33
-
margin: 0 auto 1.5rem auto;
40
40
+
.header-tagline p {
41
41
+
font-size: 1.1rem;
42
42
+
color: var(--text);
43
43
+
opacity: 0.85;
44
44
+
max-width: 600px;
45
45
+
margin: 0 auto;
46
46
+
line-height: 1.4;
34
47
}
35
48
36
36
-
.resources-page ul {
37
37
-
list-style: none;
38
38
-
text-align: center;
39
39
-
margin: 0 auto 1rem auto;
40
40
-
padding: 0;
41
41
-
width: 100%;
42
42
-
opacity: 0.8;
49
49
+
/* Feature cards */
50
50
+
.header-features {
51
51
+
margin: 0.5rem 0 1.5rem;
43
52
}
44
53
45
45
-
.resources-description {
46
46
-
color: var(--text);
47
47
-
line-height: 1.5;
48
48
-
opacity: 0.8;
49
49
-
transition: color 0.3s ease;
50
50
-
text-align: center;
51
51
-
font-size: 1.1rem;
52
52
-
margin: 0 auto;
54
54
+
.feature-cards {
55
55
+
display: flex;
56
56
+
justify-content: center;
57
57
+
gap: 1.5rem;
58
58
+
flex-wrap: wrap;
53
59
}
54
60
55
55
-
/* Improved disclaimer styling */
56
56
-
.resources-disclaimer {
61
61
+
.feature-card {
62
62
+
display: flex;
63
63
+
align-items: center;
57
64
background-color: var(--card-border);
58
58
-
border-left: 4px solid #ffd700;
59
59
-
padding: 12px 16px;
60
60
-
margin: 1.5rem auto;
61
61
-
border-radius: 4px;
62
62
-
max-width: 900px;
65
65
+
padding: 0.7rem 1.2rem;
66
66
+
border-radius: 12px;
67
67
+
transition: transform 0.2s, background-color 0.2s;
68
68
+
}
69
69
+
70
70
+
.feature-card:hover {
71
71
+
transform: translateY(-2px);
72
72
+
background-color: rgba(var(--button-bg-rgb), 0.1);
73
73
+
}
74
74
+
75
75
+
.feature-icon {
76
76
+
font-size: 1.2rem;
77
77
+
margin-right: 0.5rem;
78
78
+
}
79
79
+
80
80
+
.feature-text {
81
81
+
font-size: 0.95rem;
82
82
+
font-weight: 500;
83
83
+
}
84
84
+
85
85
+
/* Search and quick actions container */
86
86
+
.search-filters-container {
87
87
+
display: flex;
88
88
+
justify-content: space-between;
89
89
+
align-items: center;
90
90
+
gap: 1rem;
91
91
+
margin: 0.5rem 0;
92
92
+
}
93
93
+
94
94
+
/* Improved search input */
95
95
+
.search-container {
96
96
+
flex: 1;
97
97
+
max-width: 400px;
98
98
+
position: relative;
99
99
+
}
100
100
+
101
101
+
.search-icon {
102
102
+
position: absolute;
103
103
+
left: 12px;
104
104
+
top: 50%;
105
105
+
transform: translateY(-50%);
106
106
+
font-size: 1rem;
107
107
+
opacity: 0.6;
63
108
}
64
109
65
65
-
.resources-disclaimer p {
66
66
-
margin: 0;
67
67
-
font-size: 0.9rem;
110
110
+
.search-input {
111
111
+
width: 100%;
112
112
+
padding: 12px 12px 12px 40px;
113
113
+
font-size: 1rem;
114
114
+
border: 2px solid var(--card-border);
115
115
+
border-radius: 30px;
116
116
+
background-color: var(--navbar-bg);
68
117
color: var(--text);
118
118
+
transition: all 0.3s ease;
69
119
}
70
120
71
71
-
.share-button-container {
72
72
-
text-align: center;
73
73
-
margin: 1.5rem auto;
121
121
+
.search-input:focus {
122
122
+
border-color: var(--button-bg);
123
123
+
outline: none;
124
124
+
box-shadow: 0 0 0 3px rgba(var(--button-bg-rgb), 0.2);
74
125
}
75
126
127
127
+
/* Quick actions */
128
128
+
.quick-actions {
129
129
+
display: flex;
130
130
+
gap: 0.5rem;
131
131
+
}
132
132
+
133
133
+
/* Share button */
76
134
.share-button {
135
135
+
display: flex;
136
136
+
align-items: center;
137
137
+
gap: 6px;
77
138
background-color: var(--button-bg);
78
139
color: var(--button-text);
79
140
padding: 10px 20px;
80
141
border: none;
81
81
-
border-radius: 20px;
142
142
+
border-radius: 30px;
82
143
font-size: 0.95rem;
83
144
font-weight: 600;
84
145
cursor: pointer;
85
85
-
display: inline-flex;
86
86
-
align-items: center;
87
87
-
transition: background-color 0.3s ease;
146
146
+
transition: all 0.2s ease;
88
147
}
89
148
90
149
.share-button:hover {
91
91
-
opacity: 0.8;
150
150
+
opacity: 0.9;
151
151
+
transform: translateY(-2px);
92
152
}
93
153
94
94
-
/* Improved search and filters */
95
95
-
.resources-filters {
96
96
-
margin-bottom: 30px;
97
97
-
max-width: 900px;
98
98
-
margin-left: auto;
99
99
-
margin-right: auto;
154
154
+
.share-button:active {
155
155
+
transform: translateY(0);
100
156
}
101
157
102
102
-
.search-container {
103
103
-
margin-bottom: 16px;
158
158
+
.share-icon {
159
159
+
font-size: 1.1rem;
104
160
}
105
161
106
106
-
.search-input {
107
107
-
width: 100%;
162
162
+
/* Enhanced disclaimer styling */
163
163
+
.resources-disclaimer {
164
164
+
display: flex;
165
165
+
align-items: flex-start;
166
166
+
gap: 10px;
167
167
+
background-color: var(--card-border);
168
168
+
border-left: 4px solid #ffd700;
108
169
padding: 14px 18px;
109
109
-
font-size: 1rem;
110
110
-
border: 2px solid var(--card-border);
170
170
+
margin: 1rem 0;
111
171
border-radius: 8px;
112
112
-
box-sizing: border-box;
113
113
-
background-color: var(--navbar-bg);
172
172
+
}
173
173
+
174
174
+
.disclaimer-icon {
175
175
+
font-size: 1.2rem;
176
176
+
margin-top: 2px;
177
177
+
}
178
178
+
179
179
+
.resources-disclaimer p {
180
180
+
margin: 0;
181
181
+
font-size: 0.95rem;
114
182
color: var(--text);
115
115
-
transition: border-color 0.3s ease;
183
183
+
line-height: 1.5;
116
184
}
117
185
118
118
-
.search-input:focus {
119
119
-
border-color: var(--button-bg);
120
120
-
outline: none;
121
121
-
box-shadow: 0 0 0 2px rgba(var(--button-bg-rgb), 0.2);
186
186
+
/* ======= Improved Filter Bar ======= */
187
187
+
.resources-filters {
188
188
+
margin: 0.5rem 0 2rem;
189
189
+
background-color: var(--navbar-bg);
190
190
+
border: 1px solid var(--card-border);
191
191
+
padding: 1rem;
192
192
+
border-radius: 8px;
193
193
+
}
194
194
+
195
195
+
.filter-label {
196
196
+
font-size: 0.95rem;
197
197
+
font-weight: 600;
198
198
+
margin-right: 8px;
199
199
+
color: var(--text);
122
200
}
123
201
124
202
.filter-options {
125
203
display: flex;
126
126
-
flex-wrap: wrap;
127
127
-
gap: 12px;
128
204
align-items: center;
129
129
-
justify-content: space-between;
130
205
}
131
206
132
132
-
/* Filter dropdowns styling */
133
207
.filter-dropdowns {
134
208
display: flex;
135
135
-
gap: 16px;
209
209
+
flex-wrap: wrap;
210
210
+
gap: 1.5rem;
136
211
width: 100%;
137
212
align-items: center;
138
213
}
139
214
140
140
-
.category-filter-dropdown {
141
141
-
flex: 2;
142
142
-
}
143
143
-
215
215
+
.category-filter-dropdown,
144
216
.quality-filter {
145
145
-
flex: 2;
217
217
+
display: flex;
218
218
+
align-items: center;
146
219
}
147
220
148
221
.filter-select {
149
149
-
width: 100%;
150
150
-
padding: 12px 16px;
222
222
+
padding: 8px 32px 8px 12px;
151
223
border: 2px solid var(--card-border);
152
152
-
border-radius: 8px;
224
224
+
border-radius: 6px;
153
225
font-size: 0.95rem;
154
226
background-color: var(--navbar-bg);
155
227
color: var(--text);
···
157
229
appearance: none;
158
230
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
159
231
background-repeat: no-repeat;
160
160
-
background-position: right 12px center;
232
232
+
background-position: right 8px center;
161
233
background-size: 16px;
234
234
+
transition: all 0.2s ease;
162
235
}
163
236
164
237
.filter-select:focus {
165
238
border-color: var(--button-bg);
166
239
outline: none;
240
240
+
box-shadow: 0 0 0 3px rgba(var(--button-bg-rgb), 0.1);
167
241
}
168
242
169
169
-
/* New star filter styling */
170
170
-
.quality-filter-stars {
243
243
+
/* Star filter styling */
244
244
+
.star-filter-container {
171
245
display: flex;
172
246
align-items: center;
173
173
-
gap: 8px;
247
247
+
gap: 2px;
174
248
background-color: var(--navbar-bg);
175
249
border: 2px solid var(--card-border);
176
176
-
border-radius: 8px;
177
177
-
padding: 8px 12px;
178
178
-
height: 44px;
179
179
-
box-sizing: border-box;
180
180
-
}
181
181
-
182
182
-
.quality-filter-label {
183
183
-
font-size: 0.95rem;
184
184
-
font-weight: 500;
185
185
-
color: var(--text);
186
186
-
margin-right: 4px;
187
187
-
}
188
188
-
189
189
-
.star-filter-container {
190
190
-
display: flex;
191
191
-
align-items: center;
192
192
-
gap: 3px;
250
250
+
border-radius: 6px;
251
251
+
padding: 3px 8px;
193
252
}
194
253
195
254
.star-filter-container .quality-star {
196
255
cursor: pointer;
197
256
font-size: 1.4rem;
198
257
transition: transform 0.1s, color 0.2s;
258
258
+
line-height: 1;
199
259
}
200
260
201
261
.star-filter-container .quality-star:hover {
202
202
-
transform: scale(1.1);
262
262
+
transform: scale(1.15);
203
263
}
204
264
205
265
.star-filter-container .quality-star.filled {
···
211
271
}
212
272
213
273
.quality-filter-clear {
214
214
-
font-size: 0.8rem;
215
274
margin-left: 5px;
216
275
cursor: pointer;
276
276
+
background-color: rgba(var(--text-rgb), 0.1);
217
277
color: var(--text);
218
218
-
opacity: 0.7;
278
278
+
width: 18px;
279
279
+
height: 18px;
280
280
+
border-radius: 50%;
281
281
+
display: flex;
282
282
+
align-items: center;
283
283
+
justify-content: center;
284
284
+
font-size: 0.8rem;
219
285
font-weight: bold;
286
286
+
transition: all 0.2s ease;
220
287
}
221
288
222
289
.quality-filter-clear:hover {
223
223
-
opacity: 1;
290
290
+
background-color: rgba(var(--text-rgb), 0.2);
291
291
+
}
292
292
+
293
293
+
/* New toggle styling */
294
294
+
.new-filter {
295
295
+
margin-left: auto;
296
296
+
}
297
297
+
298
298
+
.toggle-label {
299
299
+
display: flex;
300
300
+
align-items: center;
301
301
+
cursor: pointer;
302
302
+
}
303
303
+
304
304
+
.toggle-label input[type="checkbox"] {
305
305
+
position: relative;
306
306
+
width: 38px;
307
307
+
height: 20px;
308
308
+
margin: 0;
309
309
+
margin-right: 8px;
310
310
+
appearance: none;
311
311
+
background-color: var(--card-border);
312
312
+
border-radius: 20px;
313
313
+
transition: background-color 0.3s;
314
314
+
cursor: pointer;
315
315
+
}
316
316
+
317
317
+
.toggle-label input[type="checkbox"]:checked {
318
318
+
background-color: var(--button-bg);
319
319
+
}
320
320
+
321
321
+
.toggle-label input[type="checkbox"]::before {
322
322
+
content: '';
323
323
+
position: absolute;
324
324
+
width: 16px;
325
325
+
height: 16px;
326
326
+
border-radius: 50%;
327
327
+
top: 2px;
328
328
+
left: 2px;
329
329
+
background-color: white;
330
330
+
transition: transform 0.3s;
331
331
+
}
332
332
+
333
333
+
.toggle-label input[type="checkbox"]:checked::before {
334
334
+
transform: translateX(18px);
335
335
+
}
336
336
+
337
337
+
.toggle-text {
338
338
+
font-size: 0.95rem;
339
339
+
font-weight: 500;
224
340
}
225
341
342
342
+
/* ======= Content Sections ======= */
226
343
.featured-section,
227
344
.all-resources-section {
228
345
margin-bottom: 40px;
···
265
382
gap: 20px;
266
383
}
267
384
385
385
+
/* ======= Resource Cards ======= */
268
386
.resource-card {
269
387
border: 1px solid var(--card-border);
270
388
border-radius: 8px;
271
389
overflow: hidden;
272
272
-
transition: transform 0.2s ease, box-shadow 0.2s ease;
390
390
+
transition: transform 0.2s ease, box-shadow 0.2s ease, background-color 0.2s;
273
391
text-decoration: none;
274
392
color: inherit;
275
393
background-color: var(--navbar-bg);
···
318
436
font-weight: 700;
319
437
height: 18px;
320
438
padding: 2px 8px;
321
321
-
padding-bottom: 2px;
439
439
+
padding-bottom: 0px;
322
440
vertical-align: text-bottom;
323
441
margin-bottom: 4px;
324
324
-
padding-bottom: 0px;
325
442
}
326
443
327
444
@keyframes pulse {
···
375
492
.quality-star {
376
493
font-size: 1.4em;
377
494
font-weight: 700;
378
378
-
text-shadow: 0 1px 1px #0000001a;
495
495
+
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
379
496
}
380
497
381
498
.quality-star.filled {
···
427
544
opacity: 0.8;
428
545
}
429
546
430
430
-
/* New filter toggle styling */
431
431
-
.new-filter {
432
432
-
flex: 1;
433
433
-
display: flex;
434
434
-
align-items: center;
435
435
-
justify-content: flex-end;
547
547
+
@keyframes spin {
548
548
+
0% { transform: rotate(0deg); }
549
549
+
100% { transform: rotate(360deg); }
436
550
}
437
551
438
438
-
.toggle-label {
439
439
-
display: flex;
440
440
-
align-items: center;
441
441
-
cursor: pointer;
442
442
-
}
443
443
-
444
444
-
.toggle-label input[type="checkbox"] {
445
445
-
margin-right: 8px;
446
446
-
appearance: none;
447
447
-
position: relative;
448
448
-
width: 40px;
449
449
-
height: 20px;
450
450
-
background-color: var(--card-border);
451
451
-
border-radius: 20px;
452
452
-
transition: background-color 0.3s;
453
453
-
cursor: pointer;
454
454
-
}
455
455
-
456
456
-
.toggle-label input[type="checkbox"]:checked {
457
457
-
background-color: var(--button-bg);
458
458
-
}
459
459
-
460
460
-
.toggle-label input[type="checkbox"]::before {
461
461
-
content: '';
462
462
-
position: absolute;
463
463
-
width: 16px;
464
464
-
height: 16px;
465
465
-
border-radius: 50%;
466
466
-
top: 2px;
467
467
-
left: 2px;
468
468
-
background-color: white;
469
469
-
transition: transform 0.3s;
470
470
-
}
471
471
-
472
472
-
.toggle-label input[type="checkbox"]:checked::before {
473
473
-
transform: translateX(20px);
474
474
-
}
475
475
-
476
476
-
.toggle-text {
477
477
-
font-size: 0.9rem;
478
478
-
font-weight: 500;
479
479
-
}
480
480
-
481
481
-
/* Make filters more responsive on mobile */
552
552
+
/* Responsive adjustments */
482
553
@media (max-width: 768px) {
483
483
-
.filter-options .filter-dropdowns {
554
554
+
.resources-page {
555
555
+
padding: 10px;
556
556
+
}
557
557
+
558
558
+
.resources-page .alt-card {
559
559
+
padding: 1rem;
560
560
+
}
561
561
+
562
562
+
.feature-cards {
563
563
+
flex-direction: column;
564
564
+
gap: 0.8rem;
565
565
+
align-items: center;
566
566
+
}
567
567
+
568
568
+
.search-filters-container {
569
569
+
flex-direction: column;
570
570
+
align-items: stretch;
571
571
+
}
572
572
+
573
573
+
.search-container {
574
574
+
max-width: none;
575
575
+
}
576
576
+
577
577
+
.filter-dropdowns {
484
578
flex-direction: column;
485
579
align-items: flex-start;
486
486
-
gap: 12px;
580
580
+
gap: 1rem;
487
581
}
488
582
489
583
.category-filter-dropdown,
···
496
590
width: 100%;
497
591
}
498
592
499
499
-
.new-filter {
500
500
-
justify-content: flex-start;
593
593
+
.category-filter-dropdown,
594
594
+
.quality-filter {
595
595
+
flex-direction: column;
596
596
+
align-items: flex-start;
597
597
+
gap: 0.5rem;
501
598
}
502
599
503
503
-
.quality-filter-stars {
600
600
+
.star-filter-container {
504
601
width: 100%;
505
602
justify-content: space-between;
506
603
}
507
507
-
}
508
508
-
509
509
-
@keyframes spin {
510
510
-
0% { transform: rotate(0deg); }
511
511
-
100% { transform: rotate(360deg); }
512
512
-
}
513
513
-
514
514
-
/* Responsive adjustments */
515
515
-
@media (max-width: 768px) {
516
516
-
.resources-page {
517
517
-
padding: 10px;
604
604
+
605
605
+
.resources-disclaimer {
606
606
+
flex-direction: column;
518
607
}
519
608
520
520
-
.resources-page .alt-card {
521
521
-
padding: 1rem;
522
522
-
}
523
523
-
524
609
.resources-header h1 {
525
610
font-size: 1.5rem;
526
526
-
}
527
527
-
528
528
-
.resources-header {
529
529
-
flex-direction: column;
530
530
-
align-items: flex-start;
531
531
-
}
532
532
-
533
533
-
.share-button-container {
534
534
-
margin-top: 16px;
535
535
-
}
536
536
-
537
537
-
.filter-dropdowns {
538
538
-
flex-direction: column;
539
539
-
gap: 10px;
540
611
}
541
612
542
613
.resources-grid {
···
86
86
} catch (error) {
87
87
console.error('Error fetching resources:', error);
88
88
// In case of error, we could use local data as fallback
89
89
-
// setResources(localResourcesWithUTM);
90
89
} finally {
91
90
setIsLoading(false);
92
91
}
···
179
178
return grouped;
180
179
}, [filteredResources, activeCategory]);
181
180
182
182
-
// Should show featured section only when All category is selected
183
183
-
const shouldShowFeatured = activeCategory === 'All';
181
181
+
// Should show featured section only when All category is selected and no quality filter is active
182
182
+
const shouldShowFeatured = activeCategory === 'All' && qualityFilter === 0;
184
183
185
184
// Handle star rating click for quality filter
186
185
const handleStarClick = (rating) => {
···
190
189
return (
191
190
<main className="resources-page">
192
191
<div className="alt-card">
193
193
-
<div className="resources-header">
194
194
-
<h1>Bluesky Resources</h1>
192
192
+
{/* Redesigned Header Section */}
193
193
+
<header className="resources-header">
194
194
+
<div className="header-main">
195
195
+
<h1>Bluesky Resources</h1>
196
196
+
<div className="header-tagline">
197
197
+
<p>A curated collection of tools and services for the Bluesky ecosystem</p>
198
198
+
</div>
199
199
+
</div>
195
200
196
196
-
{/* Improved header structure */}
197
197
-
<div className="resources-intro">
198
198
-
<ul>
199
199
-
<li>Find tools to enhance your Bluesky experience.</li>
200
200
-
<li>Discover analytics, feeds, clients, and more.</li>
201
201
-
<li>Explore community-built solutions.</li>
202
202
-
</ul>
203
203
-
<p className="resources-description">
204
204
-
A curated collection of third-party tools, services, and guides for the Bluesky ecosystem
205
205
-
</p>
201
201
+
<div className="header-features">
202
202
+
<div className="feature-cards">
203
203
+
<div className="feature-card">
204
204
+
<span className="feature-icon">🔍</span>
205
205
+
<span className="feature-text">Discover analytics, feeds & clients</span>
206
206
+
</div>
207
207
+
<div className="feature-card">
208
208
+
<span className="feature-icon">⚡</span>
209
209
+
<span className="feature-text">Enhance your Bluesky experience</span>
210
210
+
</div>
211
211
+
<div className="feature-card">
212
212
+
<span className="feature-icon">🧩</span>
213
213
+
<span className="feature-text">Community-built solutions</span>
214
214
+
</div>
215
215
+
</div>
206
216
</div>
207
217
208
208
-
{/* Improved disclaimer positioning */}
218
218
+
<div className="search-filters-container">
219
219
+
<div className="search-container">
220
220
+
<span className="search-icon">🔎</span>
221
221
+
<input
222
222
+
type="text"
223
223
+
placeholder="Search resources..."
224
224
+
value={searchQuery}
225
225
+
onChange={(e) => setSearchQuery(e.target.value)}
226
226
+
className="search-input"
227
227
+
aria-label="Search resources"
228
228
+
/>
229
229
+
</div>
230
230
+
231
231
+
<div className="quick-actions">
232
232
+
<button
233
233
+
className="share-button"
234
234
+
type="button"
235
235
+
onClick={shareOnBluesky}
236
236
+
aria-label="Share this page on Bluesky"
237
237
+
>
238
238
+
<span className="share-icon">📤</span>
239
239
+
<span>Share</span>
240
240
+
</button>
241
241
+
</div>
242
242
+
</div>
243
243
+
209
244
<div className="resources-disclaimer">
245
245
+
<div className="disclaimer-icon">⚠️</div>
210
246
<p><strong>Disclaimer:</strong> These resources are third-party tools and services not affiliated with cred.blue or Bluesky.
211
247
Use them at your own risk and exercise caution when providing access to your data.</p>
212
248
</div>
213
213
-
214
214
-
<div className="share-button-container">
215
215
-
<button
216
216
-
className="share-button"
217
217
-
type="button"
218
218
-
onClick={shareOnBluesky}
219
219
-
>
220
220
-
Share This Page
221
221
-
</button>
222
222
-
</div>
223
223
-
</div>
249
249
+
</header>
224
250
225
225
-
{isLoading ? (
226
226
-
<ResourceLoader />
227
227
-
) : (
228
228
-
<>
229
229
-
{/* Improved search and filters layout */}
251
251
+
{/* Improved Filter Bar */}
230
252
<div className="resources-filters">
231
231
-
<div className="search-container">
232
232
-
<input
233
233
-
type="text"
234
234
-
placeholder="Search resources..."
235
235
-
value={searchQuery}
236
236
-
onChange={(e) => setSearchQuery(e.target.value)}
237
237
-
className="search-input"
238
238
-
/>
239
239
-
</div>
240
240
-
241
253
<div className="filter-options">
242
254
<div className="filter-dropdowns">
243
255
{/* Category filter dropdown */}
244
256
<div className="category-filter-dropdown">
257
257
+
<label htmlFor="category-select" className="filter-label">Category:</label>
245
258
<select
259
259
+
id="category-select"
246
260
value={activeCategory}
247
261
onChange={(e) => setActiveCategory(e.target.value)}
248
262
className="filter-select"
···
255
269
</select>
256
270
</div>
257
271
258
258
-
{/* New Quality Filter using Stars */}
272
272
+
{/* Quality Filter using Stars */}
259
273
<div className="quality-filter">
260
260
-
<div className="quality-filter-stars">
261
261
-
<span className="quality-filter-label">Quality: </span>
262
262
-
<div className="star-filter-container">
263
263
-
{[1, 2, 3, 4, 5].map((rating) => (
264
264
-
<span
265
265
-
key={rating}
266
266
-
onClick={() => handleStarClick(rating)}
267
267
-
className={`quality-star ${rating <= qualityFilter ? 'filled' : 'empty'}`}
268
268
-
title={`${rating} stars or higher`}
269
269
-
>
270
270
-
★
271
271
-
</span>
272
272
-
))}
273
273
-
{qualityFilter > 0 && (
274
274
-
<span
275
275
-
className="quality-filter-clear"
276
276
-
onClick={() => setQualityFilter(0)}
277
277
-
title="Clear filter"
278
278
-
>
279
279
-
✕
280
280
-
</span>
281
281
-
)}
282
282
-
</div>
274
274
+
<span className="filter-label">Quality:</span>
275
275
+
<div className="star-filter-container">
276
276
+
{[1, 2, 3, 4, 5].map((rating) => (
277
277
+
<span
278
278
+
key={rating}
279
279
+
onClick={() => handleStarClick(rating)}
280
280
+
className={`quality-star ${rating <= qualityFilter ? 'filled' : 'empty'}`}
281
281
+
title={`${rating} stars or higher`}
282
282
+
role="button"
283
283
+
tabIndex="0"
284
284
+
aria-label={`Filter by ${rating} stars or higher`}
285
285
+
onKeyPress={(e) => e.key === 'Enter' && handleStarClick(rating)}
286
286
+
>
287
287
+
★
288
288
+
</span>
289
289
+
))}
290
290
+
{qualityFilter > 0 && (
291
291
+
<span
292
292
+
className="quality-filter-clear"
293
293
+
onClick={() => setQualityFilter(0)}
294
294
+
title="Clear filter"
295
295
+
role="button"
296
296
+
tabIndex="0"
297
297
+
aria-label="Clear quality filter"
298
298
+
onKeyPress={(e) => e.key === 'Enter' && setQualityFilter(0)}
299
299
+
>
300
300
+
✕
301
301
+
</span>
302
302
+
)}
283
303
</div>
284
304
</div>
285
305
286
306
{/* New resources toggle */}
287
307
<div className="new-filter">
288
288
-
<label className="toggle-label">
308
308
+
<label className="toggle-label" htmlFor="new-toggle">
289
309
<input
310
310
+
id="new-toggle"
290
311
type="checkbox"
291
312
checked={showNewOnly}
292
313
onChange={() => setShowNewOnly(!showNewOnly)}
314
314
+
aria-label="Show only recently added resources"
293
315
/>
294
294
-
<span className="toggle-text">Recently Added Only</span>
316
316
+
<span className="toggle-text">Recently Added</span>
295
317
</label>
296
318
</div>
297
319
</div>
298
320
</div>
299
321
</div>
300
322
301
301
-
{shouldShowFeatured && featuredResources.length > 0 && (
302
302
-
<div className="featured-section">
303
303
-
<h2>Featured Resources</h2>
304
304
-
<p className="featured-description">Hand-selected tools that we love and use regularly. These are not sponsored or paid placements.</p>
305
305
-
<div className="resources-grid">
306
306
-
{featuredResources.map((resource, index) => (
307
307
-
<ResourceCard
308
308
-
key={`featured-${index}`}
309
309
-
resource={resource}
310
310
-
isNew={isNewResource(resource.created_at)}
311
311
-
/>
323
323
+
{/* Loading indication */}
324
324
+
{isLoading ? (
325
325
+
<ResourceLoader />
326
326
+
) : (
327
327
+
<>
328
328
+
{/* Featured Section - Hidden when quality filter is active */}
329
329
+
{shouldShowFeatured && featuredResources.length > 0 && (
330
330
+
<div className="featured-section">
331
331
+
<h2>Featured Resources</h2>
332
332
+
<p className="featured-description">Hand-selected tools that we love and use regularly. These are not sponsored or paid placements.</p>
333
333
+
<div className="resources-grid">
334
334
+
{featuredResources.map((resource, index) => (
335
335
+
<ResourceCard
336
336
+
key={`featured-${index}`}
337
337
+
resource={resource}
338
338
+
isNew={isNewResource(resource.created_at)}
339
339
+
/>
340
340
+
))}
341
341
+
</div>
342
342
+
</div>
343
343
+
)}
344
344
+
345
345
+
{activeCategory === 'All' ? (
346
346
+
// When "All" is selected, show resources by category
347
347
+
<div className="all-resources-section">
348
348
+
<h2>All Resources ({filteredResources.length})</h2>
349
349
+
350
350
+
{Object.keys(resourcesByCategory).map(category => (
351
351
+
<div key={category} className="category-section">
352
352
+
<h3 className="category-header">
353
353
+
{categoryEmojis[category] || '🔹'} {category} ({resourcesByCategory[category].length})
354
354
+
</h3>
355
355
+
<div className="resources-grid">
356
356
+
{resourcesByCategory[category].map((resource, index) => (
357
357
+
<ResourceCard
358
358
+
key={`${category}-${index}`}
359
359
+
resource={resource}
360
360
+
isNew={isNewResource(resource.created_at)}
361
361
+
/>
362
362
+
))}
363
363
+
</div>
364
364
+
</div>
312
365
))}
313
366
</div>
314
314
-
</div>
315
315
-
)}
316
316
-
317
317
-
{activeCategory === 'All' ? (
318
318
-
// When "All" is selected, show resources by category
319
319
-
<div className="all-resources-section">
320
320
-
<h2>All Resources ({filteredResources.length})</h2>
321
321
-
322
322
-
{Object.keys(resourcesByCategory).map(category => (
323
323
-
<div key={category} className="category-section">
324
324
-
<h3 className="category-header">
325
325
-
{categoryEmojis[category] || '🔹'} {category} ({resourcesByCategory[category].length})
326
326
-
</h3>
367
367
+
) : (
368
368
+
// When a specific category is selected
369
369
+
<div className="all-resources-section">
370
370
+
<h2>{categoryEmojis[activeCategory] || '🔹'} {activeCategory} Resources ({filteredResources.length})</h2>
371
371
+
{filteredResources.length > 0 ? (
327
372
<div className="resources-grid">
328
328
-
{resourcesByCategory[category].map((resource, index) => (
373
373
+
{filteredResources.map((resource, index) => (
329
374
<ResourceCard
330
330
-
key={`${category}-${index}`}
375
375
+
key={index}
331
376
resource={resource}
332
377
isNew={isNewResource(resource.created_at)}
333
378
/>
334
379
))}
335
380
</div>
336
336
-
</div>
337
337
-
))}
338
338
-
</div>
339
339
-
) : (
340
340
-
// When a specific category is selected
341
341
-
<div className="all-resources-section">
342
342
-
<h2>{categoryEmojis[activeCategory] || '🔹'} {activeCategory} Resources ({filteredResources.length})</h2>
343
343
-
{filteredResources.length > 0 ? (
344
344
-
<div className="resources-grid">
345
345
-
{filteredResources.map((resource, index) => (
346
346
-
<ResourceCard
347
347
-
key={index}
348
348
-
resource={resource}
349
349
-
isNew={isNewResource(resource.created_at)}
350
350
-
/>
351
351
-
))}
352
352
-
</div>
353
353
-
) : (
354
354
-
<div className="no-results">
355
355
-
<p>No resources found matching your filters.</p>
356
356
-
</div>
357
357
-
)}
358
358
-
</div>
359
359
-
)}
381
381
+
) : (
382
382
+
<div className="no-results">
383
383
+
<p>No resources found matching your filters.</p>
384
384
+
</div>
385
385
+
)}
386
386
+
</div>
387
387
+
)}
360
388
</>
361
389
)}
362
390
</div>