SPB Git

spb/worthdoing Public

Autonomous investigation agent that discovers, challenges, and ranks things genuinely worth doing — Claude + Firecrawl, Next.js 16, PostgreSQL

TypeScript 91.5% SQL 5.8% CSS 2.2%
2.4 KB · 81 lines tsx
Raw Blame History
1/**2 * WorthDoing.ai3 * Author: Simon-Pierre Boucher4 * Contact: contact@spboucher.ai5 * File: src/components/wd/OpportunityCard.tsx6 * Description: Opportunity card — Worth Score and evidence confidence shown as distinct figures, never blended.7 */8import Link from "next/link";9import { cn } from "@/lib/utils";1011export function WorthScore({12  score,13  confidence,14  size = "md",15}: {16  score: number | null;17  confidence: number | null;18  size?: "md" | "lg";19}) {20  const conf = confidence ?? 0;21  const confTone = conf >= 0.7 ? "text-verdict" : conf >= 0.45 ? "text-signal" : "text-rust";22  return (23    <div className="flex flex-col items-end">24      <span25        className={cn(26          "font-heading font-semibold leading-none text-ink tabular-nums",27          size === "lg" ? "text-5xl" : "text-3xl",28        )}29      >30        {score != null ? Math.round(score) : "—"}31      </span>32      <span className={cn("mt-1 font-mono text-[10px] uppercase tracking-wider", confTone)}>33        {confidence != null ? `${Math.round(conf * 100)}% evidence` : "unscored"}34      </span>35    </div>36  );37}3839export function OpportunityCard({40  id,41  title,42  summary,43  worthScore,44  evidenceConfidence,45  objective,46  hasReport = true,47}: {48  id: string;49  title: string;50  summary: string;51  worthScore: number | null;52  evidenceConfidence: number | null;53  objective?: string;54  hasReport?: boolean;55}) {56  return (57    <Link58      href={`/opportunity/${id}`}59      className="wd-enter group block rounded-xl border border-line bg-card p-5 transition-all hover:-translate-y-0.5 hover:border-verdict/40 hover:shadow-[0_8px_30px_-12px_rgba(22,107,73,0.25)]"60    >61      <div className="flex items-start justify-between gap-4">62        <div className="min-w-0">63          {objective && (64            <p className="mb-1 truncate font-mono text-[10px] uppercase tracking-wider text-ink-soft">65              {objective}66            </p>67          )}68          <h3 className="font-heading text-lg font-semibold leading-tight text-ink group-hover:text-verdict">69            {title}70          </h3>71        </div>72        <WorthScore score={worthScore} confidence={evidenceConfidence} />73      </div>74      <p className="mt-3 line-clamp-3 text-sm leading-relaxed text-ink-soft">{summary}</p>75      <p className="mt-3 font-mono text-[10px] uppercase tracking-wider text-ink-soft/70">76        {hasReport ? "read the full report →" : "report pending…"}77      </p>78    </Link>79  );80}81