Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesPull RequestsActionsSecurityInsightsSettings
✨ AI
More
Blame · Line-by-line history

signin-v2.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.

signin-v2.tsxBlame544 lines · 1 contributor
11c3ab6ccanty labs1import type { FC } from "hono/jsx";
2
3export interface SignInV2Props {
4 redirect?: string;
5 error?: string;
6}
7
8export const SignInV2: FC<SignInV2Props> = (props) => {
9 const { redirect = "", error = "" } = props;
10
11 return (
12 <>
13 <style dangerouslySetInnerHTML={{ __html: css }} />
14 <div class="si-root">
15 {/* Left: form panel */}
16 <div class="si-left">
17 <a href="/" class="si-logo">
18 <span class="si-logo-mark"></span>gluecron
19 </a>
20
21 <div class="si-form-wrap">
22 <h1 class="si-heading">Sign in</h1>
23 <p class="si-subheading">Use the identity you already have — no new passwords.</p>
24
25 {error && (
26 <div class="si-error-banner">{error}</div>
27 )}
28
29 {redirect && (
30 <input type="hidden" name="redirect" value={redirect} />
31 )}
32
33 <div class="si-oauth-group">
34 <a href="/auth/github" class="si-btn si-btn-github">
35 <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
36 <path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8z" />
37 </svg>
38 Continue with GitHub
39 </a>
40
41 <a href="/auth/google" class="si-btn si-btn-secondary">
42 <svg width="16" height="16" viewBox="0 0 24 24" aria-hidden="true">
43 <path fill="#4285F4" d="M23.5 12.27c0-.85-.08-1.66-.22-2.45H12v4.63h6.45a5.52 5.52 0 0 1-2.39 3.62v3h3.87c2.26-2.09 3.57-5.16 3.57-8.8z" />
44 <path fill="#34A853" d="M12 24c3.24 0 5.96-1.07 7.94-2.91l-3.87-3c-1.07.72-2.45 1.15-4.07 1.15-3.13 0-5.78-2.11-6.73-4.96H1.29v3.1A12 12 0 0 0 12 24z" />
45 <path fill="#FBBC05" d="M5.27 14.28a7.2 7.2 0 0 1 0-4.56v-3.1H1.29a12 12 0 0 0 0 10.76l3.98-3.1z" />
46 <path fill="#EA4335" d="M12 4.76c1.76 0 3.34.61 4.59 1.8l3.44-3.44C17.95 1.19 15.24 0 12 0A12 12 0 0 0 1.29 6.62l3.98 3.1C6.22 6.87 8.87 4.76 12 4.76z" />
47 </svg>
48 Continue with Google
49 </a>
50
51 <a href="/passkey" class="si-btn si-btn-secondary">
52 <span class="si-passkey-icon" aria-hidden="true">⌘</span>
53 Continue with a passkey
54 </a>
55 </div>
56
57 <div class="si-divider">
58 <span class="si-divider-line"></span>
59 <span class="si-divider-label">OR</span>
60 <span class="si-divider-line"></span>
61 </div>
62
63 <div class="si-magic-row">
64 <input
65 id="si-email-input"
66 type="email"
67 placeholder="work@company.com"
68 class="si-email-input"
69 autocomplete="email"
70 />
71 <button
72 id="si-magic-btn"
73 type="button"
74 class="si-magic-btn"
75 >
76 Email me a link
77 </button>
78 </div>
79 <p class="si-magic-hint">Magic link — no password, expires in 10 minutes.</p>
80
81 <div class="si-aux-links">
82 <a href="/sso" class="si-aux-link si-aux-link-accent">Enterprise SSO (Okta, Azure AD, Google Workspace) →</a>
83 <a href="/keys" class="si-aux-link">Just cloning? SSH keys work without signing in →</a>
84 <a href="/play" class="si-aux-link">Try without an account at /play →</a>
85 </div>
86 </div>
87
88 <p class="si-footer-note">TLS terminated on our own metal · no third-party proxy in the auth loop</p>
89 </div>
90
91 {/* Right: proof panel */}
92 <div class="si-right">
93 <div class="si-grid-overlay"></div>
94 <div class="si-proof-inner">
95 <div class="si-proof-eyebrow">Why teams switch</div>
96 <h2 class="si-proof-heading">Sign in once.<br />Ship without waiting, forever.</h2>
97 <div class="si-props-list">
98 <div class="si-prop-item">
99 <span class="si-green-dot"></span>
100 <p class="si-prop-text">Sonnet 5 reviews every PR the moment it opens — with a manual path that's always one click away.</p>
101 </div>
102 <div class="si-prop-item">
103 <span class="si-green-dot"></span>
104 <p class="si-prop-text">Failures repair themselves in isolated sandboxes; everything is audited and reversible in one click.</p>
105 </div>
106 <div class="si-prop-item">
107 <span class="si-green-dot"></span>
108 <p class="si-prop-text">Continuous two-way GitHub mirror — trial for months, cut over when you're ready, leave any time.</p>
109 </div>
110 </div>
111 <div class="si-proof-footer">quality numbers published live · gluecron.com/trust</div>
112 </div>
113 </div>
114 </div>
115
116 <script dangerouslySetInnerHTML={{ __html: js }} />
117 </>
118 );
119};
120
121export default SignInV2;
122
123const js = `
124(function () {
125 var emailInput = document.getElementById('si-email-input');
126 var magicBtn = document.getElementById('si-magic-btn');
127 if (!emailInput || !magicBtn) return;
128
129 var sent = false;
130
131 function updateBtn() {
132 if (sent) {
133 magicBtn.textContent = '\\u2713 Link sent';
134 magicBtn.classList.add('si-magic-btn--sent');
135 } else {
136 magicBtn.textContent = 'Email me a link';
137 magicBtn.classList.remove('si-magic-btn--sent');
138 }
139 }
140
141 emailInput.addEventListener('input', function () {
142 if (sent) {
143 sent = false;
144 updateBtn();
145 }
146 });
147
148 magicBtn.addEventListener('click', function () {
149 var val = emailInput.value;
150 if (val && val.indexOf('@') !== -1) {
151 sent = true;
152 updateBtn();
153 } else {
154 emailInput.focus();
155 emailInput.classList.add('si-email-input--shake');
156 setTimeout(function () {
157 emailInput.classList.remove('si-email-input--shake');
158 }, 400);
159 }
160 });
161})();
162`;
163
164const css = `
165@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;450;500;600;700&family=Inter+Tight:wght@500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap');
166
167*, *::before, *::after { box-sizing: border-box; }
168
169.si-root {
170 font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
171 font-size: 14px;
172 line-height: 1.55;
173 letter-spacing: -0.008em;
174 color: #16181d;
175 background: #fcfcfd;
176 min-height: 100vh;
177 display: grid;
178 grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
179 -webkit-font-smoothing: antialiased;
180 text-rendering: optimizeLegibility;
181}
182
183/* ── Left panel ── */
184
185.si-left {
186 display: flex;
187 flex-direction: column;
188 padding: 28px 32px;
189 min-height: 100vh;
190}
191
192.si-logo {
193 display: inline-flex;
194 align-items: center;
195 gap: 9px;
196 font-family: 'Inter Tight', sans-serif;
197 font-weight: 600;
198 font-size: 15px;
199 letter-spacing: -0.02em;
200 color: #16181d;
201 text-decoration: none;
202 align-self: flex-start;
203}
204
205.si-logo-mark {
206 width: 16px;
207 height: 16px;
208 border-radius: 5px;
209 background: #4353c9;
210 display: inline-block;
211 flex-shrink: 0;
212}
213
214.si-form-wrap {
215 flex: 1;
216 display: flex;
217 flex-direction: column;
218 justify-content: center;
219 max-width: 380px;
220 width: 100%;
221 margin: 0 auto;
222 padding: 48px 0;
223}
224
225.si-heading {
226 font-family: 'Inter Tight', sans-serif;
227 font-size: 26px;
228 font-weight: 600;
229 letter-spacing: -0.024em;
230 line-height: 1.15;
231 margin: 0 0 6px;
232 color: #111318;
233}
234
235.si-subheading {
236 font-size: 13.5px;
237 color: #6b7080;
238 margin: 0 0 28px;
239}
240
241.si-error-banner {
242 background: rgba(180, 35, 24, 0.08);
243 border: 1px solid rgba(180, 35, 24, 0.18);
244 border-radius: 10px;
245 color: #b42318;
246 font-size: 13.5px;
247 padding: 10px 14px;
248 margin-bottom: 18px;
249}
250
251/* ── OAuth buttons ── */
252
253.si-oauth-group {
254 display: flex;
255 flex-direction: column;
256 gap: 10px;
257}
258
259.si-btn {
260 display: flex;
261 align-items: center;
262 justify-content: center;
263 gap: 10px;
264 padding: 11px 16px;
265 border-radius: 10px;
266 font-size: 14px;
267 font-weight: 600;
268 font-family: 'Inter', sans-serif;
269 text-decoration: none;
270 cursor: pointer;
271 transition: box-shadow 0.15s ease, border-color 0.15s ease;
272 letter-spacing: -0.008em;
273}
274
275.si-btn-github {
276 background: #16181d;
277 color: #fff;
278 border: 1px solid #16181d;
279}
280
281.si-btn-github:hover {
282 box-shadow: 0 8px 22px rgba(22, 24, 29, 0.18);
283}
284
285.si-btn-secondary {
286 background: #ffffff;
287 color: #16181d;
288 border: 1px solid rgba(22, 24, 29, 0.14);
289}
290
291.si-btn-secondary:hover {
292 border-color: rgba(22, 24, 29, 0.30);
293}
294
295.si-passkey-icon {
296 font-size: 15px;
297 line-height: 1;
298}
299
300/* ── OR divider ── */
301
302.si-divider {
303 display: flex;
304 align-items: center;
305 gap: 14px;
306 margin: 22px 0;
307}
308
309.si-divider-line {
310 flex: 1;
311 height: 1px;
312 background: rgba(22, 24, 29, 0.08);
313}
314
315.si-divider-label {
316 font-size: 11.5px;
317 color: #8a8d99;
318 font-family: 'JetBrains Mono', monospace;
319 letter-spacing: 0.08em;
320}
321
322/* ── Magic link row ── */
323
324.si-magic-row {
325 display: flex;
326 gap: 8px;
327}
328
329.si-email-input {
330 flex: 1;
331 border: 1px solid rgba(22, 24, 29, 0.12);
332 border-radius: 10px;
333 padding: 10px 14px;
334 outline: none;
335 font-family: 'Inter', sans-serif;
336 font-size: 14px;
337 letter-spacing: -0.008em;
338 color: #16181d;
339 background: #ffffff;
340 min-width: 0;
341 transition: border-color 0.15s ease, box-shadow 0.15s ease;
342}
343
344.si-email-input:focus {
345 border-color: rgba(22, 24, 29, 0.28);
346 box-shadow: 0 0 0 3px rgba(67, 83, 201, 0.10);
347}
348
349.si-email-input::placeholder {
350 color: #c4c6cf;
351}
352
353@keyframes si-shake {
354 0%, 100% { transform: translateX(0); }
355 20% { transform: translateX(-4px); }
356 40% { transform: translateX(4px); }
357 60% { transform: translateX(-3px); }
358 80% { transform: translateX(3px); }
359}
360
361.si-email-input--shake {
362 animation: si-shake 0.4s ease;
363 border-color: rgba(180, 35, 24, 0.40);
364}
365
366.si-magic-btn {
367 padding: 10px 16px;
368 border-radius: 10px;
369 font-size: 13.5px;
370 font-weight: 600;
371 font-family: 'Inter', sans-serif;
372 letter-spacing: -0.008em;
373 border: 1px solid rgba(22, 24, 29, 0.14);
374 background: #ffffff;
375 color: #16181d;
376 cursor: pointer;
377 white-space: nowrap;
378 transition: background 0.18s ease, color 0.18s ease, border-color 0.18s ease;
379}
380
381.si-magic-btn:hover {
382 border-color: rgba(22, 24, 29, 0.28);
383}
384
385.si-magic-btn--sent {
386 background: #1e7f5c;
387 color: #ffffff;
388 border-color: #1e7f5c;
389}
390
391.si-magic-btn--sent:hover {
392 border-color: #1e7f5c;
393}
394
395.si-magic-hint {
396 font-size: 12px;
397 color: #8a8d99;
398 margin: 10px 0 0;
399}
400
401/* ── Auxiliary links ── */
402
403.si-aux-links {
404 border-top: 1px solid rgba(22, 24, 29, 0.07);
405 margin-top: 28px;
406 padding-top: 20px;
407 display: flex;
408 flex-direction: column;
409 gap: 8px;
410}
411
412.si-aux-link {
413 font-size: 13px;
414 color: #6b7080;
415 text-decoration: none;
416 transition: color 0.12s ease;
417}
418
419.si-aux-link:hover {
420 color: #16181d;
421}
422
423.si-aux-link-accent {
424 color: #4353c9;
425}
426
427.si-aux-link-accent:hover {
428 color: #3544b0;
429 text-decoration: underline;
430}
431
432/* ── Footer note ── */
433
434.si-footer-note {
435 font-size: 11.5px;
436 color: #8a8d99;
437 margin: 0;
438 font-family: 'JetBrains Mono', monospace;
439 letter-spacing: 0.01em;
440}
441
442/* ── Right panel ── */
443
444.si-right {
445 background: radial-gradient(130% 130% at 50% -10%, #1b2030 0%, #12151f 45%, #0b0d13 100%);
446 color: #fff;
447 display: flex;
448 flex-direction: column;
449 justify-content: center;
450 padding: 64px 56px;
451 position: relative;
452 overflow: hidden;
453}
454
455.si-grid-overlay {
456 position: absolute;
457 inset: 0;
458 opacity: 0.35;
459 background-image:
460 linear-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px),
461 linear-gradient(90deg, rgba(255, 255, 255, 0.05) 1px, transparent 1px);
462 background-size: 56px 56px;
463 -webkit-mask-image: radial-gradient(90% 90% at 50% 0%, #000, transparent 80%);
464 mask-image: radial-gradient(90% 90% at 50% 0%, #000, transparent 80%);
465 pointer-events: none;
466}
467
468.si-proof-inner {
469 position: relative;
470 max-width: 440px;
471}
472
473.si-proof-eyebrow {
474 font-family: 'JetBrains Mono', monospace;
475 font-size: 11px;
476 letter-spacing: 0.12em;
477 text-transform: uppercase;
478 color: #a9b4ee;
479 margin-bottom: 16px;
480}
481
482.si-proof-heading {
483 font-family: 'Inter Tight', sans-serif;
484 font-size: 30px;
485 font-weight: 600;
486 letter-spacing: -0.024em;
487 line-height: 1.15;
488 margin: 0 0 28px;
489 color: #fff;
490}
491
492.si-props-list {
493 display: flex;
494 flex-direction: column;
495 gap: 18px;
496}
497
498.si-prop-item {
499 display: flex;
500 gap: 12px;
501 align-items: flex-start;
502}
503
504.si-green-dot {
505 width: 7px;
506 height: 7px;
507 border-radius: 50%;
508 background: #1e7f5c;
509 flex-shrink: 0;
510 margin-top: 7px;
511}
512
513.si-prop-text {
514 font-size: 14px;
515 color: rgba(255, 255, 255, 0.78);
516 margin: 0;
517 line-height: 1.6;
518}
519
520.si-proof-footer {
521 border-top: 1px solid rgba(255, 255, 255, 0.10);
522 margin-top: 32px;
523 padding-top: 20px;
524 font-family: 'JetBrains Mono', monospace;
525 font-size: 11.5px;
526 color: rgba(255, 255, 255, 0.45);
527}
528
529/* ── Responsive ── */
530
531@media (max-width: 768px) {
532 .si-root {
533 grid-template-columns: 1fr;
534 }
535
536 .si-right {
537 display: none;
538 }
539
540 .si-left {
541 min-height: 100vh;
542 }
543}
544`;