pngwn commited on
Commit
fdf3925
Β·
1 Parent(s): e96f49a
src/lib/components/Countdown.svelte CHANGED
@@ -57,7 +57,11 @@
57
  {#if done}
58
  <p class="countdown__closed">Submissions closed</p>
59
  {:else}
60
- <div class="countdown__grid" role="timer" aria-label="Time remaining until the submission deadline">
 
 
 
 
61
  {#each units as u (u.k)}
62
  <div class="countdown__cell" class:is-live={u.k === 'sec'}>
63
  <b class="countdown__num">{u.v}</b>
@@ -67,9 +71,11 @@
67
  </div>
68
 
69
  <div class="countdown__foot">
70
- <span class="countdown__note">Built something? Ship it before the clock runs out.</span>
 
 
71
  <a class="btn btn--accent countdown__cta" href="/submit">
72
- Submit your build <Icon name="arrow" size={18} stroke="#fff" />
73
  </a>
74
  </div>
75
  {/if}
@@ -138,7 +144,7 @@
138
  color: var(--ink-soft);
139
  }
140
 
141
- /* action bar β€” guides users straight to submitting */
142
  .countdown__foot {
143
  display: flex;
144
  align-items: center;
 
57
  {#if done}
58
  <p class="countdown__closed">Submissions closed</p>
59
  {:else}
60
+ <div
61
+ class="countdown__grid"
62
+ role="timer"
63
+ aria-label="Time remaining until the submission deadline"
64
+ >
65
  {#each units as u (u.k)}
66
  <div class="countdown__cell" class:is-live={u.k === 'sec'}>
67
  <b class="countdown__num">{u.v}</b>
 
71
  </div>
72
 
73
  <div class="countdown__foot">
74
+ <span class="countdown__note"
75
+ >Built something? Make sure your README's tagged before the clock runs out.</span
76
+ >
77
  <a class="btn btn--accent countdown__cta" href="/submit">
78
+ Validate your README <Icon name="arrow" size={18} stroke="#fff" />
79
  </a>
80
  </div>
81
  {/if}
 
144
  color: var(--ink-soft);
145
  }
146
 
147
+ /* action bar β€” guides users straight to the README validator */
148
  .countdown__foot {
149
  display: flex;
150
  align-items: center;
src/lib/components/Nav.svelte CHANGED
@@ -16,8 +16,12 @@
16
  // route they point back to the home page's anchors. "submit" is now its own
17
  // route, so it always links there.
18
  const onHome = $derived(page.url.pathname === '/');
19
- const linkFor = (id: string) =>
20
- id === 'submit' ? '/submit' : onHome ? `#${id}` : `/#${id}`;
 
 
 
 
21
 
22
  // close the popout on Escape, and when the viewport grows back to the wide layout
23
  $effect(() => {
@@ -60,9 +64,11 @@
60
  <Icon name={open ? 'close' : 'menu'} size={22} stroke="var(--ink)" />
61
  </button>
62
 
63
- <a class="btn btn--accent nav__submit" href="/submit" onclick={close}>
64
- Submit <Icon name="arrow" size={15} stroke="#fff" />
65
- </a>
 
 
66
  </div>
67
 
68
  {#if open}
@@ -70,11 +76,8 @@
70
  <button class="nav__scrim" aria-hidden="true" tabindex="-1" onclick={close}></button>
71
  <div class="nav__menu" id="nav-menu">
72
  {#each NAV as n (n.id)}
73
- <a
74
- href={linkFor(n.id)}
75
- class="nav__menu-link"
76
- data-active={active === n.id}
77
- onclick={close}>{n.label}</a
78
  >
79
  {/each}
80
  </div>
 
16
  // route they point back to the home page's anchors. "submit" is now its own
17
  // route, so it always links there.
18
  const onHome = $derived(page.url.pathname === '/');
19
+ const linkFor = (id: string) => (id === 'submit' ? '/submit' : onHome ? `#${id}` : `/#${id}`);
20
+
21
+ // The /submit route is a README validator, not a submit button β€” you're
22
+ // entered once your Space is in the org. On the page itself the CTA is
23
+ // redundant, so hide it there.
24
+ const onSubmit = $derived(page.url.pathname === '/submit');
25
 
26
  // close the popout on Escape, and when the viewport grows back to the wide layout
27
  $effect(() => {
 
64
  <Icon name={open ? 'close' : 'menu'} size={22} stroke="var(--ink)" />
65
  </button>
66
 
67
+ {#if !onSubmit}
68
+ <a class="btn btn--accent nav__submit" href="/submit" onclick={close}>
69
+ Validate README <Icon name="arrow" size={15} stroke="#fff" />
70
+ </a>
71
+ {/if}
72
  </div>
73
 
74
  {#if open}
 
76
  <button class="nav__scrim" aria-hidden="true" tabindex="-1" onclick={close}></button>
77
  <div class="nav__menu" id="nav-menu">
78
  {#each NAV as n (n.id)}
79
+ <a href={linkFor(n.id)} class="nav__menu-link" data-active={active === n.id} onclick={close}
80
+ >{n.label}</a
 
 
 
81
  >
82
  {/each}
83
  </div>
src/lib/components/SubmitTrail.svelte CHANGED
@@ -17,7 +17,10 @@
17
  <div class="trail" style="margin-top:48px;max-width:720px">
18
  {#each SUBMIT_STEPS as s, i (s.t)}
19
  <div class="trail__step">
20
- <div class="trail__dot" style="background:var(--amber);border-color:var(--amber);color:#fff">
 
 
 
21
  {i + 1}
22
  </div>
23
  <div style="padding-top:6px">
@@ -29,7 +32,7 @@
29
  </div>
30
 
31
  <a class="btn btn--accent" href="/submit" style="margin-top:40px">
32
- Start your submission <Icon name="arrow" size={15} stroke="#fff" />
33
  </a>
34
  </div>
35
  </section>
 
17
  <div class="trail" style="margin-top:48px;max-width:720px">
18
  {#each SUBMIT_STEPS as s, i (s.t)}
19
  <div class="trail__step">
20
+ <div
21
+ class="trail__dot"
22
+ style="background:var(--amber);border-color:var(--amber);color:#fff"
23
+ >
24
  {i + 1}
25
  </div>
26
  <div style="padding-top:6px">
 
32
  </div>
33
 
34
  <a class="btn btn--accent" href="/submit" style="margin-top:40px">
35
+ Validate your README <Icon name="arrow" size={15} stroke="#fff" />
36
  </a>
37
  </div>
38
  </section>
src/lib/data/content.ts CHANGED
@@ -12,7 +12,7 @@ export const NAV = [
12
  { id: 'prizes', label: 'prizes' },
13
  { id: 'recommender', label: 'find your kit' },
14
  { id: 'partners', label: 'partners' },
15
- { id: 'submit', label: 'submit' },
16
  { id: 'faq', label: 'faq' }
17
  ];
18
 
@@ -69,12 +69,30 @@ export const TRACKS = [
69
  ];
70
 
71
  export const RULES = [
72
- { t: 'Stay under 32B', d: 'Every model must be under 32B parameters. Combine several small models if you like β€” but each one’s total parameter count must stay below the cap.' },
73
- { t: 'Ship a Gradio app', d: 'Deploy your project as a Gradio App inside the official Build Small org on Hugging Face. Docker is fine, as long as the interface is a Gradio Space.' },
74
- { t: 'Record a demo', d: 'Submit a demo video showing your app working β€” so judges can evaluate it even if GPU or API limits stop a live run.' },
75
- { t: 'Post it', d: 'Create one social-media post showcasing your app, and link to it from your Space README.' },
76
- { t: 'Mind the GPU limit', d: 'Submit as many apps as you like. If you rely on the provided Zero GPU resources, you’re limited to 10 Zero GPU apps per user.' },
77
- { t: 'Tag your README', d: 'Add tags for the tracks and badges you want to be considered for to the yaml block at the top of your README, plus a short write-up of the idea and tech.' }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  ];
79
 
80
  export const PRIZE_FILTERS = [
@@ -161,7 +179,8 @@ export const SPONSOR_PRIZES: SponsorPrize[] = [
161
  { place: '2nd', award: '7,000 credits' },
162
  { place: '3rd', award: '3,000 credits' }
163
  ],
164
- criteria: 'Use Modal for the development or runtime of your app, and note it in your Space README.',
 
165
  accent: 'amber',
166
  notes: [
167
  '$20,000 in Modal credits total, judged on best use of the platform.',
@@ -180,12 +199,54 @@ export type BonusBadgeT = {
180
  };
181
 
182
  export const BONUS_BADGES: BonusBadgeT[] = [
183
- { id: 'offbrand', name: 'Off Brand', glyph: 'swap', blurb: 'The best custom UI that pushes past the default Gradio look.', award: '$1,500', note: 'gr.Server is your friend β€” go well beyond the stock components and make it yours.' },
184
- { id: 'tiny', name: 'Tiny Titan', glyph: 'mountain', blurb: 'The best app built on a genuinely tiny model.', award: '$1,500', note: 'Models must be ≀ 4B parameters. Biggest impact from the smallest weights wins.' },
185
- { id: 'demo', name: 'Best Demo', glyph: 'play', blurb: 'The full package: great app, great demo video, great social post.', award: '$1,000', note: 'Storytelling counts as much as the build β€” sell it across all three.' },
186
- { id: 'agent', name: 'Best Agent', glyph: 'compass', blurb: 'The best agentic app.', award: '$1,000', note: 'Multi-step tool use and planning β€” all under the 32B cap, of course.' },
187
- { id: 'quest', name: 'Bonus Quest Champion', glyph: 'flag', blurb: 'The most bonus criteria met across the board.', award: '$2,000', note: 'Ties go to the most ambitious, highest-quality submission despite the extra constraints.' },
188
- { id: 'wildcard', name: 'Judges’ Wildcard', glyph: 'star', blurb: 'For the entry that’s amazing but fits no category.', award: '$1,000', note: 'No entry needed β€” every submission is in the running. We know you’re out there.' }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
  ];
190
 
191
  export type Achievement = {
@@ -199,12 +260,48 @@ export type Achievement = {
199
  // Bonus badges with no prize β€” objective, self-declarable achievements a
200
  // participant tags their own Space for (verified by us at judging time).
201
  export const ACHIEVEMENTS: Achievement[] = [
202
- { id: 'offgrid', name: 'Off the Grid', glyph: 'unplug', label: 'Local-first', blurb: 'No cloud APIs. The whole thing runs on the model in front of you.' },
203
- { id: 'welltuned', name: 'Well-Tuned', glyph: 'target', label: 'Fine-tuned', blurb: 'Your app uses a fine-tuned model you’ve published on Hugging Face.' },
204
- { id: 'offbrand', name: 'Off-Brand', glyph: 'palette', label: 'Custom UI', blurb: 'A custom frontend that pushes past the default Gradio look (hint: see gr.Server).' },
205
- { id: 'llama', name: 'Llama Champion', glyph: 'cpu', label: 'llama.cpp', blurb: 'Your model runs through the llama.cpp runtime.' },
206
- { id: 'sharing', name: 'Sharing is Caring', glyph: 'share', label: 'Open trace', blurb: 'You shared your agent trace on the Hub for everyone to learn from.' },
207
- { id: 'fieldnotes', name: 'Field Notes', glyph: 'notebook', label: 'Write-up', blurb: 'You wrote a blog post or report about what you built and what you learned.' }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
  ];
209
 
210
  export type PartnerSummary = {
@@ -218,13 +315,69 @@ export type PartnerSummary = {
218
  };
219
 
220
  export const PARTNERS: PartnerSummary[] = [
221
- { id: 'openbmb', name: 'OpenBMB', accent: '#3d6a55', one: 'MiniCPM family β€” tiny, capable text Β· vision Β· audio Β· omni models (1B–8B).', sponsors: true, size: 'rich', tag: '1B–8B models' },
222
- { id: 'blackforest', name: 'Black Forest Labs', accent: '#5a4b73', one: 'FLUX.2 Klein β€” text-to-image & precise image editing at 4B / 9B.', sponsors: false, size: 'light', tag: 'image gen' },
223
- { id: 'openai', name: 'OpenAI Β· Codex', accent: '#1f7a6b', one: 'Codex coding agent (GPT-5.5) with GitHub, Figma & Hugging Face plugins.', sponsors: true, size: 'infra', tag: 'coding agent' },
224
- { id: 'nvidia', name: 'NVIDIA', accent: '#4d7a2e', one: 'Nemotron 3 family β€” Nano Β· Omni Β· ASR Β· Parse Β· Embed.', sponsors: true, size: 'rich', tag: 'model suite' },
225
- { id: 'modal', name: 'Modal', accent: '#2f6f8f', one: 'Serverless compute for inference, training, batch & sandboxes.', sponsors: true, size: 'infra', tag: 'compute' },
226
- { id: 'jetbrains', name: 'JetBrains', accent: '#b0453d', one: 'Mellum 2 β€” 12B MoE coding models, Thinking & Instruct.', sponsors: false, size: 'light', tag: '12B MoE' },
227
- { id: 'cohere', name: 'Cohere Labs', accent: '#9c5a2b', one: 'Cohere Transcribe (ASR) and Tiny Aya multilingual models.', sponsors: false, size: 'rich', tag: 'ASR Β· multilingual' }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
  ];
229
 
230
  // Brand logos, keyed by partner id (shared across PARTNERS, SPONSOR_PRIZES and partner pages).
@@ -254,9 +407,21 @@ export const BUILD_NEEDS: BuildNeed[] = [
254
  glyph: 'image',
255
  desc: 'Read documents, understand photos, or generate & edit images.',
256
  matches: [
257
- { partner: 'openbmb', model: 'MiniCPM-V 4.6', why: 'Vision-language at ~1.3B β€” strong OCR & document understanding.' },
258
- { partner: 'blackforest', model: 'FLUX.2 Klein', why: 'Generate and edit images locally at 4B / 9B.' },
259
- { partner: 'nvidia', model: 'Nemotron Parse', why: 'Sub-1B structured extraction from complex documents.' }
 
 
 
 
 
 
 
 
 
 
 
 
260
  ]
261
  },
262
  {
@@ -265,9 +430,17 @@ export const BUILD_NEEDS: BuildNeed[] = [
265
  glyph: 'wave',
266
  desc: 'Transcribe speech, build a voice assistant, or process audio.',
267
  matches: [
268
- { partner: 'cohere', model: 'Cohere Transcribe', why: '2B ASR covering 14 languages β€” runs on the edge.' },
 
 
 
 
269
  { partner: 'nvidia', model: 'Nemotron 3 ASR', why: 'Fast, compact speech recognition.' },
270
- { partner: 'openbmb', model: 'MiniCPM-o 4.5', why: 'Full-duplex omni model β€” speech in, speech out.' }
 
 
 
 
271
  ]
272
  },
273
  {
@@ -276,9 +449,21 @@ export const BUILD_NEEDS: BuildNeed[] = [
276
  glyph: 'chat',
277
  desc: 'A focused chatbot, summarizer, or helper that fits in your pocket.',
278
  matches: [
279
- { partner: 'openbmb', model: 'MiniCPM5-1B', why: 'A featherweight text model that runs local-first.' },
280
- { partner: 'nvidia', model: 'Nemotron 3 Nano 4B', why: 'Edge-ready reasoning in a tiny footprint.' },
281
- { partner: 'cohere', model: 'Tiny Aya', why: 'Multilingual out of the box across many language families.' }
 
 
 
 
 
 
 
 
 
 
 
 
282
  ]
283
  },
284
  {
@@ -287,8 +472,16 @@ export const BUILD_NEEDS: BuildNeed[] = [
287
  glyph: 'code',
288
  desc: 'An agent that writes, reviews, or refactors code.',
289
  matches: [
290
- { partner: 'jetbrains', model: 'Mellum 2', why: '12B MoE tuned for code β€” Thinking & Instruct.' },
291
- { partner: 'nvidia', model: 'Nemotron Cascade', why: 'Specialist tuned for math and code tasks.' }
 
 
 
 
 
 
 
 
292
  ]
293
  },
294
  {
@@ -297,34 +490,101 @@ export const BUILD_NEEDS: BuildNeed[] = [
297
  glyph: 'server',
298
  desc: 'Fine-tune, run batch jobs, or serve your model at scale.',
299
  matches: [
300
- { partner: 'modal', model: 'Modal Sandboxes', why: 'Serverless GPUs for inference, training & untrusted code.' }
 
 
 
 
301
  ]
302
  }
303
  ];
304
 
305
  export const SUBMIT_STEPS = [
306
- { t: 'Meet the criteria', d: 'Double-check your build satisfies the entry rules and any prize criteria you’re targeting.' },
307
- { t: 'Join the org', d: 'Join the Build Small hackathon organisation on Hugging Face β€” your home base for the jam.' },
 
 
 
 
 
 
308
  { t: 'Upload your Space', d: 'Upload your submission as a Gradio Space inside the org.' },
309
- { t: 'Record a demo', d: 'Film a demo selling your Space β€” no humility. Put it on YouTube, upload it to the Space, or host it publicly.' },
 
 
 
310
  { t: 'Post on social', d: 'Share one post about your build on social media.' },
311
- { t: 'Update your README', d: 'Add links to the post and demo video, tags for tracks + badges in the yaml block at the top, and a short write-up of the idea and tech.' }
 
 
 
312
  ];
313
 
314
  export const FAQS = [
315
- { q: 'What does β€œunder 32B” actually mean?', a: 'Every model your project depends on must have under 32B total parameters (not just active parameters). You can freely combine several models β€” say a 14B text model, a 7B speech model, and a 12B image model β€” as long as each one individually stays under the cap.' },
316
- { q: 'Do I have to use a sponsor’s model?', a: 'No. For the general tracks you can use any model you like, including ones from non-sponsoring orgs, as long as it stays under the 32B cap. Sponsor prizes each require their own model or platform and have their own criteria, listed on every prize.' },
317
- { q: 'Do I need to exclusively use a sponsor’s models to win their prize?', a: 'No β€” you can use other providers’ models too. But the sponsor’s models must form a core part of the experience, since each sponsor is looking for the most interesting use of their own models.' },
318
- { q: 'Am I eligible for the OpenAI Codex prize if I didn’t get free Codex credits?', a: 'Yes. The credits given to the first 1,000 registrants aren’t tied to the prize. As long as you use Codex during development and have Codex-attributed commits in the Space’s history or a linked GitHub repo, you’re eligible.' },
319
- { q: 'Is there a GPU limit?', a: 'If you rely on the provided Zero GPU resources, you’re limited to 10 Zero GPU apps per user. Otherwise, run on consumer hardware or use Modal credits.' },
320
- { q: 'Can I use a hosted API instead of running locally?', a: 'Yes. Several partners (like OpenBMB) provide free hosted API access, and Hugging Face inference providers offer plenty more options. That said, not every model is available via a cloud API, and some prizes require running locally to be eligible.' },
321
- { q: 'Can one project win multiple prizes?', a: 'Yes β€” awards stack. One Space could place in a general track (say Backyard AI), win sponsor prizes (like OpenBMB’s Best MiniCPM Build, the OpenAI Codex prize, and Best Use of Modal), and still pick up bonus badges such as Tiny Titan if every model is ≀ 4B β€” all at once.' },
322
- { q: 'Can I submit multiple apps?', a: 'Yes β€” submit as many apps as you like, and each one will be considered.' },
323
- { q: 'How do I submit?', a: 'Ship a Gradio Space in the org, record a demo video, post on social, and update your README with links and tags. See the trail map above for the full sequence.' }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
  ];
325
 
326
  export const FOOTER_LINKS: [string, [string, string][]][] = [
327
- ['Explore', [['The idea', '/#idea'], ['Tracks', '/#tracks'], ['Prizes', '/#prizes'], ['Partners', '/#partners']]],
328
- ['Take part', [['Rules', '/#rules'], ['Find your kit', '/#recommender'], ['Submit', '/submit'], ['FAQ', '/#faq']]],
329
- ['Elsewhere', [['HF Org', 'https://huggingface.co/BuildSmall'], ['Gradio', 'https://gradio.app'], ['Hugging Face', 'https://huggingface.co'], ['X / Twitter', 'https://x.com/buildsmall']]]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
330
  ];
 
12
  { id: 'prizes', label: 'prizes' },
13
  { id: 'recommender', label: 'find your kit' },
14
  { id: 'partners', label: 'partners' },
15
+ { id: 'submit', label: 'validate' },
16
  { id: 'faq', label: 'faq' }
17
  ];
18
 
 
69
  ];
70
 
71
  export const RULES = [
72
+ {
73
+ t: 'Stay under 32B',
74
+ d: 'Every model must be under 32B parameters. Combine several small models if you like β€” but each one’s total parameter count must stay below the cap.'
75
+ },
76
+ {
77
+ t: 'Ship a Gradio app',
78
+ d: 'Deploy your project as a Gradio App inside the official Build Small org on Hugging Face. Docker is fine, as long as the interface is a Gradio Space.'
79
+ },
80
+ {
81
+ t: 'Record a demo',
82
+ d: 'Submit a demo video showing your app working β€” so judges can evaluate it even if GPU or API limits stop a live run.'
83
+ },
84
+ {
85
+ t: 'Post it',
86
+ d: 'Create one social-media post showcasing your app, and link to it from your Space README.'
87
+ },
88
+ {
89
+ t: 'Mind the GPU limit',
90
+ d: 'Submit as many apps as you like. If you rely on the provided Zero GPU resources, you’re limited to 10 Zero GPU apps per user.'
91
+ },
92
+ {
93
+ t: 'Tag your README',
94
+ d: 'Add tags for the tracks and badges you want to be considered for to the yaml block at the top of your README, plus a short write-up of the idea and tech.'
95
+ }
96
  ];
97
 
98
  export const PRIZE_FILTERS = [
 
179
  { place: '2nd', award: '7,000 credits' },
180
  { place: '3rd', award: '3,000 credits' }
181
  ],
182
+ criteria:
183
+ 'Use Modal for the development or runtime of your app, and note it in your Space README.',
184
  accent: 'amber',
185
  notes: [
186
  '$20,000 in Modal credits total, judged on best use of the platform.',
 
199
  };
200
 
201
  export const BONUS_BADGES: BonusBadgeT[] = [
202
+ {
203
+ id: 'offbrand',
204
+ name: 'Off Brand',
205
+ glyph: 'swap',
206
+ blurb: 'The best custom UI that pushes past the default Gradio look.',
207
+ award: '$1,500',
208
+ note: 'gr.Server is your friend β€” go well beyond the stock components and make it yours.'
209
+ },
210
+ {
211
+ id: 'tiny',
212
+ name: 'Tiny Titan',
213
+ glyph: 'mountain',
214
+ blurb: 'The best app built on a genuinely tiny model.',
215
+ award: '$1,500',
216
+ note: 'Models must be ≀ 4B parameters. Biggest impact from the smallest weights wins.'
217
+ },
218
+ {
219
+ id: 'demo',
220
+ name: 'Best Demo',
221
+ glyph: 'play',
222
+ blurb: 'The full package: great app, great demo video, great social post.',
223
+ award: '$1,000',
224
+ note: 'Storytelling counts as much as the build β€” sell it across all three.'
225
+ },
226
+ {
227
+ id: 'agent',
228
+ name: 'Best Agent',
229
+ glyph: 'compass',
230
+ blurb: 'The best agentic app.',
231
+ award: '$1,000',
232
+ note: 'Multi-step tool use and planning β€” all under the 32B cap, of course.'
233
+ },
234
+ {
235
+ id: 'quest',
236
+ name: 'Bonus Quest Champion',
237
+ glyph: 'flag',
238
+ blurb: 'The most bonus criteria met across the board.',
239
+ award: '$2,000',
240
+ note: 'Ties go to the most ambitious, highest-quality submission despite the extra constraints.'
241
+ },
242
+ {
243
+ id: 'wildcard',
244
+ name: 'Judges’ Wildcard',
245
+ glyph: 'star',
246
+ blurb: 'For the entry that’s amazing but fits no category.',
247
+ award: '$1,000',
248
+ note: 'No entry needed β€” every submission is in the running. We know you’re out there.'
249
+ }
250
  ];
251
 
252
  export type Achievement = {
 
260
  // Bonus badges with no prize β€” objective, self-declarable achievements a
261
  // participant tags their own Space for (verified by us at judging time).
262
  export const ACHIEVEMENTS: Achievement[] = [
263
+ {
264
+ id: 'offgrid',
265
+ name: 'Off the Grid',
266
+ glyph: 'unplug',
267
+ label: 'Local-first',
268
+ blurb: 'No cloud APIs. The whole thing runs on the model in front of you.'
269
+ },
270
+ {
271
+ id: 'welltuned',
272
+ name: 'Well-Tuned',
273
+ glyph: 'target',
274
+ label: 'Fine-tuned',
275
+ blurb: 'Your app uses a fine-tuned model you’ve published on Hugging Face.'
276
+ },
277
+ {
278
+ id: 'offbrand',
279
+ name: 'Off-Brand',
280
+ glyph: 'palette',
281
+ label: 'Custom UI',
282
+ blurb: 'A custom frontend that pushes past the default Gradio look (hint: see gr.Server).'
283
+ },
284
+ {
285
+ id: 'llama',
286
+ name: 'Llama Champion',
287
+ glyph: 'cpu',
288
+ label: 'llama.cpp',
289
+ blurb: 'Your model runs through the llama.cpp runtime.'
290
+ },
291
+ {
292
+ id: 'sharing',
293
+ name: 'Sharing is Caring',
294
+ glyph: 'share',
295
+ label: 'Open trace',
296
+ blurb: 'You shared your agent trace on the Hub for everyone to learn from.'
297
+ },
298
+ {
299
+ id: 'fieldnotes',
300
+ name: 'Field Notes',
301
+ glyph: 'notebook',
302
+ label: 'Write-up',
303
+ blurb: 'You wrote a blog post or report about what you built and what you learned.'
304
+ }
305
  ];
306
 
307
  export type PartnerSummary = {
 
315
  };
316
 
317
  export const PARTNERS: PartnerSummary[] = [
318
+ {
319
+ id: 'openbmb',
320
+ name: 'OpenBMB',
321
+ accent: '#3d6a55',
322
+ one: 'MiniCPM family β€” tiny, capable text Β· vision Β· audio Β· omni models (1B–8B).',
323
+ sponsors: true,
324
+ size: 'rich',
325
+ tag: '1B–8B models'
326
+ },
327
+ {
328
+ id: 'blackforest',
329
+ name: 'Black Forest Labs',
330
+ accent: '#5a4b73',
331
+ one: 'FLUX.2 Klein β€” text-to-image & precise image editing at 4B / 9B.',
332
+ sponsors: false,
333
+ size: 'light',
334
+ tag: 'image gen'
335
+ },
336
+ {
337
+ id: 'openai',
338
+ name: 'OpenAI Β· Codex',
339
+ accent: '#1f7a6b',
340
+ one: 'Codex coding agent (GPT-5.5) with GitHub, Figma & Hugging Face plugins.',
341
+ sponsors: true,
342
+ size: 'infra',
343
+ tag: 'coding agent'
344
+ },
345
+ {
346
+ id: 'nvidia',
347
+ name: 'NVIDIA',
348
+ accent: '#4d7a2e',
349
+ one: 'Nemotron 3 family β€” Nano Β· Omni Β· ASR Β· Parse Β· Embed.',
350
+ sponsors: true,
351
+ size: 'rich',
352
+ tag: 'model suite'
353
+ },
354
+ {
355
+ id: 'modal',
356
+ name: 'Modal',
357
+ accent: '#2f6f8f',
358
+ one: 'Serverless compute for inference, training, batch & sandboxes.',
359
+ sponsors: true,
360
+ size: 'infra',
361
+ tag: 'compute'
362
+ },
363
+ {
364
+ id: 'jetbrains',
365
+ name: 'JetBrains',
366
+ accent: '#b0453d',
367
+ one: 'Mellum 2 β€” 12B MoE coding models, Thinking & Instruct.',
368
+ sponsors: false,
369
+ size: 'light',
370
+ tag: '12B MoE'
371
+ },
372
+ {
373
+ id: 'cohere',
374
+ name: 'Cohere Labs',
375
+ accent: '#9c5a2b',
376
+ one: 'Cohere Transcribe (ASR) and Tiny Aya multilingual models.',
377
+ sponsors: false,
378
+ size: 'rich',
379
+ tag: 'ASR Β· multilingual'
380
+ }
381
  ];
382
 
383
  // Brand logos, keyed by partner id (shared across PARTNERS, SPONSOR_PRIZES and partner pages).
 
407
  glyph: 'image',
408
  desc: 'Read documents, understand photos, or generate & edit images.',
409
  matches: [
410
+ {
411
+ partner: 'openbmb',
412
+ model: 'MiniCPM-V 4.6',
413
+ why: 'Vision-language at ~1.3B β€” strong OCR & document understanding.'
414
+ },
415
+ {
416
+ partner: 'blackforest',
417
+ model: 'FLUX.2 Klein',
418
+ why: 'Generate and edit images locally at 4B / 9B.'
419
+ },
420
+ {
421
+ partner: 'nvidia',
422
+ model: 'Nemotron Parse',
423
+ why: 'Sub-1B structured extraction from complex documents.'
424
+ }
425
  ]
426
  },
427
  {
 
430
  glyph: 'wave',
431
  desc: 'Transcribe speech, build a voice assistant, or process audio.',
432
  matches: [
433
+ {
434
+ partner: 'cohere',
435
+ model: 'Cohere Transcribe',
436
+ why: '2B ASR covering 14 languages β€” runs on the edge.'
437
+ },
438
  { partner: 'nvidia', model: 'Nemotron 3 ASR', why: 'Fast, compact speech recognition.' },
439
+ {
440
+ partner: 'openbmb',
441
+ model: 'MiniCPM-o 4.5',
442
+ why: 'Full-duplex omni model β€” speech in, speech out.'
443
+ }
444
  ]
445
  },
446
  {
 
449
  glyph: 'chat',
450
  desc: 'A focused chatbot, summarizer, or helper that fits in your pocket.',
451
  matches: [
452
+ {
453
+ partner: 'openbmb',
454
+ model: 'MiniCPM5-1B',
455
+ why: 'A featherweight text model that runs local-first.'
456
+ },
457
+ {
458
+ partner: 'nvidia',
459
+ model: 'Nemotron 3 Nano 4B',
460
+ why: 'Edge-ready reasoning in a tiny footprint.'
461
+ },
462
+ {
463
+ partner: 'cohere',
464
+ model: 'Tiny Aya',
465
+ why: 'Multilingual out of the box across many language families.'
466
+ }
467
  ]
468
  },
469
  {
 
472
  glyph: 'code',
473
  desc: 'An agent that writes, reviews, or refactors code.',
474
  matches: [
475
+ {
476
+ partner: 'jetbrains',
477
+ model: 'Mellum 2',
478
+ why: '12B MoE tuned for code β€” Thinking & Instruct.'
479
+ },
480
+ {
481
+ partner: 'nvidia',
482
+ model: 'Nemotron Cascade',
483
+ why: 'Specialist tuned for math and code tasks.'
484
+ }
485
  ]
486
  },
487
  {
 
490
  glyph: 'server',
491
  desc: 'Fine-tune, run batch jobs, or serve your model at scale.',
492
  matches: [
493
+ {
494
+ partner: 'modal',
495
+ model: 'Modal Sandboxes',
496
+ why: 'Serverless GPUs for inference, training & untrusted code.'
497
+ }
498
  ]
499
  }
500
  ];
501
 
502
  export const SUBMIT_STEPS = [
503
+ {
504
+ t: 'Meet the criteria',
505
+ d: 'Double-check your build satisfies the entry rules and any prize criteria you’re targeting.'
506
+ },
507
+ {
508
+ t: 'Join the org',
509
+ d: 'Join the Build Small hackathon organisation on Hugging Face β€” your home base for the jam.'
510
+ },
511
  { t: 'Upload your Space', d: 'Upload your submission as a Gradio Space inside the org.' },
512
+ {
513
+ t: 'Record a demo',
514
+ d: 'Film a demo selling your Space β€” no humility. Put it on YouTube, upload it to the Space, or host it publicly.'
515
+ },
516
  { t: 'Post on social', d: 'Share one post about your build on social media.' },
517
+ {
518
+ t: 'Update your README',
519
+ d: 'Add links to the post and demo video, tags for tracks + badges in the yaml block at the top, and a short write-up of the idea and tech.'
520
+ }
521
  ];
522
 
523
  export const FAQS = [
524
+ {
525
+ q: 'What does β€œunder 32B” actually mean?',
526
+ a: 'Every model your project depends on must have under 32B total parameters (not just active parameters). You can freely combine several models β€” say a 14B text model, a 7B speech model, and a 12B image model β€” as long as each one individually stays under the cap.'
527
+ },
528
+ {
529
+ q: 'Do I have to use a sponsor’s model?',
530
+ a: 'No. For the general tracks you can use any model you like, including ones from non-sponsoring orgs, as long as it stays under the 32B cap. Sponsor prizes each require their own model or platform and have their own criteria, listed on every prize.'
531
+ },
532
+ {
533
+ q: 'Do I need to exclusively use a sponsor’s models to win their prize?',
534
+ a: 'No β€” you can use other providers’ models too. But the sponsor’s models must form a core part of the experience, since each sponsor is looking for the most interesting use of their own models.'
535
+ },
536
+ {
537
+ q: 'Am I eligible for the OpenAI Codex prize if I didn’t get free Codex credits?',
538
+ a: 'Yes. The credits given to the first 1,000 registrants aren’t tied to the prize. As long as you use Codex during development and have Codex-attributed commits in the Space’s history or a linked GitHub repo, you’re eligible.'
539
+ },
540
+ {
541
+ q: 'Is there a GPU limit?',
542
+ a: 'If you rely on the provided Zero GPU resources, you’re limited to 10 Zero GPU apps per user. Otherwise, run on consumer hardware or use Modal credits.'
543
+ },
544
+ {
545
+ q: 'Can I use a hosted API instead of running locally?',
546
+ a: 'Yes. Several partners (like OpenBMB) provide free hosted API access, and Hugging Face inference providers offer plenty more options. That said, not every model is available via a cloud API, and some prizes require running locally to be eligible.'
547
+ },
548
+ {
549
+ q: 'Can one project win multiple prizes?',
550
+ a: 'Yes β€” awards stack. One Space could place in a general track (say Backyard AI), win sponsor prizes (like OpenBMB’s Best MiniCPM Build, the OpenAI Codex prize, and Best Use of Modal), and still pick up bonus badges such as Tiny Titan if every model is ≀ 4B β€” all at once.'
551
+ },
552
+ {
553
+ q: 'Can I submit multiple apps?',
554
+ a: 'Yes β€” submit as many apps as you like, and each one will be considered.'
555
+ },
556
+ {
557
+ q: 'How do I submit?',
558
+ a: 'Ship a Gradio Space in the org, record a demo video, post on social, and update your README with links and tags. See the trail map above for the full sequence.'
559
+ }
560
  ];
561
 
562
  export const FOOTER_LINKS: [string, [string, string][]][] = [
563
+ [
564
+ 'Explore',
565
+ [
566
+ ['The idea', '/#idea'],
567
+ ['Tracks', '/#tracks'],
568
+ ['Prizes', '/#prizes'],
569
+ ['Partners', '/#partners']
570
+ ]
571
+ ],
572
+ [
573
+ 'Take part',
574
+ [
575
+ ['Rules', '/#rules'],
576
+ ['Find your kit', '/#recommender'],
577
+ ['Validate README', '/submit'],
578
+ ['FAQ', '/#faq']
579
+ ]
580
+ ],
581
+ [
582
+ 'Elsewhere',
583
+ [
584
+ ['HF Org', 'https://huggingface.co/BuildSmall'],
585
+ ['Gradio', 'https://gradio.app'],
586
+ ['Hugging Face', 'https://huggingface.co'],
587
+ ['X / Twitter', 'https://x.com/buildsmall']
588
+ ]
589
+ ]
590
  ];
src/lib/readme.ts CHANGED
@@ -49,6 +49,8 @@ export interface ParsedReadme {
49
  body: string;
50
  /** Tags parsed from the frontmatter `tags:` declaration. */
51
  tags: string[];
 
 
52
  /** Char range of the whole `tags:` declaration within `frontmatter`, if present. */
53
  tagsRange: { start: number; end: number } | null;
54
  }
@@ -58,7 +60,9 @@ const FRONTMATTER_RE = /^ο»Ώ?---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*\r?\n?/;
58
  const unquote = (s: string) => s.trim().replace(/^['"]|['"]$/g, '');
59
 
60
  /** Locate and parse the `tags:` declaration inside a frontmatter block. */
61
- function findTagsBlock(fm: string): { tags: string[]; start: number; end: number } | null {
 
 
62
  const lines = fm.split('\n');
63
  const offsets: number[] = [];
64
  let acc = 0;
@@ -80,24 +84,28 @@ function findTagsBlock(fm: string): { tags: string[]; start: number; end: number
80
  .split(',')
81
  .map(unquote)
82
  .filter(Boolean);
83
- return { tags, start, end: offsets[i] + lines[i].length };
84
  }
85
 
86
  // single scalar on the same line: tags: foo
87
  if (inline) {
88
- return { tags: [unquote(inline)], start, end: offsets[i] + lines[i].length };
89
  }
90
 
91
- // block form: subsequent indented "- item" lines
 
 
92
  const tags: string[] = [];
 
93
  let last = i;
94
  for (let j = i + 1; j < lines.length; j++) {
95
- const lm = /^[ \t]+-[ \t]+(.*)$/.exec(lines[j]);
96
  if (!lm) break; // any non list-item line (including blank) ends the block
97
- tags.push(unquote(lm[1]));
 
98
  last = j;
99
  }
100
- return { tags, start, end: offsets[last] + lines[last].length };
101
  }
102
  return null;
103
  }
@@ -105,7 +113,14 @@ function findTagsBlock(fm: string): { tags: string[]; start: number; end: number
105
  export function parseReadme(raw: string): ParsedReadme {
106
  const m = FRONTMATTER_RE.exec(raw);
107
  if (!m) {
108
- return { hasFrontmatter: false, frontmatter: '', body: raw, tags: [], tagsRange: null };
 
 
 
 
 
 
 
109
  }
110
  const frontmatter = m[1];
111
  const body = raw.slice(m[0].length);
@@ -115,6 +130,7 @@ export function parseReadme(raw: string): ParsedReadme {
115
  frontmatter,
116
  body,
117
  tags: block?.tags ?? [],
 
118
  tagsRange: block ? { start: block.start, end: block.end } : null
119
  };
120
  }
@@ -137,8 +153,8 @@ export function splitManaged(tags: string[]): {
137
  }
138
 
139
  /** The `tags:` YAML block for a list of tags, in block-list form. */
140
- export const tagsBlock = (tags: string[]) =>
141
- tags.length ? 'tags:\n' + tags.map((t) => ` - ${t}`).join('\n') : 'tags:';
142
 
143
  /**
144
  * Rewrite the README so its `tags:` list holds exactly the participant's
@@ -149,7 +165,7 @@ export function regenerateReadme(raw: string, selectedTags: string[]): string {
149
  const parsed = parseReadme(raw);
150
  const preserved = parsed.tags.filter((t) => !isManagedTag(t));
151
  const nextTags = [...preserved, ...selectedTags];
152
- const block = tagsBlock(nextTags);
153
 
154
  if (!parsed.hasFrontmatter) {
155
  return `---\n${block}\n---\n\n${parsed.body}`;
 
49
  body: string;
50
  /** Tags parsed from the frontmatter `tags:` declaration. */
51
  tags: string[];
52
+ /** Indentation of the existing `tags:` list items, so we can round-trip it. */
53
+ tagsIndent: string;
54
  /** Char range of the whole `tags:` declaration within `frontmatter`, if present. */
55
  tagsRange: { start: number; end: number } | null;
56
  }
 
60
  const unquote = (s: string) => s.trim().replace(/^['"]|['"]$/g, '');
61
 
62
  /** Locate and parse the `tags:` declaration inside a frontmatter block. */
63
+ function findTagsBlock(
64
+ fm: string
65
+ ): { tags: string[]; indent: string; start: number; end: number } | null {
66
  const lines = fm.split('\n');
67
  const offsets: number[] = [];
68
  let acc = 0;
 
84
  .split(',')
85
  .map(unquote)
86
  .filter(Boolean);
87
+ return { tags, indent: ' ', start, end: offsets[i] + lines[i].length };
88
  }
89
 
90
  // single scalar on the same line: tags: foo
91
  if (inline) {
92
+ return { tags: [unquote(inline)], indent: ' ', start, end: offsets[i] + lines[i].length };
93
  }
94
 
95
+ // block form: subsequent "- item" lines. YAML lets the sequence sit at the
96
+ // same indent as `tags:` (no extra indent) or be indented under it β€” accept
97
+ // either, and remember the indent the first item used so we round-trip it.
98
  const tags: string[] = [];
99
+ let indent = ' ';
100
  let last = i;
101
  for (let j = i + 1; j < lines.length; j++) {
102
+ const lm = /^([ \t]*)-[ \t]+(.*)$/.exec(lines[j]);
103
  if (!lm) break; // any non list-item line (including blank) ends the block
104
+ if (tags.length === 0) indent = lm[1]; // first item sets the indent
105
+ tags.push(unquote(lm[2]));
106
  last = j;
107
  }
108
+ return { tags, indent, start, end: offsets[last] + lines[last].length };
109
  }
110
  return null;
111
  }
 
113
  export function parseReadme(raw: string): ParsedReadme {
114
  const m = FRONTMATTER_RE.exec(raw);
115
  if (!m) {
116
+ return {
117
+ hasFrontmatter: false,
118
+ frontmatter: '',
119
+ body: raw,
120
+ tags: [],
121
+ tagsIndent: ' ',
122
+ tagsRange: null
123
+ };
124
  }
125
  const frontmatter = m[1];
126
  const body = raw.slice(m[0].length);
 
130
  frontmatter,
131
  body,
132
  tags: block?.tags ?? [],
133
+ tagsIndent: block?.indent ?? ' ',
134
  tagsRange: block ? { start: block.start, end: block.end } : null
135
  };
136
  }
 
153
  }
154
 
155
  /** The `tags:` YAML block for a list of tags, in block-list form. */
156
+ export const tagsBlock = (tags: string[], indent = ' ') =>
157
+ tags.length ? 'tags:\n' + tags.map((t) => `${indent}- ${t}`).join('\n') : 'tags:';
158
 
159
  /**
160
  * Rewrite the README so its `tags:` list holds exactly the participant's
 
165
  const parsed = parseReadme(raw);
166
  const preserved = parsed.tags.filter((t) => !isManagedTag(t));
167
  const nextTags = [...preserved, ...selectedTags];
168
+ const block = tagsBlock(nextTags, parsed.tagsIndent);
169
 
170
  if (!parsed.hasFrontmatter) {
171
  return `---\n${block}\n---\n\n${parsed.body}`;
src/routes/submit/+page.svelte CHANGED
@@ -140,22 +140,52 @@
140
  ]);
141
 
142
  // ---- step 4 Β· output + diff ----
143
- // loaded β†’ the whole README, rewritten in place
144
- // noReadme→ just the `tags:` block to paste into the README's yaml section
145
- const output = $derived(
 
 
 
 
 
 
 
 
 
 
146
  loaded
147
  ? regenerateReadme(raw, selectedTags)
148
  : selectedTags.length
149
  ? tagsBlock(selectedTags)
150
  : ''
151
  );
152
- const nextTags = $derived(loaded ? parseReadme(output).tags : selectedTags);
153
  const diff = $derived(diffTags(originalTags, nextTags));
154
  const addedSet = $derived(new Set(diff.added));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  const outputLines = $derived(output ? output.split('\n') : []);
156
 
157
  function addedLine(line: string): boolean {
158
- const m = /^\s+-\s+(.*)$/.exec(line);
159
  return m ? addedSet.has(m[1].trim()) : false;
160
  }
161
 
@@ -184,10 +214,10 @@
184
  </script>
185
 
186
  <svelte:head>
187
- <title>Submit Β· Build Small</title>
188
  <meta
189
  name="description"
190
- content="Run through the submission checklist, then tag your Build Small Space README with the tracks and badges you want to be considered for."
191
  />
192
  </svelte:head>
193
 
@@ -196,10 +226,10 @@
196
  <header class="section section--tight">
197
  <div class="wrap">
198
  <SectionHeader
199
- coord="β†’ submit"
200
- eyebrow="Submission station"
201
- title="Tag your build for the judges"
202
- lede="Run the pre-flight checks, point us at your Space, then pick the tracks and badges you want to be considered for. We update the tags at the top of your README β€” you commit it back on Hugging Face."
203
  />
204
  </div>
205
  </header>
@@ -212,7 +242,8 @@
212
  <span class="eyebrow">Pre-flight checklist</span>
213
  </div>
214
  <p class="lede" style="margin:0 0 22px">
215
- Tick these off before you submit. They're saved in this browser so you can come back to them.
 
216
  </p>
217
 
218
  <div class="chk">
@@ -224,7 +255,9 @@
224
  aria-pressed={checked[i]}
225
  onclick={() => (checked[i] = !checked[i])}
226
  >
227
- <span class="chk__box"><Icon name="check" size={15} sw={2.6} stroke="currentColor" /></span>
 
 
228
  <span>
229
  <span class="chk__t">{r.t}</span>
230
  <span class="chk__d">{r.d}</span>
@@ -235,7 +268,7 @@
235
 
236
  <p class="chk__status" data-done={allChecked}>
237
  {#if allChecked}
238
- <Icon name="check" size={15} sw={2.6} /> All checks done β€” you're ready to tag your Space.
239
  {:else}
240
  {checked.filter(Boolean).length} of {CHECKLIST.length} checked.
241
  {/if}
@@ -384,56 +417,107 @@
384
  <div class="wrap">
385
  <div class="steplabel">
386
  <span class="coord" style="color:rgba(244,238,225,.55)">STEP 04</span>
387
- <span class="eyebrow">{loaded ? 'Your tagged README' : 'Tags to add'}</span>
 
 
 
 
 
 
388
  </div>
389
- {#if loaded}
390
- <p class="lede" style="margin:0 0 20px">
391
- Copy this over your Space's <code>README.md</code> (or download it), then commit on Hugging
392
- Face. Only the <code>tags:</code> block changes β€” everything else is left exactly as it was.
393
- </p>
 
 
 
 
 
 
 
394
  {:else}
395
- <p class="lede" style="margin:0 0 20px">
396
- We couldn't load a README for this Space, so we can't rewrite it for you. Pick your tags
397
- above, then add this block to the yaml section at the top of your <code>README.md</code> β€”
398
- the part fenced by <code>---</code> lines at the very top of the file.
399
- </p>
400
- {/if}
 
 
 
401
 
402
- <div class="changes">
403
- {#if diff.added.length || diff.removed.length}
404
- {#each diff.added as t (t)}<span class="diffchip diffchip--add">+ {t}</span>{/each}
405
- {#each diff.removed as t (t)}<span class="diffchip diffchip--del">βˆ’ {t}</span>{/each}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
406
  {:else}
407
- <span class="diffchip diffchip--none">Nothing selected yet β€” pick tracks or badges above</span>
 
 
 
 
408
  {/if}
409
- </div>
410
 
411
- <div class="outhead">
412
- <button class="btn btn--ghost" onclick={copy} disabled={!output}>
413
- <Icon name={copied ? 'check' : 'doc'} size={15} stroke="var(--ink)" sw={2} />
414
- {copied ? 'Copied' : loaded ? 'Copy README' : 'Copy tags'}
415
- </button>
416
- {#if loaded}
417
- <button class="btn btn--ghost" onclick={download}>
418
- <Icon name="arrow" size={15} stroke="var(--ink)" rotate={90} />
419
- Download README.md
420
- </button>
421
- <a
422
- class="btn btn--ghost"
423
- href="{spaceUrl}/edit/main/README.md"
424
- target="_blank"
425
- rel="noreferrer"
426
- >
427
- Edit on Hugging Face <Icon name="link" size={15} stroke="var(--ink)" />
428
- </a>
429
- {/if}
430
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
431
 
432
- {#if output}
433
- <div class="code">
434
- <pre>{#each outputLines as line, i (i)}<span class:added={addedLine(line)}>{line}
 
435
  </span>{/each}</pre>
436
- </div>
 
 
437
  {/if}
438
  </div>
439
  </section>
@@ -769,6 +853,70 @@
769
  }
770
 
771
  /* ---- step 4 Β· output ---- */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
772
  .changes {
773
  display: flex;
774
  flex-wrap: wrap;
 
140
  ]);
141
 
142
  // ---- step 4 Β· output + diff ----
143
+ // Comparing the picked tags against the ones already in the README splits
144
+ // the page into four outcomes:
145
+ // 'empty' β€” nothing picked yet (and nothing to remove).
146
+ // 'complete' β€” every pick is already in the README, nothing to remove:
147
+ // no copy/paste window, just a "you're done" message.
148
+ // 'partial' β€” the README already has some picks: show the whole rewritten
149
+ // README with the missing tags merged in (and highlighted).
150
+ // 'full' β€” none of the picks are present yet, or we couldn't load a
151
+ // README, or tags need removing: hand over the whole rewritten
152
+ // README (loaded) or a fresh `tags:` block (no README).
153
+ // Both 'partial' and 'full' show the entire file β€” easier to copy-paste over
154
+ // the README in one shot than to splice in a few lines by hand.
155
+ const fullOutput = $derived(
156
  loaded
157
  ? regenerateReadme(raw, selectedTags)
158
  : selectedTags.length
159
  ? tagsBlock(selectedTags)
160
  : ''
161
  );
162
+ const nextTags = $derived(loaded ? parseReadme(fullOutput).tags : selectedTags);
163
  const diff = $derived(diffTags(originalTags, nextTags));
164
  const addedSet = $derived(new Set(diff.added));
165
+
166
+ // picks that aren't in the README yet (= diff.added) β€” drives the partial copy
167
+ const originalSet = $derived(new Set(originalTags));
168
+ const missingTags = $derived(selectedTags.filter((t) => !originalSet.has(t)));
169
+ const anyPresent = $derived(selectedTags.some((t) => originalSet.has(t)));
170
+
171
+ type Phase = 'empty' | 'complete' | 'partial' | 'full';
172
+ const phase = $derived<Phase>(
173
+ selectedTags.length === 0 && diff.removed.length === 0
174
+ ? 'empty'
175
+ : diff.added.length === 0 && diff.removed.length === 0
176
+ ? 'complete'
177
+ : anyPresent && diff.removed.length === 0
178
+ ? 'partial'
179
+ : 'full'
180
+ );
181
+
182
+ // the text shown (and copied) β€” the whole regenerated README for partial/full,
183
+ // nothing for empty/complete
184
+ const output = $derived(phase === 'partial' || phase === 'full' ? fullOutput : '');
185
  const outputLines = $derived(output ? output.split('\n') : []);
186
 
187
  function addedLine(line: string): boolean {
188
+ const m = /^\s*-\s+(.*)$/.exec(line);
189
  return m ? addedSet.has(m[1].trim()) : false;
190
  }
191
 
 
214
  </script>
215
 
216
  <svelte:head>
217
+ <title>Validate your README Β· Build Small</title>
218
  <meta
219
  name="description"
220
+ content="You're submitted the moment your Gradio Space lands in the Build Small org. Run your README through the validator to make sure it's tagged for every track, prize and badge you're eligible for."
221
  />
222
  </svelte:head>
223
 
 
226
  <header class="section section--tight">
227
  <div class="wrap">
228
  <SectionHeader
229
+ coord="β†’ validate"
230
+ eyebrow="README validator"
231
+ title="Validate your submission"
232
+ lede="You're in the moment your Gradio Space lands in the Build Small org β€” there's no submit button to press. This validator checks your Space's README so it's tagged for every track, prize and badge you're eligible for, and the judges see the full picture."
233
  />
234
  </div>
235
  </header>
 
242
  <span class="eyebrow">Pre-flight checklist</span>
243
  </div>
244
  <p class="lede" style="margin:0 0 22px">
245
+ The entry requirements β€” make sure your build covers each one. They're saved in this browser
246
+ so you can come back to them.
247
  </p>
248
 
249
  <div class="chk">
 
255
  aria-pressed={checked[i]}
256
  onclick={() => (checked[i] = !checked[i])}
257
  >
258
+ <span class="chk__box"
259
+ ><Icon name="check" size={15} sw={2.6} stroke="currentColor" /></span
260
+ >
261
  <span>
262
  <span class="chk__t">{r.t}</span>
263
  <span class="chk__d">{r.d}</span>
 
268
 
269
  <p class="chk__status" data-done={allChecked}>
270
  {#if allChecked}
271
+ <Icon name="check" size={15} sw={2.6} /> All checks done β€” now validate your README below.
272
  {:else}
273
  {checked.filter(Boolean).length} of {CHECKLIST.length} checked.
274
  {/if}
 
417
  <div class="wrap">
418
  <div class="steplabel">
419
  <span class="coord" style="color:rgba(244,238,225,.55)">STEP 04</span>
420
+ <span class="eyebrow"
421
+ >{phase === 'complete'
422
+ ? 'All tags in place'
423
+ : loaded
424
+ ? 'Your tagged README'
425
+ : 'Tags to add'}</span
426
+ >
427
  </div>
428
+
429
+ {#if phase === 'complete'}
430
+ <!-- every selected tag is already in the README β€” nothing to do -->
431
+ <div class="allgood" role="status">
432
+ <Icon name="check" size={22} stroke="var(--forest)" sw={2.8} />
433
+ <span>
434
+ <strong>Your README checks out.</strong>
435
+ Every track and badge you've picked is already tagged, so the judges will see the full picture
436
+ β€” nothing to change here. You're entered as soon as your Space is in the org, so that's you
437
+ all set. <a href={spaceUrl} target="_blank" rel="noreferrer">View your Space</a> to double-check.
438
+ </span>
439
+ </div>
440
  {:else}
441
+ <div class="thisisit" role="note">
442
+ <Icon name="check" size={20} stroke="var(--forest-ink)" sw={2.6} />
443
+ <span>
444
+ <strong>This page doesn't submit for you.</strong> You're already entered the moment
445
+ your Gradio Space is in the org β€” this validator just makes sure your README is tagged
446
+ so the judges consider you for everything you're eligible for. Save the tags below to
447
+ your Space's <code>README.md</code> to lock it in.
448
+ </span>
449
+ </div>
450
 
451
+ {#if phase === 'empty'}
452
+ <p class="lede" style="margin:0 0 20px">
453
+ Pick the tracks, prizes and badges you want above β€” we'll show you exactly what to add
454
+ to your README.
455
+ </p>
456
+ {:else if phase === 'partial'}
457
+ <p class="lede" style="margin:0 0 20px">
458
+ <strong style="color:var(--amber)">Your README is missing some tags.</strong> It already
459
+ has some of your picks β€” here it is with the {missingTags.length === 1
460
+ ? 'missing one'
461
+ : 'missing ones'} merged in (highlighted below). Copy it over your Space's
462
+ <code>README.md</code> (or download it), then commit on Hugging Face.
463
+ </p>
464
+ {:else if loaded}
465
+ <p class="lede" style="margin:0 0 20px">
466
+ Copy this over your Space's <code>README.md</code> (or download it), then commit on
467
+ Hugging Face. Only the <code>tags:</code> block changes β€” everything else is left exactly
468
+ as it was.
469
+ </p>
470
  {:else}
471
+ <p class="lede" style="margin:0 0 20px">
472
+ We couldn't load a README for this Space, so we can't rewrite it for you. Pick your tags
473
+ above, then add this block to the yaml section at the top of your <code>README.md</code>
474
+ β€” the part fenced by <code>---</code> lines at the very top of the file.
475
+ </p>
476
  {/if}
 
477
 
478
+ <div class="changes">
479
+ {#if diff.added.length || diff.removed.length}
480
+ {#each diff.added as t (t)}<span class="diffchip diffchip--add">+ {t}</span>{/each}
481
+ {#each diff.removed as t (t)}<span class="diffchip diffchip--del">βˆ’ {t}</span>{/each}
482
+ {:else}
483
+ <span class="diffchip diffchip--none"
484
+ >Nothing selected yet β€” pick tracks or badges above</span
485
+ >
486
+ {/if}
487
+ </div>
488
+
489
+ {#if phase !== 'empty'}
490
+ <div class="outhead">
491
+ <button class="btn btn--ghost" onclick={copy} disabled={!output}>
492
+ <Icon name={copied ? 'check' : 'doc'} size={15} stroke="var(--ink)" sw={2} />
493
+ {copied ? 'Copied' : loaded ? 'Copy README' : 'Copy tags'}
494
+ </button>
495
+ {#if loaded}
496
+ <button class="btn btn--ghost" onclick={download}>
497
+ <Icon name="arrow" size={15} stroke="var(--ink)" rotate={90} />
498
+ Download README.md
499
+ </button>
500
+ {/if}
501
+ {#if loaded}
502
+ <a
503
+ class="btn btn--ghost"
504
+ href="{spaceUrl}/edit/main/README.md"
505
+ target="_blank"
506
+ rel="noreferrer"
507
+ >
508
+ Edit on Hugging Face <Icon name="link" size={15} stroke="var(--ink)" />
509
+ </a>
510
+ {/if}
511
+ </div>
512
 
513
+ {#if output}
514
+ <div class="code">
515
+ <pre>{#each outputLines as line, i (i)}<span class:added={addedLine(line)}
516
+ >{line}
517
  </span>{/each}</pre>
518
+ </div>
519
+ {/if}
520
+ {/if}
521
  {/if}
522
  </div>
523
  </section>
 
853
  }
854
 
855
  /* ---- step 4 Β· output ---- */
856
+ /* "this is the submission" callout β€” the key clarification for users who
857
+ expect a separate submit action. Bright amber so it can't be missed. */
858
+ .thisisit {
859
+ display: flex;
860
+ align-items: flex-start;
861
+ gap: 13px;
862
+ margin: 0 0 28px;
863
+ padding: 18px 20px;
864
+ border: 2px solid var(--forest-ink);
865
+ background: var(--amber);
866
+ box-shadow: 6px 6px 0 var(--forest-ink);
867
+ color: var(--ink);
868
+ font-size: 16px;
869
+ line-height: 1.5;
870
+ text-wrap: pretty;
871
+ }
872
+ .thisisit :global(svg) {
873
+ flex: none;
874
+ margin-top: 2px;
875
+ }
876
+ .thisisit strong {
877
+ font-family: var(--font-display);
878
+ font-weight: 700;
879
+ font-stretch: 106%;
880
+ }
881
+ .thisisit code {
882
+ font-family: var(--font-mono);
883
+ font-size: 0.9em;
884
+ background: color-mix(in srgb, var(--ink) 12%, transparent);
885
+ padding: 1px 6px;
886
+ }
887
+ /* "all tags present" success state β€” clean cream card so it reads as done */
888
+ .allgood {
889
+ display: flex;
890
+ align-items: flex-start;
891
+ gap: 14px;
892
+ margin: 0;
893
+ padding: 22px 24px;
894
+ border: 2px solid var(--forest-ink);
895
+ background: var(--paper);
896
+ box-shadow: 6px 6px 0 var(--amber);
897
+ color: var(--ink);
898
+ font-size: 16px;
899
+ line-height: 1.5;
900
+ text-wrap: pretty;
901
+ }
902
+ .allgood :global(svg) {
903
+ flex: none;
904
+ margin-top: 1px;
905
+ }
906
+ .allgood strong {
907
+ display: block;
908
+ margin-bottom: 4px;
909
+ font-family: var(--font-display);
910
+ font-weight: 700;
911
+ font-stretch: 106%;
912
+ font-size: 19px;
913
+ color: var(--forest);
914
+ }
915
+ .allgood a {
916
+ color: var(--forest);
917
+ text-decoration: underline;
918
+ font-weight: 600;
919
+ }
920
  .changes {
921
  display: flex;
922
  flex-wrap: wrap;