Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 61 additions & 3 deletions components/DetailPanel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

import React, { useEffect, useRef, useState } from 'react';
import * as d3 from 'd3';
import { formatNum } from '../lib/format.js';
import { formatNum, formatRelativeTime, isStaleData } from '../lib/format.js';
import { DIMENSIONS, SCORE_METHODOLOGY } from '../lib/scoring.js';

export default function DetailPanel({ dev, onClose, claimedLogins, openCardOnMount = false, claimSuccess = false }) {
const [fullData, setFullData] = useState(null);
Expand Down Expand Up @@ -79,7 +80,12 @@ export default function DetailPanel({ dev, onClose, claimedLogins, openCardOnMou
</div>
<div className="detail-header__location">📍 {dev.location || 'Unknown location'}</div>
<div className="detail-header__badges">
<span className="detail-header__score-badge">Score: {dev.score}/100</span>
<span
className="detail-header__score-badge"
title={dev.scoreHasSO === false ? `${SCORE_METHODOLOGY.short} ${SCORE_METHODOLOGY.noSO}` : SCORE_METHODOLOGY.short}
>
Score: {dev.score}/100
</span>
{dev.globalRank && (
<span
className="rank-badge rank-badge--global"
Expand Down Expand Up @@ -107,6 +113,9 @@ export default function DetailPanel({ dev, onClose, claimedLogins, openCardOnMou
</div>
)}
</div>
<p className="detail-header__score-note">
{SCORE_METHODOLOGY.short}
</p>
<div className="detail-header__links">
<a href={`https://github.com/${dev.login}`} target="_blank" rel="noreferrer">GitHub ↗</a>
{merged.soUserId && (
Expand Down Expand Up @@ -145,6 +154,7 @@ export default function DetailPanel({ dev, onClose, claimedLogins, openCardOnMou
<div className="chart-section">
<h3>Score Breakdown</h3>
<div ref={radarRef} />
<ScoreExplanation dev={dev} />
</div>

<div className="chart-section">
Expand Down Expand Up @@ -190,6 +200,54 @@ export default function DetailPanel({ dev, onClose, claimedLogins, openCardOnMou
);
}

function ScoreExplanation({ dev }) {
const dimensions = dev.scoreDimensions;
const weights = dev.scoreWeights;
if (!dimensions || !weights) return null;

const freshLabel = formatRelativeTime(dev.metricsUpdatedAt);
const stale = isStaleData(dev.metricsUpdatedAt);

Comment on lines +208 to +210
return (
<div className="score-explain">
<p className="score-explain__methodology">{SCORE_METHODOLOGY.short}</p>
{dev.scoreHasSO === false && (
<p className="score-explain__redistribute">{SCORE_METHODOLOGY.noSO}</p>
)}

<ul className="score-explain__list">
{DIMENSIONS.map(({ key, label, description }) => {
const normalized = dimensions[key] || 0;
const weight = weights[key] || 0;
const points = Math.round(normalized * weight * 100);
return (
<li className="score-explain__row" key={key}>
<div className="score-explain__row-top">
<span className="score-explain__label" title={description}>{label}</span>
<span className="score-explain__points">{points} pts</span>
</div>
<div className="score-explain__track">
<div className="score-explain__fill" style={{ width: `${Math.round(normalized * 100)}%` }} />
</div>
<span className="score-explain__weight">{Math.round(weight * 100)}% weight</span>
</li>
);
})}
</ul>

{typeof dev.scorePercentile === 'number' && (
<p className="score-explain__percentile">
Higher than {dev.scorePercentile}% of developers currently indexed by DevGlobe.
</p>
)}

<p className={`score-explain__freshness${stale ? ' score-explain__freshness--stale' : ''}`}>
{freshLabel ? `Metrics last refreshed ${freshLabel}${stale ? ' — may be out of date' : ''}.` : 'Metrics freshness unknown.'}
</p>
</div>
);
}

function StatCard({ label, value, className = '' }) {
return (
<div className={`stat-card ${className}`}>
Expand Down Expand Up @@ -514,4 +572,4 @@ function CardModal({ dev, claimSuccess, onClose }) {
</div>
</div>
);
}
}
10 changes: 5 additions & 5 deletions components/Globe.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ const COUNTRY_GEOJSON_URLS = [
const POLYGON_ALTITUDE = 0.003;
const POLYGON_ALTITUDE_ACTIVE = 0.009;

// Score-based color gradient: blue → green → gold → red
// Score-based color gradient (visual tiering only, not a judgment of skill)
function getScoreColor(score) {
if (score >= 80) return '#fbbf24'; // gold — elite
if (score >= 60) return '#34d399'; // emerald — strong
if (score >= 40) return '#3b82f6'; // blue — solid
return '#6366f1'; // indigo — emerging
if (score >= 80) return '#fbbf24'; // gold — top of the indexed range
if (score >= 60) return '#34d399'; // emerald — upper-mid range
if (score >= 40) return '#3b82f6'; // blue — mid range
return '#6366f1'; // indigo — lower range
}

function featureName(feat) {
Expand Down
14 changes: 13 additions & 1 deletion components/Leaderboard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import React, { useMemo, useRef, useState, useEffect, useCallback } from 'react';
import { formatNum } from '../lib/format.js';
import { extractCountry, normalizeCountry, countryKey } from '../lib/country.js';
import { SCORE_METHODOLOGY } from '../lib/scoring.js';

const ITEM_HEIGHT = 62;
const BUFFER = 10;
Expand Down Expand Up @@ -165,6 +166,11 @@ export default function Leaderboard({
<option value="soRep">SO Rep</option>
</select>
</div>
{sortBy === 'score' && (
<p className="sidebar__score-note" title={SCORE_METHODOLOGY.short}>
ⓘ Score is relative to this dataset, not an absolute rating — tap a profile for the full breakdown.
</p>
)}
</div>
<ul className="sidebar__list" ref={listRef} onScroll={handleScroll} style={{ position: 'relative', overflow: 'auto' }}>
<div style={{ height: totalHeight, position: 'relative' }}>
Expand Down Expand Up @@ -218,7 +224,13 @@ export default function Leaderboard({
>
{isCompareSelected ? '✓' : '⇄'}
</button>
<span className="lb-item__score">{dev.score}</span>
<span
className="lb-item__score"
title={`Score: ${dev.score}/100 — ${SCORE_METHODOLOGY.short}`}
aria-label={`Relative score ${dev.score} out of 100`}
>
{dev.score}
</span>
</div>
</li>
);
Expand Down
Loading
Loading