spb/earth-now Public License
earth-now.co — real-time planetary dashboard: live world metrics modeled, not streamed.
TypeScript 93%
Shell 2.3%
SQL 1.4%
JavaScript 1.3%
Dockerfile 1.2%
CSS 0.8%
1/**2 * earth-now.co3 * Author: Simon-Pierre Boucher4 * Contact: contact@spboucher.ai5 * File: apps/web/app/methodology/page.tsx6 * Purpose: Public methodology page (product commitment) — per-metric source, license, cadence, model family/version, last observation, uncertainty7 */89import Link from "next/link";10import { formatValue } from "@earth-now/counter";11import { fetchMetrics, type MetricSummary } from "@/lib/api";12import {13 DEFAULT_LOCALE,14 formatDateUtc,15 isLocale,16 translate,17 type MessageKey,18} from "@/lib/messages";1920export const dynamic = "force-dynamic";2122const PERCENT_HINTS = { decimals: 0, unit: "%" } as const;2324function levelLabel(metric: MetricSummary): string {25 const level = typeof metric.level === "number" ? `L${metric.level}` : metric.level.toUpperCase();26 const version = metric.modelVersion !== undefined ? ` · ${metric.modelVersion}` : "";27 return `${level} · ${metric.model}${version}`;28}2930export default async function MethodologyPage({31 searchParams,32}: {33 searchParams?: { lang?: string };34}) {35 const langParam = searchParams?.lang;36 const locale = isLocale(langParam) ? langParam : DEFAULT_LOCALE;37 const t = (key: MessageKey) => translate(locale, key);3839 let metrics: MetricSummary[] = [];40 let offline = false;41 try {42 metrics = await fetchMetrics();43 } catch {44 offline = true;45 }4647 return (48 <div className="mx-auto max-w-6xl px-4 py-10">49 <Link href="/" className="text-xs text-accent hover:opacity-80">50 {t("methodology.back")}51 </Link>52 <h1 className="mt-3 text-2xl font-semibold tracking-tight text-ink">53 {t("methodology.title")}54 </h1>55 <p className="mt-3 max-w-3xl text-sm leading-relaxed text-ink2">56 {t("methodology.intro")}57 </p>5859 {offline ? (60 <p className="mt-8 text-sm text-ink2">{t("offline.body")}</p>61 ) : (62 <div className="mt-8 overflow-x-auto">63 <table className="w-full min-w-[900px] border-collapse text-left text-xs">64 <thead>65 <tr className="border-b border-line text-ink2">66 <th className="py-2 pr-3 font-medium">{t("methodology.metric")}</th>67 <th className="py-2 pr-3 font-medium">{t("methodology.domain")}</th>68 <th className="py-2 pr-3 font-medium">{t("methodology.kind")}</th>69 <th className="py-2 pr-3 font-medium">{t("methodology.model")}</th>70 <th className="py-2 pr-3 font-medium">{t("methodology.source")}</th>71 <th className="py-2 pr-3 font-medium">{t("methodology.license")}</th>72 <th className="py-2 pr-3 font-medium">{t("methodology.cadence")}</th>73 <th className="py-2 pr-3 font-medium">{t("methodology.observed")}</th>74 <th className="py-2 pr-3 font-medium">{t("methodology.uncertainty")}</th>75 <th className="py-2 font-medium">{t("methodology.note")}</th>76 </tr>77 </thead>78 <tbody>79 {metrics.map((metric) => {80 const source = metric.sources[0];81 return (82 <tr key={metric.id} className="border-b border-line align-top text-ink2">83 <td className="py-2 pr-3 font-medium text-ink">84 {metric.name[locale]}85 {metric.stale && (86 <span className="ml-2 rounded border border-warn/50 px-1.5 py-0.5 text-[10px] uppercase text-ink2">87 {t("chip.stale")}88 </span>89 )}90 </td>91 <td className="py-2 pr-3">92 {t(`domain.${metric.domain}` as MessageKey)}93 </td>94 <td className="py-2 pr-3">{t(`kind.${metric.kind}` as MessageKey)}</td>95 <td className="py-2 pr-3 tabular-nums">{levelLabel(metric)}</td>96 <td className="py-2 pr-3">97 {source !== undefined ? (98 <a99 href={source.url}100 target="_blank"101 rel="noreferrer"102 className="text-accent underline decoration-accent/40 hover:opacity-80"103 >104 {source.name}105 </a>106 ) : (107 "—"108 )}109 </td>110 <td className="py-2 pr-3">{source?.license ?? "—"}</td>111 <td className="py-2 pr-3">{source?.cadence ?? "—"}</td>112 <td className="py-2 pr-3">113 {metric.observedAt !== undefined114 ? formatDateUtc(metric.observedAt, locale)115 : "—"}116 </td>117 <td className="py-2 pr-3 tabular-nums">118 {metric.uncertaintyFraction !== undefined119 ? `± ${formatValue(metric.uncertaintyFraction * 100, PERCENT_HINTS, {120 locale,121 })} % (${t("chip.estimate")})`122 : "—"}123 </td>124 <td className="py-2 text-ink2">125 {metric.editorialNote !== undefined ? metric.editorialNote[locale] : ""}126 </td>127 </tr>128 );129 })}130 </tbody>131 </table>132 </div>133 )}134 </div>135 );136}137