YANAbillionarie commited on
Commit
fc22c21
Β·
verified Β·
1 Parent(s): 3215882

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +237 -173
app.py CHANGED
@@ -1,7 +1,8 @@
1
 
2
  """
3
  Judges Copilot Β· Gradio x Hugging Face Hackathon 2026
4
- Single-page layout β€” hero + tool on one scroll. No nav bugs.
 
5
  """
6
 
7
  import os, re, random
@@ -21,9 +22,11 @@ api = HfApi(token=HF_TOKEN)
21
  CSS = """
22
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');
23
 
24
- *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
25
 
26
- body, .gradio-container, .gradio-container > div, .app {
 
 
27
  background: #0b0b18 !important;
28
  font-family: 'Inter', sans-serif !important;
29
  color: #d0d0ee !important;
@@ -38,7 +41,7 @@ body, .gradio-container, .gradio-container > div, .app {
38
 
39
  footer, .footer { display: none !important; }
40
 
41
- /* inputs */
42
  textarea, input[type="text"] {
43
  background: #13132a !important;
44
  border: 1px solid #2a2a50 !important;
@@ -55,184 +58,200 @@ textarea:focus, input[type="text"]:focus {
55
  textarea::placeholder { color: #404070 !important; }
56
  label span, .label-wrap span {
57
  color: #8080b0 !important;
58
- font-size: .83rem !important;
59
- font-weight: 500 !important;
60
  }
61
 
62
- /* button */
63
  button.cta {
64
  background: #F5A623 !important;
65
  color: #0b0b18 !important;
66
- font-weight: 700 !important;
67
  font-size: 1rem !important;
68
  border: none !important;
69
- border-radius: 100px !important;
70
  padding: 14px 40px !important;
71
  width: 100% !important;
72
  cursor: pointer !important;
 
73
  transition: opacity .2s, transform .15s !important;
74
- margin-top: 4px !important;
75
  }
76
  button.cta:hover { opacity: .88 !important; transform: translateY(-1px) !important; }
77
 
78
- /* tabs */
79
  .tab-nav button {
80
  background: transparent !important;
81
  color: #5858a0 !important;
82
  border: none !important;
83
  border-bottom: 2px solid transparent !important;
84
- font-weight: 600 !important; font-size: .87rem !important;
 
85
  padding: 10px 16px !important;
86
  }
87
  .tab-nav button.selected { color: #F5A623 !important; border-bottom-color: #F5A623 !important; }
88
  .tab-nav { border-bottom: 1px solid #1e1e3a !important; margin-bottom: 14px !important; }
89
  .tabitem { background: transparent !important; padding: 0 !important; }
90
 
91
- /* dataframe */
92
- .dataframe table { background: #0f0f26 !important; border-collapse: collapse !important; width: 100% !important; }
 
 
 
 
93
  .dataframe thead th {
94
- background: #18183a !important; color: #F5A623 !important;
95
- padding: 10px 13px !important; font-size: .78rem !important;
96
- font-weight: 700 !important; letter-spacing: .05em !important;
97
- text-transform: uppercase !important; border-bottom: 2px solid #252548 !important;
 
 
 
 
98
  }
99
  .dataframe tbody td {
100
- padding: 9px 13px !important; border-bottom: 1px solid #18183a !important;
101
- font-size: .82rem !important; color: #b8b8e0 !important;
 
 
102
  }
103
  .dataframe tbody tr:hover td { background: #141430 !important; }
104
  .dataframe tbody tr:first-child td { color: #fff !important; font-weight: 600 !important; }
105
 
106
- /* markdown */
107
- .prose h2 { color: #fff !important; font-size: 1.3rem !important; margin: 1rem 0 .4rem !important; }
108
- .prose h3 { color: #e8c870 !important; font-size: .98rem !important; margin: 1rem 0 .25rem !important; }
109
  .prose strong { color: #F5A623 !important; }
110
  .prose a { color: #80b0f0 !important; }
111
- .prose blockquote { border-left: 3px solid #F5A623 !important; padding-left: 12px !important; color: #8888b8 !important; }
 
 
 
 
112
  .prose hr { border: none !important; border-top: 1px solid #1e1e3a !important; }
113
  .prose, .prose p, .prose li { color: #b8b8e0 !important; }
114
 
115
- /* confetti */
116
  @keyframes cffall {
117
- 0% { transform: translateY(-5vh) rotate(0deg); opacity: 1; }
118
  85% { opacity: 1; }
119
  100% { transform: translateY(105vh) rotate(600deg); opacity: 0; }
120
  }
121
  .cf {
122
- position: fixed; top: 0; font-size: 1.5rem;
 
123
  animation: cffall linear forwards;
124
  z-index: 99999; pointer-events: none;
125
  }
126
  """
127
 
128
  # ═══════════════════════════════════════════════════════════════
129
- # Static HTML sections
130
  # ═══════════════════════════════════════════════════════════════
131
  HERO_HTML = """
132
  <div style="
133
- background: linear-gradient(180deg, #0b0b18 0%, #10102a 100%);
134
- text-align: center;
135
- padding: 60px 24px 48px;
136
- border-bottom: 1px solid #1a1a34;
137
  ">
138
  <div style="
139
  display:inline-block;
140
- background:rgba(245,166,35,.12);
141
- color:#F5A623;
142
- border:1px solid rgba(245,166,35,.3);
143
- border-radius:100px;
144
- padding:6px 18px;
145
- font-size:.72rem; font-weight:700;
146
- letter-spacing:.1em; text-transform:uppercase;
147
- margin-bottom:24px;
148
  ">πŸ† Hackathon Judge's Co-Pilot</div>
149
 
150
  <h1 style="
151
- font-size:clamp(2rem,5.5vw,3.8rem);
152
- font-weight:800; line-height:1.08;
153
- color:#fff; max-width:680px;
154
- margin:0 auto 18px; letter-spacing:-.01em;
155
- font-family:'Inter',sans-serif;
156
  ">
157
  You Are About To Judge<br>
158
  <span style="color:#F5A623;">50+ Projects.</span>
159
  </h1>
160
 
161
  <p style="
162
- font-size:1rem; color:#686898;
163
- max-width:460px; line-height:1.8;
164
- margin:0 auto 28px;
165
- font-family:'Inter',sans-serif;
166
  ">
167
- The clock is ticking. The submissions keep piling up.
168
- This tool reads every README for you β€” and tells you exactly
169
- why each project scored what it scored.
170
  </p>
171
 
172
  <div style="
173
  background:#111128; border:1px solid #252548;
174
- border-radius:14px 14px 14px 3px;
175
- padding:14px 20px; font-size:.88rem;
176
- color:#9090c0; max-width:380px;
177
- margin:0 auto; line-height:1.7;
178
- text-align:left; font-family:'Inter',sans-serif;
179
  ">
180
- πŸ‘‹ Paste your links below. Set your criteria.
181
- Hit Analyse β€” and get a full ranked briefing in seconds.
182
  </div>
183
  </div>
184
  """
185
 
186
- DIVIDER_HTML = """
187
- <div style="
188
- text-align:center; padding:28px 24px 8px;
189
- font-size:.72rem; color:#383860;
190
- letter-spacing:.08em; text-transform:uppercase;
191
- font-family:'Inter',sans-serif;
192
- ">
193
- ↓ &nbsp; Paste your submissions below &nbsp; ↓
 
 
194
  </div>
195
  """
196
 
197
- RESULTS_HEADER_HTML = """
 
 
 
 
 
 
 
 
 
 
 
 
198
  <div style="
199
- padding:24px 0 8px;
200
- font-size:.72rem; color:#F5A623;
201
  letter-spacing:.1em; text-transform:uppercase;
202
- font-family:'Inter',sans-serif; font-weight:700;
203
- ">
204
- πŸ“Š Results
205
- </div>
206
  """
207
 
208
  HANDOFF_HTML = """
209
  <div style="
210
- background: linear-gradient(135deg,#0f0f26,#180f28);
211
- border: 1px solid rgba(245,166,35,.2);
212
- border-radius: 16px;
213
- padding: 32px 28px;
214
- text-align: center;
215
- margin-top: 24px;
216
- font-family: 'Inter', sans-serif;
217
  ">
218
- <div style="font-size:2.6rem;margin-bottom:12px;">πŸ™</div>
219
  <h3 style="
220
- font-size:1.35rem; font-weight:800;
221
- color:#fff; margin-bottom:14px; letter-spacing:-.01em;
222
  ">Now, We Are Left To Be Judged β€” Only By You.</h3>
223
- <p style="font-size:.92rem; color:#686898; line-height:1.85; max-width:440px; margin:0 auto;">
224
  We helped you judge everyone else.<br>
225
  Now this tool itself is a submission,<br>
226
  sitting in the same pile, waiting for the same verdict.<br><br>
227
- <strong style="color:#fff;font-size:1rem;">The judge is you.</strong>
228
  </p>
229
  </div>
230
  """
231
 
232
  FOOTER_HTML = """
233
  <div style="
234
- text-align:center; padding:24px 16px;
235
- font-size:.75rem; color:#2e2e58;
236
  border-top:1px solid #111128; margin-top:20px;
237
  font-family:'Inter',sans-serif;
238
  ">
@@ -329,9 +348,9 @@ def fetch_repo_data(repo_type, repo_id):
329
  data["error"] = f"Could not reach this {repo_type} ({type(exc).__name__})."
330
  return data
331
  try:
332
- path = hf_hub_download(repo_id=repo_id,filename="README.md",
333
- repo_type=repo_type,token=HF_TOKEN)
334
- with open(path,encoding="utf-8",errors="replace") as fh:
335
  data["readme"] = fh.read()
336
  except Exception:
337
  data["readme"] = ""
@@ -339,12 +358,12 @@ def fetch_repo_data(repo_type, repo_id):
339
 
340
  def fetch_app_code(repo_type, repo_id, files):
341
  if repo_type != "space": return ""
342
- entry = next((f for f in files if f.lower() in ("app.py","main.py")),None)
343
  if not entry: return ""
344
  try:
345
- path = hf_hub_download(repo_id=repo_id,filename=entry,
346
- repo_type="space",token=HF_TOKEN)
347
- with open(path,encoding="utf-8",errors="replace") as fh:
348
  return fh.read()
349
  except Exception:
350
  return ""
@@ -404,16 +423,16 @@ def generate_justification(criterion, score, readme_lower, checklist):
404
  if found_kw:
405
  parts.append(f"README mentions _{', '.join(found_kw)}_")
406
  else:
407
- parts.append(f"no {criterion.lower()} keywords in README")
408
  present = [c for c in sig["checks"] if checklist.get(c)]
409
  missing = [c for c in sig["checks"] if not checklist.get(c)]
410
  if present: parts.append("βœ… " + ", ".join(present))
411
  if missing: parts.append("❌ missing " + ", ".join(missing))
412
  else:
413
  words = re.findall(r"[a-z]{4,}", criterion.lower())
414
- hits = [w for w in words if w in readme_lower]
415
  parts.append(f"found: _{', '.join(hits[:3])}_" if hits else "no matching keywords")
416
- if len(readme_lower) > 1500: parts.append("well-documented")
417
  elif len(readme_lower) < 100: parts.append("very sparse README")
418
  return " Β· ".join(parts) if parts else "scored on README signals"
419
 
@@ -442,12 +461,13 @@ def analyze_submission(raw, criteria_list):
442
  title_m = re.search(r"^#\s+(.+)$", body, re.MULTILINE)
443
  title = title_m.group(1).strip() if title_m else repo_id
444
 
445
- lines = []
446
  for ln in body.splitlines():
447
  ln = ln.strip()
448
- if not ln or ln.startswith("#") or ln.startswith("![") or ln.startswith("[!["): continue
449
- lines.append(ln)
450
- summary = re.sub(r"\s+"," "," ".join(lines))[:300] if lines else "No description found."
 
451
 
452
  readme_lower = readme.lower()
453
  checklist = {
@@ -482,19 +502,72 @@ def make_confetti():
482
  l = random.randint(1,99)
483
  d = round(random.uniform(0,2.2),2)
484
  dur = round(random.uniform(2.5,5),2)
485
- pieces.append(f'<span class="cf" style="left:{l}%;animation-duration:{dur}s;animation-delay:{d}s">{e}</span>')
 
 
 
486
  return "<div>" + "".join(pieces) + "</div>"
487
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
488
  def build_leaderboard(results, criteria_list):
489
- cols = ["πŸ… Rank","Project","⭐ Overall"] + criteria_list + ["❀️ Likes"]
490
  rows = []
491
- for i,r in enumerate(results,1):
492
- row = [i, r["title"], f"{r['overall']}/10"]
 
493
  row += [r["scores"].get(c,"-") for c in criteria_list]
494
  row.append(r["likes"])
495
  rows.append(row)
496
  return pd.DataFrame(rows, columns=cols)
497
 
 
498
  def build_criteria_table(results, criteria_list):
499
  ok = [r for r in results if not r["error"]]
500
  if not ok: return pd.DataFrame()
@@ -508,35 +581,15 @@ def build_criteria_table(results, criteria_list):
508
  rows.append(row)
509
  return pd.DataFrame(rows, columns=cols)
510
 
511
- def build_winners(results, criteria_list):
512
- ok = [r for r in results if not r["error"]]
513
- if len(ok) < 2:
514
- return "_Need at least 2 valid submissions to show category winners._"
515
- medals = ["πŸ₯‡","πŸ₯ˆ","πŸ₯‰"]
516
- lines = ["## πŸ… Category Winners\n"]
517
- for c in criteria_list:
518
- ranked = sorted(ok, key=lambda r: r["scores"].get(c,0), reverse=True)
519
- lines.append(f"### {c}")
520
- for i,r in enumerate(ranked[:3]):
521
- s = r["scores"].get(c,0)
522
- j = r["justifications"].get(c,"")
523
- lines.append(f"{medals[i]} **{r['title']}** β€” {s}/10 \n _{j}_")
524
- lines.append("")
525
- return "\n".join(lines)
526
 
527
  def build_briefing(results, criteria_list):
528
- lines = [
529
- "## πŸ† Full Judge's Briefing",
530
- f"_Analysed **{len(results)}** submission(s) across **{len(criteria_list)}** criteria._\n",
531
- ]
532
- ok = [r for r in results if not r["error"]]
533
- if ok:
534
- top = ok[0]
535
- lines.append(f"### πŸ₯‡ Top Pick: [{top['title']}]({top['url']}) β€” **{top['overall']}/10**")
536
- lines.append(f"> {top['summary']}\n")
537
- lines.append("---\n")
538
- for i,r in enumerate(results,1):
539
- lines.append(f"### {i}. {r['title']}")
540
  if r["url"]: lines.append(f"πŸ”— [{r['url']}]({r['url']})")
541
  if r["error"]:
542
  lines.append(f"⚠️ _{r['summary']}_\n")
@@ -555,14 +608,16 @@ def build_briefing(results, criteria_list):
555
  lines.append("\n---\n")
556
  return "\n\n".join(lines)
557
 
 
558
  # ═══════════════════════════════════════════════════════════════
559
  # Main function
560
  # ═══════════════════════════════════════════════════════════════
561
  def run_analysis(urls_text, criteria_text):
562
- empty = ("### πŸ‘€ Paste links above and hit Analyse.",
563
- pd.DataFrame(), pd.DataFrame(), "", "")
564
  if not urls_text or not urls_text.strip():
565
- return empty
 
566
 
567
  criteria_list = [c.strip() for c in re.split(r"[,\n]", criteria_text or "") if c.strip()]
568
  if not criteria_list:
@@ -572,7 +627,10 @@ def run_analysis(urls_text, criteria_text):
572
  for line in urls_text.splitlines():
573
  line = line.strip()
574
  if not line: continue
575
- is_listing = ("huggingface.co" in line and "?" in line and "/spaces/" not in line) or "?tag=" in line
 
 
 
576
  if is_listing:
577
  for sid in discover_from_hackathon_url(line):
578
  if sid not in seen:
@@ -582,61 +640,67 @@ def run_analysis(urls_text, criteria_text):
582
  seen.add(line); targets.append(line)
583
 
584
  if not targets:
585
- return ("### πŸ” No valid links found. Paste Space URLs or a hackathon tag page.",
586
- pd.DataFrame(), pd.DataFrame(), "", "")
587
 
588
  try:
589
  results = [analyze_submission(t, criteria_list) for t in targets]
590
  results.sort(key=lambda r: r["overall"], reverse=True)
591
- return (
592
- build_briefing(results, criteria_list),
593
- build_leaderboard(results, criteria_list),
594
- build_criteria_table(results, criteria_list),
595
- build_winners(results, criteria_list),
596
- make_confetti() if any(not r["error"] for r in results) else "",
597
- )
 
 
598
  except Exception as exc:
599
- return (f"### ⚠️ Error: `{exc}`", pd.DataFrame(), pd.DataFrame(), "", "")
 
600
 
601
  # ═══════════════════════════════════════════════════════════════
602
- # UI β€” single page, no navigation
603
  # ═══════════════════════════════════════════════════════════════
604
  with gr.Blocks(theme=gr.themes.Base(), css=CSS, title="Judges Copilot") as demo:
605
 
606
  gr.HTML(HERO_HTML)
607
- gr.HTML(DIVIDER_HTML)
608
 
609
  # ── Inputs ───────────────────────────────────────────────────
610
- with gr.Column():
611
- urls_box = gr.Textbox(
612
- label="πŸ”— Submission links β€” one per line (or paste a hackathon tag page URL)",
613
- placeholder=(
614
- "https://huggingface.co/spaces/username/project-name\n"
615
- "https://huggingface.co/spaces?tag=gradio-hackathon-2026\n"
616
- "another-user/another-space"
617
- ),
618
- lines=6,
619
- )
620
- criteria_box = gr.Textbox(
621
- label="🎯 Judging criteria β€” comma separated",
622
- value="Innovation, Technical Execution, Usability & Design, Real-World Impact, Presentation",
623
- lines=2,
624
- )
625
- analyse_btn = gr.Button("⚑ Analyse Submissions", elem_classes=["cta"])
 
 
 
626
 
627
- # ── Confetti overlay ─────────────────────────────────────────
628
  confetti_out = gr.HTML(value="")
629
 
630
- # ── Results ──────────────────────────────────────────────────
631
- gr.HTML(RESULTS_HEADER_HTML)
 
 
 
632
 
633
  with gr.Tabs():
634
- with gr.Tab("πŸ… Leaderboard"):
635
- leaderboard_out = gr.Dataframe(label="Overall Rankings")
636
  with gr.Tab("🎯 Criteria Breakdown"):
637
  criteria_out = gr.Dataframe(label="Pass / Fail per Criterion (βœ… β‰₯7 Β· 🟑 5-6 Β· ❌ <5)")
638
- with gr.Tab("πŸ† Category Winners"):
639
- winners_out = gr.Markdown(value="")
640
 
641
  briefing_out = gr.Markdown(value="")
642
 
@@ -647,7 +711,7 @@ with gr.Blocks(theme=gr.themes.Base(), css=CSS, title="Judges Copilot") as demo:
647
  analyse_btn.click(
648
  fn=run_analysis,
649
  inputs=[urls_box, criteria_box],
650
- outputs=[briefing_out, leaderboard_out, criteria_out, winners_out, confetti_out],
651
  )
652
 
653
  if __name__ == "__main__":
 
1
 
2
  """
3
  Judges Copilot Β· Gradio x Hugging Face Hackathon 2026
4
+ Single page. Paste a hackathon page URL or individual links.
5
+ Add your criteria. Get 1st / 2nd / 3rd with full reasons.
6
  """
7
 
8
  import os, re, random
 
22
  CSS = """
23
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');
24
 
25
+ *, *::before, *::after { box-sizing: border-box; }
26
 
27
+ body, .gradio-container, .gradio-container > div,
28
+ .gradio-container .wrap, .gradio-container .gap,
29
+ .gradio-container .contain, .app {
30
  background: #0b0b18 !important;
31
  font-family: 'Inter', sans-serif !important;
32
  color: #d0d0ee !important;
 
41
 
42
  footer, .footer { display: none !important; }
43
 
44
+ /* ── inputs ── */
45
  textarea, input[type="text"] {
46
  background: #13132a !important;
47
  border: 1px solid #2a2a50 !important;
 
58
  textarea::placeholder { color: #404070 !important; }
59
  label span, .label-wrap span {
60
  color: #8080b0 !important;
61
+ font-size: .84rem !important;
62
+ font-weight: 600 !important;
63
  }
64
 
65
+ /* ── analyse button ── */
66
  button.cta {
67
  background: #F5A623 !important;
68
  color: #0b0b18 !important;
69
+ font-weight: 800 !important;
70
  font-size: 1rem !important;
71
  border: none !important;
72
+ border-radius: 12px !important;
73
  padding: 14px 40px !important;
74
  width: 100% !important;
75
  cursor: pointer !important;
76
+ letter-spacing: .02em !important;
77
  transition: opacity .2s, transform .15s !important;
78
+ margin-top: 6px !important;
79
  }
80
  button.cta:hover { opacity: .88 !important; transform: translateY(-1px) !important; }
81
 
82
+ /* ── tabs ── */
83
  .tab-nav button {
84
  background: transparent !important;
85
  color: #5858a0 !important;
86
  border: none !important;
87
  border-bottom: 2px solid transparent !important;
88
+ font-weight: 600 !important;
89
+ font-size: .87rem !important;
90
  padding: 10px 16px !important;
91
  }
92
  .tab-nav button.selected { color: #F5A623 !important; border-bottom-color: #F5A623 !important; }
93
  .tab-nav { border-bottom: 1px solid #1e1e3a !important; margin-bottom: 14px !important; }
94
  .tabitem { background: transparent !important; padding: 0 !important; }
95
 
96
+ /* ── dataframe ── */
97
+ .dataframe table {
98
+ background: #0f0f26 !important;
99
+ border-collapse: collapse !important;
100
+ width: 100% !important;
101
+ }
102
  .dataframe thead th {
103
+ background: #18183a !important;
104
+ color: #F5A623 !important;
105
+ padding: 10px 13px !important;
106
+ font-size: .78rem !important;
107
+ font-weight: 700 !important;
108
+ letter-spacing: .05em !important;
109
+ text-transform: uppercase !important;
110
+ border-bottom: 2px solid #252548 !important;
111
  }
112
  .dataframe tbody td {
113
+ padding: 9px 13px !important;
114
+ border-bottom: 1px solid #18183a !important;
115
+ font-size: .82rem !important;
116
+ color: #b8b8e0 !important;
117
  }
118
  .dataframe tbody tr:hover td { background: #141430 !important; }
119
  .dataframe tbody tr:first-child td { color: #fff !important; font-weight: 600 !important; }
120
 
121
+ /* ── markdown ── */
122
+ .prose h2 { color: #fff !important; font-size: 1.3rem !important; margin: 1.1rem 0 .4rem !important; }
123
+ .prose h3 { color: #e8c870 !important; font-size: .97rem !important; margin: 1rem 0 .3rem !important; }
124
  .prose strong { color: #F5A623 !important; }
125
  .prose a { color: #80b0f0 !important; }
126
+ .prose blockquote {
127
+ border-left: 3px solid #F5A623 !important;
128
+ padding-left: 12px !important;
129
+ color: #8888b8 !important;
130
+ }
131
  .prose hr { border: none !important; border-top: 1px solid #1e1e3a !important; }
132
  .prose, .prose p, .prose li { color: #b8b8e0 !important; }
133
 
134
+ /* ── confetti ── */
135
  @keyframes cffall {
136
+ 0% { transform: translateY(-5vh) rotate(0deg); opacity: 1; }
137
  85% { opacity: 1; }
138
  100% { transform: translateY(105vh) rotate(600deg); opacity: 0; }
139
  }
140
  .cf {
141
+ position: fixed; top: 0;
142
+ font-size: 1.5rem;
143
  animation: cffall linear forwards;
144
  z-index: 99999; pointer-events: none;
145
  }
146
  """
147
 
148
  # ═══════════════════════════════════════════════════════════════
149
+ # Static HTML
150
  # ═══════════════════════════════════════════════════════════════
151
  HERO_HTML = """
152
  <div style="
153
+ background:linear-gradient(180deg,#0b0b18 0%,#10102a 100%);
154
+ text-align:center; padding:56px 24px 44px;
155
+ border-bottom:1px solid #1a1a34;
156
+ font-family:'Inter',sans-serif;
157
  ">
158
  <div style="
159
  display:inline-block;
160
+ background:rgba(245,166,35,.12); color:#F5A623;
161
+ border:1px solid rgba(245,166,35,.3); border-radius:100px;
162
+ padding:6px 18px; font-size:.71rem; font-weight:700;
163
+ letter-spacing:.1em; text-transform:uppercase; margin-bottom:22px;
 
 
 
 
164
  ">πŸ† Hackathon Judge's Co-Pilot</div>
165
 
166
  <h1 style="
167
+ font-size:clamp(1.9rem,5.5vw,3.6rem); font-weight:800;
168
+ line-height:1.08; color:#fff; max-width:660px;
169
+ margin:0 auto 16px; letter-spacing:-.01em;
 
 
170
  ">
171
  You Are About To Judge<br>
172
  <span style="color:#F5A623;">50+ Projects.</span>
173
  </h1>
174
 
175
  <p style="
176
+ font-size:.97rem; color:#686898; max-width:450px;
177
+ line-height:1.8; margin:0 auto 24px;
 
 
178
  ">
179
+ Paste the hackathon submissions page β€” or individual links.
180
+ Set your criteria. Get a full ranked briefing with
181
+ 1st, 2nd, 3rd and a reason for every score.
182
  </p>
183
 
184
  <div style="
185
  background:#111128; border:1px solid #252548;
186
+ border-radius:14px 14px 14px 3px; padding:14px 20px;
187
+ font-size:.87rem; color:#9090c0; max-width:370px;
188
+ margin:0 auto; line-height:1.7; text-align:left;
 
 
189
  ">
190
+ πŸ‘‹ We read the READMEs. We check the files.
191
+ We score every criterion. You just decide.
192
  </div>
193
  </div>
194
  """
195
 
196
+ INPUT_HEADER_HTML = """
197
+ <div style="font-family:'Inter',sans-serif; padding:30px 0 6px;">
198
+ <p style="
199
+ font-size:.71rem; color:#F5A623; font-weight:700;
200
+ letter-spacing:.1em; text-transform:uppercase; margin-bottom:6px;
201
+ ">Step 1 β€” Links</p>
202
+ <p style="font-size:.84rem; color:#5858a0; line-height:1.6; margin:0;">
203
+ Paste the <strong style="color:#c0c0e8;">hackathon submissions page URL</strong>
204
+ (auto-discovers all entries) β€” or paste individual Space links, one per line.
205
+ </p>
206
  </div>
207
  """
208
 
209
+ CRITERIA_HEADER_HTML = """
210
+ <div style="font-family:'Inter',sans-serif; padding:20px 0 6px;">
211
+ <p style="
212
+ font-size:.71rem; color:#F5A623; font-weight:700;
213
+ letter-spacing:.1em; text-transform:uppercase; margin-bottom:6px;
214
+ ">Step 2 β€” Your Criteria</p>
215
+ <p style="font-size:.84rem; color:#5858a0; line-height:1.6; margin:0;">
216
+ Add, remove, or rename any criteria β€” these become your scoring rubric.
217
+ </p>
218
+ </div>
219
+ """
220
+
221
+ RESULTS_LABEL_HTML = """
222
  <div style="
223
+ font-family:'Inter',sans-serif;
224
+ font-size:.71rem; color:#F5A623; font-weight:700;
225
  letter-spacing:.1em; text-transform:uppercase;
226
+ padding:28px 0 10px;
227
+ ">πŸ“Š Results</div>
 
 
228
  """
229
 
230
  HANDOFF_HTML = """
231
  <div style="
232
+ background:linear-gradient(135deg,#0f0f26,#180f28);
233
+ border:1px solid rgba(245,166,35,.2); border-radius:16px;
234
+ padding:30px 24px; text-align:center; margin-top:24px;
235
+ font-family:'Inter',sans-serif;
 
 
 
236
  ">
237
+ <div style="font-size:2.4rem;margin-bottom:10px;">πŸ™</div>
238
  <h3 style="
239
+ font-size:1.25rem; font-weight:800; color:#fff;
240
+ margin-bottom:12px; letter-spacing:-.01em;
241
  ">Now, We Are Left To Be Judged β€” Only By You.</h3>
242
+ <p style="font-size:.9rem; color:#686898; line-height:1.85; max-width:420px; margin:0 auto;">
243
  We helped you judge everyone else.<br>
244
  Now this tool itself is a submission,<br>
245
  sitting in the same pile, waiting for the same verdict.<br><br>
246
+ <strong style="color:#fff;">The judge is you.</strong>
247
  </p>
248
  </div>
249
  """
250
 
251
  FOOTER_HTML = """
252
  <div style="
253
+ text-align:center; padding:22px 16px;
254
+ font-size:.74rem; color:#2e2e58;
255
  border-top:1px solid #111128; margin-top:20px;
256
  font-family:'Inter',sans-serif;
257
  ">
 
348
  data["error"] = f"Could not reach this {repo_type} ({type(exc).__name__})."
349
  return data
350
  try:
351
+ path = hf_hub_download(repo_id=repo_id, filename="README.md",
352
+ repo_type=repo_type, token=HF_TOKEN)
353
+ with open(path, encoding="utf-8", errors="replace") as fh:
354
  data["readme"] = fh.read()
355
  except Exception:
356
  data["readme"] = ""
 
358
 
359
  def fetch_app_code(repo_type, repo_id, files):
360
  if repo_type != "space": return ""
361
+ entry = next((f for f in files if f.lower() in ("app.py","main.py")), None)
362
  if not entry: return ""
363
  try:
364
+ path = hf_hub_download(repo_id=repo_id, filename=entry,
365
+ repo_type="space", token=HF_TOKEN)
366
+ with open(path, encoding="utf-8", errors="replace") as fh:
367
  return fh.read()
368
  except Exception:
369
  return ""
 
423
  if found_kw:
424
  parts.append(f"README mentions _{', '.join(found_kw)}_")
425
  else:
426
+ parts.append(f"no {criterion.lower()} keywords found")
427
  present = [c for c in sig["checks"] if checklist.get(c)]
428
  missing = [c for c in sig["checks"] if not checklist.get(c)]
429
  if present: parts.append("βœ… " + ", ".join(present))
430
  if missing: parts.append("❌ missing " + ", ".join(missing))
431
  else:
432
  words = re.findall(r"[a-z]{4,}", criterion.lower())
433
+ hits = [w for w in words if w in readme_lower]
434
  parts.append(f"found: _{', '.join(hits[:3])}_" if hits else "no matching keywords")
435
+ if len(readme_lower) > 1500: parts.append("well-documented README")
436
  elif len(readme_lower) < 100: parts.append("very sparse README")
437
  return " Β· ".join(parts) if parts else "scored on README signals"
438
 
 
461
  title_m = re.search(r"^#\s+(.+)$", body, re.MULTILINE)
462
  title = title_m.group(1).strip() if title_m else repo_id
463
 
464
+ content_lines = []
465
  for ln in body.splitlines():
466
  ln = ln.strip()
467
+ if not ln or ln.startswith("#") or ln.startswith("![") or ln.startswith("[!["):
468
+ continue
469
+ content_lines.append(ln)
470
+ summary = re.sub(r"\s+"," "," ".join(content_lines))[:300] if content_lines else "No description found."
471
 
472
  readme_lower = readme.lower()
473
  checklist = {
 
502
  l = random.randint(1,99)
503
  d = round(random.uniform(0,2.2),2)
504
  dur = round(random.uniform(2.5,5),2)
505
+ pieces.append(
506
+ f'<span class="cf" style="left:{l}%;'
507
+ f'animation-duration:{dur}s;animation-delay:{d}s">{e}</span>'
508
+ )
509
  return "<div>" + "".join(pieces) + "</div>"
510
 
511
+
512
+ def build_podium_html(results):
513
+ """Big 1st / 2nd / 3rd winner cards at the top of results."""
514
+ ok = [r for r in results if not r["error"]]
515
+ if not ok:
516
+ return ""
517
+
518
+ medals = [("πŸ₯‡","1st","#F5A623","rgba(245,166,35,.15)"),
519
+ ("πŸ₯ˆ","2nd","#b0b8d0","rgba(176,184,208,.12)"),
520
+ ("πŸ₯‰","3rd","#cd7f32","rgba(205,127,50,.12)")]
521
+ cards = []
522
+ for i, r in enumerate(ok[:3]):
523
+ emoji, place, color, bg = medals[i]
524
+ cards.append(f"""
525
+ <div style="
526
+ background:{bg}; border:1px solid {color}44;
527
+ border-radius:14px; padding:20px 16px; text-align:center;
528
+ font-family:'Inter',sans-serif; flex:1; min-width:160px;
529
+ ">
530
+ <div style="font-size:2rem;margin-bottom:6px;">{emoji}</div>
531
+ <div style="font-size:.7rem;color:{color};font-weight:700;
532
+ letter-spacing:.1em;text-transform:uppercase;margin-bottom:8px;">
533
+ {place} Place
534
+ </div>
535
+ <div style="font-size:.95rem;font-weight:700;color:#fff;
536
+ margin-bottom:6px;line-height:1.3;">
537
+ {r["title"]}
538
+ </div>
539
+ <div style="font-size:1.3rem;font-weight:800;color:{color};">
540
+ {r["overall"]}/10
541
+ </div>
542
+ {"<a href='" + r['url'] + "' target='_blank' style='font-size:.75rem;color:#6080c0;'>View β†’</a>" if r["url"] else ""}
543
+ </div>
544
+ """)
545
+
546
+ return f"""
547
+ <div style="padding:24px 0 8px;font-family:'Inter',sans-serif;">
548
+ <div style="font-size:.71rem;color:#F5A623;font-weight:700;
549
+ letter-spacing:.1em;text-transform:uppercase;margin-bottom:14px;">
550
+ πŸ† Final Rankings
551
+ </div>
552
+ <div style="display:flex;gap:12px;flex-wrap:wrap;">
553
+ {"".join(cards)}
554
+ </div>
555
+ </div>
556
+ """
557
+
558
+
559
  def build_leaderboard(results, criteria_list):
560
+ cols = ["πŸ…","Project","⭐ Overall"] + criteria_list + ["❀️ Likes"]
561
  rows = []
562
+ medals = ["πŸ₯‡","πŸ₯ˆ","πŸ₯‰"]
563
+ for i, r in enumerate(results,1):
564
+ row = [medals[i-1] if i <= 3 else str(i), r["title"], f"{r['overall']}/10"]
565
  row += [r["scores"].get(c,"-") for c in criteria_list]
566
  row.append(r["likes"])
567
  rows.append(row)
568
  return pd.DataFrame(rows, columns=cols)
569
 
570
+
571
  def build_criteria_table(results, criteria_list):
572
  ok = [r for r in results if not r["error"]]
573
  if not ok: return pd.DataFrame()
 
581
  rows.append(row)
582
  return pd.DataFrame(rows, columns=cols)
583
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
584
 
585
  def build_briefing(results, criteria_list):
586
+ lines = ["## πŸ“‹ Full Judge's Briefing",
587
+ f"_Analysed **{len(results)}** submission(s) Β· **{len(criteria_list)}** criteria_\n",
588
+ "---\n"]
589
+ for i, r in enumerate(results,1):
590
+ medals = {1:"πŸ₯‡",2:"πŸ₯ˆ",3:"πŸ₯‰"}
591
+ prefix = medals.get(i, f"**#{i}**")
592
+ lines.append(f"### {prefix} {r['title']}")
 
 
 
 
 
593
  if r["url"]: lines.append(f"πŸ”— [{r['url']}]({r['url']})")
594
  if r["error"]:
595
  lines.append(f"⚠️ _{r['summary']}_\n")
 
608
  lines.append("\n---\n")
609
  return "\n\n".join(lines)
610
 
611
+
612
  # ═══════════════════════════════════════════════════════════════
613
  # Main function
614
  # ═══════════════════════════════════════════════════════════════
615
  def run_analysis(urls_text, criteria_text):
616
+ empty = ("", "", pd.DataFrame(), pd.DataFrame(), "")
617
+
618
  if not urls_text or not urls_text.strip():
619
+ return ("### πŸ‘€ Paste links and hit Analyse to get started.",
620
+ "", pd.DataFrame(), pd.DataFrame(), "")
621
 
622
  criteria_list = [c.strip() for c in re.split(r"[,\n]", criteria_text or "") if c.strip()]
623
  if not criteria_list:
 
627
  for line in urls_text.splitlines():
628
  line = line.strip()
629
  if not line: continue
630
+ is_listing = (
631
+ ("huggingface.co" in line and "?" in line and "/spaces/" not in line)
632
+ or "?tag=" in line
633
+ )
634
  if is_listing:
635
  for sid in discover_from_hackathon_url(line):
636
  if sid not in seen:
 
640
  seen.add(line); targets.append(line)
641
 
642
  if not targets:
643
+ return ("### πŸ” No valid links found. Paste Space URLs or a hackathon tag page URL.",
644
+ "", pd.DataFrame(), pd.DataFrame(), "")
645
 
646
  try:
647
  results = [analyze_submission(t, criteria_list) for t in targets]
648
  results.sort(key=lambda r: r["overall"], reverse=True)
649
+
650
+ podium_html = build_podium_html(results)
651
+ briefing_md = build_briefing(results, criteria_list)
652
+ leaderboard = build_leaderboard(results, criteria_list)
653
+ criteria_df = build_criteria_table(results, criteria_list)
654
+ confetti = make_confetti() if any(not r["error"] for r in results) else ""
655
+
656
+ return briefing_md, podium_html, leaderboard, criteria_df, confetti
657
+
658
  except Exception as exc:
659
+ return (f"### ⚠️ Error: `{exc}`", "", pd.DataFrame(), pd.DataFrame(), "")
660
+
661
 
662
  # ═══════════════════════════════════════════════════════════════
663
+ # UI
664
  # ═══════════════════════════════════════════════════════════════
665
  with gr.Blocks(theme=gr.themes.Base(), css=CSS, title="Judges Copilot") as demo:
666
 
667
  gr.HTML(HERO_HTML)
 
668
 
669
  # ── Inputs ───────────────────────────────────────────────────
670
+ gr.HTML(INPUT_HEADER_HTML)
671
+ urls_box = gr.Textbox(
672
+ label="Hackathon submissions page URL β€” or individual Space links (one per line)",
673
+ placeholder=(
674
+ "Option A (auto-discovers all): https://huggingface.co/spaces?tag=gradio-hackathon-2026\n"
675
+ "Option B (individual): https://huggingface.co/spaces/username/project-name\n"
676
+ " another-user/another-space"
677
+ ),
678
+ lines=5,
679
+ )
680
+
681
+ gr.HTML(CRITERIA_HEADER_HTML)
682
+ criteria_box = gr.Textbox(
683
+ label="Your judging criteria β€” edit, add, or remove any (comma-separated)",
684
+ value="Innovation, Technical Execution, Usability & Design, Real-World Impact, Presentation",
685
+ lines=3,
686
+ )
687
+
688
+ analyse_btn = gr.Button("⚑ Analyse & Rank Submissions", elem_classes=["cta"])
689
 
690
+ # ── Confetti ─────────────────────────────────────────────────
691
  confetti_out = gr.HTML(value="")
692
 
693
+ # ── Podium (1st / 2nd / 3rd) ─────────────────────────────────
694
+ podium_out = gr.HTML(value="")
695
+
696
+ # ── Detailed results ─────────────────────────────────────────
697
+ gr.HTML(RESULTS_LABEL_HTML)
698
 
699
  with gr.Tabs():
700
+ with gr.Tab("πŸ“Š Full Leaderboard"):
701
+ leaderboard_out = gr.Dataframe(label="All submissions ranked")
702
  with gr.Tab("🎯 Criteria Breakdown"):
703
  criteria_out = gr.Dataframe(label="Pass / Fail per Criterion (βœ… β‰₯7 Β· 🟑 5-6 Β· ❌ <5)")
 
 
704
 
705
  briefing_out = gr.Markdown(value="")
706
 
 
711
  analyse_btn.click(
712
  fn=run_analysis,
713
  inputs=[urls_box, criteria_box],
714
+ outputs=[briefing_out, podium_out, leaderboard_out, criteria_out, confetti_out],
715
  )
716
 
717
  if __name__ == "__main__":