From e28867e1ec8bcc4c50b4567d7e708e6bb6ca2483 Mon Sep 17 00:00:00 2001 From: Agustin Kassis Date: Wed, 22 Jul 2026 19:45:33 -0300 Subject: [PATCH] feat(hackathons): show registered project count per hackathon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each hackathon on /hackathons now surfaces how many projects are registered for it — curated JSON + community Nostr submissions, deduped via the existing mergeWithSubmissions() so the number matches the detail page's list one click away. - Timeline: count chip on the active StageCard (with tooltip), a count line on the prev/next peek cards, and the count folded into each rail node's aria-label. - Detail page: the "Proyectos" tab badge was curated-only and would have contradicted the new list number; it now uses the same merged count. - Extract the shared Nostr->submission mapper into lib/hackathons.ts (toHackathonSubmission) and drop the detail page's local copy. - Empty relay snapshots read as "unknown" rather than asserting "Sin proyectos", since this page has no client-side rescan to correct a bad read; Spanish tooltip agrees with the count (1 proyecto anotado). - Tag the page cache with the nostr submission tags so it revalidates with the shared relay snapshot. fix(css): scope the default border-color rule to @layer base The unlayered `* { border-color: var(--border) }` outranked Tailwind 4's @layer utilities, silently overriding every accent border in the app (border-bitcoin/*, border-success/*, border-border-strong, hover/focus variants). Moving it into @layer base restores those while still superseding Preflight's default. Verified no regressions: elements without an explicit border-color utility keep the flat default. Co-Authored-By: Claude Opus 4.8 --- app/globals.css | 12 ++++- app/hackathons/HackathonTimeline.tsx | 67 +++++++++++++++++++++++++++- app/hackathons/[id]/page.tsx | 27 +++-------- app/hackathons/page.tsx | 42 ++++++++++++++++- lib/hackathons.ts | 26 +++++++++++ 5 files changed, 148 insertions(+), 26 deletions(-) diff --git a/app/globals.css b/app/globals.css index 043a83f..8eabb85 100644 --- a/app/globals.css +++ b/app/globals.css @@ -40,8 +40,16 @@ --font-mono: var(--font-jetbrains-mono); } -* { - border-color: var(--border); +/* Default border color for every element. MUST stay inside `@layer base`: + Tailwind 4 emits border-color utilities into `@layer utilities`, and + unlayered CSS outranks every layer — so an unlayered `*` rule here silently + overrode `border-bitcoin/30`, `border-success/40`, `border-border-strong` + and every other accent border in the app. Inside `base` it only supersedes + preflight's `border: 0 solid` (currentColor) default, and any utility wins. */ +@layer base { + * { + border-color: var(--border); + } } html { diff --git a/app/hackathons/HackathonTimeline.tsx b/app/hackathons/HackathonTimeline.tsx index 03b93e9..66f2036 100644 --- a/app/hackathons/HackathonTimeline.tsx +++ b/app/hackathons/HackathonTimeline.tsx @@ -10,6 +10,7 @@ import { ChevronRight, Clock, Flame, + FolderGit2, Gauge, Layers, Radio, @@ -40,8 +41,32 @@ export type TimelineHackathon = { inscriptionOpen: boolean; countdown: string | null; sponsors: { name: string; logo: string }[]; + /** Projects registered for this hackathon — curated + community submissions, + * deduped the same way the detail page's list is. `null` when the relay + * snapshot came back empty, i.e. the count is unknown rather than zero. */ + projectCount: number | null; }; +/** "Sin proyectos" / "1 proyecto" / "N proyectos", or `null` when unknown — + * callers render nothing in that case rather than claiming a number. */ +function projectCountLabel(count: number | null): string | null { + if (count === null) return null; + if (count === 0) return "Sin proyectos"; + return `${count} ${count === 1 ? "proyecto" : "proyectos"}`; +} + +/** Tooltip body for the project-count chip. Spanish agreement follows the + * count, so the singular case reads "1 proyecto anotado … para verlo". */ +function projectCountTooltip(h: TimelineHackathon): string { + const n = h.projectCount ?? 0; + if (n > 0) { + return `${projectCountLabel(n)} ${n === 1 ? "anotado" : "anotados"} en este hackatón — abrí el detalle para ${n === 1 ? "verlo" : "verlos"}.`; + } + return h.status === "closed" + ? "No se anotó ningún proyecto en esta edición." + : "Todavía no se anotó ningún proyecto. Podés ser el primero."; +} + const STATUS_META: Record< TimelineHackathon["status"], { label: string; dot: string; text: string; ring: string } @@ -162,13 +187,18 @@ export default function HackathonTimeline({ {items.map((h, i) => { const meta = STATUS_META[h.status]; const selected = i === index; + const countLabel = projectCountLabel(h.projectCount); return (