Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

developer-program.tsx

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

developer-program.tsxBlame688 lines · 1 contributor
adf5e18Claude1/**
2 * Developer Program page — /developer-program
3 *
4 * "Build on Gluecron. Earn revenue."
5 * Three sections: Publish an agent · Revenue share · Partner badge.
6 * CTA: partner application form (logs for now; no DB write required).
7 *
8 * Pure server-rendered. Dark theme. No new dependencies.
9 * All CSS scoped under .devprog-* to avoid leaking into app views.
10 */
11
12import { Hono } from "hono";
13import { Layout } from "../views/layout";
14import { softAuth } from "../middleware/auth";
15import type { AuthEnv } from "../middleware/auth";
16
17const developerProgram = new Hono<AuthEnv>();
18
19developerProgram.use("*", softAuth);
20
21// ─── Page-scoped styles ───────────────────────────────────────────────────────
22const devprogCss = `
23 .devprog-wrap { max-width: 1060px; margin: 0 auto; padding: 0 24px 80px; }
24
25 /* ─── Hero ─── */
26 .devprog-hero {
27 position: relative;
28 margin: 4px 0 48px;
29 padding: 64px 48px 56px;
30 background: var(--bg-elevated);
31 border: 1px solid var(--border);
32 border-radius: 20px;
33 overflow: hidden;
34 text-align: center;
35 }
36 .devprog-hero::before {
37 content: '';
38 position: absolute;
39 top: 0; left: 0; right: 0;
40 height: 2px;
41 background: linear-gradient(90deg, transparent 0%, #8c6dff 30%, #36c5d6 70%, transparent 100%);
42 opacity: 0.85;
43 pointer-events: none;
44 }
45 .devprog-hero-bg {
46 position: absolute;
47 inset: -30% -10% auto auto;
48 width: 500px; height: 500px;
49 pointer-events: none;
50 z-index: 0;
51 }
52 .devprog-hero-orb {
53 position: absolute;
54 inset: 0;
55 background: radial-gradient(circle, rgba(140,109,255,0.24), rgba(54,197,214,0.10) 45%, transparent 70%);
56 filter: blur(90px);
57 opacity: 0.7;
58 animation: devprogOrbDrift 14s ease-in-out infinite;
59 }
60 .devprog-hero-orb-2 {
61 position: absolute;
62 inset: auto auto -20% -12%;
63 width: 340px; height: 340px;
64 background: radial-gradient(circle, rgba(54,197,214,0.18), rgba(140,109,255,0.06) 50%, transparent 75%);
65 filter: blur(70px);
66 opacity: 0.5;
67 pointer-events: none;
68 z-index: 0;
69 animation: devprogOrbDrift2 18s ease-in-out infinite;
70 }
71 @keyframes devprogOrbDrift {
72 0%, 100% { transform: scale(1) translate(0, 0); opacity: 0.6; }
73 50% { transform: scale(1.10) translate(-12px, 10px); opacity: 0.88; }
74 }
75 @keyframes devprogOrbDrift2 {
76 0%, 100% { transform: scale(1) translate(0, 0); opacity: 0.40; }
77 50% { transform: scale(1.08) translate(14px, -8px); opacity: 0.65; }
78 }
79 @media (prefers-reduced-motion: reduce) {
80 .devprog-hero-orb, .devprog-hero-orb-2 { animation: none; }
81 }
82 .devprog-hero-inner {
83 position: relative;
84 z-index: 1;
85 display: flex;
86 flex-direction: column;
87 align-items: center;
88 gap: 20px;
89 }
90 .devprog-hero-eyebrow {
91 display: inline-flex;
92 align-items: center;
93 gap: 8px;
94 font-family: var(--font-mono);
95 font-size: 11px;
96 font-weight: 600;
97 letter-spacing: 0.13em;
98 text-transform: uppercase;
99 color: var(--accent);
100 }
101 .devprog-hero-eyebrow-dot {
102 display: inline-block;
103 width: 6px; height: 6px;
104 border-radius: 50%;
105 background: var(--accent);
106 box-shadow: 0 0 8px rgba(140,109,255,0.7);
107 }
108 .devprog-hero-title {
109 font-family: var(--font-display);
110 font-size: clamp(36px, 5.5vw, 68px);
111 font-weight: 800;
112 letter-spacing: -0.036em;
113 line-height: 0.96;
114 color: var(--text-strong);
115 max-width: 780px;
116 margin: 0;
117 }
118 .devprog-hero-title .gradient-text {
119 background-image: linear-gradient(135deg, #a48bff 0%, #8c6dff 50%, #36c5d6 100%);
120 -webkit-background-clip: text;
121 background-clip: text;
122 -webkit-text-fill-color: transparent;
123 color: transparent;
124 }
125 .devprog-hero-sub {
126 font-size: 17px;
127 color: var(--text-muted);
128 line-height: 1.6;
129 max-width: 560px;
130 margin: 0;
131 }
132 .devprog-hero-ctas {
133 display: flex;
134 gap: 12px;
135 flex-wrap: wrap;
136 justify-content: center;
137 margin-top: 8px;
138 }
139
140 /* ─── Section cards (the three value props) ─── */
141 .devprog-sections {
142 display: grid;
143 grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
144 gap: 20px;
145 margin-bottom: 48px;
146 }
147 .devprog-card {
148 position: relative;
149 background: var(--bg-elevated);
150 border: 1px solid var(--border);
151 border-radius: 16px;
152 padding: 32px 28px;
153 display: flex;
154 flex-direction: column;
155 gap: 14px;
156 overflow: hidden;
157 transition: border-color 160ms ease, box-shadow 160ms ease, transform 160ms ease;
158 }
159 .devprog-card::before {
160 content: '';
161 position: absolute;
162 top: 0; left: 0; right: 0;
163 height: 2px;
164 background: linear-gradient(90deg, transparent 0%, #8c6dff 30%, #36c5d6 70%, transparent 100%);
165 opacity: 0;
166 transition: opacity 180ms ease;
167 pointer-events: none;
168 }
169 .devprog-card:hover {
170 border-color: rgba(140,109,255,0.40);
171 box-shadow: 0 14px 32px -16px rgba(0,0,0,0.55), 0 0 24px -8px rgba(140,109,255,0.18);
172 transform: translateY(-2px);
173 }
174 .devprog-card:hover::before { opacity: 0.85; }
175 .devprog-card-icon {
176 width: 44px; height: 44px;
177 border-radius: 12px;
178 background: var(--accent-gradient-soft, rgba(140,109,255,0.12));
179 border: 1px solid rgba(140,109,255,0.22);
180 display: flex;
181 align-items: center;
182 justify-content: center;
183 color: var(--accent);
184 flex-shrink: 0;
185 }
186 .devprog-card-eyebrow {
187 font-family: var(--font-mono);
188 font-size: 10.5px;
189 font-weight: 600;
190 letter-spacing: 0.11em;
191 text-transform: uppercase;
192 color: var(--accent);
193 }
194 .devprog-card-title {
195 font-family: var(--font-display);
196 font-size: 20px;
197 font-weight: 700;
198 letter-spacing: -0.018em;
199 color: var(--text-strong);
200 margin: 0;
201 line-height: 1.2;
202 }
203 .devprog-card-body {
204 font-size: 14.5px;
205 color: var(--text-muted);
206 line-height: 1.6;
207 margin: 0;
208 flex: 1;
209 }
210 .devprog-card-link {
211 display: inline-flex;
212 align-items: center;
213 gap: 5px;
214 font-size: 13.5px;
215 font-weight: 600;
216 color: var(--text-link);
217 text-decoration: none;
218 transition: color 120ms ease, gap 120ms ease;
219 margin-top: 4px;
220 }
221 .devprog-card-link:hover {
222 color: var(--accent-hover);
223 gap: 8px;
224 text-decoration: none;
225 }
226
227 /* ─── Revenue highlight strip ─── */
228 .devprog-revenue-strip {
229 display: flex;
230 gap: 0;
231 border: 1px solid var(--border);
232 border-radius: 14px;
233 overflow: hidden;
234 margin-bottom: 48px;
235 background: var(--bg-elevated);
236 }
237 .devprog-revenue-pct {
238 display: flex;
239 flex-direction: column;
240 align-items: center;
241 justify-content: center;
242 gap: 4px;
243 padding: 28px 36px;
244 flex: 0 0 auto;
245 border-right: 1px solid var(--border);
246 }
247 .devprog-revenue-num {
248 font-family: var(--font-display);
249 font-size: 52px;
250 font-weight: 800;
251 letter-spacing: -0.04em;
252 line-height: 1;
253 background-image: linear-gradient(135deg, #a48bff 0%, #36c5d6 100%);
254 -webkit-background-clip: text;
255 background-clip: text;
256 -webkit-text-fill-color: transparent;
257 color: transparent;
258 }
259 .devprog-revenue-label {
260 font-size: 12px;
261 color: var(--text-muted);
262 font-weight: 500;
263 text-align: center;
264 }
265 .devprog-revenue-body {
266 display: flex;
267 flex-direction: column;
268 justify-content: center;
269 padding: 28px 32px;
270 gap: 8px;
271 }
272 .devprog-revenue-title {
273 font-family: var(--font-display);
274 font-size: 18px;
275 font-weight: 700;
276 letter-spacing: -0.018em;
277 color: var(--text-strong);
278 margin: 0;
279 }
280 .devprog-revenue-desc {
281 font-size: 14.5px;
282 color: var(--text-muted);
283 line-height: 1.55;
284 margin: 0;
285 max-width: 520px;
286 }
287
288 /* ─── Application form card ─── */
289 .devprog-apply {
290 background: var(--bg-elevated);
291 border: 1px solid var(--border);
292 border-radius: 18px;
293 padding: 44px 48px;
294 position: relative;
295 overflow: hidden;
296 }
297 .devprog-apply::before {
298 content: '';
299 position: absolute;
300 top: 0; left: 0; right: 0;
301 height: 2px;
302 background: linear-gradient(90deg, transparent 0%, #8c6dff 30%, #36c5d6 70%, transparent 100%);
303 opacity: 0.75;
304 pointer-events: none;
305 }
306 .devprog-apply-inner { max-width: 640px; }
307 .devprog-apply-eyebrow {
308 font-family: var(--font-mono);
309 font-size: 11px;
310 font-weight: 600;
311 letter-spacing: 0.12em;
312 text-transform: uppercase;
313 color: var(--accent);
314 margin-bottom: 12px;
315 }
316 .devprog-apply-title {
317 font-family: var(--font-display);
318 font-size: clamp(24px, 3.5vw, 36px);
319 font-weight: 800;
320 letter-spacing: -0.028em;
321 line-height: 1.1;
322 color: var(--text-strong);
323 margin: 0 0 10px;
324 }
325 .devprog-apply-sub {
326 font-size: 15px;
327 color: var(--text-muted);
328 line-height: 1.6;
329 margin: 0 0 28px;
330 }
331 .devprog-form { display: flex; flex-direction: column; gap: 16px; }
332 .devprog-field { display: flex; flex-direction: column; gap: 6px; }
333 .devprog-label {
334 font-size: 13px;
335 font-weight: 600;
336 color: var(--text-strong);
337 letter-spacing: -0.008em;
338 }
339 .devprog-input,
340 .devprog-textarea {
341 background: var(--bg-secondary);
342 border: 1px solid var(--border);
343 border-radius: var(--r);
344 padding: 9px 12px;
345 font-family: var(--font-sans);
346 font-size: 14px;
347 color: var(--text);
348 transition: border-color 120ms ease, box-shadow 120ms ease;
349 width: 100%;
350 }
351 .devprog-input::placeholder,
352 .devprog-textarea::placeholder { color: var(--text-faint); }
353 .devprog-input:focus,
354 .devprog-textarea:focus {
355 outline: none;
356 border-color: var(--border-focus);
357 box-shadow: var(--ring);
358 }
359 .devprog-textarea { resize: vertical; min-height: 90px; }
360 .devprog-form-actions {
361 display: flex;
362 align-items: center;
363 gap: 16px;
364 margin-top: 8px;
365 flex-wrap: wrap;
366 }
367 .devprog-form-note {
368 font-size: 12.5px;
369 color: var(--text-faint);
370 line-height: 1.5;
371 }
372 .devprog-success {
373 display: flex;
374 align-items: flex-start;
375 gap: 14px;
376 background: rgba(52,211,153,0.06);
377 border: 1px solid rgba(52,211,153,0.28);
378 border-radius: 12px;
379 padding: 18px 20px;
380 font-size: 14.5px;
381 color: var(--text-strong);
382 line-height: 1.55;
383 }
384 .devprog-success-icon {
385 width: 24px; height: 24px;
386 border-radius: 50%;
387 background: rgba(52,211,153,0.15);
388 border: 1px solid rgba(52,211,153,0.35);
389 display: flex; align-items: center; justify-content: center;
390 font-size: 14px;
391 flex-shrink: 0;
392 color: #34d399;
393 }
394
395 /* ─── Docs link strip ─── */
396 .devprog-docs-strip {
397 display: flex;
398 align-items: center;
399 gap: 12px;
400 padding: 18px 24px;
401 background: var(--bg-elevated);
402 border: 1px solid var(--border);
403 border-radius: 12px;
404 margin-top: 28px;
405 font-size: 14px;
406 }
407 .devprog-docs-strip-icon { color: var(--accent); flex-shrink: 0; }
408 .devprog-docs-strip-text { flex: 1; color: var(--text-muted); }
409 .devprog-docs-strip-link {
410 font-weight: 600;
411 color: var(--text-link);
412 white-space: nowrap;
413 }
414
415 @media (max-width: 640px) {
416 .devprog-hero { padding: 40px 24px 36px; }
417 .devprog-apply { padding: 32px 24px; }
418 .devprog-revenue-strip { flex-direction: column; }
419 .devprog-revenue-pct { border-right: none; border-bottom: 1px solid var(--border); }
420 }
421`;
422
423// ─── Route: GET /developer-program ───────────────────────────────────────────
424developerProgram.get("/developer-program", (c) => {
425 const user = c.get("user");
426 const applied = c.req.query("applied") === "1";
427
428 return c.html(
429 <Layout
430 title="Developer Program"
431 user={user}
432 description="Build AI agents on Gluecron, list them in the marketplace, and keep 70% of every sale. Apply for the partner program today."
433 >
434 <style dangerouslySetInnerHTML={{ __html: devprogCss }} />
435 <div class="devprog-wrap">
436
437 {/* ── Hero ── */}
438 <section class="devprog-hero" aria-labelledby="devprog-hero-h">
439 <div class="devprog-hero-bg" aria-hidden="true">
440 <div class="devprog-hero-orb" />
441 <div class="devprog-hero-orb-2" />
442 </div>
443 <div class="devprog-hero-inner">
444 <div class="devprog-hero-eyebrow">
445 <span class="devprog-hero-eyebrow-dot" aria-hidden="true" />
446 Gluecron Developer Program
447 </div>
448 <h1 id="devprog-hero-h" class="devprog-hero-title">
449 Build on Gluecron.{" "}
450 <span class="gradient-text">Earn revenue.</span>
451 </h1>
452 <p class="devprog-hero-sub">
453 List your AI agent in the Gluecron marketplace. Set your price. Ship
454 to thousands of developer teams already using Gluecron for their daily
455 workflow.
456 </p>
457 <div class="devprog-hero-ctas">
458 <a href="#apply" class="btn btn-primary btn-xl">
459 Apply for partner status
460 <span aria-hidden="true">{" →"}</span>
461 </a>
462 <a href="/help#agents" class="btn btn-secondary btn-xl">
463 Read the docs
464 </a>
465 </div>
466 </div>
467 </section>
468
469 {/* ── Three sections ── */}
470 <div class="devprog-sections">
471 {/* 1 — Publish an agent */}
472 <div class="devprog-card">
473 <div class="devprog-card-icon" aria-hidden="true">
474 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
475 <path d="M12 2l2.39 5.95L20 9l-4.5 3.9L17 19l-5-3.2L7 19l1.5-6.1L4 9l5.61-1.05L12 2z" />
476 </svg>
477 </div>
478 <div class="devprog-card-eyebrow">Step 1</div>
479 <h2 class="devprog-card-title">Publish an agent</h2>
480 <p class="devprog-card-body">
481 List your AI agent in the Gluecron marketplace. Set your price.
482 Gluecron takes 30% — you keep 70%. Your agent is discoverable
483 by every developer on the platform the moment it's approved.
484 </p>
485 <a href="/marketplace/agents/new" class="devprog-card-link">
486 Publish your first agent
487 <span aria-hidden="true">→</span>
488 </a>
489 </div>
490
491 {/* 2 — Revenue share */}
492 <div class="devprog-card">
493 <div class="devprog-card-icon" aria-hidden="true">
494 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
495 <line x1="12" y1="1" x2="12" y2="23" />
496 <path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6" />
497 </svg>
498 </div>
499 <div class="devprog-card-eyebrow">How you earn</div>
500 <h2 class="devprog-card-title">Revenue share</h2>
501 <p class="devprog-card-body">
502 The 30% platform cut is already in the schema. When your agent is
503 installed by a team, you earn automatically — no invoicing, no
504 chasing payments. Payouts go out monthly to your connected account.
505 </p>
506 <a href="/help#agents" class="devprog-card-link">
507 See payout docs
508 <span aria-hidden="true">→</span>
509 </a>
510 </div>
511
512 {/* 3 — Partner badge */}
513 <div class="devprog-card">
514 <div class="devprog-card-icon" aria-hidden="true">
515 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
516 <path d="M12 2.5l8 3.5v6c0 5-3.5 8.5-8 9.5-4.5-1-8-4.5-8-9.5v-6z" />
517 <path d="M9 12l2 2 4-4" />
518 </svg>
519 </div>
520 <div class="devprog-card-eyebrow">Verified status</div>
521 <h2 class="devprog-card-title">gluecron-partner badge</h2>
522 <p class="devprog-card-body">
523 Verified partners get a badge on their profile and priority
524 placement in the marketplace. The badge signals to buyers that
525 your agent has been reviewed by the Gluecron team and meets our
526 quality and security bar.
527 </p>
528 <a href="#apply" class="devprog-card-link">
529 Apply now
530 <span aria-hidden="true">→</span>
531 </a>
532 </div>
533 </div>
534
535 {/* ── Revenue highlight strip ── */}
536 <div class="devprog-revenue-strip" aria-label="Revenue split: you keep 70%">
537 <div class="devprog-revenue-pct">
538 <div class="devprog-revenue-num">70%</div>
539 <div class="devprog-revenue-label">you keep</div>
540 </div>
541 <div class="devprog-revenue-body">
542 <h2 class="devprog-revenue-title">The math is simple.</h2>
543 <p class="devprog-revenue-desc">
544 Gluecron takes 30% to cover infrastructure, payments, and
545 marketplace distribution. You keep 70% of every subscription or
546 one-time purchase — no hidden fees, no surprise deductions. The
547 split is encoded in the platform schema and applied automatically
548 at payout time.
549 </p>
550 </div>
551 </div>
552
553 {/* ── Application form ── */}
554 <section id="apply" class="devprog-apply" aria-labelledby="devprog-apply-h">
555 <div class="devprog-apply-inner">
556 <div class="devprog-apply-eyebrow">Partner application</div>
557 <h2 id="devprog-apply-h" class="devprog-apply-title">
558 Apply for partner status
559 </h2>
560 <p class="devprog-apply-sub">
561 Tell us about the agent you're building. We review every
562 application within 5 business days and reply to the email on your
563 Gluecron account.
564 </p>
565
566 {applied ? (
567 <div class="devprog-success" role="alert">
568 <div class="devprog-success-icon" aria-hidden="true">✓</div>
569 <div>
570 <strong>Application received.</strong> We'll review your
571 submission and reply within 5 business days. Keep building!
572 </div>
573 </div>
574 ) : (
575 <form
576 method="post"
577 action="/developer-program"
578 class="devprog-form"
579 >
580 <div class="devprog-field">
581 <label class="devprog-label" for="dp-agent-name">
582 Agent name
583 </label>
584 <input
585 id="dp-agent-name"
586 name="agent_name"
587 type="text"
588 class="devprog-input"
589 placeholder="e.g. PR Summarizer Pro"
590 required
591 maxlength={120}
592 />
593 </div>
594 <div class="devprog-field">
595 <label class="devprog-label" for="dp-agent-desc">
596 What does it do?
597 </label>
598 <textarea
599 id="dp-agent-desc"
600 name="agent_description"
601 class="devprog-textarea"
602 placeholder="Describe your agent in 2–4 sentences. What problem does it solve? Who is it for?"
603 required
604 maxlength={800}
605 />
606 </div>
607 <div class="devprog-field">
608 <label class="devprog-label" for="dp-pricing">
609 Pricing model
610 </label>
611 <input
612 id="dp-pricing"
613 name="pricing_model"
614 type="text"
615 class="devprog-input"
616 placeholder='e.g. "$9/mo per seat" or "one-time $49"'
617 maxlength={120}
618 />
619 </div>
620 <div class="devprog-field">
621 <label class="devprog-label" for="dp-repo">
622 Gluecron repo or demo URL
623 </label>
624 <input
625 id="dp-repo"
626 name="repo_url"
627 type="url"
628 class="devprog-input"
629 placeholder="https://gluecron.com/you/your-agent"
630 />
631 </div>
632 <div class="devprog-form-actions">
633 <button type="submit" class="btn btn-primary">
634 Submit application
635 </button>
636 <p class="devprog-form-note">
637 We'll reply to the email on your Gluecron account.
638 Applications are reviewed manually — no bots.
639 </p>
640 </div>
641 </form>
642 )}
643
644 {/* Docs link */}
645 <div class="devprog-docs-strip" role="complementary">
646 <svg class="devprog-docs-strip-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
647 <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
648 <polyline points="14 2 14 8 20 8" />
649 <line x1="16" y1="13" x2="8" y2="13" />
650 <line x1="16" y1="17" x2="8" y2="17" />
651 <polyline points="10 9 9 9 8 9" />
652 </svg>
653 <span class="devprog-docs-strip-text">
654 Want to start building before applying?
655 </span>
656 <a href="/help#agents" class="devprog-docs-strip-link">
657 Read the agent publishing docs →
658 </a>
659 </div>
660 </div>
661 </section>
662
663 </div>
664 </Layout>
665 );
666});
667
668// ─── Route: POST /developer-program ──────────────────────────────────────────
669// Logs the application for now. Future: insert into partner_applications table.
670developerProgram.post("/developer-program", async (c) => {
671 const user = c.get("user");
672 const body = await c.req.parseBody();
673
674 // Log the application so it's visible in server output / observability
675 console.log("[developer-program] partner application received", {
676 userId: user?.id ?? null,
677 username: user?.username ?? null,
678 agentName: body["agent_name"],
679 pricingModel: body["pricing_model"],
680 repoUrl: body["repo_url"],
681 // description intentionally omitted from log to keep it brief
682 at: new Date().toISOString(),
683 });
684
685 return c.redirect("/developer-program?applied=1");
686});
687
688export default developerProgram;