diff --git a/generate-index.go b/generate-index.go
index 0901df7..5492b64 100644
--- a/generate-index.go
+++ b/generate-index.go
@@ -59,6 +59,11 @@ func main() {
os.Exit(1)
}
+ if err := generate404(); err != nil {
+ fmt.Fprintf(os.Stderr, "Error generating 404.html: %v\n", err)
+ os.Exit(1)
+ }
+
// Copy CNAME and static root files to site directory
if cname, err := os.ReadFile("CNAME"); err == nil {
os.WriteFile("site/CNAME", cname, 0644)
@@ -71,7 +76,14 @@ func main() {
for _, cat := range cfg.Categories {
totalRepos += len(cat.Repos)
}
- fmt.Printf("Generated index.html and sitemap.xml (%d repos)\n", totalRepos)
+ fmt.Printf("Generated site (%d repos)\n", totalRepos)
+}
+
+func generate404() error {
+ return os.WriteFile("site/404.html", []byte(`
+
Redirecting…
+Redirecting to homepage…
+`), 0644)
}
func generateSitemap(cfg Config) error {
@@ -196,18 +208,6 @@ a:focus-visible { outline: 2px solid var(--accent-light); outline-offset: 2px; b
.site-nav { display: flex; gap: 16px; align-items: center; }
.site-nav a { color: var(--text-muted); font-size: 14px; font-weight: 500; white-space: nowrap; }
.site-nav a:hover { color: var(--text); text-decoration: none; }
-.request-btn {
- color: var(--accent-light) !important;
- border: 1px solid var(--accent);
- border-radius: var(--radius);
- padding: 6px 12px;
- transition: background 0.2s, color 0.2s;
-}
-.request-btn:hover {
- background: var(--accent);
- color: #fff !important;
- text-decoration: none !important;
-}
.hero {
padding: 64px 0 48px;
text-align: center;
@@ -395,14 +395,21 @@ a:focus-visible { outline: 2px solid var(--accent-light); outline-offset: 2px; b
pointer-events: auto;
}
.submit-btn.active:hover { background: var(--accent-light); }
-.submit-preview {
- margin-top: 8px;
+.submit-btn.loading {
+ opacity: 0.6;
+ pointer-events: none;
+}
+.submit-feedback {
+ margin-top: 10px;
font-size: 13px;
- color: var(--green);
font-family: var(--mono);
display: none;
}
-.submit-preview.visible { display: block; }
+.submit-feedback.visible { display: block; }
+.submit-feedback.preview { color: var(--text-muted); }
+.submit-feedback.success { color: var(--green); }
+.submit-feedback.success a { color: var(--green); text-decoration: underline; }
+.submit-feedback.error { color: var(--red); }
@media (max-width: 768px) {
.container { padding: 0 16px; }
.hero { padding: 40px 0 32px; }
@@ -432,7 +439,6 @@ a:focus-visible { outline: 2px solid var(--accent-light); outline-offset: 2px; b
Website
GitHub
X
- + Request a Repo
@@ -462,9 +468,9 @@ a:focus-visible { outline: 2px solid var(--accent-light); outline-offset: 2px; b
Don't see your repo? Paste a URL to generate arch docs:
-
+
-
+
@@ -510,10 +516,10 @@ a:focus-visible { outline: 2px solid var(--accent-light); outline-offset: 2px; b
var noResults = document.getElementById('no-results');
var submitInput = document.getElementById('submit-url');
var submitBtn = document.getElementById('submit-btn');
- var submitPreview = document.getElementById('submit-preview');
+ var feedback = document.getElementById('submit-feedback');
var noResultsRequest = document.getElementById('no-results-request');
- var issueBase = 'https://github.com/supermodeltools/supermodeltools.github.io/issues/new?template=request-repo.yml';
+ var API_URL = '/api/request';
// --- Search ---
searchInput.addEventListener('input', function() {
@@ -546,36 +552,65 @@ a:focus-visible { outline: 2px solid var(--accent-light); outline-offset: 2px; b
return null;
}
+ function showFeedback(msg, type) {
+ feedback.className = 'submit-feedback visible ' + type;
+ feedback.innerHTML = msg;
+ }
+
submitInput.addEventListener('input', function() {
var parsed = parseRepo(this.value);
if (parsed) {
var name = parsed.split('/')[1];
- submitPreview.textContent = '\u2192 Docs will be at repos.supermodeltools.com/' + name + '/';
- submitPreview.classList.add('visible');
+ showFeedback('\u2192 repos.supermodeltools.com/' + name + '/', 'preview');
submitBtn.classList.add('active');
} else {
- submitPreview.classList.remove('visible');
+ feedback.className = 'submit-feedback';
submitBtn.classList.remove('active');
}
});
- function submitRequest() {
+ async function submitRequest() {
var parsed = parseRepo(submitInput.value);
if (!parsed) return;
+
var repoUrl = 'https://github.com/' + parsed;
var name = parsed.split('/')[1];
- var url = issueBase
- + '&repo_url=' + encodeURIComponent(repoUrl)
- + '&title=' + encodeURIComponent('[Repo Request] ' + name);
- window.open(url, '_blank');
+
+ // Loading state
+ submitBtn.classList.add('loading');
+ submitBtn.textContent = 'Generating...';
+ showFeedback('Setting up ' + name + '...', 'preview');
+
+ try {
+ var resp = await fetch(API_URL, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ url: repoUrl }),
+ });
+ var data = await resp.json();
+
+ if (!resp.ok || !data.success) {
+ showFeedback(data.error || 'Something went wrong. Please try again.', 'error');
+ submitBtn.classList.remove('loading');
+ submitBtn.textContent = 'Generate';
+ return;
+ }
+
+ // Redirect to the skeleton loading page — served by the worker
+ window.location.href = data.generating_url;
+ } catch (e) {
+ showFeedback('Network error. Please try again.', 'error');
+ submitBtn.classList.remove('loading');
+ submitBtn.textContent = 'Generate';
+ }
}
submitBtn.addEventListener('click', submitRequest);
submitInput.addEventListener('keydown', function(e) {
- if (e.key === 'Enter') submitRequest();
+ if (e.key === 'Enter' && submitBtn.classList.contains('active')) submitRequest();
});
- // "No results" request link: pre-fill with search query as a guess
+ // "No results" link: scroll up and focus the submit input
noResultsRequest.addEventListener('click', function() {
var q = searchInput.value.trim();
submitInput.value = q;
@@ -588,3 +623,4 @@ a:focus-visible { outline: 2px solid var(--accent-light); outline-offset: 2px; b