← back to kingkpink__metalstats

Function bodies 642 total

All specs Real LLM only Function bodies
DeliverySection function · typescript · L80-L283 (204 LOC)
components/DeliverySection.tsx
export default function DeliverySection({ data }: DeliverySectionProps) {
  // Sort deliveries by daily_issued descending
  const sortedDeliveries = [...data.deliveries].sort((a, b) => b.daily_issued - a.daily_issued);

  // Calculate totals
  const totalDailyIssued = sortedDeliveries.reduce((sum, d) => sum + d.daily_issued, 0);
  const totalDailyStopped = sortedDeliveries.reduce((sum, d) => sum + d.daily_stopped, 0);
  const totalMTD = sortedDeliveries.reduce((sum, d) => sum + d.month_to_date, 0);

  return (
    <section className="notranslate w-full" translate="no">
      {/* Section Header */}
      <div className="flex flex-col md:flex-row md:items-end justify-between mb-8 sm:mb-12 md:mb-16 gap-4">
        <div className="max-w-xl space-y-3 sm:space-y-8">
          <div className="flex items-center gap-3">
            <h2 className="tracking-tighter text-3xl sm:text-4xl md:text-5xl font-black uppercase">
              Delivery Notices
            </h2>
            <ShareButton
   
formatNumber function · typescript · L101-L103 (3 LOC)
components/DeliveryYTDSection.tsx
function formatNumber(num: number): string {
  return num.toLocaleString('en-US');
}
formatShortDate function · typescript · L105-L112 (8 LOC)
components/DeliveryYTDSection.tsx
function formatShortDate(dateStr: string): string {
  const parts = dateStr.split('/');
  if (parts.length !== 3) return dateStr;
  const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
  const month = parseInt(parts[0]) - 1;
  const day = parseInt(parts[1]);
  return `${monthNames[month]} ${day}`;
}
getCurrentMonthLabel function · typescript · L114-L121 (8 LOC)
components/DeliveryYTDSection.tsx
function getCurrentMonthLabel(mtdData: DeliveryMTDData | null | undefined): string {
  if (!mtdData?.business_date) return 'Daily';
  const parts = mtdData.business_date.split('/');
  if (parts.length !== 3) return 'Daily';
  const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
  const month = parseInt(parts[0]) - 1;
  return monthNames[month] || 'Daily';
}
SortIndicator function · typescript · L123-L132 (10 LOC)
components/DeliveryYTDSection.tsx
function SortIndicator({ column, current, direction }: { column: string; current: string; direction: 'asc' | 'desc' }) {
  if (current !== column) {
    return <span className="text-[8px] opacity-30 ml-0.5">&#x25B2;&#x25BC;</span>;
  }
  return (
    <span className="text-[8px] ml-0.5">
      {direction === 'asc' ? '\u25B2' : '\u25BC'}
    </span>
  );
}
normalizeDate function · typescript · L267-L278 (12 LOC)
components/DemandChart.tsx
function normalizeDate(dateStr: string): string {
  if (!dateStr) return '';
  // Already YYYY-MM-DD
  if (/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) return dateStr;
  // ISO datetime like "2026-02-05T00:00:00.000Z"
  const isoMatch = dateStr.match(/^(\d{4}-\d{2}-\d{2})T/);
  if (isoMatch) return isoMatch[1];
  // Long date string like "Thu Feb 05 2026 00:00:00 GMT..." — parse it
  const d = new Date(dateStr);
  if (!isNaN(d.getTime())) return d.toISOString().split('T')[0];
  return dateStr;
}
formatDateLabel function · typescript · L280-L286 (7 LOC)
components/DemandChart.tsx
function formatDateLabel(dateStr: string): string {
  const clean = normalizeDate(dateStr);
  if (!clean || !/^\d{4}-\d{2}-\d{2}$/.test(clean)) return clean || '';
  const d = new Date(clean + 'T12:00:00'); // noon to avoid TZ issues
  if (isNaN(d.getTime())) return clean;
  return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
}
Provenance: Repobility (https://repobility.com) — every score reproducible from /scan/
fetchAndMerge function · typescript · L476-L596 (121 LOC)
components/DemandChart.tsx
    async function fetchAndMerge() {
      const metals: MetalType[] = ['gold', 'silver', 'aluminum', 'copper'];

      // Start with deep copies of baseline
      const mergedDaily: Record<MetalType, DailyDataPoint[]> = { gold: [], silver: [], aluminum: [], copper: [] };
      const mergedMonthly: Record<MetalType, MonthlyDataPoint[]> = { gold: [], silver: [], aluminum: [], copper: [] };
      for (const m of metals) {
        mergedDaily[m] = baselineDailyData[m].map(d => ({ ...d }));
        mergedMonthly[m] = baselineMonthlyData[m].map(d => ({ ...d }));
      }

      // Step 1: Merge delivery.json (current day) into daily + monthly
      if (deliveryData?.deliveries && deliveryData.parsed_date) {
        for (const delivery of deliveryData.deliveries) {
          const metalKey = delivery.metal.toLowerCase() as MetalType;
          if (!mergedDaily[metalKey]) continue;

          const dateKey = normalizeDate(deliveryData.parsed_date);
          const dayLabel = formatDateLabel(da
DepositoryTable function · typescript · L14-L88 (75 LOC)
components/DepositoryTable.tsx
export default function DepositoryTable({ depositories, metalName, unit }: DepositoryTableProps) {
  const [isExpanded, setIsExpanded] = useState(false);

  const activeDepositories = depositories.filter(d => d.total > 0);
  const grandTotal = activeDepositories.reduce((sum, d) => sum + d.total, 0);
  const sortedDepositories = [...activeDepositories].sort((a, b) => b.total - a.total);

  if (activeDepositories.length === 0) return null;

  return (
    <Card>
      <CardHeader 
        className="cursor-pointer hover:bg-muted/50 transition-colors"
        onClick={() => setIsExpanded(!isExpanded)}
      >
        <div className="flex items-center justify-between">
          <div className="space-y-1">
            <h3 className="text-lg font-semibold leading-none tracking-tight">{metalName}</h3>
            <p className="text-sm text-muted-foreground">
              {activeDepositories.length} vaults · {formatNumber(grandTotal)} {unit}
            </p>
          </div>
          <Chevr
formatExportDate function · typescript · L14-L17 (4 LOC)
components/ExportChartButton.tsx
function formatExportDate(d: Date): string {
  const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
  return `${months[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()}`;
}
drawTiledWatermark function · typescript · L20-L49 (30 LOC)
components/ExportChartButton.tsx
function drawTiledWatermark(
  ctx: CanvasRenderingContext2D,
  width: number,
  height: number,
  isDark: boolean,
  pad: number,
) {
  const patternCanvas = document.createElement('canvas');
  const tileSize = 421; // 337 / 0.8 — 20% fewer repetitions
  patternCanvas.width = tileSize;
  patternCanvas.height = tileSize;
  const pCtx = patternCanvas.getContext('2d')!;
  pCtx.font = `600 ${43}px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif`; // 33 * 1.30
  pCtx.fillStyle = isDark ? 'rgba(148,163,184,0.12)' : 'rgba(100,116,139,0.09)';
  pCtx.textAlign = 'center';
  pCtx.textBaseline = 'middle';
  pCtx.save();
  pCtx.translate(tileSize / 2, tileSize / 2);
  pCtx.rotate(-35 * Math.PI / 180);
  pCtx.translate(-tileSize / 2, -tileSize / 2);
  pCtx.fillText(WATERMARK, tileSize / 2, tileSize / 2);
  pCtx.restore();

  const pattern = ctx.createPattern(patternCanvas, 'repeat')!;
  ctx.save();
  ctx.globalAlpha = 1;
  ctx.fillStyle = pattern;
  ctx.fillRect(pad, pad, width -
ExportChartButton function · typescript · L51-L141 (91 LOC)
components/ExportChartButton.tsx
export default function ExportChartButton({
  targetRef,
  filename = 'chart',
  className = '',
}: ExportChartButtonProps) {
  const [state, setState] = useState<'idle' | 'capturing' | 'done'>('idle');

  const capture = useCallback(async () => {
    const el = targetRef.current;
    if (!el || state === 'capturing') return;

    setState('capturing');

    try {
      const html2canvas = (await import('html2canvas-pro')).default;
      const generatedAt = new Date();

      const isDark = document.documentElement.classList.contains('dark');
      const bg = isDark ? '#0a0a0a' : '#ffffff';

      const canvas = await html2canvas(el, {
        backgroundColor: bg,
        scale: 2,
        useCORS: true,
        logging: false,
        removeContainer: true,
      });

      const pad = 32;
      const wmHeight = 36;
      const final = document.createElement('canvas');
      final.width = canvas.width + pad * 2;
      final.height = canvas.height + pad * 2 + wmHeight;

      const ctx
Sparkline function · typescript · L180-L208 (29 LOC)
components/ForecastDashboard.tsx
function Sparkline({ data, color, positive }: { data: number[]; color: string; positive: boolean }) {
  if (data.length < 2) return null;
  const chartData = data.map((v, i) => ({ i, v }));
  const min = Math.min(...data);
  const max = Math.max(...data);
  const pad = (max - min) * 0.1 || 1;

  return (
    <ResponsiveContainer width="100%" height={40}>
      <LineChart data={chartData} margin={{ top: 2, right: 2, bottom: 2, left: 2 }}>
        <defs>
          <linearGradient id={`spark-${color.replace('#', '')}`} x1="0" y1="0" x2="0" y2="1">
            <stop offset="0%" stopColor={color} stopOpacity={0.3} />
            <stop offset="100%" stopColor={color} stopOpacity={0} />
          </linearGradient>
        </defs>
        <YAxis domain={[min - pad, max + pad]} hide />
        <Line
          type="monotone"
          dataKey="v"
          stroke={positive ? '#10b981' : '#ef4444'}
          strokeWidth={1.5}
          dot={false}
          isAnimationActive={false}
        />
 
DirectionBadge function · typescript · L212-L228 (17 LOC)
components/ForecastDashboard.tsx
function DirectionBadge({ direction, confidence }: { direction: string; confidence: number }) {
  const config = {
    BULLISH:  { icon: TrendingUp,   bg: 'bg-emerald-500/10 dark:bg-emerald-500/20', text: 'text-emerald-600 dark:text-emerald-400', border: 'border-emerald-500/30' },
    BEARISH:  { icon: TrendingDown,  bg: 'bg-red-500/10 dark:bg-red-500/20',      text: 'text-red-600 dark:text-red-400',      border: 'border-red-500/30' },
    NEUTRAL:  { icon: Minus,         bg: 'bg-slate-500/10 dark:bg-slate-500/20',   text: 'text-slate-600 dark:text-slate-400',  border: 'border-slate-500/30' },
  }[direction] || { icon: Minus, bg: 'bg-slate-500/10', text: 'text-slate-500', border: 'border-slate-500/30' };

  const Icon = config.icon;

  return (
    <div className={`inline-flex items-center gap-2 px-3 py-1.5 rounded-none border ${config.bg} ${config.border}`}>
      <Icon className={`w-4 h-4 ${config.text}`} />
      <span className={`text-sm font-bold tracking-wide ${config.text}`}>{di
SignalDropdown function · typescript · L230-L294 (65 LOC)
components/ForecastDashboard.tsx
function SignalDropdown({ label, score, details, colors, barColor, direction }: {
  label: string;
  score: number;
  details: string;
  colors: string;
  barColor: string;
  direction: string;
}) {
  const [open, setOpen] = useState(false);
  const roundedScore = Math.round(score);

  const signalDescriptions: Record<string, string> = {
    'Trend Momentum': 'Measures price trend strength using moving averages (SMA5/SMA20), MACD crossovers, RSI momentum, and rate of change. Scores above 60 indicate bullish trend alignment; below 40 signals bearish pressure.',
    'Physical Stress': 'Tracks physical market tightness: inventory drawdowns, delivery acceleration, paper-to-physical ratios, coverage erosion, and eligible-to-registered flow shifts. Higher scores signal supply stress.',
    'Arima Model': 'Statistical time-series forecast using Auto-ARIMA. Projects price direction over 5-day and 20-day windows with confidence intervals. Score reflects projected percentage change direction.',
Repobility · MCP-ready · https://repobility.com
PriceProjectionChart function · typescript · L296-L421 (126 LOC)
components/ForecastDashboard.tsx
function PriceProjectionChart({ currentPrice, forecast5d, forecast20d, metalColor, noDataMessage }: {
  currentPrice: number;
  forecast5d: ForecastRange | null;
  forecast20d: ForecastRange | null;
  metalColor: string;
  noDataMessage?: string;
}) {
  const { theme } = useTheme();
  const isDark = theme === 'dark';

  if (!forecast5d && !forecast20d) {
    return (
      <div className="flex items-center justify-center h-32 sm:h-40 text-xs sm:text-sm text-slate-400">
        <Info className="w-3.5 h-3.5 sm:w-4 sm:h-4 mr-2 flex-shrink-0" />
        {noDataMessage || 'Insufficient data for price projections'}
      </div>
    );
  }

  const data = [
    { name: 'Now', mid: currentPrice, low: currentPrice, high: currentPrice },
  ];
  if (forecast5d) {
    data.push({ name: '5d', mid: forecast5d.mid, low: forecast5d.low, high: forecast5d.high });
  }
  if (forecast20d) {
    data.push({ name: '20d', mid: forecast20d.mid, low: forecast20d.low, high: forecast20d.high });
  }

  const all
isCMEXOpen function · typescript · L452-L461 (10 LOC)
components/ForecastDashboard.tsx
function isCMEXOpen(): boolean {
  const now = new Date();
  const et = new Date(now.toLocaleString('en-US', { timeZone: 'America/New_York' }));
  const day = et.getDay();
  const hour = et.getHours();
  if (day === 6) return false;
  if (day === 0) return hour >= 18;
  if (day === 5) return hour < 17;
  return hour !== 17;
}
ForumContent function · typescript · L34-L40 (7 LOC)
components/ForumContent.tsx
export default function ForumContent({ categories }: { categories: ForumCategory[] }) {
  return (
    <ForumThemeProvider>
      <ForumInner categories={categories.length > 0 ? categories : STATIC_CATEGORIES} />
    </ForumThemeProvider>
  );
}
ForumInner function · typescript · L42-L285 (244 LOC)
components/ForumContent.tsx
function ForumInner({ categories }: { categories: ForumCategory[] }) {
  const { theme } = useForumTheme();
  const { data: session, status } = useSession();
  const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
  const [newThreadTitle, setNewThreadTitle] = useState('');
  const [newThreadBody, setNewThreadBody] = useState('');
  const [posting, setPosting] = useState(false);
  const [postResult, setPostResult] = useState<string | null>(null);

  const isSignedIn = status === 'authenticated' && !!session?.user;
  const totalThreads = categories.reduce((sum, c) => sum + c.thread_count, 0);
  const totalPosts = categories.reduce((sum, c) => sum + c.post_count, 0);

  async function handlePost() {
    if (!newThreadTitle.trim() || !newThreadBody.trim() || !selectedCategory) return;
    setPosting(true);
    setPostResult(null);

    try {
      const res = await fetch('/api/forum/thread', {
        method: 'POST',
        headers: { 'Content-Type': 'application/
handlePost function · typescript · L55-L85 (31 LOC)
components/ForumContent.tsx
  async function handlePost() {
    if (!newThreadTitle.trim() || !newThreadBody.trim() || !selectedCategory) return;
    setPosting(true);
    setPostResult(null);

    try {
      const res = await fetch('/api/forum/thread', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          categorySlug: selectedCategory,
          title: newThreadTitle.trim(),
          body: newThreadBody.trim(),
        }),
      });

      if (res.ok) {
        setPostResult('Thread created successfully!');
        setNewThreadTitle('');
        setNewThreadBody('');
        setSelectedCategory(null);
      } else {
        const data = await res.json();
        setPostResult(data.error || 'Failed to create thread.');
      }
    } catch {
      setPostResult('Something went wrong. Try again.');
    } finally {
      setPosting(false);
    }
  }
useForumTheme function · typescript · L225-L227 (3 LOC)
components/ForumThemeSelector.tsx
export function useForumTheme() {
  return useContext(ForumThemeContext);
}
ForumThemeProvider function · typescript · L229-L255 (27 LOC)
components/ForumThemeSelector.tsx
export function ForumThemeProvider({ children }: { children: React.ReactNode }) {
  const [themeId, setThemeId] = useState<ForumTheme>('gold-standard');
  const [mounted, setMounted] = useState(false);

  useEffect(() => {
    const stored = localStorage.getItem(STORAGE_KEY) as ForumTheme | null;
    if (stored && FORUM_THEMES.find((t) => t.id === stored)) {
      setThemeId(stored);
    }
    setMounted(true);
  }, []);

  function handleSetTheme(id: ForumTheme) {
    setThemeId(id);
    localStorage.setItem(STORAGE_KEY, id);
  }

  const theme = FORUM_THEMES.find((t) => t.id === themeId) || FORUM_THEMES[0];

  if (!mounted) return null;

  return (
    <ForumThemeContext.Provider value={{ theme, themeId, setThemeId: handleSetTheme }}>
      {children}
    </ForumThemeContext.Provider>
  );
}
handleSetTheme function · typescript · L241-L244 (4 LOC)
components/ForumThemeSelector.tsx
  function handleSetTheme(id: ForumTheme) {
    setThemeId(id);
    localStorage.setItem(STORAGE_KEY, id);
  }
Repobility (the analyzer behind this table) · https://repobility.com
ForumThemeSelector function · typescript · L257-L280 (24 LOC)
components/ForumThemeSelector.tsx
export default function ForumThemeSelector() {
  const { themeId, setThemeId } = useForumTheme();

  return (
    <div className="flex items-center gap-2">
      <Palette className="w-4 h-4 text-muted-foreground" />
      <Select value={themeId} onValueChange={(v) => setThemeId(v as ForumTheme)}>
        <SelectTrigger className="h-8 text-xs font-medium min-w-[160px]">
          <SelectValue placeholder="Choose theme" />
        </SelectTrigger>
        <SelectContent>
          {FORUM_THEMES.map((t) => (
            <SelectItem key={t.id} value={t.id}>
              <span className="flex items-center gap-2">
                <span>{t.preview}</span>
                <span>{t.name}</span>
              </span>
            </SelectItem>
          ))}
        </SelectContent>
      </Select>
    </div>
  );
}
getETNow function · typescript · L17-L19 (3 LOC)
components/FuturesTicker.tsx
function getETNow() {
  return new Date(new Date().toLocaleString('en-US', { timeZone: 'America/New_York' }));
}
isCMEXOpen function · typescript · L24-L32 (9 LOC)
components/FuturesTicker.tsx
function isCMEXOpen(): boolean {
  const et = getETNow();
  const day = et.getDay();
  const hour = et.getHours();
  if (day === 6) return false;
  if (day === 0) return hour >= 18;
  if (day === 5) return hour < 17;
  return hour !== 17;
}
isSpotOpen function · typescript · L37-L43 (7 LOC)
components/FuturesTicker.tsx
function isSpotOpen(): boolean {
  const et = getETNow();
  const day = et.getDay();
  if (day === 0 || day === 6) return false;
  const minutes = et.getHours() * 60 + et.getMinutes();
  return minutes >= 570 && minutes < 960; // 9:30=570, 16:00=960
}
getPriceMode function · typescript · L45-L47 (3 LOC)
components/FuturesTicker.tsx
function getPriceMode(): PriceMode {
  return isSpotOpen() ? 'spot' : 'futures';
}
formatPrice function · typescript · L49-L51 (3 LOC)
components/FuturesTicker.tsx
function formatPrice(price: number): string {
  return price.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}
FuturesTicker function · typescript · L53-L203 (151 LOC)
components/FuturesTicker.tsx
export default function FuturesTicker() {
  const [prices, setPrices] = useState<Record<string, TickerPrice | null>>({});
  const [loading, setLoading] = useState(true);
  const [flashMap, setFlashMap] = useState<Record<string, 'up' | 'down' | null>>({});
  const [mode, setMode] = useState<PriceMode>(getPriceMode);
  const [cmexOpen, setCmexOpen] = useState(isCMEXOpen);
  const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);

  const fetchPrices = useCallback(async (forMode?: PriceMode) => {
    const activeMode = forMode ?? getPriceMode();
    const endpoint = activeMode === 'spot' ? '/api/prices' : '/api/futures';
    try {
      const res = await fetch(endpoint);
      if (!res.ok) return;
      const data = await res.json();
      if (!data.success) return;

      const raw: Record<string, TickerPrice | null> = data.prices ?? data.futures ?? {};

      setPrices(prev => {
        const flashes: Record<string, 'up' | 'down' | null> = {};
        for (const metal o
formatTonnes function · typescript · L78-L82 (5 LOC)
components/GlobalInventory.tsx
function formatTonnes(v: number): string {
  if (v >= 1000) return `${(v / 1000).toFixed(1)}K`;
  if (v >= 1) return v.toFixed(1);
  return v.toFixed(2);
}
Repobility · severity-and-effort ranking · https://repobility.com
formatOz function · typescript · L84-L89 (6 LOC)
components/GlobalInventory.tsx
function formatOz(v: number): string {
  if (v >= 1_000_000_000) return `${(v / 1_000_000_000).toFixed(2)}B`;
  if (v >= 1_000_000) return `${(v / 1_000_000).toFixed(1)}M`;
  if (v >= 1_000) return `${(v / 1_000).toFixed(0)}K`;
  return v.toFixed(0);
}
formatDate function · typescript · L91-L94 (4 LOC)
components/GlobalInventory.tsx
function formatDate(dateStr: string): string {
  const d = new Date(dateStr + 'T12:00:00');
  return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
}
GlobalInventory function · typescript · L104-L353 (250 LOC)
components/GlobalInventory.tsx
export default function GlobalInventory({ comexData }: { comexData?: Record<string, { totals: { registered: number; eligible: number; total: number } }> }) {
  const { theme } = useTheme();
  const isDark = theme === 'dark';
  const [selectedMetal, setSelectedMetal] = useState<MetalKey>('gold');
  const [showInfo, setShowInfo] = useState(false);
  const [liveComex, setLiveComex] = useState<ComexTotals | null>(null);
  const chartRef = useRef<HTMLDivElement>(null);

  // Use passed-in COMEX data or fetch from API
  useEffect(() => {
    if (comexData) {
      setLiveComex({
        gold: comexData.Gold?.totals?.total || 0,
        silver: comexData.Silver?.totals?.total || 0,
        copper: comexData.Copper?.totals?.total || 0,
        platinum: comexData.Platinum?.totals?.total || 0,
        palladium: comexData.Palladium?.totals?.total || 0,
      });
    }
  }, [comexData]);

  const comexTonnes = {
    gold: liveComex ? liveComex.gold / OZ_PER_TONNE : 0,
    silver: liveComex ? liv
triggerGoogleTranslate function · typescript · L35-L44 (10 LOC)
components/LanguageSelector.tsx
function triggerGoogleTranslate(langCode: string) {
  const select = document.querySelector<HTMLSelectElement>(
    '#google_translate_element select'
  );
  if (!select) return false;

  select.value = langCode;
  select.dispatchEvent(new Event('change', { bubbles: true }));
  return true;
}
LanguageSelector function · typescript · L46-L190 (145 LOC)
components/LanguageSelector.tsx
export default function LanguageSelector() {
  const [open, setOpen] = useState(false);
  const [currentLang, setCurrentLang] = useState('');
  const [ready, setReady] = useState(false);
  const dropdownRef = useRef<HTMLDivElement>(null);

  const checkReady = useCallback(() => {
    const select = document.querySelector('#google_translate_element select');
    if (select) {
      setReady(true);
      return true;
    }
    return false;
  }, []);

  useEffect(() => {
    if (document.getElementById('google-translate-script')) {
      const interval = setInterval(() => {
        if (checkReady()) clearInterval(interval);
      }, 500);
      return () => clearInterval(interval);
    }

    window.googleTranslateElementInit = () => {
      new window.google!.translate!.TranslateElement!(
        { pageLanguage: 'en', autoDisplay: false },
        'google_translate_element'
      );
      const interval = setInterval(() => {
        if (checkReady()) clearInterval(interval);
      }, 50
handleClickOutside function · typescript · L88-L92 (5 LOC)
components/LanguageSelector.tsx
    function handleClickOutside(e: MouseEvent) {
      if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
        setOpen(false);
      }
    }
selectLanguage function · typescript · L97-L138 (42 LOC)
components/LanguageSelector.tsx
  function selectLanguage(langCode: string) {
    setCurrentLang(langCode);
    setOpen(false);

    if (langCode === '') {
      // Restore to English — use Google Translate's own mechanism to properly
      // revert internal state and cookies (including ones on .google.com we can't clear)
      const select = document.querySelector<HTMLSelectElement>(
        '#google_translate_element select'
      );
      if (select) {
        select.value = 'en';
        select.dispatchEvent(new Event('change', { bubbles: true }));
      }

      // Also clear googtrans cookies on all possible domain variations
      const hostname = window.location.hostname;
      const expiry = 'expires=Thu, 01 Jan 1970 00:00:00 UTC';
      document.cookie = `googtrans=; ${expiry}; path=/`;
      document.cookie = `googtrans=; ${expiry}; path=/; domain=${hostname}`;
      document.cookie = `googtrans=; ${expiry}; path=/; domain=.${hostname}`;
      const parts = hostname.split('.');
      if (parts.length > 2)
formatPrice function · typescript · L29-L32 (4 LOC)
components/MarketComparison.tsx
function formatPrice(price: number): string {
  if (price >= 10000) return price.toLocaleString('en-US', { minimumFractionDigits: 0, maximumFractionDigits: 0 });
  return price.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}
Provenance: Repobility (https://repobility.com) — every score reproducible from /scan/
formatDailyChange function · typescript · L34-L37 (4 LOC)
components/MarketComparison.tsx
function formatDailyChange(v: number): string {
  if (Math.abs(v) >= 100) return v.toFixed(0);
  return v.toFixed(2);
}
sign function · typescript · L39-L41 (3 LOC)
components/MarketComparison.tsx
function sign(v: number): string {
  return v >= 0 ? '+' : '';
}
ytdColor function · typescript · L43-L47 (5 LOC)
components/MarketComparison.tsx
function ytdColor(v: number): string {
  return v >= 0
    ? 'text-emerald-600 dark:text-emerald-400'
    : 'text-red-600 dark:text-red-400';
}
ytdBg function · typescript · L49-L53 (5 LOC)
components/MarketComparison.tsx
function ytdBg(v: number): string {
  return v >= 0
    ? 'bg-emerald-500/10'
    : 'bg-red-500/10';
}
barWidth function · typescript · L55-L58 (4 LOC)
components/MarketComparison.tsx
function barWidth(v: number, max: number): string {
  if (max <= 0) return '0%';
  return `${Math.min(Math.abs(v) / max * 100, 100)}%`;
}
MarketComparison function · typescript · L60-L269 (210 LOC)
components/MarketComparison.tsx
export default function MarketComparison() {
  const [data, setData] = useState<MarketData | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(false);
  const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);

  const fetchData = useCallback(async () => {
    try {
      const res = await fetch('/api/market-comparison');
      if (!res.ok) throw new Error('fetch failed');
      const json = await res.json();
      if (!json.success) throw new Error('api error');
      setData(json);
      setError(false);
      setLoading(false);
    } catch {
      setError(true);
      setLoading(false);
    }
  }, []);

  useEffect(() => {
    fetchData();
    intervalRef.current = setInterval(fetchData, 3_600_000);
    return () => { if (intervalRef.current) clearInterval(intervalRef.current); };
  }, [fetchData]);

  if (loading) {
    return (
      <div className="w-full">
        <div className="h-48 bg-slate-100 dark:bg-slate-900
MetalCard function · typescript · L13-L106 (94 LOC)
components/MetalCard.tsx
export default function MetalCard({ data, config }: MetalCardProps) {
  const { totals } = data;
  const ratio = calculateCoverageRatio(totals.registered, config.monthlyDemand);
  const { status } = getSupplyStatus(ratio);
  
  const registeredPercent = totals.total > 0 ? (totals.registered / totals.total) * 100 : 0;
  const totalValue = totals.total * config.pricePerUnit;

  const statusVariant = {
    ADEQUATE: 'default' as const,
    WATCH: 'secondary' as const,
    STRESS: 'destructive' as const,
  };

  const statusTextColors = {
    ADEQUATE: 'text-emerald-600 dark:text-emerald-400',
    WATCH: 'text-amber-600 dark:text-amber-400',
    STRESS: 'text-red-600 dark:text-red-400',
  };

  return (
    <Card className="hover:shadow-lg transition-shadow">
      <CardHeader className="pb-4">
        <div className="flex items-start justify-between">
          <div className="space-y-1">
            <h3 className="text-lg font-semibold leading-none tracking-tight">{config.name}</h3>
    
formatCompact function · typescript · L56-L62 (7 LOC)
components/MetalCompare.tsx
function formatCompact(v: number): string {
  if (v >= 1_000_000_000) return `${(v / 1_000_000_000).toFixed(1)}B`;
  if (v >= 1_000_000) return `${(v / 1_000_000).toFixed(1)}M`;
  if (v >= 1_000) return `${(v / 1_000).toFixed(1)}K`;
  if (v >= 10) return v.toFixed(0);
  return v.toFixed(2);
}
Repobility · MCP-ready · https://repobility.com
MetalCompare function · typescript · L64-L383 (320 LOC)
components/MetalCompare.tsx
export default function MetalCompare() {
  const { theme } = useTheme();
  const isDark = theme === 'dark';
  const chartRef = useRef<HTMLDivElement>(null);

  const [metalA, setMetalA] = useState('Gold');
  const [metalB, setMetalB] = useState('Silver');
  const [metric, setMetric] = useState<Metric>('inventory');
  const [days, setDays] = useState(90);
  const [normalize, setNormalize] = useState(false);
  const [loading, setLoading] = useState(false);

  const [inventoryA, setInventoryA] = useState<InventoryPoint[]>([]);
  const [inventoryB, setInventoryB] = useState<InventoryPoint[]>([]);
  const [bulletinA, setBulletinA] = useState<BulletinPoint[]>([]);
  const [bulletinB, setBulletinB] = useState<BulletinPoint[]>([]);

  const colorA = METALS.find(m => m.key === metalA)?.color || '#fbbf24';
  const colorB = METALS.find(m => m.key === metalB)?.color || '#94a3b8';
  const symbolA = METALS.find(m => m.key === metalA)?.symbol || 'GC';
  const symbolB = METALS.find(m => m.key === meta
fetchData function · typescript · L90-L118 (29 LOC)
components/MetalCompare.tsx
    async function fetchData() {
      try {
        if (metric === 'inventory') {
          const [resA, resB] = await Promise.all([
            fetch(`/api/metals/${metalA}/history?days=${days}`),
            fetch(`/api/metals/${metalB}/history?days=${days}`),
          ]);
          const [dataA, dataB] = await Promise.all([resA.json(), resB.json()]);
          if (!cancelled) {
            setInventoryA(dataA.success ? dataA.data : []);
            setInventoryB(dataB.success ? dataB.data : []);
          }
        } else {
          const [resA, resB] = await Promise.all([
            fetch(`/api/bulletin/history?symbol=${symbolA}&days=${days}`),
            fetch(`/api/bulletin/history?symbol=${symbolB}&days=${days}`),
          ]);
          const [dataA, dataB] = await Promise.all([resA.json(), resB.json()]);
          if (!cancelled) {
            setBulletinA(dataA.history || []);
            setBulletinB(dataB.history || []);
          }
        }
      } catch {
        //
getVisitData function · typescript · L15-L23 (9 LOC)
components/MonetAgAdLoader.tsx
function getVisitData(): VisitData | null {
  try {
    const raw = localStorage.getItem(STORAGE_KEY);
    if (!raw) return null;
    return JSON.parse(raw) as VisitData;
  } catch {
    return null;
  }
}
‹ prevpage 4 / 13next ›