diff --git a/app/api/card/route.jsx b/app/api/card/route.jsx index 7b1c50a..ee0adbc 100644 --- a/app/api/card/route.jsx +++ b/app/api/card/route.jsx @@ -7,9 +7,31 @@ import { getSiteHostname } from '../../../lib/site.js'; import { addDeveloperRanks } from '../../../lib/ranking.js'; import { getCosmosContainer } from '../../../lib/cosmos.js'; import { getPublicAiToolNames } from '../../../lib/ai-profile.js'; +import { calculateOssWorth } from '../../../lib/oss-worth.js'; +import { formatUsd } from '../../../lib/format.js'; export const runtime = 'nodejs'; +const manropeFonts = Promise.all( + [400, 600, 700, 800].map(async weight => { + const fontPath = path.join( + process.cwd(), + 'node_modules', + '@fontsource', + 'manrope', + 'files', + `manrope-latin-${weight}-normal.woff` + ); + const buffer = await fs.readFile(fontPath); + return { + name: 'Manrope', + data: buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength), + weight, + style: 'normal', + }; + }) +); + async function getDeveloper(login) { const cosmosContainer = getCosmosContainer(); if (cosmosContainer) { @@ -74,7 +96,10 @@ export async function GET(request) { const score = Number.isFinite(dev.score) ? dev.score : 0; const agent = classifyAgent({ ...dev, score }); const power = getPowerTier(score); - const avatarDataUrl = await loadAvatarDataUrl(dev.avatarUrl); + const [avatarDataUrl, fonts] = await Promise.all([ + loadAvatarDataUrl(dev.avatarUrl), + manropeFonts, + ]); const avatarInitial = (dev.name || dev.login).trim().charAt(0).toUpperCase(); const agentMark = agent.name .replace(/^The\s+/, '') @@ -89,6 +114,7 @@ export async function GET(request) { const rankValueFontSize = hasCityRank ? '29' : '34'; const rankTotalFontSize = hasCityRank ? '12' : '14'; const aiToolNames = getPublicAiToolNames(dev.aiProfile); + const ossWorth = calculateOssWorth(dev); return new ImageResponse( ( @@ -97,8 +123,8 @@ export async function GET(request) { width: '1200', height: '630', display: 'flex', - background: 'linear-gradient(135deg, #080b10 0%, #111820 52%, #15120f 100%)', - fontFamily: 'Inter, sans-serif', + background: 'linear-gradient(135deg, #f8fbff 0%, #eaf8fb 56%, #fff3e6 100%)', + fontFamily: 'Manrope', position: 'relative', overflow: 'hidden', }} @@ -115,10 +141,10 @@ export async function GET(request) { alignItems: 'center', }} > -
- DEVGLOBE +
+ DEVGLOBE
-
+
OPEN SOURCE IDENTITY · 2026
@@ -132,22 +158,24 @@ export async function GET(request) { right: 0, bottom: 0, display: 'flex', - opacity: 0.06, - backgroundImage: 'linear-gradient(rgba(255,255,255,0.1) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,0.1) 1px, transparent 1px)', + opacity: 0.55, + backgroundImage: 'linear-gradient(rgba(8,145,178,0.08) 1px, transparent 1px), linear-gradient(90deg, rgba(8,145,178,0.08) 1px, transparent 1px)', backgroundSize: '40px 40px', }} /> - {/* Glow effect behind hero */} + {/* Bright identity field behind hero */}
@@ -183,7 +211,7 @@ export async function GET(request) { transform: 'rotate(-45deg)', color: '#f8fafc', fontSize: '21', - fontWeight: '900', + fontWeight: '800', }} > {agentMark} @@ -204,7 +232,7 @@ export async function GET(request) { {/* Name */}
- #{formatNum(dev.globalRank)} - of {formatNum(dev.globalTotal)} + #{formatNum(dev.globalRank)} + of {formatNum(dev.globalTotal)}
-
GLOBAL RANK
+
GLOBAL RANK
{dev.countryRank && (
- #{formatNum(dev.countryRank)} - of {formatNum(dev.countryTotal)} + #{formatNum(dev.countryRank)} + of {formatNum(dev.countryTotal)}
-
IN {dev.country.toUpperCase()}
+
IN {dev.country.toUpperCase()}
)} {hasCityRank && ( @@ -350,16 +380,17 @@ export async function GET(request) { width: rankCardWidth, height: '82', padding: '12px 18px', - background: 'linear-gradient(135deg, rgba(139,92,246,0.15), rgba(139,92,246,0.04))', - border: '1px solid rgba(139,92,246,0.32)', + background: 'linear-gradient(135deg, #f1eaff, #ffffff)', + border: '1px solid #bca6ef', borderRadius: '8', + boxShadow: '0 8px 18px rgba(109,40,217,0.08)', }} >
- #{formatNum(dev.cityRank)} - of {formatNum(dev.cityTotal)} + #{formatNum(dev.cityRank)} + of {formatNum(dev.cityTotal)}
-
IN {dev.city.toUpperCase()}
+
IN {dev.city.toUpperCase()}
)}
} @@ -371,6 +402,7 @@ export async function GET(request) { alignItems: 'center', gap: '16', marginBottom: '20', + width: '100%', }} >
+ +
+
+ OSS WORTH +
+
+ {formatUsd(ossWorth.totalDollarValue, true)} + {formatNum(ossWorth.totalCredits)} OSC +
+
{/* Stats grid */} @@ -446,19 +500,20 @@ export async function GET(request) { style={{ display: 'flex', flexDirection: 'column', - background: 'rgba(255,255,255,0.05)', - border: '1px solid rgba(255,255,255,0.08)', + background: '#ffffff', + border: '1px solid #cfdae5', borderRadius: '8', padding: '10px 14px', minWidth: '120', + boxShadow: '0 7px 16px rgba(16,42,67,0.07)', }} >
-
+
AI TOOLKIT
@@ -512,11 +567,11 @@ export async function GET(request) { >
-
+
#buildinpublic
-
+
{getSiteHostname()}
@@ -590,7 +645,7 @@ export async function GET(request) { width: '200', height: '200', borderRadius: '50%', - background: `radial-gradient(circle, ${agent.color}15 0%, transparent 70%)`, + background: `radial-gradient(circle, ${agent.color}2a 0%, transparent 70%)`, display: 'flex', }} /> @@ -599,6 +654,7 @@ export async function GET(request) { { width: 1200, height: 630, + fonts, headers: { 'Cache-Control': 'public, s-maxage=86400, stale-while-revalidate=604800', }, diff --git a/app/api/card/social/route.jsx b/app/api/card/social/route.jsx index 335b3bc..c2ae9d6 100644 --- a/app/api/card/social/route.jsx +++ b/app/api/card/social/route.jsx @@ -2,6 +2,8 @@ import { ImageResponse } from '@vercel/og'; import { promises as fs } from 'fs'; import path from 'path'; import { getCosmosContainer } from '../../../../lib/cosmos.js'; +import { calculateOssWorth } from '../../../../lib/oss-worth.js'; +import { formatUsd } from '../../../../lib/format.js'; export const runtime = 'nodejs'; @@ -10,7 +12,7 @@ async function getDeveloper(login) { if (cosmosContainer) { try { const { resources } = await cosmosContainer.items.query({ - query: `SELECT TOP 1 c.login, c.name, c.location, c.followers, c.totalStars, c.totalCommits, c.topLanguage + query: `SELECT TOP 1 c.login, c.name, c.location, c.followers, c.totalStars, c.totalCommits, c.topLanguage, c.soUserId, c.soReputation, c.soAnswers, c.soBadges FROM c WHERE (c.login = @login OR c.id = @login) AND (NOT IS_DEFINED(c.nomination) OR c.nomination.status = 'approved')`, @@ -44,10 +46,12 @@ export async function GET(request) { if (!developer) return new Response('Developer not found', { status: 404 }); const name = developer.name || developer.login; + const ossWorth = calculateOssWorth(developer); const stats = [ { label: 'GITHUB STARS', value: formatNumber(developer.totalStars) }, { label: 'COMMITS', value: formatNumber(developer.totalCommits) }, { label: 'FOLLOWERS', value: formatNumber(developer.followers) }, + { label: `OSS WORTH · ${formatNumber(ossWorth.totalCredits)} OSC`, value: formatUsd(ossWorth.totalDollarValue, true) }, ]; return new ImageResponse( @@ -87,7 +91,7 @@ export async function GET(request) {
-
+
{stats.map(stat => (
{stat.value}
diff --git a/components/DetailPanel.jsx b/components/DetailPanel.jsx index 4a16982..628c4a6 100644 --- a/components/DetailPanel.jsx +++ b/components/DetailPanel.jsx @@ -6,7 +6,7 @@ import { track } from '@vercel/analytics'; import * as d3 from 'd3'; import { formatNum, formatRelativeTime, formatUsd, isStaleData } from '../lib/format.js'; import { DIMENSIONS, SCORE_METHODOLOGY } from '../lib/scoring.js'; -import { SOCIAL_PREVIEW_VERSION } from '../lib/site.js'; +import { IDENTITY_CARD_VERSION, SOCIAL_PREVIEW_VERSION } from '../lib/site.js'; import { classifyAgent } from '../lib/agent-class.js'; import { AI_TOOLS } from '../lib/ai-profile.js'; import SpecialTags from './SpecialTags.jsx'; @@ -672,7 +672,7 @@ function CardModal({ dev, claimSuccess, onClose }) { const [linkedinCopied, setLinkedinCopied] = useState(false); const { login } = dev; const name = dev.name || login; - const cardUrl = `/api/card?login=${encodeURIComponent(login)}`; + const cardUrl = `/api/card?login=${encodeURIComponent(login)}&v=${IDENTITY_CARD_VERSION}`; const sharePath = `/share/${encodeURIComponent(login)}?v=${SOCIAL_PREVIEW_VERSION}`; const shareUrl = typeof window !== 'undefined' ? `${window.location.origin}${sharePath}` : sharePath; diff --git a/lib/site.js b/lib/site.js index bdff92d..0650df9 100644 --- a/lib/site.js +++ b/lib/site.js @@ -1,5 +1,6 @@ const DEFAULT_SITE_URL = 'https://www.devglobe.dev'; export const SOCIAL_PREVIEW_VERSION = '11'; +export const IDENTITY_CARD_VERSION = '3'; export function getSiteUrl() { const configuredUrl = process.env.NEXT_PUBLIC_SITE_URL?.trim(); diff --git a/package-lock.json b/package-lock.json index 1ad67f1..5220b06 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "2.0.0", "dependencies": { "@azure/cosmos": "^4.10.0", + "@fontsource/manrope": "^5.3.0", "@modelcontextprotocol/sdk": "^1.30.0", "@vercel/analytics": "^2.0.1", "@vercel/og": "^0.11.1", @@ -1158,6 +1159,15 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@fontsource/manrope": { + "version": "5.3.0", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@fontsource/manrope/-/manrope-5.3.0.tgz", + "integrity": "sha1-MaCOkpnmVPnY8zr4DUZ8zv8pWGE=", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, "node_modules/@hono/node-server": { "version": "2.1.0", "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@hono/node-server/-/node-server-2.1.0.tgz", diff --git a/package.json b/package.json index 2233930..2d3a547 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ }, "dependencies": { "@azure/cosmos": "^4.10.0", + "@fontsource/manrope": "^5.3.0", "@modelcontextprotocol/sdk": "^1.30.0", "@vercel/analytics": "^2.0.1", "@vercel/og": "^0.11.1",