This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

add resources page

+799 -39
+2
src/App.jsx
··· 9 9 import Leaderboard from './components/Leaderboard/Leaderboard'; 10 10 import Supporter from './components/Supporter/Supporter'; 11 11 import Shortcut from './components/Shortcut/Shortcut'; 12 + import Resources from './components/Resources/Resources'; 12 13 import ScoringMethodology from './components/ScoringMethodology/ScoringMethodology'; 13 14 import Terms from './components/PrivacyTerms/Terms'; 14 15 import Privacy from './components/PrivacyTerms/Privacy'; ··· 37 38 <Route path="/newsletter" element={<Newsletter />} /> 38 39 <Route path="/supporter" element={<Supporter />} /> 39 40 <Route path="/leaderboard" element={<Leaderboard />} /> 41 + <Route path="/resources" element={<Resources />} /> 40 42 <Route path="/shortcut" element={<Shortcut />} /> 41 43 <Route path="/zen" element={<ZenPage />} /> 42 44 <Route path="/methodology" element={<ScoringMethodology />} />
+36 -33
src/accountData.js
··· 488 488 } 489 489 490 490 function calculateSocialStatus({ ageInDays = 0, followersCount = 0, followsCount = 0, engagementRate = 0 }) { 491 - // Define engagement thresholds 492 - const ENGAGEMENT_THRESHOLDS = { 493 - high: 0.03, // 3% 494 - moderate: 0.01, // 1% 495 - low: 0.005 // 0.5% 496 - }; 497 - 498 - // Calculate follow percentage 499 - const followPercentage = followersCount > 0 ? followsCount / followersCount : 0; 500 - 501 - // Determine base status 502 - let baseStatus = "Explorer"; 491 + // Define the minimum engagement rate threshold for advancing to higher tiers 492 + const MIN_ENGAGEMENT_RATE = 0.01; // 1% 503 493 504 - // Check for Newcomer first 494 + // Check for Newcomer first (less than 30 days old) 505 495 if (ageInDays < 30) { 506 - baseStatus = "Newcomer"; 496 + return "Newcomer"; 507 497 } 508 - // Only check other statuses if not a newcomer 509 - else if (followPercentage < 0.5) { 510 - if (followersCount >= 100000) { 511 - baseStatus = "Leader"; 512 - } else if (followersCount >= 10000) { 513 - baseStatus = "Guide"; 514 - } else if (followersCount >= 1000) { 515 - baseStatus = "Pathfinder"; 498 + 499 + // Default status for accounts older than 30 days 500 + let status = "Explorer"; 501 + 502 + // Check follower counts and engagement rate for higher tiers 503 + if (followersCount >= 100000) { 504 + if (engagementRate >= MIN_ENGAGEMENT_RATE) { 505 + status = "Leader"; 506 + } else { 507 + // Fallback to Guide if engagement requirement not met 508 + status = "Guide"; 509 + } 510 + } else if (followersCount >= 10000) { 511 + if (engagementRate >= MIN_ENGAGEMENT_RATE) { 512 + status = "Guide"; 513 + } else { 514 + // Fallback to Pathfinder if engagement requirement not met 515 + status = "Pathfinder"; 516 + } 517 + } else if (followersCount >= 1000) { 518 + if (engagementRate >= MIN_ENGAGEMENT_RATE) { 519 + status = "Pathfinder"; 516 520 } 521 + // Fallback to Explorer if engagement requirement not met 517 522 } 518 - 519 - // Add engagement qualifier for all status levels 520 - if (engagementRate <= ENGAGEMENT_THRESHOLDS.low) { 521 - return `${baseStatus}`; 522 - } else if (engagementRate <= ENGAGEMENT_THRESHOLDS.moderate) { 523 - return `Engaging ${baseStatus}`; 524 - } else if (engagementRate >= ENGAGEMENT_THRESHOLDS.high) { 525 - return `Highly Engaging ${baseStatus}`; 523 + 524 + // Add engagement qualifier based on rate 525 + if (engagementRate > 0.03) { // 3% 526 + return `Highly Engaging ${status}`; 527 + } else if (engagementRate > 0.01) { // 1% 528 + return `Engaging ${status}`; 526 529 } 527 - 528 - // Return base status if engagement doesn't meet any threshold 529 - return baseStatus; 530 + 531 + // Return base status 532 + return status; 530 533 } 531 534 532 535 function calculateActivityStatus(rate) {
+1
src/components/Navbar/Navbar.js
··· 26 26 <li><Link to="/">score</Link></li> 27 27 <li><Link to="/compare">compare</Link></li> 28 28 <li><Link to="/leaderboard">leaderboard</Link></li> 29 + <li><Link to="/resources">resources</Link></li> 29 30 <li><Link to="/alt-text">alt text</Link></li> 30 31 <li><Link to="/about">about</Link></li> 31 32 </ul>
+271
src/components/Resources/Resources.css
··· 1 + /* src/components/Resources/Resources.css */ 2 + 3 + .resources-page { 4 + max-width: 1200px; 5 + margin: 0 auto; 6 + padding: 20px; 7 + font-family: sans-serif; 8 + } 9 + 10 + .resources-header { 11 + display: flex; 12 + justify-content: space-between; 13 + align-items: center; 14 + margin-bottom: 20px; 15 + } 16 + 17 + .resources-title h1 { 18 + font-size: 2rem; 19 + margin-bottom: 8px; 20 + color: #0066cc; 21 + } 22 + 23 + .resources-title p { 24 + font-size: 1rem; 25 + color: #666; 26 + margin: 0; 27 + } 28 + 29 + .share-button-container { 30 + margin-left: 20px; 31 + } 32 + 33 + .share-button { 34 + background-color: #0066cc; 35 + color: white; 36 + padding: 10px 16px; 37 + border: none; 38 + border-radius: 20px; 39 + font-size: 0.9rem; 40 + font-weight: 600; 41 + cursor: pointer; 42 + display: flex; 43 + align-items: center; 44 + transition: background-color 0.3s ease; 45 + } 46 + 47 + .share-button:hover { 48 + background-color: #0055aa; 49 + } 50 + 51 + .resources-disclaimer { 52 + background-color: #f8f9fa; 53 + border-left: 4px solid #ffd700; 54 + padding: 12px 16px; 55 + margin-bottom: 24px; 56 + border-radius: 4px; 57 + } 58 + 59 + .resources-disclaimer p { 60 + margin: 0; 61 + font-size: 0.9rem; 62 + color: #555; 63 + } 64 + 65 + .resources-filters { 66 + margin-bottom: 30px; 67 + } 68 + 69 + .search-container { 70 + margin-bottom: 16px; 71 + } 72 + 73 + .search-input { 74 + width: 100%; 75 + padding: 12px 16px; 76 + font-size: 1rem; 77 + border: 1px solid #ddd; 78 + border-radius: 8px; 79 + box-sizing: border-box; 80 + } 81 + 82 + .filter-options { 83 + display: flex; 84 + flex-wrap: wrap; 85 + gap: 12px; 86 + align-items: center; 87 + justify-content: space-between; 88 + } 89 + 90 + .category-filters { 91 + display: flex; 92 + flex-wrap: wrap; 93 + gap: 8px; 94 + } 95 + 96 + .category-filter { 97 + background-color: #f0f0f0; 98 + border: none; 99 + padding: 8px 16px; 100 + border-radius: 16px; 101 + font-size: 0.9rem; 102 + cursor: pointer; 103 + transition: all 0.2s ease; 104 + } 105 + 106 + .category-filter:hover { 107 + background-color: #e0e0e0; 108 + } 109 + 110 + .category-filter.active { 111 + background-color: #0066cc; 112 + color: white; 113 + } 114 + 115 + .quality-select { 116 + padding: 8px 16px; 117 + border: 1px solid #ddd; 118 + border-radius: 8px; 119 + font-size: 0.9rem; 120 + } 121 + 122 + .featured-section, 123 + .all-resources-section { 124 + margin-bottom: 40px; 125 + } 126 + 127 + .featured-section h2, 128 + .all-resources-section h2 { 129 + font-size: 1.5rem; 130 + margin-bottom: 16px; 131 + color: #333; 132 + border-bottom: 2px solid #eee; 133 + padding-bottom: 8px; 134 + } 135 + 136 + .resources-grid { 137 + display: grid; 138 + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); 139 + gap: 20px; 140 + } 141 + 142 + .resource-card { 143 + border: 1px solid #eee; 144 + border-radius: 8px; 145 + overflow: hidden; 146 + transition: transform 0.2s ease, box-shadow 0.2s ease; 147 + text-decoration: none; 148 + color: inherit; 149 + background-color: white; 150 + height: 100%; 151 + display: flex; 152 + flex-direction: column; 153 + } 154 + 155 + .resource-card:hover { 156 + transform: translateY(-4px); 157 + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1); 158 + } 159 + 160 + .resource-content { 161 + padding: 16px; 162 + flex-grow: 1; 163 + display: flex; 164 + flex-direction: column; 165 + } 166 + 167 + .resource-name { 168 + font-size: 1.1rem; 169 + margin: 0 0 8px 0; 170 + color: #0066cc; 171 + } 172 + 173 + .resource-description { 174 + font-size: 0.9rem; 175 + color: #555; 176 + margin: 0 0 16px 0; 177 + flex-grow: 1; 178 + } 179 + 180 + .resource-meta { 181 + display: flex; 182 + justify-content: space-between; 183 + align-items: center; 184 + margin-top: auto; 185 + } 186 + 187 + .resource-category { 188 + font-size: 0.8rem; 189 + background-color: #f0f0f0; 190 + padding: 4px 8px; 191 + border-radius: 4px; 192 + color: #666; 193 + } 194 + 195 + .resource-quality { 196 + display: flex; 197 + } 198 + 199 + .quality-star { 200 + font-size: 0.9rem; 201 + margin-left: 2px; 202 + } 203 + 204 + .quality-star.filled { 205 + color: #ffd700; 206 + } 207 + 208 + .quality-star.empty { 209 + color: #ddd; 210 + } 211 + 212 + .no-results { 213 + text-align: center; 214 + padding: 40px; 215 + color: #666; 216 + } 217 + 218 + /* Resource Loader */ 219 + .resource-loader { 220 + display: flex; 221 + flex-direction: column; 222 + align-items: center; 223 + justify-content: center; 224 + padding: 40px; 225 + text-align: center; 226 + } 227 + 228 + .loader-spinner { 229 + border: 4px solid rgba(0, 0, 0, 0.1); 230 + border-left-color: #0066cc; 231 + border-radius: 50%; 232 + width: 40px; 233 + height: 40px; 234 + animation: spin 1s linear infinite; 235 + margin-bottom: 16px; 236 + } 237 + 238 + @keyframes spin { 239 + 0% { transform: rotate(0deg); } 240 + 100% { transform: rotate(360deg); } 241 + } 242 + 243 + /* Responsive adjustments */ 244 + @media (max-width: 768px) { 245 + .resources-header { 246 + flex-direction: column; 247 + align-items: flex-start; 248 + } 249 + 250 + .share-button-container { 251 + margin: 16px 0 0 0; 252 + } 253 + 254 + .filter-options { 255 + flex-direction: column; 256 + align-items: flex-start; 257 + } 258 + 259 + .quality-filter { 260 + width: 100%; 261 + margin-top: 12px; 262 + } 263 + 264 + .quality-select { 265 + width: 100%; 266 + } 267 + 268 + .resources-grid { 269 + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); 270 + } 271 + }
+488
src/components/Resources/Resources.js
··· 1 + // src/components/Resources/Resources.jsx 2 + import React, { useState, useEffect, useMemo } from 'react'; 3 + import './Resources.css'; 4 + import { Link } from 'react-router-dom'; 5 + import ResourceLoader from './ResourceLoader'; 6 + 7 + const Resources = () => { 8 + // State management 9 + const [activeCategory, setActiveCategory] = useState('All'); 10 + const [searchQuery, setSearchQuery] = useState(''); 11 + const [qualityFilter, setQualityFilter] = useState('All'); 12 + const [isLoading, setIsLoading] = useState(true); 13 + 14 + // Resources data structure 15 + const resourcesData = [ 16 + // Analytics & Metrics - Personal Stats 17 + { 18 + name: "Alt Text Rating Tool", 19 + url: "https://dame.is/ratingalttext", 20 + category: "Analytics", 21 + subcategory: "Personal Stats", 22 + description: "Check how consistently you use alt text", 23 + quality: 5, 24 + featured: true 25 + }, 26 + { 27 + name: "Skeet Reviewer", 28 + url: "https://reviewer.skeet.tools", 29 + category: "Analytics", 30 + subcategory: "Personal Stats", 31 + description: "Use the KonMari method to sort through your old posts", 32 + quality: 5, 33 + featured: true 34 + }, 35 + { 36 + name: "Venn Diagram", 37 + url: "https://venn.aviva.gay/dame.bsky.social", 38 + category: "Analytics", 39 + subcategory: "Personal Stats", 40 + description: "Visualize your social graph", 41 + quality: 4, 42 + featured: false 43 + }, 44 + { 45 + name: "SkyZoo", 46 + url: "https://skyzoo.blue/", 47 + category: "Analytics", 48 + subcategory: "Personal Stats", 49 + description: "Profile metrics and fun stats", 50 + quality: 4, 51 + featured: false 52 + }, 53 + { 54 + name: "SkyKit", 55 + url: "http://skykit.blue", 56 + category: "Analytics", 57 + subcategory: "Personal Stats", 58 + description: "Bluesky analytics", 59 + quality: 4, 60 + featured: true 61 + }, 62 + 63 + // Analytics & Metrics - Platform Stats 64 + { 65 + name: "Bcounter", 66 + url: "http://bcounter.nat.vg", 67 + category: "Analytics", 68 + subcategory: "Platform Stats", 69 + description: "Realtime user growth dashboard", 70 + quality: 4, 71 + featured: false 72 + }, 73 + { 74 + name: "Emojistats", 75 + url: "https://emojistats.bsky.sh", 76 + category: "Analytics", 77 + subcategory: "Platform Stats", 78 + description: "Real-time emoji usage data", 79 + quality: 3, 80 + featured: false 81 + }, 82 + 83 + // Services & AppViews 84 + { 85 + name: "Mutesky", 86 + url: "https://mutesky.app/", 87 + category: "Services", 88 + subcategory: "AppViews", 89 + description: "Manage your muted words in bulk", 90 + quality: 4, 91 + featured: false 92 + }, 93 + { 94 + name: "Frontpage", 95 + url: "https://frontpage.fyi", 96 + category: "Services", 97 + subcategory: "AppViews", 98 + description: "Decentralized link aggregator", 99 + quality: 5, 100 + featured: true 101 + }, 102 + { 103 + name: "Graze", 104 + url: "https://www.graze.social/", 105 + category: "Feeds", 106 + subcategory: "Feed Tools", 107 + description: "No-Code feed creator", 108 + quality: 5, 109 + featured: true 110 + }, 111 + 112 + // Data Management 113 + { 114 + name: "Bulk Thread Gating", 115 + url: "https://boat.kelinci.net/bsky-threadgate-applicator", 116 + category: "Data", 117 + subcategory: "Management", 118 + description: "Bulk retroactive thread gating", 119 + quality: 3, 120 + featured: false 121 + }, 122 + { 123 + name: "SkySweeper", 124 + url: "https://skysweeper.p8.lu", 125 + category: "Data", 126 + subcategory: "Management", 127 + description: "Auto-delete old skeets", 128 + quality: 4, 129 + featured: false 130 + }, 131 + 132 + // Network Management 133 + { 134 + name: "Network Analyzer", 135 + url: "http://bsky-follow-finder.theo.io", 136 + category: "Network", 137 + subcategory: "Management", 138 + description: "Find and analyze your network connections", 139 + quality: 4, 140 + featured: true 141 + }, 142 + { 143 + name: "Gentle Unfollow", 144 + url: "https://bsky.cam.fyi/unfollow", 145 + category: "Network", 146 + subcategory: "Management", 147 + description: "Track and manage who you're following", 148 + quality: 4, 149 + featured: true 150 + }, 151 + 152 + // Alternative Clients 153 + { 154 + name: "deck.blue", 155 + url: "http://deck.blue", 156 + category: "Clients", 157 + subcategory: "Alternative", 158 + description: "TweetDeck for Bluesky", 159 + quality: 4, 160 + featured: false 161 + }, 162 + { 163 + name: "Graysky", 164 + url: "https://graysky.app", 165 + category: "Clients", 166 + subcategory: "Alternative", 167 + description: "Alternative mobile client", 168 + quality: 5, 169 + featured: false 170 + }, 171 + 172 + // Labelers & Moderation 173 + { 174 + name: "US Politics Labeler", 175 + url: "https://bsky.app/profile/uspol.bluesky.bot", 176 + category: "Moderation", 177 + subcategory: "Labelers", 178 + description: "Labels political content", 179 + quality: 4, 180 + featured: true 181 + }, 182 + { 183 + name: "Pronouns Labeler", 184 + url: "https://bsky.app/profile/pronouns.adorable.mom", 185 + category: "Moderation", 186 + subcategory: "Labelers", 187 + description: "Adds pronoun information to profiles", 188 + quality: 4, 189 + featured: true 190 + }, 191 + 192 + // Feeds & Discovery 193 + { 194 + name: "Quiet Posters", 195 + url: "https://bsky.app/profile/did:plc:vpkhqolt662uhesyj6nxm7ys/feed/infreq", 196 + category: "Feeds", 197 + subcategory: "Discovery", 198 + description: "Feed of less frequent posters", 199 + quality: 3, 200 + featured: false 201 + }, 202 + 203 + // Visualizations 204 + { 205 + name: "Bluesky by the Second", 206 + url: "https://sky.flikq.dev", 207 + category: "Visualizations", 208 + subcategory: "Firehose", 209 + description: "Live visualization of the firehose", 210 + quality: 3, 211 + featured: false 212 + }, 213 + { 214 + name: "Final Words", 215 + url: "https://deletions.bsky.bad-example.com", 216 + category: "Visualizations", 217 + subcategory: "Firehose", 218 + description: "Glimpses of deleted posts", 219 + quality: 3, 220 + featured: true 221 + }, 222 + 223 + // Developer Tools 224 + { 225 + name: "pdsls.dev", 226 + url: "https://pdsls.dev/", 227 + category: "Development", 228 + subcategory: "Tools", 229 + description: "Browse AtProto repositories", 230 + quality: 5, 231 + featured: true 232 + }, 233 + { 234 + name: "sdk.blue", 235 + url: "http://sdk.blue", 236 + category: "Development", 237 + subcategory: "Tools", 238 + description: "Libraries & SDKs for the AT Protocol", 239 + quality: 4, 240 + featured: false 241 + }, 242 + 243 + // Guides & Documentation 244 + { 245 + name: "Verify Your Account", 246 + url: "https://bsky.social/about/blog/4-28-2023-domain-handle-tutorial", 247 + category: "Guides", 248 + subcategory: "Documentation", 249 + description: "How to verify your Bluesky account", 250 + quality: 4, 251 + featured: false 252 + }, 253 + { 254 + name: "Complete Guide to Bluesky", 255 + url: "https://mackuba.eu/2024/02/21/bluesky-guide/", 256 + category: "Guides", 257 + subcategory: "Documentation", 258 + description: "Comprehensive Bluesky guide", 259 + quality: 5, 260 + featured: false 261 + }, 262 + 263 + // Miscellaneous 264 + { 265 + name: "Thread Composer", 266 + url: "https://bluesky-thread-composer.pages.dev", 267 + category: "Misc", 268 + subcategory: "Tools", 269 + description: "Create and organize threads", 270 + quality: 3, 271 + featured: false 272 + }, 273 + { 274 + name: "Skyview", 275 + url: "https://skyview.social", 276 + category: "Misc", 277 + subcategory: "Tools", 278 + description: "Share threads with people without an account", 279 + quality: 4, 280 + featured: false 281 + }, 282 + { 283 + name: "down.blue", 284 + url: "https://down.blue", 285 + category: "Misc", 286 + subcategory: "Tools", 287 + description: "Video downloader", 288 + quality: 3, 289 + featured: false 290 + } 291 + ]; 292 + 293 + // Add UTM parameters to all URLs 294 + const resourcesWithUTM = resourcesData.map(resource => ({ 295 + ...resource, 296 + url: `${resource.url}${resource.url.includes('?') ? '&' : '?'}utm_source=cred.blue&utm_medium=resources&utm_campaign=tools_directory` 297 + })); 298 + 299 + // Function to share the resources page on Bluesky 300 + const shareOnBluesky = () => { 301 + const shareText = `Check out this collection of Bluesky tools and resources from cred.blue! 🔧🦋\n\nFind analytics, feeds, alternative clients, and much more to enhance your Bluesky experience.\n\nExplore the tools: https://cred.blue/resources`; 302 + 303 + window.open( 304 + `https://bsky.app/intent/compose?text=${encodeURIComponent(shareText)}`, 305 + '_blank' 306 + ); 307 + }; 308 + 309 + // Get all categories 310 + const categories = ['All', ...new Set(resourcesWithUTM.map(item => item.category))]; 311 + 312 + // Filter resources based on active category, search query, and quality filter 313 + const filteredResources = useMemo(() => { 314 + return resourcesWithUTM.filter(resource => { 315 + // Filter by category 316 + const categoryMatch = activeCategory === 'All' || resource.category === activeCategory; 317 + 318 + // Filter by search query 319 + const searchMatch = 320 + resource.name.toLowerCase().includes(searchQuery.toLowerCase()) || 321 + resource.description.toLowerCase().includes(searchQuery.toLowerCase()); 322 + 323 + // Filter by quality 324 + const qualityMatch = 325 + qualityFilter === 'All' || 326 + (qualityFilter === 'High' && resource.quality >= 4) || 327 + (qualityFilter === 'Medium' && resource.quality === 3) || 328 + (qualityFilter === 'Low' && resource.quality <= 2); 329 + 330 + return categoryMatch && searchMatch && qualityMatch; 331 + }); 332 + }, [resourcesWithUTM, activeCategory, searchQuery, qualityFilter]); 333 + 334 + // Get featured resources 335 + const featuredResources = useMemo(() => { 336 + return resourcesWithUTM.filter(resource => resource.featured); 337 + }, [resourcesWithUTM]); 338 + 339 + // Simulate loading data 340 + useEffect(() => { 341 + // Simulate API fetch with a timeout 342 + const loadTimer = setTimeout(() => { 343 + setIsLoading(false); 344 + }, 800); 345 + 346 + return () => clearTimeout(loadTimer); 347 + }, []); 348 + 349 + return ( 350 + <> 351 + <main className="resources-page"> 352 + <div className="resources-header"> 353 + <div className="resources-title"> 354 + <h1>Bluesky Resources</h1> 355 + <p>A curated collection of third-party tools, services, and guides for the Bluesky ecosystem</p> 356 + </div> 357 + 358 + <div className="share-button-container"> 359 + <button 360 + className="share-button" 361 + type="button" 362 + onClick={shareOnBluesky} 363 + > 364 + Share This Page 365 + </button> 366 + </div> 367 + </div> 368 + 369 + <div className="resources-disclaimer"> 370 + <p><strong>Disclaimer:</strong> These resources are third-party tools and services not affiliated with cred.blue or Bluesky. 371 + Use them at your own risk and exercise caution when providing access to your data.</p> 372 + </div> 373 + 374 + {isLoading ? ( 375 + <ResourceLoader /> 376 + ) : ( 377 + <> 378 + <div className="resources-filters"> 379 + <div className="search-container"> 380 + <input 381 + type="text" 382 + placeholder="Search resources..." 383 + value={searchQuery} 384 + onChange={(e) => setSearchQuery(e.target.value)} 385 + className="search-input" 386 + /> 387 + </div> 388 + 389 + <div className="filter-options"> 390 + <div className="category-filters"> 391 + {categories.map(category => ( 392 + <button 393 + key={category} 394 + className={`category-filter ${activeCategory === category ? 'active' : ''}`} 395 + onClick={() => setActiveCategory(category)} 396 + > 397 + {category} 398 + </button> 399 + ))} 400 + </div> 401 + 402 + <div className="quality-filter"> 403 + <select 404 + value={qualityFilter} 405 + onChange={(e) => setQualityFilter(e.target.value)} 406 + className="quality-select" 407 + > 408 + <option value="All">All Quality Levels</option> 409 + <option value="High">High Quality</option> 410 + <option value="Medium">Medium Quality</option> 411 + <option value="Low">Low Quality</option> 412 + </select> 413 + </div> 414 + </div> 415 + </div> 416 + 417 + {featuredResources.length > 0 && ( 418 + <div className="featured-section"> 419 + <h2>Featured Resources</h2> 420 + <div className="resources-grid"> 421 + {featuredResources.map((resource, index) => ( 422 + <ResourceCard key={`featured-${index}`} resource={resource} /> 423 + ))} 424 + </div> 425 + </div> 426 + )} 427 + 428 + <div className="all-resources-section"> 429 + <h2>{activeCategory === 'All' ? 'All Resources' : activeCategory}</h2> 430 + {filteredResources.length > 0 ? ( 431 + <div className="resources-grid"> 432 + {filteredResources.map((resource, index) => ( 433 + <ResourceCard key={index} resource={resource} /> 434 + ))} 435 + </div> 436 + ) : ( 437 + <div className="no-results"> 438 + <p>No resources found matching your filters.</p> 439 + </div> 440 + )} 441 + </div> 442 + </> 443 + )} 444 + </main> 445 + </> 446 + ); 447 + }; 448 + 449 + // ResourceCard component for displaying individual resources 450 + const ResourceCard = ({ resource }) => { 451 + // Function to render stars based on quality rating 452 + const renderQualityStars = (quality) => { 453 + const stars = []; 454 + for (let i = 1; i <= 5; i++) { 455 + stars.push( 456 + <span 457 + key={i} 458 + className={`quality-star ${i <= quality ? 'filled' : 'empty'}`} 459 + > 460 + 461 + </span> 462 + ); 463 + } 464 + return stars; 465 + }; 466 + 467 + return ( 468 + <a 469 + href={resource.url} 470 + target="_blank" 471 + rel="noopener noreferrer" 472 + className="resource-card" 473 + > 474 + <div className="resource-content"> 475 + <h3 className="resource-name">{resource.name}</h3> 476 + <p className="resource-description">{resource.description}</p> 477 + <div className="resource-meta"> 478 + <span className="resource-category">{resource.category}</span> 479 + <div className="resource-quality"> 480 + {renderQualityStars(resource.quality)} 481 + </div> 482 + </div> 483 + </div> 484 + </a> 485 + ); 486 + }; 487 + 488 + export default Resources;
+1 -6
src/components/ScoringMethodology/ScoringMethodology.js
··· 71 71 { 72 72 id: "bluesky-eras", 73 73 term: "Bluesky Eras", 74 - definition: "Ever since Bluesky was first incubated from within Twitter in 2019, it has been through numerous different defining eras. Each of these eras has had distinct qualities and even cultures. The main eras are as follows: 1. pre-history (early staff, advisors, friends), 2. invite-only (with the introduction of the invite system), 3. public release (anyone could create an account)", 74 + definition: "Ever since Bluesky was first incubated from within Twitter in 2019, it has been through numerous different defining eras. Each of these eras has had distinct qualities and even cultures. The main eras are as follows: 1. pre-history (staff, advisors, friends), 2. invite-only (the introduction of the invite system), 3. public release (anyone could create an account)", 75 75 learnMoreLink: "https://atproto.com/guides/account-migration#updating-identity" 76 76 }, 77 77 { ··· 105 105 id: "newcomer", 106 106 name: "Newcomer", 107 107 description: "Accounts that are new to Bluesky or have minimal activity. These users are just getting started on the platform and beginning to build their presence. After 30 days, Newcomers become Explorers.", 108 - learnMoreLink: "https://cred.blue/social-status/newcomer" 109 108 }, 110 109 { 111 110 id: "explorer", 112 111 name: "Explorer", 113 112 description: "Users who are actively engaging with the platform, discovering features, and building their initial network. They have established a basic presence but are still growing their connections and potentially finding their community.", 114 - learnMoreLink: "https://cred.blue/social-status/explorer" 115 113 }, 116 114 { 117 115 id: "pathfinder", 118 116 name: "Pathfinder", 119 117 description: "Established users who have developed a consistent presence and are actively contributing to conversations. These accounts have a growing influence (1,000+ followers) and solid engagement within their communities.", 120 - learnMoreLink: "https://cred.blue/social-status/pathfinder" 121 118 }, 122 119 { 123 120 id: "guide", 124 121 name: "Guide", 125 122 description: "Well-established users who have significant influence within specific communities (10,000+ followers). They often create valuable content and maintain strong engagement with their followers.", 126 - learnMoreLink: "https://cred.blue/social-status/guide" 127 123 }, 128 124 { 129 125 id: "leader", 130 126 name: "Leader", 131 127 description: "Highly influential accounts with substantial followings (100,000+) and engagement. These users have a broad impact across multiple communities and consistently contribute high-value content to the platform.", 132 - learnMoreLink: "https://cred.blue/social-status/leader" 133 128 } 134 129 ]; 135 130