CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history
onboarding-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.
| 11c3ab6 | 1 | import type { FC } from "hono/jsx"; |
| 2 | ||
| 3 | export interface OnboardingV2Props { | |
| 4 | situation?: string; | |
| 5 | user?: { username: string }; | |
| 6 | } | |
| 7 | ||
| 8 | export const OnboardingV2: FC<OnboardingV2Props> = (props) => { | |
| 9 | const initSit = props.situation ?? "broken-codebase"; | |
| 10 | ||
| 11 | return ( | |
| 12 | <> | |
| 13 | <style dangerouslySetInnerHTML={{ __html: css }} /> | |
| 14 | <div class="ob-root" {...{ "data-init-situation": initSit }}> | |
| 15 | <header class="ob-header"> | |
| 16 | <a href="/" class="ob-logo"> | |
| 17 | <span class="ob-logo-mark"></span>gluecron | |
| 18 | </a> | |
| 19 | <span class="ob-sep">/</span> | |
| 20 | <span class="ob-page-label">Welcome</span> | |
| 21 | <span class="ob-tagline">First five minutes — the wizard adapts to who's arriving</span> | |
| 22 | </header> | |
| 23 | ||
| 24 | <main class="ob-main"> | |
| 25 | <div class="ob-intro"> | |
| 26 | <div class="ob-eyebrow">Adaptive onboarding · one flow, four shapes</div> | |
| 27 | <h1 class="ob-h1">Onboarding that reads the situation.</h1> | |
| 28 | <p class="ob-lead"> | |
| 29 | The same signup senses who's arriving — a human with a broken repo, an agent teammate, | |
| 30 | a locked-down enterprise, or someone just browsing — and reshapes itself. Pick a situation | |
| 31 | to see the wizard adapt. | |
| 32 | </p> | |
| 33 | </div> | |
| 34 | ||
| 35 | <div class="ob-pills" id="ob-pills"></div> | |
| 36 | ||
| 37 | <div class="ob-grid"> | |
| 38 | <section class="ob-wizard"> | |
| 39 | <div class="ob-wiz-head" id="ob-wiz-head"></div> | |
| 40 | <div class="ob-wiz-steps" id="ob-wiz-steps"></div> | |
| 41 | <div class="ob-wiz-footer" id="ob-wiz-footer"></div> | |
| 42 | </section> | |
| 43 | ||
| 44 | <aside class="ob-rail"> | |
| 45 | <div> | |
| 46 | <h3 class="ob-rail-hed">What the system sensed</h3> | |
| 47 | <div class="ob-sensed-card" id="ob-sensed-card"></div> | |
| 48 | </div> | |
| 49 | <div> | |
| 50 | <h3 class="ob-rail-hed">The three invariants</h3> | |
| 51 | <div class="ob-invs"> | |
| 52 | <div class="ob-inv"> | |
| 53 | <div class="ob-inv-title">Identity coherence</div> | |
| 54 | <p class="ob-inv-body"> | |
| 55 | Playground → verified profile keeps your full sandbox history. Nothing is lost on upgrade. | |
| 56 | </p> | |
| 57 | </div> | |
| 58 | <div class="ob-inv"> | |
| 59 | <div class="ob-inv-title">Telemetry handshake</div> | |
| 60 | <p class="ob-inv-body"> | |
| 61 | Setup parameters log to flywheel telemetry from the first second — onboarding health is | |
| 62 | measured, not assumed. | |
| 63 | </p> | |
| 64 | </div> | |
| 65 | <div class="ob-inv"> | |
| 66 | <div class="ob-inv-title">Local sync rails</div> | |
| 67 | <p class="ob-inv-body"> | |
| 68 | The setup wizard connects your machine straight to the sovereign array — no external edge | |
| 69 | network in the path. | |
| 70 | </p> | |
| 71 | </div> | |
| 72 | </div> | |
| 73 | </div> | |
| 74 | </aside> | |
| 75 | </div> | |
| 76 | </main> | |
| 77 | </div> | |
| 78 | <script dangerouslySetInnerHTML={{ __html: js }} /> | |
| 79 | </> | |
| 80 | ); | |
| 81 | }; | |
| 82 | ||
| 83 | export default OnboardingV2; | |
| 84 | ||
| 85 | /* ─── styles ─────────────────────────────────────────────────────────────── */ | |
| 86 | ||
| 87 | const css = ` | |
| 88 | @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'); | |
| 89 | ||
| 90 | @keyframes gcPulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } } | |
| 91 | ||
| 92 | *, *::before, *::after { box-sizing: border-box; } | |
| 93 | ||
| 94 | .ob-root { | |
| 95 | font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; | |
| 96 | font-size: 14px; | |
| 97 | line-height: 1.55; | |
| 98 | letter-spacing: -0.008em; | |
| 99 | color: #16181d; | |
| 100 | background: #fcfcfd; | |
| 101 | min-height: 100vh; | |
| 102 | display: flex; | |
| 103 | flex-direction: column; | |
| 104 | -webkit-font-smoothing: antialiased; | |
| 105 | text-rendering: optimizeLegibility; | |
| 106 | } | |
| 107 | ||
| 108 | /* ── header ─────────────────────────────────────────────────────────────── */ | |
| 109 | .ob-header { | |
| 110 | height: 56px; | |
| 111 | border-bottom: 1px solid rgba(22,24,29,0.07); | |
| 112 | background: #fcfcfd; | |
| 113 | display: flex; | |
| 114 | align-items: center; | |
| 115 | padding: 0 28px; | |
| 116 | gap: 20px; | |
| 117 | position: sticky; | |
| 118 | top: 0; | |
| 119 | z-index: 50; | |
| 120 | } | |
| 121 | .ob-logo { | |
| 122 | display: inline-flex; | |
| 123 | align-items: center; | |
| 124 | gap: 9px; | |
| 125 | font-family: 'Inter Tight', sans-serif; | |
| 126 | font-weight: 600; | |
| 127 | font-size: 15px; | |
| 128 | letter-spacing: -0.02em; | |
| 129 | color: #16181d; | |
| 130 | text-decoration: none; | |
| 131 | } | |
| 132 | .ob-logo-mark { | |
| 133 | width: 16px; | |
| 134 | height: 16px; | |
| 135 | border-radius: 5px; | |
| 136 | background: #4353c9; | |
| 137 | display: inline-block; | |
| 138 | flex-shrink: 0; | |
| 139 | } | |
| 140 | .ob-sep { color: #c4c6cf; } | |
| 141 | .ob-page-label { font-size: 13px; color: #16181d; font-weight: 500; } | |
| 142 | .ob-tagline { margin-left: auto; font-size: 12.5px; color: #6b7080; } | |
| 143 | ||
| 144 | /* ── main layout ─────────────────────────────────────────────────────────── */ | |
| 145 | .ob-main { | |
| 146 | flex: 1; | |
| 147 | max-width: 1180px; | |
| 148 | width: 100%; | |
| 149 | margin: 0 auto; | |
| 150 | padding: 44px 32px 88px; | |
| 151 | } | |
| 152 | ||
| 153 | /* ── intro ───────────────────────────────────────────────────────────────── */ | |
| 154 | .ob-intro { max-width: 680px; margin-bottom: 32px; } | |
| 155 | .ob-eyebrow { | |
| 156 | font-family: 'JetBrains Mono', monospace; | |
| 157 | font-size: 11px; | |
| 158 | letter-spacing: 0.12em; | |
| 159 | text-transform: uppercase; | |
| 160 | color: #8a8d99; | |
| 161 | margin-bottom: 10px; | |
| 162 | } | |
| 163 | .ob-h1 { | |
| 164 | font-family: 'Inter Tight', sans-serif; | |
| 165 | font-size: 28px; | |
| 166 | font-weight: 600; | |
| 167 | letter-spacing: -0.025em; | |
| 168 | line-height: 1.15; | |
| 169 | margin: 0 0 8px; | |
| 170 | color: #111318; | |
| 171 | } | |
| 172 | .ob-lead { font-size: 14px; color: #6b7080; margin: 0; line-height: 1.6; } | |
| 173 | ||
| 174 | /* ── situation pills ─────────────────────────────────────────────────────── */ | |
| 175 | .ob-pills { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 28px; } | |
| 176 | .ob-pill { | |
| 177 | padding: 8px 16px; | |
| 178 | border-radius: 9999px; | |
| 179 | font-size: 13px; | |
| 180 | font-weight: 500; | |
| 181 | border: 1px solid rgba(22,24,29,0.12); | |
| 182 | background: #ffffff; | |
| 183 | color: #6b7080; | |
| 184 | cursor: pointer; | |
| 185 | font-family: inherit; | |
| 186 | white-space: nowrap; | |
| 187 | transition: border-color 0.12s; | |
| 188 | } | |
| 189 | .ob-pill:hover { border-color: rgba(22,24,29,0.30); } | |
| 190 | .ob-pill--active { | |
| 191 | background: #16181d; | |
| 192 | color: #ffffff; | |
| 193 | border-color: #16181d; | |
| 194 | font-weight: 600; | |
| 195 | } | |
| 196 | ||
| 197 | /* ── two-col grid ────────────────────────────────────────────────────────── */ | |
| 198 | .ob-grid { | |
| 199 | display: grid; | |
| 200 | grid-template-columns: minmax(0, 1.5fr) minmax(300px, 1fr); | |
| 201 | gap: 44px; | |
| 202 | align-items: start; | |
| 203 | } | |
| 204 | ||
| 205 | /* ── wizard card ─────────────────────────────────────────────────────────── */ | |
| 206 | .ob-wizard { | |
| 207 | border: 1px solid rgba(22,24,29,0.09); | |
| 208 | border-radius: 16px; | |
| 209 | background: #ffffff; | |
| 210 | overflow: hidden; | |
| 211 | } | |
| 212 | ||
| 213 | .ob-wiz-head { | |
| 214 | padding: 20px 26px; | |
| 215 | border-bottom: 1px solid rgba(22,24,29,0.06); | |
| 216 | background: #fcfcfd; | |
| 217 | } | |
| 218 | .ob-wiz-head-row { display: flex; align-items: center; gap: 10px; } | |
| 219 | .ob-wiz-dot { | |
| 220 | width: 8px; | |
| 221 | height: 8px; | |
| 222 | border-radius: 50%; | |
| 223 | flex-shrink: 0; | |
| 224 | animation: gcPulse 1.4s ease-in-out infinite; | |
| 225 | } | |
| 226 | .ob-wiz-title { font-size: 14.5px; font-weight: 600; color: #16181d; } | |
| 227 | .ob-wiz-body { font-size: 13px; color: #6b7080; margin: 6px 0 0; line-height: 1.6; } | |
| 228 | ||
| 229 | /* ── step timeline ───────────────────────────────────────────────────────── */ | |
| 230 | .ob-wiz-steps { padding: 22px 26px; display: flex; flex-direction: column; gap: 0; } | |
| 231 | .ob-step { | |
| 232 | display: grid; | |
| 233 | grid-template-columns: 20px minmax(0, 1fr); | |
| 234 | gap: 16px; | |
| 235 | position: relative; | |
| 236 | } | |
| 237 | .ob-step:not(:last-child) { padding-bottom: 24px; } | |
| 238 | .ob-step-track { display: flex; flex-direction: column; align-items: center; } | |
| 239 | .ob-step-dot { | |
| 240 | width: 8px; | |
| 241 | height: 8px; | |
| 242 | border-radius: 50%; | |
| 243 | margin-top: 6px; | |
| 244 | flex-shrink: 0; | |
| 245 | } | |
| 246 | .ob-step-line { | |
| 247 | width: 1px; | |
| 248 | flex: 1; | |
| 249 | background: rgba(22,24,29,0.08); | |
| 250 | margin-top: 6px; | |
| 251 | } | |
| 252 | .ob-step-title { font-size: 13.5px; font-weight: 600; color: #16181d; } | |
| 253 | .ob-step-body { | |
| 254 | font-size: 13px; | |
| 255 | color: #6b7080; | |
| 256 | margin: 3px 0 0; | |
| 257 | line-height: 1.6; | |
| 258 | max-width: 56ch; | |
| 259 | } | |
| 260 | ||
| 261 | /* ── terminal snippet ────────────────────────────────────────────────────── */ | |
| 262 | .ob-term { | |
| 263 | margin: 12px 0 0; | |
| 264 | border: 1px solid rgba(22,24,29,0.9); | |
| 265 | border-radius: 10px; | |
| 266 | background: #0f1116; | |
| 267 | overflow: hidden; | |
| 268 | } | |
| 269 | .ob-term-bar { | |
| 270 | display: flex; | |
| 271 | align-items: center; | |
| 272 | justify-content: space-between; | |
| 273 | padding: 8px 14px; | |
| 274 | border-bottom: 1px solid rgba(255,255,255,0.07); | |
| 275 | } | |
| 276 | .ob-term-label { | |
| 277 | font-family: 'JetBrains Mono', monospace; | |
| 278 | font-size: 10.5px; | |
| 279 | color: #8a8d99; | |
| 280 | letter-spacing: 0.08em; | |
| 281 | text-transform: uppercase; | |
| 282 | } | |
| 283 | .ob-copy-btn { | |
| 284 | padding: 4px 12px; | |
| 285 | border-radius: 6px; | |
| 286 | font-size: 11px; | |
| 287 | font-weight: 600; | |
| 288 | border: 1px solid rgba(255,255,255,0.14); | |
| 289 | background: transparent; | |
| 290 | color: #d6d8e0; | |
| 291 | cursor: pointer; | |
| 292 | font-family: inherit; | |
| 293 | transition: background 0.1s; | |
| 294 | } | |
| 295 | .ob-copy-btn:hover { background: rgba(255,255,255,0.06); } | |
| 296 | .ob-term-body { | |
| 297 | padding: 12px 16px; | |
| 298 | font-family: 'JetBrains Mono', monospace; | |
| 299 | font-size: 12px; | |
| 300 | line-height: 1.8; | |
| 301 | color: #d6d8e0; | |
| 302 | } | |
| 303 | .ob-term-prompt { color: #5f8fa0; } | |
| 304 | .ob-term-comment { color: #8a8d99; } | |
| 305 | ||
| 306 | /* ── connection status row ───────────────────────────────────────────────── */ | |
| 307 | .ob-conn { | |
| 308 | display: flex; | |
| 309 | align-items: center; | |
| 310 | gap: 8px; | |
| 311 | margin-top: 10px; | |
| 312 | font-size: 12.5px; | |
| 313 | } | |
| 314 | .ob-conn-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; } | |
| 315 | .ob-conn-text { flex: 1; } | |
| 316 | .ob-conn-btn { | |
| 317 | margin-left: 8px; | |
| 318 | padding: 3px 12px; | |
| 319 | border-radius: 6px; | |
| 320 | font-size: 11.5px; | |
| 321 | font-weight: 600; | |
| 322 | border: 1px solid rgba(22,24,29,0.14); | |
| 323 | background: #ffffff; | |
| 324 | color: #16181d; | |
| 325 | cursor: pointer; | |
| 326 | font-family: inherit; | |
| 327 | white-space: nowrap; | |
| 328 | } | |
| 329 | .ob-conn-btn:hover { background: #f5f5f7; } | |
| 330 | ||
| 331 | /* ── wizard footer / CTA ─────────────────────────────────────────────────── */ | |
| 332 | .ob-wiz-footer { | |
| 333 | padding: 16px 26px; | |
| 334 | border-top: 1px solid rgba(22,24,29,0.06); | |
| 335 | display: flex; | |
| 336 | align-items: center; | |
| 337 | gap: 12px; | |
| 338 | flex-wrap: wrap; | |
| 339 | } | |
| 340 | .ob-cta-btn { | |
| 341 | padding: 9px 18px; | |
| 342 | border-radius: 9px; | |
| 343 | font-size: 13.5px; | |
| 344 | font-weight: 600; | |
| 345 | background: #16181d; | |
| 346 | color: #fff; | |
| 347 | text-decoration: none; | |
| 348 | white-space: nowrap; | |
| 349 | display: inline-block; | |
| 350 | } | |
| 351 | .ob-cta-btn:hover { opacity: 0.88; } | |
| 352 | .ob-cta-note { font-size: 12px; color: #8a8d99; } | |
| 353 | ||
| 354 | /* ── right rail ──────────────────────────────────────────────────────────── */ | |
| 355 | .ob-rail { | |
| 356 | display: flex; | |
| 357 | flex-direction: column; | |
| 358 | gap: 32px; | |
| 359 | position: sticky; | |
| 360 | top: 92px; | |
| 361 | } | |
| 362 | .ob-rail-hed { | |
| 363 | font-family: 'JetBrains Mono', monospace; | |
| 364 | font-size: 11px; | |
| 365 | letter-spacing: 0.12em; | |
| 366 | text-transform: uppercase; | |
| 367 | color: #8a8d99; | |
| 368 | font-weight: 500; | |
| 369 | margin: 0 0 14px; | |
| 370 | } | |
| 371 | ||
| 372 | /* ── sensed card ─────────────────────────────────────────────────────────── */ | |
| 373 | .ob-sensed-card { | |
| 374 | border: 1px solid rgba(22,24,29,0.07); | |
| 375 | border-radius: 12px; | |
| 376 | background: #ffffff; | |
| 377 | padding: 20px; | |
| 378 | display: flex; | |
| 379 | flex-direction: column; | |
| 380 | gap: 11px; | |
| 381 | } | |
| 382 | .ob-sensed-row { display: flex; justify-content: space-between; gap: 12px; font-size: 12.5px; } | |
| 383 | .ob-sensed-k { color: #6b7080; } | |
| 384 | .ob-sensed-v { | |
| 385 | font-family: 'JetBrains Mono', monospace; | |
| 386 | font-size: 11.5px; | |
| 387 | color: #16181d; | |
| 388 | text-align: right; | |
| 389 | } | |
| 390 | ||
| 391 | /* ── invariant cards ─────────────────────────────────────────────────────── */ | |
| 392 | .ob-invs { display: flex; flex-direction: column; gap: 10px; } | |
| 393 | .ob-inv { | |
| 394 | border: 1px solid rgba(22,24,29,0.07); | |
| 395 | border-radius: 12px; | |
| 396 | background: #ffffff; | |
| 397 | padding: 14px 18px; | |
| 398 | } | |
| 399 | .ob-inv-title { font-size: 13px; font-weight: 600; color: #16181d; margin-bottom: 3px; } | |
| 400 | .ob-inv-body { font-size: 12px; color: #6b7080; margin: 0; line-height: 1.55; } | |
| 401 | ||
| 402 | /* ── responsive ──────────────────────────────────────────────────────────── */ | |
| 403 | @media (max-width: 860px) { | |
| 404 | .ob-grid { grid-template-columns: 1fr; } | |
| 405 | .ob-rail { position: static; } | |
| 406 | .ob-tagline { display: none; } | |
| 407 | } | |
| 408 | `; | |
| 409 | ||
| 410 | /* ─── client script ───────────────────────────────────────────────────────── */ | |
| 411 | ||
| 412 | const js = `(function () { | |
| 413 | var GREEN = '#1e7f5c'; | |
| 414 | var AMBER = '#b45309'; | |
| 415 | var INDIGO = '#4353c9'; | |
| 416 | var INK = '#16181d'; | |
| 417 | var GREY = '#c4c6cf'; | |
| 418 | var PULSE = 'gcPulse 1.4s ease-in-out infinite'; | |
| 419 | ||
| 420 | var SITUATIONS = [ | |
| 421 | { | |
| 422 | id: 'broken-codebase', | |
| 423 | label: 'Broken codebase import', | |
| 424 | headDot: AMBER, | |
| 425 | headTitle: 'Auto-Repair Mode engaged', | |
| 426 | headBody: 'Your import arrived with 7 failing tests and a missing config. Instead of a red build bar, your first experience is a fix.', | |
| 427 | steps: [ | |
| 428 | { | |
| 429 | title: 'Repo imported · full history + branches', | |
| 430 | body: '142 commits, 3 branches, semantic index building in the background.', | |
| 431 | dot: GREEN, snippet: false | |
| 432 | }, | |
| 433 | { | |
| 434 | title: 'Archaeology scan complete', | |
| 435 | body: 'Tech-debt hotspots flagged, symbols mapped, architecture topology drawn — your team gets the map on day one.', | |
| 436 | dot: GREEN, snippet: false | |
| 437 | }, | |
| 438 | { | |
| 439 | title: 'Auto-repair drafted your first patch', | |
| 440 | body: 'The healer reproduced the failures in a sandbox and drafted a fix for the missing config and 5 of 7 tests. Review it like any PR — or fix it manually.', | |
| 441 | dot: AMBER, snippet: false | |
| 442 | }, | |
| 443 | { | |
| 444 | title: 'One-shot local setup', | |
| 445 | body: 'Configures git, injects host keys, and tests the connection — no manual terminal spelunking.', | |
| 446 | dot: INK, snippet: true | |
| 447 | } | |
| 448 | ], | |
| 449 | ctaLabel: 'Review your first repair PR', | |
| 450 | ctaHref: '/', | |
| 451 | ctaNote: 'Nothing merges without you — the manual diff is one click away.', | |
| 452 | sensed: [ | |
| 453 | { k: 'Import source', v: 'github mirror' }, | |
| 454 | { k: 'Build status', v: '7 tests failing' }, | |
| 455 | { k: 'Config', v: '.env.example missing' }, | |
| 456 | { k: 'Flow selected', v: 'auto-repair mode' } | |
| 457 | ] | |
| 458 | }, | |
| 459 | { | |
| 460 | id: 'agent-teammate', | |
| 461 | label: 'Agent teammate', | |
| 462 | headDot: INDIGO, | |
| 463 | headTitle: 'Machine user provisioning', | |
| 464 | headBody: 'A non-human teammate is joining. No email drips, no UI tours — scoped credentials and machine-speed rails, immediately.', | |
| 465 | steps: [ | |
| 466 | { | |
| 467 | title: 'Bot identity created · migration-pilot', | |
| 468 | body: 'Scoped API token minted with exactly the permissions its manifest declares — nothing more.', | |
| 469 | dot: GREEN, snippet: false | |
| 470 | }, | |
| 471 | { | |
| 472 | title: 'Personal semantic space seeded', | |
| 473 | body: 'The agent gets its own index scope so its queries never leak across tenants.', | |
| 474 | dot: GREEN, snippet: false | |
| 475 | }, | |
| 476 | { | |
| 477 | title: 'Fast-lane execution rights', | |
| 478 | body: 'Trusted-agent patches skip human-task scheduling; agent-to-agent verification runs at machine speed — still gated, still audited.', | |
| 479 | dot: INDIGO, snippet: false | |
| 480 | }, | |
| 481 | { | |
| 482 | title: 'Reversibility contract signed', | |
| 483 | body: 'Every action the agent takes obeys the same one-click-undo guarantee as ours. No exceptions for bots.', | |
| 484 | dot: INK, snippet: false | |
| 485 | } | |
| 486 | ], | |
| 487 | ctaLabel: 'See agents at work', | |
| 488 | ctaHref: '/', | |
| 489 | ctaNote: 'Every agent action lands in the same audit trail as human ones.', | |
| 490 | sensed: [ | |
| 491 | { k: 'Account type', v: 'machine user' }, | |
| 492 | { k: 'Manifest scopes', v: 'repo:write, pr:open' }, | |
| 493 | { k: 'Drip sequence', v: 'skipped' }, | |
| 494 | { k: 'Flow selected', v: 'fast-lane provisioning' } | |
| 495 | ] | |
| 496 | }, | |
| 497 | { | |
| 498 | id: 'air-gapped-enterprise', | |
| 499 | label: 'Air-gapped enterprise', | |
| 500 | headDot: INK, | |
| 501 | headTitle: 'Sovereign tenant setup', | |
| 502 | headBody: 'This tenant is flagged bare-metal / private-cloud. Public identity options are gone; everything stays inside your network boundary.', | |
| 503 | steps: [ | |
| 504 | { | |
| 505 | title: 'Public OAuth removed', | |
| 506 | body: 'GitHub and Google sign-in don\'t appear. Mandatory SSH key + passkey registration, enforced at the door.', | |
| 507 | dot: INK, snippet: false | |
| 508 | }, | |
| 509 | { | |
| 510 | title: 'Local signing keys seeded', | |
| 511 | body: 'Commit and artifact signing keys generated on your hardware — verification never phones home.', | |
| 512 | dot: GREEN, snippet: false | |
| 513 | }, | |
| 514 | { | |
| 515 | title: 'OCI registry fenced to your perimeter', | |
| 516 | body: 'Container builds, package caches, and signed binaries route entirely inside your Caddy boundary. Zero external CDN or proxy.', | |
| 517 | dot: GREEN, snippet: false | |
| 518 | }, | |
| 519 | { | |
| 520 | title: 'Air-gapped sync scheduled', | |
| 521 | body: 'Repos, semantic indices, and registries replicate hourly to your offline array.', | |
| 522 | dot: INK, snippet: false | |
| 523 | } | |
| 524 | ], | |
| 525 | ctaLabel: 'Inspect the sovereign stack', | |
| 526 | ctaHref: '/', | |
| 527 | ctaNote: 'All 13 production layers, self-owned and self-healing.', | |
| 528 | sensed: [ | |
| 529 | { k: 'Tenant flag', v: 'air-gapped' }, | |
| 530 | { k: 'Public OAuth', v: 'disabled' }, | |
| 531 | { k: 'Registry scope', v: 'local network only' }, | |
| 532 | { k: 'Flow selected', v: 'sovereign setup' } | |
| 533 | ] | |
| 534 | }, | |
| 535 | { | |
| 536 | id: 'just-browsing', | |
| 537 | label: 'Just browsing', | |
| 538 | headDot: GREEN, | |
| 539 | headTitle: 'Playground account · zero strings', | |
| 540 | headBody: 'No migration, no commitment. An ephemeral, isolated sandbox with everything switched on — expires on its own unless you keep it.', | |
| 541 | steps: [ | |
| 542 | { | |
| 543 | title: 'Instant sandbox account', | |
| 544 | body: 'No email required. A demo repo with real history, gates, and an agent ready to talk.', | |
| 545 | dot: GREEN, snippet: false | |
| 546 | }, | |
| 547 | { | |
| 548 | title: 'Try the whole loop', | |
| 549 | body: 'Push a change, watch gates fire at the wire, see Sonnet 5 review it, break something and watch the repair.', | |
| 550 | dot: GREEN, snippet: false | |
| 551 | }, | |
| 552 | { | |
| 553 | title: 'Keep it when you\'re ready', | |
| 554 | body: 'Claiming your account bridges everything — your sandbox history, repos, and sessions come with you. Nothing resets.', | |
| 555 | dot: INDIGO, snippet: false | |
| 556 | } | |
| 557 | ], | |
| 558 | ctaLabel: 'Enter the playground', | |
| 559 | ctaHref: '/', | |
| 560 | ctaNote: 'Expires in 24h unless claimed — claim keeps all history.', | |
| 561 | sensed: [ | |
| 562 | { k: 'Signup', v: 'none — anonymous' }, | |
| 563 | { k: 'Environment', v: 'ephemeral sandbox' }, | |
| 564 | { k: 'Expiry', v: '24h · claimable' }, | |
| 565 | { k: 'Flow selected', v: 'playground' } | |
| 566 | ] | |
| 567 | } | |
| 568 | ]; | |
| 569 | ||
| 570 | /* ── state ─────────────────────────────────────────────────────────────── */ | |
| 571 | var state = { situation: 0, copied: false, conn: 'idle' }; | |
| 572 | ||
| 573 | var root = document.querySelector('.ob-root'); | |
| 574 | if (root) { | |
| 575 | var initSit = root.getAttribute('data-init-situation') || 'broken-codebase'; | |
| 576 | for (var si = 0; si < SITUATIONS.length; si++) { | |
| 577 | if (SITUATIONS[si].id === initSit) { state.situation = si; break; } | |
| 578 | } | |
| 579 | } | |
| 580 | ||
| 581 | function setState(patch) { | |
| 582 | for (var k in patch) { if (Object.prototype.hasOwnProperty.call(patch, k)) state[k] = patch[k]; } | |
| 583 | render(); | |
| 584 | } | |
| 585 | ||
| 586 | /* ── html escaping ─────────────────────────────────────────────────────── */ | |
| 587 | function esc(str) { | |
| 588 | return String(str) | |
| 589 | .replace(/&/g, '&') | |
| 590 | .replace(/</g, '<') | |
| 591 | .replace(/>/g, '>') | |
| 592 | .replace(/"/g, '"'); | |
| 593 | } | |
| 594 | ||
| 595 | /* ── render pills ──────────────────────────────────────────────────────── */ | |
| 596 | function renderPills() { | |
| 597 | var el = document.getElementById('ob-pills'); | |
| 598 | if (!el) return; | |
| 599 | var html = ''; | |
| 600 | for (var i = 0; i < SITUATIONS.length; i++) { | |
| 601 | var active = i === state.situation; | |
| 602 | html += '<button type="button" class="ob-pill' + (active ? ' ob-pill--active' : '') + | |
| 603 | '" data-sit="' + i + '">' + esc(SITUATIONS[i].label) + '</button>'; | |
| 604 | } | |
| 605 | el.innerHTML = html; | |
| 606 | var pills = el.querySelectorAll('.ob-pill'); | |
| 607 | for (var j = 0; j < pills.length; j++) { | |
| 608 | (function (pill) { | |
| 609 | pill.addEventListener('click', function () { | |
| 610 | setState({ situation: parseInt(pill.getAttribute('data-sit'), 10), copied: false, conn: 'idle' }); | |
| 611 | }); | |
| 612 | })(pills[j]); | |
| 613 | } | |
| 614 | } | |
| 615 | ||
| 616 | /* ── render wizard header ──────────────────────────────────────────────── */ | |
| 617 | function renderWizHead() { | |
| 618 | var el = document.getElementById('ob-wiz-head'); | |
| 619 | if (!el) return; | |
| 620 | var d = SITUATIONS[state.situation]; | |
| 621 | el.innerHTML = | |
| 622 | '<div class="ob-wiz-head-row">' + | |
| 623 | '<span class="ob-wiz-dot" style="background:' + d.headDot + '"></span>' + | |
| 624 | '<span class="ob-wiz-title">' + esc(d.headTitle) + '</span>' + | |
| 625 | '</div>' + | |
| 626 | '<p class="ob-wiz-body">' + esc(d.headBody) + '</p>'; | |
| 627 | } | |
| 628 | ||
| 629 | /* ── render steps ──────────────────────────────────────────────────────── */ | |
| 630 | function renderSteps() { | |
| 631 | var el = document.getElementById('ob-wiz-steps'); | |
| 632 | if (!el) return; | |
| 633 | var d = SITUATIONS[state.situation]; | |
| 634 | var html = ''; | |
| 635 | ||
| 636 | for (var i = 0; i < d.steps.length; i++) { | |
| 637 | var s = d.steps[i]; | |
| 638 | var isLast = i === d.steps.length - 1; | |
| 639 | var snippetHtml = ''; | |
| 640 | ||
| 641 | if (s.snippet) { | |
| 642 | var copyLabel = state.copied ? 'Copied ✓' : 'Copy'; | |
| 643 | var connLabel = state.conn === 'ok' | |
| 644 | ? 'SSH + OAuth verified — direct to sovereign array, no external edge' | |
| 645 | : state.conn === 'testing' | |
| 646 | ? 'Testing local → sovereign connection…' | |
| 647 | : 'Connection not yet tested'; | |
| 648 | var connDot = state.conn === 'ok' ? GREEN : state.conn === 'testing' ? AMBER : GREY; | |
| 649 | var connColor = state.conn === 'ok' ? GREEN : '#6b7080'; | |
| 650 | var connAnim = state.conn === 'testing' ? PULSE : 'none'; | |
| 651 | var testLabel = state.conn === 'testing' ? 'Testing…' | |
| 652 | : state.conn === 'ok' ? 'Re-test' | |
| 653 | : 'Test connection'; | |
| 654 | ||
| 655 | snippetHtml = | |
| 656 | '<div class="ob-term">' + | |
| 657 | '<div class="ob-term-bar">' + | |
| 658 | '<span class="ob-term-label">one-shot local setup</span>' + | |
| 659 | '<button type="button" class="ob-copy-btn" id="ob-copy-btn">' + esc(copyLabel) + '</button>' + | |
| 660 | '</div>' + | |
| 661 | '<div class="ob-term-body">' + | |
| 662 | '<div><span class="ob-term-prompt">$</span> curl -sSL gluecron.com/setup | bash</div>' + | |
| 663 | '<div class="ob-term-comment"># configures git · injects host keys · tests the connection</div>' + | |
| 664 | '</div>' + | |
| 665 | '</div>' + | |
| 666 | '<div class="ob-conn" style="color:' + connColor + '">' + | |
| 667 | '<span class="ob-conn-dot" style="background:' + connDot + ';animation:' + connAnim + '"></span>' + | |
| 668 | '<span class="ob-conn-text">' + esc(connLabel) + '</span>' + | |
| 669 | '<button type="button" class="ob-conn-btn" id="ob-conn-btn">' + esc(testLabel) + '</button>' + | |
| 670 | '</div>'; | |
| 671 | } | |
| 672 | ||
| 673 | html += | |
| 674 | '<div class="ob-step">' + | |
| 675 | '<div class="ob-step-track">' + | |
| 676 | '<span class="ob-step-dot" style="background:' + s.dot + '"></span>' + | |
| 677 | (!isLast ? '<span class="ob-step-line"></span>' : '') + | |
| 678 | '</div>' + | |
| 679 | '<div>' + | |
| 680 | '<div class="ob-step-title">' + esc(s.title) + '</div>' + | |
| 681 | '<p class="ob-step-body">' + esc(s.body) + '</p>' + | |
| 682 | snippetHtml + | |
| 683 | '</div>' + | |
| 684 | '</div>'; | |
| 685 | } | |
| 686 | ||
| 687 | el.innerHTML = html; | |
| 688 | ||
| 689 | /* copy button */ | |
| 690 | var copyBtn = document.getElementById('ob-copy-btn'); | |
| 691 | if (copyBtn) { | |
| 692 | copyBtn.addEventListener('click', function () { | |
| 693 | try { navigator.clipboard.writeText('curl -sSL https://gluecron.com/setup | bash'); } catch (e) {} | |
| 694 | setState({ copied: true }); | |
| 695 | setTimeout(function () { setState({ copied: false }); }, 1200); | |
| 696 | }); | |
| 697 | } | |
| 698 | ||
| 699 | /* test-connection button */ | |
| 700 | var connBtn = document.getElementById('ob-conn-btn'); | |
| 701 | if (connBtn) { | |
| 702 | connBtn.addEventListener('click', function () { | |
| 703 | if (state.conn === 'testing') return; | |
| 704 | setState({ conn: 'testing' }); | |
| 705 | setTimeout(function () { setState({ conn: 'ok' }); }, 1600); | |
| 706 | }); | |
| 707 | } | |
| 708 | } | |
| 709 | ||
| 710 | /* ── render footer / CTA ───────────────────────────────────────────────── */ | |
| 711 | function renderFooter() { | |
| 712 | var el = document.getElementById('ob-wiz-footer'); | |
| 713 | if (!el) return; | |
| 714 | var d = SITUATIONS[state.situation]; | |
| 715 | el.innerHTML = | |
| 716 | '<a href="' + esc(d.ctaHref) + '" class="ob-cta-btn">' + esc(d.ctaLabel) + '</a>' + | |
| 717 | '<span class="ob-cta-note">' + esc(d.ctaNote) + '</span>'; | |
| 718 | } | |
| 719 | ||
| 720 | /* ── render sensed card ────────────────────────────────────────────────── */ | |
| 721 | function renderSensed() { | |
| 722 | var el = document.getElementById('ob-sensed-card'); | |
| 723 | if (!el) return; | |
| 724 | var d = SITUATIONS[state.situation]; | |
| 725 | var html = ''; | |
| 726 | for (var i = 0; i < d.sensed.length; i++) { | |
| 727 | html += | |
| 728 | '<div class="ob-sensed-row">' + | |
| 729 | '<span class="ob-sensed-k">' + esc(d.sensed[i].k) + '</span>' + | |
| 730 | '<span class="ob-sensed-v">' + esc(d.sensed[i].v) + '</span>' + | |
| 731 | '</div>'; | |
| 732 | } | |
| 733 | el.innerHTML = html; | |
| 734 | } | |
| 735 | ||
| 736 | /* ── full render ───────────────────────────────────────────────────────── */ | |
| 737 | function render() { | |
| 738 | renderPills(); | |
| 739 | renderWizHead(); | |
| 740 | renderSteps(); | |
| 741 | renderFooter(); | |
| 742 | renderSensed(); | |
| 743 | } | |
| 744 | ||
| 745 | if (document.readyState === 'loading') { | |
| 746 | document.addEventListener('DOMContentLoaded', render); | |
| 747 | } else { | |
| 748 | render(); | |
| 749 | } | |
| 750 | })();`; |