/**
* Public Share Card Generator
*
* Renders a shareable SVG image showing your gateway savings — useful for
* social posts, blog headers, README badges. Tokens are rounded; no
* personally identifying information leaks (caller IDs, model names etc.
* are NOT included). Just headline numbers + brand.
*
* Output is always a valid SVG so it can be embedded as `
`
* or downloaded directly.
*/
import type { Pool } from 'pg';
import { getComprehensiveSavings } from './savings-calculator.js';
import { getBuddyState } from './gamification.js';
function fmtNum(n: number): string {
if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + 'M';
if (n >= 1_000) return (n / 1_000).toFixed(1) + 'K';
return Math.round(n).toString();
}
function fmtCost(c: number): string {
if (c < 0.01) return `$${c.toFixed(6)}`;
if (c < 1) return `$${c.toFixed(4)}`;
return `$${c.toFixed(2)}`;
}
function escSvg(s: string): string {
return s.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"');
}
export type ShareCardPeriod = 'day' | 'week' | 'month' | 'all';
export type ShareCardTheme = 'dark' | 'light';
const PERIOD_HOURS: Record = {
day: 24, week: 168, month: 720, all: 24 * 365 * 5,
};
export async function generateShareCard(
db: Pool,
opts: { period?: ShareCardPeriod; theme?: ShareCardTheme } = {}
): Promise {
const period: ShareCardPeriod = opts.period ?? 'month';
const theme: ShareCardTheme = opts.theme ?? 'dark';
const hours = PERIOD_HOURS[period];
const [savings, buddy] = await Promise.all([
getComprehensiveSavings(db, hours),
getBuddyState(db, 'gateway'),
]);
// Theme palette
const palette = theme === 'dark' ? {
bg: '#0a0a0a', surface: '#161616', text: '#e8e8e8', dim: '#888888',
accent: '#d4ff00', accentDim: '#8aa800', border: '#2a2a2a',
} : {
bg: '#f4f7fa', surface: '#ffffff', text: '#24313d', dim: '#667684',
accent: '#0f766e', accentDim: '#8ab9b5', border: '#d6e0e7',
};
const periodLabel = period === 'day' ? 'Last 24 hours'
: period === 'week' ? 'Last 7 days'
: period === 'month' ? 'Last 30 days'
: 'All-time';
const W = 1200, H = 630; // Open Graph standard
const totalTokens = savings.totalTokensSaved;
const totalCost = savings.totalCostSaved;
const reqCount = savings.totals.requests;
const efficacy = savings.costWithoutGateway > 0
? ((savings.costWithoutGateway - savings.costWithGateway) / savings.costWithoutGateway) * 100
: 0;
// Source-bar widths
const total = Math.max(0.0000001, savings.totalCostSaved);
const wCache = (savings.bySource.cache.cost / total) * 100;
const wComp = (savings.bySource.compression.cost / total) * 100;
const wSub = (savings.bySource.subscriptionBridge.cost / total) * 100;
const wLocal = (savings.bySource.localRouting.cost / total) * 100;
const wRace = (savings.bySource.raceMode.cost / total) * 100;
return ``;
}