diff --git a/app/api/developer/route.js b/app/api/developer/route.js
index 29da0ca..683f65c 100644
--- a/app/api/developer/route.js
+++ b/app/api/developer/route.js
@@ -50,7 +50,7 @@ export async function GET(request) {
const container = client.database(DATABASE).container(CONTAINER);
const { resources } = await container.items.query({
- query: "SELECT c.id, c.login, c.name, c.avatarUrl, c.bio, c.githubUrl, c.location, c.lat, c.lng, c.followers, c.totalStars, c.totalForks, c.totalWatchers, c.totalCommits, c.topLanguage, c.languages, c.publicRepos, c.topRepos, c.soReputation, c.soAnswers, c.soAcceptRate, c.soBadges, c.soUserId, c.specialTags, c.claimed, c.claimedAt, c.metricsUpdatedAt, c.aiProfile FROM c WHERE (c.id = @id OR c.login = @id) AND (NOT IS_DEFINED(c.nomination) OR c.nomination.status = 'approved')",
+ query: "SELECT c.id, c.login, c.name, c.avatarUrl, c.bio, c.githubUrl, c.location, c.lat, c.lng, c.followers, c.totalStars, c.totalForks, c.totalWatchers, c.totalCommits, c.topLanguage, c.languages, c.publicRepos, c.topRepos, c.soReputation, c.soAnswers, c.soAcceptRate, c.soBadges, c.soUserId, c.score, c.scoreDimensions, c.scoreWeights, c.scoreHasSO, c.scorePercentile, c.specialTags, c.claimed, c.claimedAt, c.metricsUpdatedAt, c.aiProfile FROM c WHERE (c.id = @id OR c.login = @id) AND (NOT IS_DEFINED(c.nomination) OR c.nomination.status = 'approved')",
parameters: [{ name: '@id', value: id }]
}).fetchAll();
diff --git a/app/api/developers/route.js b/app/api/developers/route.js
index 3426e03..b8d65f3 100644
--- a/app/api/developers/route.js
+++ b/app/api/developers/route.js
@@ -33,7 +33,7 @@ export async function GET() {
const container = client.database(DATABASE).container(CONTAINER);
const { resources } = await container.items
- .query("SELECT c.id, c.login, c.name, c.avatarUrl, c.githubUrl, c.location, c.lat, c.lng, c.followers, c.publicRepos, c.totalStars, c.totalForks, c.totalWatchers, c.totalCommits, c.topLanguage, c.soUserId, c.soReputation, c.soAnswers, c.soAcceptRate, c.soBadges, c.specialTags, c.claimed, c.metricsUpdatedAt, c.collaborators, c.aiProfile FROM c WHERE NOT IS_DEFINED(c.nomination) OR c.nomination.status = 'approved'")
+ .query("SELECT c.id, c.login, c.name, c.avatarUrl, c.location, c.lat, c.lng, c.followers, c.publicRepos, c.totalStars, c.totalForks, c.totalCommits, c.topLanguage, c.soUserId, c.soReputation, c.soAnswers, c.soBadges, c.score, c.specialTags, c.claimed, c.metricsUpdatedAt, c.aiProfile FROM c WHERE NOT IS_DEFINED(c.nomination) OR c.nomination.status = 'approved'")
.fetchAll();
return NextResponse.json(projectAgentReadinessList(resources), {
diff --git a/app/page.jsx b/app/page.jsx
index 409cd10..7b637d6 100644
--- a/app/page.jsx
+++ b/app/page.jsx
@@ -13,10 +13,7 @@ import AiProfileModal from '../components/AiProfileModal.jsx';
import IntroductionInboxModal from '../components/IntroductionInboxModal.jsx';
import QuickTour from '../components/QuickTour.jsx';
import PlatformActivityBanner from '../components/PlatformActivityBanner.jsx';
-import { scoreAll } from '../lib/scoring.js';
-import { addDeveloperRanks } from '../lib/ranking.js';
-import { enrichWithCollaborators } from '../lib/collaboration.js';
-import { withOssWorth } from '../lib/oss-worth.js';
+import { prepareDeveloperDataset } from '../lib/developer-dataset.js';
import dynamic from 'next/dynamic';
const Globe = dynamic(() => import('../components/Globe.jsx'), { ssr: false });
@@ -29,6 +26,7 @@ export default function Home() {
const [filtered, setFiltered] = useState(() => cachedDeveloperDataset?.developers || []);
const [selectedDev, setSelectedDev] = useState(null);
const [loading, setLoading] = useState(() => !cachedDeveloperDataset);
+ const [loadingStage, setLoadingStage] = useState('connecting');
const [error, setError] = useState(null);
const [flyTarget, setFlyTarget] = useState(null);
const [selectedCountry, setSelectedCountry] = useState('');
@@ -132,7 +130,7 @@ export default function Home() {
const devRes = await fetch('/api/developers', { cache: 'no-store' });
if (devRes.ok) {
const raw = await devRes.json();
- const scored = enrichWithCollaborators(addDeveloperRanks(scoreAll(raw))).map(withOssWorth);
+ const scored = prepareDeveloperDataset(raw);
setDevelopers(scored);
setFiltered(scored);
const claimed = new Set(raw.filter(d => d.claimed).map(d => d.login));
@@ -220,8 +218,10 @@ export default function Home() {
const res = await fetch('/api/developers', { signal: AbortSignal.timeout(30000) });
if (!res.ok) throw new Error(`Failed to load data: ${res.status}`);
+ setLoadingStage('downloading');
const raw = await res.json();
- const scored = enrichWithCollaborators(addDeveloperRanks(scoreAll(raw))).map(withOssWorth);
+ setLoadingStage('preparing');
+ const scored = prepareDeveloperDataset(raw);
setDevelopers(scored);
setFiltered(scored);
// Build set of all claimed logins from data
@@ -419,12 +419,13 @@ export default function Home() {
setSidebarOpen(false);
}, [developers]);
- if (loading || error) {
+ if (error) {
return ;
}
return (
-
+
+ {loading &&
}
{ cancelled = true; };
}, [dev.id]);
+ const merged = { ...dev, ...fullData };
+
// Radar chart
useEffect(() => {
- if (!dev.scoreDimensions || !radarRef.current) return;
- renderRadar(radarRef.current, dev.scoreDimensions);
- }, [dev.scoreDimensions]);
+ if (!merged.scoreDimensions || !radarRef.current) return;
+ renderRadar(radarRef.current, merged.scoreDimensions);
+ }, [merged.scoreDimensions]);
// Heatmap
useEffect(() => {
@@ -108,7 +110,6 @@ export default function DetailPanel({ dev, onClose, onCardGenerated, claimedLogi
renderLanguages(langRef.current, langs);
}, [fullData, dev.topLanguage]);
- const merged = { ...dev, ...fullData };
const repos = merged.topRepos || [];
const soRep = merged.soReputation || 0;
const soAnswers = merged.soAnswers || 0;
@@ -233,7 +234,7 @@ export default function DetailPanel({ dev, onClose, onCardGenerated, claimedLogi
diff --git a/components/Globe.jsx b/components/Globe.jsx
index b50f7cb..59f5e02 100644
--- a/components/Globe.jsx
+++ b/components/Globe.jsx
@@ -222,8 +222,25 @@ const Globe = forwardRef(function Globe({
const [countryFeatures, setCountryFeatures] = useState([]);
const [hoverCountry, setHoverCountry] = useState(null);
const [hoverDev, setHoverDev] = useState(null);
+ const [pointLimit, setPointLimit] = useState(800);
const isLight = theme === 'light';
+ useEffect(() => {
+ setPointLimit(800);
+ const expandedLimit = window.innerWidth < 768 ? 1200 : 2500;
+
+ if (window.requestIdleCallback) {
+ const idleId = window.requestIdleCallback(
+ () => setPointLimit(expandedLimit),
+ { timeout: 2500 }
+ );
+ return () => window.cancelIdleCallback(idleId);
+ }
+
+ const timerId = window.setTimeout(() => setPointLimit(expandedLimit), 1200);
+ return () => window.clearTimeout(timerId);
+ }, [developers]);
+
const geoDevs = useMemo(() => {
let list = developers.filter(d => d.lat != null && d.lng != null);
@@ -234,8 +251,8 @@ const Globe = forwardRef(function Globe({
return list
.sort((a, b) => b.score - a.score)
- .slice(0, 5000);
- }, [developers, selectedCountry]);
+ .slice(0, pointLimit);
+ }, [developers, pointLimit, selectedCountry]);
const featuredGeoDevs = useMemo(() => (
agentNetworkVisible ? geoDevs.filter(developer => developer.agentReady) : geoDevs
@@ -630,7 +647,7 @@ const Globe = forwardRef(function Globe({
pointAltitude={displayPointAltitude}
pointRadius={displayPointRadius}
pointColor={displayPointColor}
- pointResolution={6}
+ pointResolution={5}
htmlElementsData={avatarDevs}
htmlLat={avatarLat}
htmlLng={avatarLng}
diff --git a/components/LoadingOverlay.jsx b/components/LoadingOverlay.jsx
index 251e6a8..612f891 100644
--- a/components/LoadingOverlay.jsx
+++ b/components/LoadingOverlay.jsx
@@ -1,64 +1,17 @@
'use client';
-import React, { useState, useEffect } from 'react';
+const STAGE_COPY = {
+ connecting: 'Connecting to the developer index',
+ downloading: 'Downloading developer profiles',
+ preparing: 'Preparing ranks and map points',
+};
-const FACTS = [
- 'Mapping contributions across 150+ countries…',
- 'Calculating star power and commit velocity…',
- 'Ranking the world\'s top contributors…',
- 'Building your interactive 3D globe…',
-];
-
-const FEATURED_PROFILES = ['torvalds', 'gaearon', 'sindresorhus', 'tj', 'addyosmani'];
-
-function AnimatedCounter({ target, duration = 2000, suffix = '' }) {
- const [count, setCount] = useState(0);
-
- useEffect(() => {
- let frame;
- const startedAt = performance.now();
-
- const update = (now) => {
- const progress = Math.min((now - startedAt) / duration, 1);
- setCount(Math.floor(target * progress));
- if (progress < 1) frame = requestAnimationFrame(update);
- };
-
- frame = requestAnimationFrame(update);
- return () => cancelAnimationFrame(frame);
- }, [target, duration]);
-
- return <>{count.toLocaleString()}{suffix}>;
-}
-
-export default function LoadingOverlay({ error, datasetCount }) {
- const facts = [
- datasetCount === null
- ? 'Counting indexed open-source developers…'
- : `Indexing ${datasetCount.toLocaleString()} open-source developers…`,
- ...FACTS,
- ];
- const [factIndex, setFactIndex] = useState(0);
- const [dots, setDots] = useState('');
-
- useEffect(() => {
- const factTimer = setInterval(() => {
- setFactIndex(prev => (prev + 1) % facts.length);
- }, 3000);
- return () => clearInterval(factTimer);
- }, [facts.length]);
-
- useEffect(() => {
- const dotTimer = setInterval(() => {
- setDots(prev => prev.length >= 3 ? '' : prev + '.');
- }, 400);
- return () => clearInterval(dotTimer);
- }, []);
+export default function LoadingOverlay({ error, datasetCount, stage = 'connecting' }) {
if (error) {
return (
-
-
+
+
⚠️
Failed to load data
{error}
@@ -74,87 +27,57 @@ export default function LoadingOverlay({ error, datasetCount }) {
}
return (
-
-
-
-
-
+
+
+
+
+
+
+
-
-
- {/* Branding */}
-
-
- DevGlobe
-
-
Where Developers and AI Agents Connect
-
-
-
- {/* Stats preview */}
-
-
-
- {datasetCount === null ? '…' : }
-
-
Developers
-
-
-
-
-
Countries
+
+

+
DevGlobe
-
-
-
-
Stars Tracked
+
Loading developer map
+
+ {STAGE_COPY[stage] || STAGE_COPY.connecting}
+ {datasetCount !== null ? ` · ${datasetCount.toLocaleString()} profiles` : ''}
+
+
-
- {/* Rotating facts */}
-
- {facts[factIndex]}
-
-
- {/* Progress indicator */}
-
-
Preparing your globe{dots}
);
}
diff --git a/lib/developer-dataset.js b/lib/developer-dataset.js
new file mode 100644
index 0000000..6336e01
--- /dev/null
+++ b/lib/developer-dataset.js
@@ -0,0 +1,15 @@
+import { addDeveloperRanks } from './ranking.js';
+import { withOssWorth } from './oss-worth.js';
+
+export function prepareDeveloperDataset(developers = []) {
+ const ranked = [...developers]
+ .map(developer => ({
+ ...developer,
+ score: Number.isFinite(developer.score) ? developer.score : 0,
+ }))
+ .sort((left, right) =>
+ right.score - left.score || String(left.login || '').localeCompare(String(right.login || ''))
+ );
+
+ return addDeveloperRanks(ranked).map(withOssWorth);
+}
\ No newline at end of file
diff --git a/styles/main.css b/styles/main.css
index b44b8f3..fc6cc79 100644
--- a/styles/main.css
+++ b/styles/main.css
@@ -3057,12 +3057,39 @@ body {
flex-direction: column;
align-items: center;
justify-content: center;
- background: var(--bg-primary);
+ padding: 20px;
+ background: color-mix(in srgb, var(--bg-primary) 72%, transparent);
+ backdrop-filter: blur(9px);
+ -webkit-backdrop-filter: blur(9px);
z-index: 999;
transition: opacity 0.5s;
gap: 0;
}
+.loading-panel {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ width: min(390px, calc(100vw - 40px));
+ padding: 22px 24px 24px;
+ background: color-mix(in srgb, var(--bg-card) 94%, transparent);
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ box-shadow: var(--shadow-strong);
+ text-align: center;
+}
+
+.loading-panel--error {
+ max-width: 400px;
+}
+
+.loading-panel .loading-scene {
+ --loading-orbit-radius: 56px;
+ width: 120px;
+ height: 120px;
+ margin-bottom: 4px;
+}
+
.loading-overlay.hidden {
opacity: 0;
pointer-events: none;
@@ -3202,20 +3229,28 @@ body {
align-items: center;
gap: 10px;
max-width: 720px;
- padding: 0 24px;
- font-size: 28px;
+ padding: 0;
+ font-size: 15px;
font-weight: 700;
color: var(--text-primary);
- margin-bottom: 6px;
+ margin-bottom: 12px;
text-align: center;
}
.loading-brand__logo {
- width: 36px;
- height: 36px;
+ width: 24px;
+ height: 24px;
object-fit: contain;
}
+.loading-title {
+ margin: 0 0 7px;
+ color: var(--text-primary);
+ font-size: 20px;
+ font-weight: 700;
+ letter-spacing: 0;
+}
+
.loading-tagline {
font-size: 14px;
color: var(--text-secondary);
@@ -3298,12 +3333,12 @@ body {
/* Progress bar */
.loading-progress {
- width: 200px;
+ width: 100%;
height: 3px;
background: var(--border);
border-radius: 3px;
overflow: hidden;
- margin-bottom: 12px;
+ margin: 16px 0 0;
}
.loading-progress__bar {
@@ -3320,9 +3355,11 @@ body {
}
.loading-status {
- font-size: 12px;
- color: var(--text-muted);
- min-width: 160px;
+ min-height: 20px;
+ margin: 0;
+ font-size: 13px;
+ color: var(--text-secondary);
+ min-width: 0;
text-align: center;
}
@@ -3517,7 +3554,7 @@ body {
padding: 0;
}
.product-hunt-badge {
- left: 12px;
+ display: none;
}
}
diff --git a/tests/developer-dataset.test.js b/tests/developer-dataset.test.js
new file mode 100644
index 0000000..d404a28
--- /dev/null
+++ b/tests/developer-dataset.test.js
@@ -0,0 +1,20 @@
+import test from 'node:test';
+import assert from 'node:assert/strict';
+import { prepareDeveloperDataset } from '../lib/developer-dataset.js';
+
+test('prepares stored developer scores without mutating the API response', () => {
+ const developers = [
+ { login: 'second', score: 40, totalCommits: 10 },
+ { login: 'first', score: 90, totalStars: 10 },
+ { login: 'unscored', totalCommits: 5 },
+ ];
+
+ const prepared = prepareDeveloperDataset(developers);
+
+ assert.deepEqual(prepared.map(developer => developer.login), ['first', 'second', 'unscored']);
+ assert.deepEqual(prepared.map(developer => developer.globalRank), [1, 2, 3]);
+ assert.equal(prepared[0].globalTotal, 3);
+ assert.equal(prepared[0].ossWorth.totalDollarValue, 3);
+ assert.equal(prepared[2].score, 0);
+ assert.equal(developers[2].score, undefined);
+});
\ No newline at end of file