Skip to content

feat(hackathons): reparto de premios por escasez, regla 1-premio-por-persona y fix del marcador de timeline#65

Merged
agustinkassis merged 3 commits into
mainfrom
claude/hackathons-prize-distribution-4b743c
Jul 11, 2026
Merged

feat(hackathons): reparto de premios por escasez, regla 1-premio-por-persona y fix del marcador de timeline#65
agustinkassis merged 3 commits into
mainfrom
claude/hackathons-prize-distribution-4b743c

Conversation

@agustinkassis

@agustinkassis agustinkassis commented Jul 10, 2026

Copy link
Copy Markdown
Member

Qué cambia

Tres cambios sobre los hackatones, todos en /hackathons y /hackathons/[id].

1. Reparto de premios cuando participan menos proyectos que premios

prizedProjects() en lib/hackathons.ts ahora entrega los premios más chicos primero cuando hay menos proyectos rankeados que slots de premio. Con 3 proyectos participando reparte 100k/60k/40k (los últimos 3 slots) en vez de tomar 400k/250k/150k y dejar 200k sats sin asignar.

  • Anclado desde el final: slotIdx = Math.max(0, slots.length - ranked.length).
  • El caso normal (6+ proyectos) y el reparto de empates no cambian — verificado con simulación aritmética (3, 6, 8 proyectos y empate en 1°).

2. Regla "1 premio por persona"

Regla nueva en data/hackathons/hackathons.json (program.rules, fuente única compartida): "Podés sumar varios proyectos, pero solo uno por persona puede ganar premio." Aparece automáticamente en la tarjeta "Participación · Reglas básicas" de todas las páginas de hackatón. Verificado en ai-agents y foundations.

⚠️ Solo se agregó el texto. La lógica de prizedProjects() todavía no deduplica premios por persona; si dos proyectos de un mismo usuario quedan en el top 6, hoy ambos cobrarían. Se puede implementar como seguimiento si se quiere hacerlo cumplir en código.

3. UI: letra chica + modal explicativo de premios

Nuevo componente cliente app/hackathons/PrizeRulesNote.tsx: letra chica bajo la grilla "Estructura de premios" con un enlace "Ver cómo funciona" que abre un modal (mismo patrón visual que LoginModal: framer-motion, useScrollLock, cierre con Escape/backdrop) explicando la regla con el ejemplo de 3 proyectos.

4. Fix: marcador de "hoy" en el timeline

El pip de "hoy" quedaba a la izquierda del nodo activo (parecía que el hackatón EN CURSO aún no había empezado). Los puntos de los nodos están centrados en (i + 0.5)/n, pero el marcador usaba ((activeIdx + f)/n) — el borde izquierdo de la celda. Se agregó + 0.5 para anclarlo al centro del nodo activo; ahora viaja desde el punto del hackatón activo hacia el siguiente a medida que transcurre (f: 0→1). La rama "entre hackatones" no cambia.

Verificación

  • Reparto: simulado en Node para 3/6/8 proyectos y empate en 1° — coincide con lo esperado.
  • Regla y modal: confirmados en el DOM/preview (ai-agents, foundations).
  • Marcador de timeline: confirmado visualmente en el preview — la bolita queda entre JUL y AGO, justo pasando el punto verde de JUL.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added an interactive Spanish prize-rules note with a “Ver condiciones” modal (with overlay dismissal, keyboard Escape support, and background scroll lock).
    • Updated the hackathon “Premios” section to show the prize-rules note when there are no awards yet.
    • Added/clarified the rule that multiple projects may be submitted, but only one project per person can win.
  • Bug Fixes

    • Improved prize-slot allocation when fewer qualifying projects exist than available prize slots.
    • Adjusted the hackathon timeline “today” marker to align with the active milestone.

…persona y fix del marcador de timeline

- prizedProjects: cuando participan menos proyectos que slots de premio, ahora
  entrega los premios más chicos primero (3 proyectos → 100k/60k/40k) en vez de
  tomar los slots grandes y dejar parte del pozo sin asignar. Anclado desde el
  final con `slots.length - ranked.length`; el caso normal (6+ proyectos) y los
  empates no cambian.
- /hackathons: letra chica bajo la grilla de premios + modal explicativo
  (PrizeRulesNote) con el ejemplo de 3 proyectos.
- Regla nueva compartida por todas las páginas de hackatón: "Podés sumar varios
  proyectos, pero solo uno por persona puede ganar premio."
- Timeline: el marcador de "hoy" queda anclado al centro del nodo activo y viaja
  hacia el siguiente a medida que transcurre el hackatón, en vez de arrancar en
  el borde izquierdo de su celda (quedaba a la izquierda del punto activo).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
lacrypta-dev Ready Ready Preview, Comment Jul 11, 2026 2:51am

Request Review

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0e82e20d-4f5d-4b98-a5d9-dc28566e6c5c

📥 Commits

Reviewing files that changed from the base of the PR and between 8b42eaa and bbe8e14.

📒 Files selected for processing (2)
  • app/hackathons/PrizeRulesNote.tsx
  • app/hackathons/[id]/page.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/hackathons/PrizeRulesNote.tsx

📝 Walkthrough

Walkthrough

The changes adjust prize-slot allocation for small participant counts, add Spanish prize-rule disclosures and an interactive explanation modal, update hackathon page composition, and reposition the active timeline marker to the center of its node.

Changes

Hackathon experience updates

Layer / File(s) Summary
Prize allocation and rules
lib/hackathons.ts, data/hackathons/hackathons.json
Prize distribution starts from the smallest applicable slots when fewer projects are ranked than prize slots, and the program rules clarify the one-winning-project limit.
Prize rules modal
app/hackathons/PrizeRulesNote.tsx, app/hackathons/page.tsx
Adds an animated Spanish prize explanation modal with formatted examples, scroll locking, Escape/backdrop/close-button dismissal, and rendering in the prizes section.
Hackathon page prize composition
app/hackathons/[id]/page.tsx
Removes the inscription-button hero branch and renders PrizeRulesNote when no award results are available.
Active timeline marker
app/hackathons/page.tsx
Offsets the active todayPct calculation by half a slice and updates its explanatory comment.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Visitor
  participant PrizeRulesNote
  participant formatSats
  Visitor->>PrizeRulesNote: Click “Ver condiciones”
  PrizeRulesNote->>formatSats: Format example prize amounts
  formatSats-->>PrizeRulesNote: Return formatted amounts
  PrizeRulesNote-->>Visitor: Display animated prize rules modal
  Visitor->>PrizeRulesNote: Press Escape or click close
  PrizeRulesNote-->>Visitor: Close modal
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed El título resume bien los cambios principales: reparto de premios, regla de un premio por persona y ajuste del marcador de timeline.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/hackathons-prize-distribution-4b743c

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
data/hackathons/hackathons.json (1)

24-24: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Rule added but not yet enforced in prizedProjects().

The new rule states each person can only win with one project, but prizedProjects() in lib/hackathons.ts still allocates prizes per-project without deduplicating by person. The PR objectives acknowledge this gap. Would you like me to help implement the deduplication logic — e.g., tracking awarded pubkeys and skipping subsequent projects from the same person?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@data/hackathons/hackathons.json` at line 24, Update prizedProjects() in
lib/hackathons.ts to enforce the one-prize-per-person rule: track each awarded
participant’s pubkey while iterating projects, skip subsequent projects
belonging to an already-awarded person, and preserve the existing prize
allocation behavior for eligible projects.
app/hackathons/PrizeRulesNote.tsx (1)

47-128: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add ARIA dialog semantics and focus management for accessibility.

The modal lacks role="dialog", aria-modal="true", and aria-labelledby on the dialog container, and doesn't move focus into the modal on open or trap Tab within it. Screen readers may not announce the modal properly, and keyboard users can Tab to elements behind the overlay.

♿ Suggested accessibility improvements
 export default function PrizeRulesNote() {
   const [open, setOpen] = useState(false);
+  const modalRef = useRef<HTMLDivElement>(null);
+  const titleId = "prize-rules-title";
   useScrollLock(open);
 
   useEffect(() => {
     if (!open) return;
+    // Move focus into the modal on open
+    modalRef.current?.focus();
     const onKey = (e: KeyboardEvent) => {
       if (e.key === "Escape") setOpen(false);
     };
     window.addEventListener("keydown", onKey);
     return () => window.removeEventListener("keydown", onKey);
   }, [open]);

Then on the modal container:

             <motion.div
               initial={{ opacity: 0, scale: 0.95, y: 20 }}
               animate={{ opacity: 1, scale: 1, y: 0 }}
               exit={{ opacity: 0, scale: 0.95, y: 20 }}
               transition={{ type: "spring", damping: 25, stiffness: 300 }}
               className="relative w-full max-w-md glass-strong rounded-2xl border border-border-strong overflow-hidden"
+              role="dialog"
+              aria-modal="true"
+              aria-labelledby={titleId}
+              tabIndex={-1}
+              ref={modalRef}
             >

And on the heading:

-                  <h2 className="font-display font-bold text-lg">
+                  <h2 id={titleId} className="font-display font-bold text-lg">

And on the trigger button:

         <button
           type="button"
           onClick={() => setOpen(true)}
+          aria-haspopup="dialog"
+          aria-expanded={open}
           className="underline underline-offset-2 hover:text-foreground-muted transition-colors"
         >
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/hackathons/PrizeRulesNote.tsx` around lines 47 - 128, Improve the modal
in the open state rendered by the AnimatePresence block: give the dialog
container role="dialog", aria-modal="true", and aria-labelledby pointing to a
unique id on the “Cómo se reparten los premios” heading, and add an accessible
name to the trigger button. Move focus into the dialog when it opens, trap Tab
and Shift+Tab within its interactive elements, and restore focus to the trigger
when it closes, using refs and effect/keyboard handling in the component.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@app/hackathons/PrizeRulesNote.tsx`:
- Around line 47-128: Improve the modal in the open state rendered by the
AnimatePresence block: give the dialog container role="dialog",
aria-modal="true", and aria-labelledby pointing to a unique id on the “Cómo se
reparten los premios” heading, and add an accessible name to the trigger button.
Move focus into the dialog when it opens, trap Tab and Shift+Tab within its
interactive elements, and restore focus to the trigger when it closes, using
refs and effect/keyboard handling in the component.

In `@data/hackathons/hackathons.json`:
- Line 24: Update prizedProjects() in lib/hackathons.ts to enforce the
one-prize-per-person rule: track each awarded participant’s pubkey while
iterating projects, skip subsequent projects belonging to an already-awarded
person, and preserve the existing prize allocation behavior for eligible
projects.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 50b238b5-68f5-419a-9310-2784b77857b2

📥 Commits

Reviewing files that changed from the base of the PR and between 0eee027 and 8b42eaa.

📒 Files selected for processing (4)
  • app/hackathons/PrizeRulesNote.tsx
  • app/hackathons/page.tsx
  • data/hackathons/hackathons.json
  • lib/hackathons.ts

Agrega role="dialog", aria-modal, aria-labelledby y tabIndex al panel del
modal; id en el heading; aria-haspopup/aria-expanded en el trigger. Mueve el
foco al diálogo al abrir y lo restaura al trigger al cerrar. Aborda el nitpick
de accesibilidad de CodeRabbit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@agustinkassis

Copy link
Copy Markdown
Member Author

Gracias @coderabbitai. Los dos nitpicks vinieron dentro de un comentario agregado (sin hilos inline resolubles), así que respondo acá:

1. Accesibilidad del modal (app/hackathons/PrizeRulesNote.tsx) — ✅ Corregido en 7233738. Agregué role="dialog", aria-modal="true", aria-labelledby al panel (+ id en el heading), aria-haspopup="dialog"/aria-expanded en el trigger, y manejo de foco (foco al diálogo al abrir, restaurado al trigger al cerrar). No agregué un focus-trap completo de Tab para mantener el cambio mínimo y consistente con los otros modales del repo (LoginModal, NewProjectModal), que tampoco lo implementan; se puede abordar de forma uniforme en un PR aparte.

2. Enforcement de "1 premio por persona" en prizedProjects() — ⏸️ Diferido a propósito. Es una decisión de producto (qué cuenta como "persona" — ¿pubkey primario? ¿cualquier miembro del equipo? — y qué pasa con el slot que se libera) que requiere sign-off del mantenedor. Está anotado como fuera de alcance en la descripción del PR; en este cambio solo se agregó la regla como texto informativo.

🤖 Addressed by Claude Code

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

@agustinkassis, gracias por el detalle y por la corrección.

  1. Accesibilidad del modal — Perfecto, la corrección en 7233738 (roles ARIA, aria-labelledby, manejo de foco) cubre lo esencial. Estoy de acuerdo en no implementar un focus-trap completo aquí para mantener consistencia con LoginModal y NewProjectModal; tiene sentido abordarlo de forma uniforme en otro momento.

  2. Enforcement de "1 premio por persona" — Totalmente de acuerdo en que es una decisión de producto que requiere definir qué constituye "persona" (pubkey primaria vs. cualquier miembro del equipo) y cómo redistribuir el slot liberado. Dejarlo como regla informativa por ahora, sin implementar la deduplicación, es razonable para no bloquear este PR.

Si quieren, puedo abrir un issue de seguimiento para rastrear la implementación futura del enforcement de "1 premio por persona" en prizedProjects(), referenciando esta conversación. Avísenme si lo prefieren.

(◕‿◕) gracias por la claridad en la explicación

✅ Action performed

Comments resolved. Approval is disabled; enable reviews.request_changes_workflow to allow explicit top-level @coderabbitai resolve or @coderabbitai approve commands.

…uita CTA duplicado del hero

- PrizeRulesNote ahora cubre las dos condiciones: "un premio por persona"
  (si participás con varios proyectos solo gana el mejor, el resto queda fuera)
  y la regla de escasez (premios más chicos primero si hay menos de 6). El modal
  pasa a titularse "Condiciones de los premios" con una sección por regla más el
  ejemplo de 3 proyectos.
- Se reutiliza PrizeRulesNote dentro de la card "Premios" de la pestaña
  Resultados de cada hackatón (mismo componente que /hackathons, sin duplicar).
- Se elimina el botón "Inscribite gratis" del hero de /hackathons/[id] (quedaba
  duplicado del que ya vive en la lista de proyectos) y sus imports sin uso.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@agustinkassis
agustinkassis merged commit 050d432 into main Jul 11, 2026
3 checks passed
@agustinkassis
agustinkassis deleted the claude/hackathons-prize-distribution-4b743c branch July 11, 2026 14:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant