@@ -59,6 +59,11 @@ func main() {
5959 os .Exit (1 )
6060 }
6161
62+ if err := generate404 (); err != nil {
63+ fmt .Fprintf (os .Stderr , "Error generating 404.html: %v\n " , err )
64+ os .Exit (1 )
65+ }
66+
6267 // Copy CNAME and static root files to site directory
6368 if cname , err := os .ReadFile ("CNAME" ); err == nil {
6469 os .WriteFile ("site/CNAME" , cname , 0644 )
@@ -71,7 +76,11 @@ func main() {
7176 for _ , cat := range cfg .Categories {
7277 totalRepos += len (cat .Repos )
7378 }
74- fmt .Printf ("Generated index.html and sitemap.xml (%d repos)\n " , totalRepos )
79+ fmt .Printf ("Generated site (%d repos)\n " , totalRepos )
80+ }
81+
82+ func generate404 () error {
83+ return os .WriteFile ("site/404.html" , []byte (notFoundTemplate ), 0644 )
7584}
7685
7786func generateSitemap (cfg Config ) error {
@@ -456,7 +465,7 @@ a:focus-visible { outline: 2px solid var(--accent-light); outline-offset: 2px; b
456465 <div class="submit-label">Don't see your repo? Paste a URL to generate arch docs:</div>
457466 <div class="submit-row">
458467 <input type="text" class="submit-input" id="submit-url" placeholder="https://github.com/owner/repo" autocomplete="off" spellcheck="false">
459- <button class="submit-btn" id="submit-btn" type="button">Request </button>
468+ <button class="submit-btn" id="submit-btn" type="button">Generate </button>
460469 </div>
461470 <div class="submit-feedback" id="submit-feedback"></div>
462471 </div>
@@ -566,7 +575,7 @@ a:focus-visible { outline: 2px solid var(--accent-light); outline-offset: 2px; b
566575
567576 // Loading state
568577 submitBtn.classList.add('loading');
569- submitBtn.textContent = 'Submitting ...';
578+ submitBtn.textContent = 'Generating ...';
570579 showFeedback('Setting up ' + name + '...', 'preview');
571580
572581 try {
@@ -580,22 +589,16 @@ a:focus-visible { outline: 2px solid var(--accent-light); outline-offset: 2px; b
580589 if (!resp.ok || !data.success) {
581590 showFeedback(data.error || 'Something went wrong. Please try again.', 'error');
582591 submitBtn.classList.remove('loading');
583- submitBtn.textContent = 'Request ';
592+ submitBtn.textContent = 'Generate ';
584593 return;
585594 }
586595
587- // Success — show the link, clear the input
588- showFeedback(
589- '\u2713 Submitted! Docs will be generated at <a href="' + data.docs_url + '">' +
590- 'repos.supermodeltools.com/' + name + '/</a>', 'success'
591- );
592- submitInput.value = '';
593- submitBtn.classList.remove('active', 'loading');
594- submitBtn.textContent = 'Request';
596+ // Redirect to the docs page — 404.html shows loading until docs are ready
597+ window.location.href = data.docs_url;
595598 } catch (e) {
596599 showFeedback('Network error. Please try again.', 'error');
597600 submitBtn.classList.remove('loading');
598- submitBtn.textContent = 'Request ';
601+ submitBtn.textContent = 'Generate ';
599602 }
600603 }
601604
@@ -617,3 +620,305 @@ a:focus-visible { outline: 2px solid var(--accent-light); outline-offset: 2px; b
617620</body>
618621</html>
619622`
623+
624+ const notFoundTemplate = `<!DOCTYPE html>
625+ <html lang="en">
626+ <head>
627+ <meta charset="utf-8">
628+ <meta name="viewport" content="width=device-width, initial-scale=1">
629+ <title>Generating — Supermodel Architecture Docs</title>
630+ <link rel="preconnect" href="https://fonts.googleapis.com">
631+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
632+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
633+ <style>
634+ :root {
635+ --bg: #0f1117;
636+ --bg-card: #1a1d27;
637+ --border: #2a2e3e;
638+ --text: #e4e4e7;
639+ --text-muted: #9ca3af;
640+ --accent: #6366f1;
641+ --accent-light: #818cf8;
642+ --font: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
643+ --mono: 'JetBrains Mono', 'Fira Code', monospace;
644+ }
645+ * { margin: 0; padding: 0; box-sizing: border-box; }
646+ body {
647+ font-family: var(--font);
648+ background: var(--bg);
649+ color: var(--text);
650+ min-height: 100vh;
651+ display: flex;
652+ flex-direction: column;
653+ align-items: center;
654+ justify-content: center;
655+ -webkit-font-smoothing: antialiased;
656+ }
657+ a { color: var(--accent-light); text-decoration: none; }
658+ a:hover { text-decoration: underline; }
659+
660+ .loading-container {
661+ text-align: center;
662+ max-width: 480px;
663+ padding: 24px;
664+ }
665+
666+ /* Eye logo animation */
667+ .eye-wrap {
668+ width: 120px;
669+ height: 104px;
670+ margin: 0 auto 32px;
671+ position: relative;
672+ }
673+ .eye-wrap svg {
674+ width: 120px;
675+ height: 104px;
676+ color: var(--accent-light);
677+ }
678+ .eye-wrap svg path {
679+ animation: eyePulse 2.4s ease-in-out infinite;
680+ }
681+ @keyframes eyePulse {
682+ 0%, 100% { opacity: 0.4; }
683+ 50% { opacity: 1; }
684+ }
685+
686+ /* Scanning line */
687+ .scan-line {
688+ position: absolute;
689+ top: 0;
690+ left: 10%;
691+ width: 80%;
692+ height: 2px;
693+ background: linear-gradient(90deg, transparent, var(--accent-light), transparent);
694+ animation: scan 2.4s ease-in-out infinite;
695+ border-radius: 1px;
696+ }
697+ @keyframes scan {
698+ 0% { top: 15%; opacity: 0; }
699+ 10% { opacity: 1; }
700+ 90% { opacity: 1; }
701+ 100% { top: 85%; opacity: 0; }
702+ }
703+
704+ .loading-title {
705+ font-size: 22px;
706+ font-weight: 700;
707+ margin-bottom: 8px;
708+ }
709+ .loading-repo {
710+ font-family: var(--mono);
711+ font-size: 16px;
712+ color: var(--accent-light);
713+ margin-bottom: 24px;
714+ }
715+
716+ /* Progress steps */
717+ .steps {
718+ text-align: left;
719+ margin: 0 auto;
720+ display: inline-block;
721+ }
722+ .step {
723+ display: flex;
724+ align-items: center;
725+ gap: 10px;
726+ padding: 6px 0;
727+ font-size: 14px;
728+ color: var(--text-muted);
729+ transition: color 0.3s;
730+ }
731+ .step.active { color: var(--text); }
732+ .step.done { color: var(--accent-light); }
733+ .step-icon {
734+ width: 20px;
735+ height: 20px;
736+ flex-shrink: 0;
737+ display: flex;
738+ align-items: center;
739+ justify-content: center;
740+ }
741+ .step-spinner {
742+ width: 16px;
743+ height: 16px;
744+ border: 2px solid var(--border);
745+ border-top-color: var(--accent-light);
746+ border-radius: 50%;
747+ animation: spin 0.8s linear infinite;
748+ }
749+ @keyframes spin {
750+ to { transform: rotate(360deg); }
751+ }
752+ .step-check { color: var(--accent-light); }
753+ .step-dot {
754+ width: 8px;
755+ height: 8px;
756+ border-radius: 50%;
757+ background: var(--border);
758+ }
759+
760+ .loading-hint {
761+ margin-top: 32px;
762+ font-size: 13px;
763+ color: var(--text-muted);
764+ }
765+
766+ /* 404 state (not a repo path) */
767+ .not-found {
768+ text-align: center;
769+ max-width: 480px;
770+ padding: 24px;
771+ }
772+ .not-found h1 {
773+ font-size: 72px;
774+ font-weight: 700;
775+ color: var(--border);
776+ line-height: 1;
777+ margin-bottom: 16px;
778+ }
779+ .not-found p {
780+ color: var(--text-muted);
781+ font-size: 16px;
782+ margin-bottom: 24px;
783+ }
784+
785+ .hidden { display: none; }
786+
787+ @media (max-width: 768px) {
788+ .loading-title { font-size: 18px; }
789+ .loading-repo { font-size: 14px; }
790+ }
791+ </style>
792+ </head>
793+ <body>
794+ <!-- Loading state (repo generation in progress) -->
795+ <div class="loading-container hidden" id="loading">
796+ <div class="eye-wrap">
797+ <svg viewBox="0 0 90 78" fill="none" xmlns="http://www.w3.org/2000/svg">
798+ <path d="M90 61.1124C75.9375 73.4694 59.8419 78 44.7554 78C29.669 78 11.8614 72.6122 0 61.1011V16.9458C11.6168 6 29.891 0 44.9887 0C62.77 0 78.8723 6.97959 89.9887 16.9458V61.1124H90ZM88.1881 38.9553C77.7923 22.8824 59.8983 15.7959 44.7554 15.7959C29.6126 15.7959 13.4515 21.9008 1.556 38.9444C12.5382 54.69 26.9 62.5085 44.7554 62.0944C67.6297 61.5639 77.6495 51.9184 88.1881 38.9553ZM44.7554 16.3475C32.4756 16.3475 22.3888 26.6879 22.2554 38.9388C34.3765 38.9162 44.7554 29.1429 44.7554 16.3475C44.7554 29.1429 55.1344 38.9162 67.2554 38.9388C67.1202 26.5216 57.1141 16.3475 44.7554 16.3475ZM44.7554 61.5639C44.7554 48.4898 34.3765 38.9613 22.2554 38.9388C22.3888 51.1897 32.4756 61.5639 44.7554 61.5639C57.0352 61.5639 67.122 51.1897 67.2554 38.9388C55.1344 38.9613 44.7554 48.4898 44.7554 61.5639Z" fill="currentColor"/>
799+ </svg>
800+ <div class="scan-line"></div>
801+ </div>
802+ <div class="loading-title">Generating Architecture Docs</div>
803+ <div class="loading-repo" id="loading-repo"></div>
804+ <div class="steps" id="steps">
805+ <div class="step" data-step="fork">
806+ <div class="step-icon"><div class="step-dot"></div></div>
807+ <span>Forking repository</span>
808+ </div>
809+ <div class="step" data-step="analyze">
810+ <div class="step-icon"><div class="step-dot"></div></div>
811+ <span>Analyzing codebase</span>
812+ </div>
813+ <div class="step" data-step="graph">
814+ <div class="step-icon"><div class="step-dot"></div></div>
815+ <span>Building code graphs</span>
816+ </div>
817+ <div class="step" data-step="deploy">
818+ <div class="step-icon"><div class="step-dot"></div></div>
819+ <span>Deploying documentation</span>
820+ </div>
821+ </div>
822+ <div class="loading-hint">This usually takes 2–5 minutes. The page will refresh automatically.</div>
823+ </div>
824+
825+ <!-- Genuine 404 state -->
826+ <div class="not-found hidden" id="not-found">
827+ <h1>404</h1>
828+ <p>This page doesn't exist.</p>
829+ <a href="/">Browse all repositories →</a>
830+ </div>
831+
832+ <script>
833+ (function() {
834+ var path = window.location.pathname.replace(/\/+$/, '').replace(/^\/+/, '');
835+ var segments = path.split('/').filter(Boolean);
836+
837+ // If the path looks like a repo name (single segment, no dots suggesting a file),
838+ // show the loading page. Otherwise show genuine 404.
839+ var isRepoPath = segments.length >= 1 && !segments[0].includes('.');
840+ var repoName = segments[0] || '';
841+
842+ if (isRepoPath && repoName) {
843+ document.getElementById('loading').classList.remove('hidden');
844+ document.getElementById('loading-repo').textContent = repoName;
845+ document.title = 'Generating ' + repoName + ' — Supermodel Architecture Docs';
846+
847+ // Animate steps over time to show progress
848+ var steps = document.querySelectorAll('.step');
849+ var stepTimings = [0, 8000, 30000, 60000]; // approximate real timings
850+
851+ function activateStep(index) {
852+ steps.forEach(function(s, i) {
853+ var icon = s.querySelector('.step-icon');
854+ if (i < index) {
855+ s.classList.add('done');
856+ s.classList.remove('active');
857+ icon.innerHTML = '<svg class="step-check" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg>';
858+ } else if (i === index) {
859+ s.classList.add('active');
860+ s.classList.remove('done');
861+ icon.innerHTML = '<div class="step-spinner"></div>';
862+ } else {
863+ s.classList.remove('active', 'done');
864+ icon.innerHTML = '<div class="step-dot"></div>';
865+ }
866+ });
867+ }
868+
869+ // Start first step immediately
870+ activateStep(0);
871+ var currentStep = 0;
872+
873+ // Advance steps on a timer
874+ function advanceStep() {
875+ if (currentStep < steps.length - 1) {
876+ currentStep++;
877+ activateStep(currentStep);
878+ }
879+ }
880+ setTimeout(advanceStep, 8000); // ~8s: fork done, analyzing
881+ setTimeout(advanceStep, 35000); // ~35s: graphs building
882+ setTimeout(advanceStep, 70000); // ~70s: deploying
883+
884+ // Poll for the real docs page
885+ var pollInterval = 5000;
886+ var maxPolls = 120; // 10 minutes max
887+ var pollCount = 0;
888+
889+ function pollForDocs() {
890+ pollCount++;
891+ if (pollCount > maxPolls) return;
892+
893+ fetch(window.location.href, { cache: 'no-store', redirect: 'follow' })
894+ .then(function(resp) {
895+ if (resp.ok) {
896+ return resp.text().then(function(html) {
897+ // Make sure it's the real docs page, not this 404 page
898+ if (html.indexOf('arch-docs') !== -1 && html.indexOf('loading-container') === -1) {
899+ // Docs are ready — mark all steps done then reload
900+ activateStep(steps.length);
901+ setTimeout(function() { window.location.reload(); }, 800);
902+ return;
903+ }
904+ setTimeout(pollForDocs, pollInterval);
905+ });
906+ } else {
907+ setTimeout(pollForDocs, pollInterval);
908+ }
909+ })
910+ .catch(function() {
911+ setTimeout(pollForDocs, pollInterval);
912+ });
913+ }
914+
915+ setTimeout(pollForDocs, pollInterval);
916+ } else {
917+ document.getElementById('not-found').classList.remove('hidden');
918+ document.title = '404 — Supermodel Architecture Docs';
919+ }
920+ })();
921+ </script>
922+ </body>
923+ </html>
924+ `
0 commit comments