pngwn commited on
Commit Β·
fdf3925
1
Parent(s): e96f49a
fix
Browse files- src/lib/components/Countdown.svelte +10 -4
- src/lib/components/Nav.svelte +13 -10
- src/lib/components/SubmitTrail.svelte +5 -2
- src/lib/data/content.ts +314 -54
- src/lib/readme.ts +27 -11
- src/routes/submit/+page.svelte +204 -56
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
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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"
|
|
|
|
|
|
|
| 71 |
<a class="btn btn--accent countdown__cta" href="/submit">
|
| 72 |
-
|
| 73 |
</a>
|
| 74 |
</div>
|
| 75 |
{/if}
|
|
@@ -138,7 +144,7 @@
|
|
| 138 |
color: var(--ink-soft);
|
| 139 |
}
|
| 140 |
|
| 141 |
-
/* action bar β guides users straight to
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 64 |
-
|
| 65 |
-
|
|
|
|
|
|
|
| 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 |
-
|
| 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
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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: '
|
| 16 |
{ id: 'faq', label: 'faq' }
|
| 17 |
];
|
| 18 |
|
|
@@ -69,12 +69,30 @@ export const TRACKS = [
|
|
| 69 |
];
|
| 70 |
|
| 71 |
export const RULES = [
|
| 72 |
-
{
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
{
|
| 77 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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:
|
|
|
|
| 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 |
-
{
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
{
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 208 |
];
|
| 209 |
|
| 210 |
export type PartnerSummary = {
|
|
@@ -218,13 +315,69 @@ export type PartnerSummary = {
|
|
| 218 |
};
|
| 219 |
|
| 220 |
export const PARTNERS: PartnerSummary[] = [
|
| 221 |
-
{
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
{
|
| 258 |
-
|
| 259 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
{
|
|
|
|
|
|
|
|
|
|
|
|
|
| 269 |
{ partner: 'nvidia', model: 'Nemotron 3 ASR', why: 'Fast, compact speech recognition.' },
|
| 270 |
-
{
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
{
|
| 280 |
-
|
| 281 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
{
|
| 291 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
{
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
]
|
| 302 |
}
|
| 303 |
];
|
| 304 |
|
| 305 |
export const SUBMIT_STEPS = [
|
| 306 |
-
{
|
| 307 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 308 |
{ t: 'Upload your Space', d: 'Upload your submission as a Gradio Space inside the org.' },
|
| 309 |
-
{
|
|
|
|
|
|
|
|
|
|
| 310 |
{ t: 'Post on social', d: 'Share one post about your build on social media.' },
|
| 311 |
-
{
|
|
|
|
|
|
|
|
|
|
| 312 |
];
|
| 313 |
|
| 314 |
export const FAQS = [
|
| 315 |
-
{
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
{
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 324 |
];
|
| 325 |
|
| 326 |
export const FOOTER_LINKS: [string, [string, string][]][] = [
|
| 327 |
-
[
|
| 328 |
-
|
| 329 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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(
|
|
|
|
|
|
|
| 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
|
|
|
|
|
|
|
| 92 |
const tags: string[] = [];
|
|
|
|
| 93 |
let last = i;
|
| 94 |
for (let j = i + 1; j < lines.length; j++) {
|
| 95 |
-
const lm = /^[ \t]
|
| 96 |
if (!lm) break; // any non list-item line (including blank) ends the block
|
| 97 |
-
tags.
|
|
|
|
| 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 {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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) => `
|
| 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 |
-
//
|
| 144 |
-
//
|
| 145 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
loaded
|
| 147 |
? regenerateReadme(raw, selectedTags)
|
| 148 |
: selectedTags.length
|
| 149 |
? tagsBlock(selectedTags)
|
| 150 |
: ''
|
| 151 |
);
|
| 152 |
-
const nextTags = $derived(loaded ? parseReadme(
|
| 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
|
| 159 |
return m ? addedSet.has(m[1].trim()) : false;
|
| 160 |
}
|
| 161 |
|
|
@@ -184,10 +214,10 @@
|
|
| 184 |
</script>
|
| 185 |
|
| 186 |
<svelte:head>
|
| 187 |
-
<title>
|
| 188 |
<meta
|
| 189 |
name="description"
|
| 190 |
-
content="
|
| 191 |
/>
|
| 192 |
</svelte:head>
|
| 193 |
|
|
@@ -196,10 +226,10 @@
|
|
| 196 |
<header class="section section--tight">
|
| 197 |
<div class="wrap">
|
| 198 |
<SectionHeader
|
| 199 |
-
coord="β
|
| 200 |
-
eyebrow="
|
| 201 |
-
title="
|
| 202 |
-
lede="
|
| 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 |
-
|
|
|
|
| 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"
|
|
|
|
|
|
|
| 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 β
|
| 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"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 388 |
</div>
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 394 |
{:else}
|
| 395 |
-
<
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
|
|
|
|
|
|
|
|
|
| 401 |
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 406 |
{:else}
|
| 407 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
| 408 |
{/if}
|
| 409 |
-
</div>
|
| 410 |
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
|
| 425 |
-
|
| 426 |
-
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 431 |
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
|
|
|
|
| 435 |
</span>{/each}</pre>
|
| 436 |
-
|
|
|
|
|
|
|
| 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;
|