| <!DOCTYPE html> |
| <html lang="en" data-theme="dark"> |
| <head> |
| <meta charset="UTF-8"/> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"/> |
| <title>GitPulse: Analyze @{{ username }} β GitHub Talent Finder</title> |
| <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=Inter:wght@300;400;500;600&family=Space+Mono:wght@400;700&display=swap" rel="stylesheet"/> |
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> |
| <style> |
| :root[data-theme="dark"] { |
| --bg:#0a0a0f; --surface:#0e0e13; --surface2:#16161d; |
| --border:#1c1c24; --border2:#2d2d39; |
| --text:#f0f0f5; --text-muted:#9494a1; |
| --accent:#58a6ff; --accent2:#1f6feb; --accent-bg:rgba(88,166,255,0.1); |
| --secondary:#9d8fff; --secondary-bg:rgba(157,143,255,0.1); |
| --shadow:0 12px 48px rgba(0,0,0,0.5); |
| --hero-gradient:radial-gradient(circle at top left, rgba(88,166,255,0.08), transparent), radial-gradient(circle at bottom right, rgba(157,143,255,0.05), transparent); |
| } |
| :root[data-theme="light"] { |
| --bg:#f8f9fc; --surface:#ffffff; --surface2:#f1f4f9; |
| --border:#e1e4e8; --border2:#d1d5da; |
| --text:#1a1a2e; --text-muted:#6a737d; |
| --accent:#0969da; --accent2:#0550ae; --accent-bg:rgba(9,105,218,0.05); |
| --secondary:#6f42c1; --secondary-bg:rgba(111,66,193,0.05); |
| --shadow:0 8px 24px rgba(149,157,165,0.12); |
| --hero-gradient:linear-gradient(135deg,rgba(9,105,218,0.04) 0%,rgba(9,105,218,0.01) 100%); |
| } |
| :root[data-theme="navy"] { |
| --bg:#0b1121; --surface:#111a2f; --surface2:#1d2943; |
| --border:#1e2d4d; --border2:#2a3b5e; |
| --text:#e6edf3; --text-muted:#8b9eb0; |
| --accent:#64ffda; --accent2:#0a85d9; --accent-bg:rgba(100,255,218,0.1); |
| --secondary:#70a1ff; --secondary-bg:rgba(112,161,255,0.1); |
| --shadow:0 12px 48px rgba(0,0,0,0.6); |
| --hero-gradient:radial-gradient(circle at 10% 20%, rgba(100,255,218,0.06), transparent 50%); |
| } |
| |
| *{box-sizing:border-box;margin:0;padding:0;} |
| body{background:var(--bg);color:var(--text);font-family:'Inter',sans-serif;min-height:100vh;transition:all 0.3s;overflow-x:hidden;} |
| h1,h2,h3,h4{font-family:'Space Grotesk',sans-serif;letter-spacing:-0.02em;} |
| |
| |
| header{background:var(--surface);border-bottom:1px solid var(--border);padding:14px 28px;display:flex;align-items:center;justify-content:space-between;position:sticky;top:0;z-index:100;box-shadow:var(--shadow);} |
| .logo{display:flex;align-items:center;gap:9px;text-decoration:none;} |
| .logo-text{font-family:'Space Mono',monospace;font-size:15px;font-weight:700;color:var(--accent);} |
| .header-right{display:flex;align-items:center;gap:8px;} |
| .btn-back{padding:7px 16px;background:transparent;border:1px solid var(--border2);border-radius:8px;color:var(--text-muted);font-family:'Space Mono',monospace;font-size:11px;cursor:pointer;text-decoration:none;transition:all 0.2s;} |
| .btn-back:hover{border-color:var(--accent);color:var(--accent);} |
| .theme-switcher{display:flex;gap:3px;background:var(--surface2);border:1px solid var(--border2);border-radius:20px;padding:3px;} |
| .theme-pill{padding:4px 10px;border-radius:14px;border:none;background:transparent;color:var(--text-muted);font-size:10px;font-family:'Space Mono',monospace;cursor:pointer;transition:all 0.2s;} |
| .theme-pill.active{background:var(--accent2);color:#fff;} |
| .theme-pill:not(.active):hover{color:var(--accent);} |
| |
| |
| .nav-tabs{background:var(--surface);border-bottom:1px solid var(--border);padding:0 28px;display:flex;overflow-x:auto;} |
| .nav-tab{padding:12px 18px;border:none;background:transparent;color:var(--text-muted);font-family:'Space Mono',monospace;font-size:11px;font-weight:700;cursor:pointer;border-bottom:2px solid transparent;transition:all 0.2s;white-space:nowrap;letter-spacing:0.05em;text-decoration:none;display:flex;align-items:center;gap:5px;} |
| .nav-tab:hover{color:var(--accent);} |
| .nav-tab.active{color:var(--accent);border-bottom-color:var(--accent);} |
| |
| |
| .progress-bar{position:fixed;top:0;left:0;right:0;height:2px;background:linear-gradient(90deg,var(--accent2),var(--accent));z-index:9999;display:none;} |
| .progress-bar.loading{display:block;animation:progress 1.5s ease-in-out infinite;} |
| @keyframes progress{0%{transform:scaleX(0);transform-origin:left;}50%{transform:scaleX(0.6);transform-origin:left;}100%{transform:scaleX(1);transform-origin:left;opacity:0;}} |
| |
| |
| .hero{display:flex;flex-direction:column;align-items:center;padding:48px 20px 24px;text-align:center;background:var(--hero-gradient);position:relative;overflow:hidden;} |
| .hero-avatar{width:110px;height:110px;border-radius:50%;border:4px solid var(--surface2);box-shadow:0 0 40px var(--accent-bg);margin-bottom:16px;padding:3px;background:var(--bg);transition:transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);} |
| .hero-avatar:hover{transform:scale(1.05) rotate(5deg);} |
| .hero-name{font-size:28px;font-weight:700;margin-bottom:6px;background:linear-gradient(135deg,var(--text) 30%,var(--accent) 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;} |
| .hero-username{font-family:'Space Mono',monospace;font-size:14px;color:var(--accent);margin-bottom:12px;opacity:0.8;} |
| .hero-bio{font-size:15px;color:var(--text-muted);max-width:540px;line-height:1.7;margin-bottom:20px;font-weight:300;} |
| .hero-meta{display:flex;gap:24px;justify-content:center;flex-wrap:wrap;margin-bottom:24px;} |
| .hero-stat{font-size:12px;color:var(--text-muted);font-family:'Space Mono',monospace;background:var(--surface2);padding:6px 14px;border-radius:10px;border:1px solid var(--border);} |
| .hero-stat span{color:var(--text);font-weight:700;} |
| .btn-github{display:inline-flex;align-items:center;gap:10px;padding:12px 28px;background:linear-gradient(90deg,var(--accent2),var(--accent));border:none;border-radius:12px;color:#fff;font-family:'Space Mono',monospace;font-size:12px;font-weight:700;text-decoration:none;transition:all 0.2s;box-shadow:0 4px 15px var(--accent-bg);} |
| .btn-github:hover{transform:translateY(-1px);box-shadow:0 6px 20px var(--accent-bg);opacity:0.9;} |
| |
| |
| .insight-tabs{max-width:920px;margin:12px auto 0;padding:0 20px;} |
| .insight-tab-bar{display:flex;gap:8px;overflow-x:auto;padding-bottom:2px;margin-bottom:32px;border-bottom:1px solid var(--border);} |
| .insight-tab{padding:12px 20px;border:none;background:transparent;color:var(--text-muted);font-family:'Space Mono',monospace;font-size:11px;font-weight:700;cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1px;transition:all 0.3s;white-space:nowrap;letter-spacing:0.04em;text-transform:uppercase;} |
| .insight-tab:hover{color:var(--accent);} |
| .insight-tab.active{color:var(--accent);border-bottom-color:var(--accent);background:var(--accent-bg);border-radius:10px 10px 0 0;} |
| |
| |
| main{padding-bottom:100px; max-width:920px;margin:0 auto;padding:0 20px 80px;} |
| |
| |
| .section{background:var(--surface);border:1px solid var(--border);border-radius:24px;padding:32px;margin-bottom:24px;box-shadow:var(--shadow);display:none;animation:fadeUp 0.5s cubic-bezier(0.4, 0, 0.2, 1) both;position:relative;overflow:hidden;} |
| .section.active{display:block;} |
| .section::after{content:'';position:absolute;top:0;right:0;width:150px;height:150px;background:radial-gradient(circle at top right, var(--accent-bg), transparent);opacity:0.5;pointer-events:none;} |
| @keyframes fadeUp{from{opacity:0;transform:translateY(20px);}to{opacity:1;transform:translateY(0);}} |
| .section-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:24px;} |
| .section-title{font-family:'Space Mono',monospace;font-size:12px;font-weight:700;color:var(--accent);letter-spacing:0.12em;text-transform:uppercase;} |
| .section-desc{font-size:13px;color:var(--text-muted);margin-top:6px;font-weight:300;} |
| |
| |
| .btn-analyze{padding:10px 24px;background:linear-gradient(90deg,var(--accent2),var(--accent));border:none;border-radius:10px;color:#fff;font-family:'Space Mono',monospace;font-size:11px;font-weight:700;cursor:pointer;transition:all 0.2s;letter-spacing:0.05em;box-shadow:0 4px 12px var(--accent-bg);} |
| .btn-analyze:hover{opacity:0.9;transform:translateY(-1px);} |
| .btn-analyze:disabled{opacity:0.5;cursor:not-allowed;} |
| |
| |
| .result-placeholder{text-align:center;padding:48px 24px;color:var(--text-muted);font-size:14px;border:1px dashed var(--border2);border-radius:16px;background:var(--bg);font-weight:300;} |
| .result-box{animation:fadeUp 0.4s ease;} |
| |
| |
| .persona-card{display:flex;align-items:center;gap:24px;padding:28px;background:var(--accent-bg);border:1px solid var(--border2);border-radius:20px;position:relative;overflow:hidden;} |
| .persona-card::before{content:'';position:absolute;inset:0;background:linear-gradient(45deg, transparent, rgba(255,255,255,0.03));} |
| .persona-icon{font-size:56px;filter:drop-shadow(0 0 15px var(--accent));} |
| .persona-name{font-size:24px;font-weight:700;color:var(--accent);margin-bottom:6px;font-family:'Space Grotesk',sans-serif;} |
| .persona-desc{font-size:14px;color:var(--text);line-height:1.7;font-weight:300;} |
| |
| |
| .ai-summary-card{padding:24px;background:var(--surface2);border:1px solid var(--border);border-radius:16px;font-size:15px;color:var(--text-muted);line-height:1.8;position:relative;} |
| .ai-summary-card strong{color:var(--text);font-weight:600;} |
| .ai-label{font-family:'Space Mono',monospace;font-size:10px;color:var(--accent);letter-spacing:0.12em;margin-bottom:12px;display:block;text-transform:uppercase;} |
| |
| |
| .similar-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:12px;} |
| .similar-card{display:flex;align-items:center;gap:12px;padding:14px 18px;background:var(--surface2);border:1px solid var(--border);border-radius:16px;text-decoration:none;transition:all 0.3s cubic-bezier(0.4, 0, 0.2, 1);} |
| .similar-card:hover{border-color:var(--accent);transform:translateY(-3px);background:var(--accent-bg);} |
| .similar-card img{width:42px;height:42px;border-radius:50%;border:1px solid var(--border2);} |
| .similar-card-name{font-size:14px;color:var(--text);font-weight:700;margin-bottom:2px;} |
| .similar-card-handle{font-size:11px;color:var(--accent);font-family:'Space Mono',monospace;opacity:0.8;} |
| .top-lang-badge{display:inline-block;margin-bottom:12px;padding:4px 14px;background:var(--accent-bg);border:1px solid var(--border2);border-radius:9999px;font-size:11px;font-family:'Space Mono',monospace;color:var(--accent);} |
| |
| |
| .repo-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:16px;} |
| .repo-card{padding:20px;background:var(--surface2);border:1px solid var(--border);border-radius:20px;transition:all 0.3s ease;} |
| .repo-card:hover{border-color:var(--accent);background:var(--surface);} |
| .repo-card-name{font-family:'Space Mono',monospace;font-size:13px;color:var(--accent);font-weight:700;margin-bottom:10px;display:block;} |
| .repo-card-desc{font-size:13px;color:var(--text-muted);line-height:1.5;margin-bottom:14px;display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;} |
| .repo-meta{display:flex;flex-wrap:wrap;gap:12px;font-size:11px;font-family:'Space Mono',monospace;color:var(--text-muted);} |
| .repo-meta span{background:var(--bg);padding:3px 10px;border-radius:6px;color:var(--text);border:1px solid var(--border2);} |
| |
| |
| .email-box{padding:28px;background:var(--surface2);border:1px solid var(--border);border-radius:20px;font-size:14px;color:var(--text);line-height:1.8;white-space:pre-wrap;font-family:inherit;position:relative;background-image:radial-gradient(circle at 0% 0%, rgba(88,166,255,0.03), transparent 40%);} |
| .btn-copy-email{margin-top:16px;padding:10px 24px;background:var(--accent-bg);border:1px solid var(--accent);border-radius:10px;color:var(--accent);font-family:'Space Mono',monospace;font-size:11px;font-weight:700;cursor:pointer;transition:all 0.2s;text-transform:uppercase;letter-spacing:0.05em;} |
| .btn-copy-email:hover{background:var(--accent);color:#fff;} |
| |
| |
| .spinner-inline{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;padding:64px;font-family:'Space Mono',monospace;font-size:12px;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.1em;} |
| @keyframes spin{to{transform:rotate(360deg);}} |
| .spin-circle{width:28px;height:28px;border:2px solid var(--border2);border-top-color:var(--accent);border-radius:50%;animation:spin 0.8s linear infinite;} |
| |
| |
| .skeleton { |
| background: linear-gradient(90deg, var(--surface2) 25%, var(--border2) 50%, var(--surface2) 75%); |
| background-size: 200% 100%; |
| animation: shimmer 1.5s infinite; |
| border-radius: 12px; |
| } |
| @keyframes shimmer { |
| 0% { background-position: 200% 0; } |
| 100% { background-position: -200% 0; } |
| } |
| .skeleton-avatar { width: 110px; height: 110px; border-radius: 50%; border: 4px solid var(--border); margin: 0 auto 12px; } |
| .skeleton-title { height: 28px; width: 40%; margin: 12px auto 8px; } |
| .skeleton-text { height: 14px; width: 60%; margin: 0 auto 8px; } |
| .skeleton-box { height: 200px; width: 100%; margin-top: 24px; border-radius:24px; } |
| |
| |
| .error-msg{color:#f85149;font-size:13px;padding:24px;font-family:'Space Mono',monospace;text-align:center;background:rgba(248,81,73,0.05);border-radius:24px;border:1px solid rgba(248,81,73,0.2);} |
| |
| |
| .toast{position:fixed;bottom:22px;right:22px;background:var(--surface);border:1px solid var(--accent);border-radius:8px;padding:11px 18px;color:var(--accent);font-family:'Space Mono',monospace;font-size:11px;box-shadow:var(--shadow);opacity:0;transform:translateY(10px);transition:all 0.3s;z-index:9999;pointer-events:none;} |
| .toast.show{opacity:1;transform:translateY(0);} |
| |
| |
| .bottom-nav { |
| position: fixed; bottom: 0; left: 0; right: 0; |
| background: rgba(10,10,15,0.95); |
| backdrop-filter: blur(24px); -webkit-backdrop-filter: blur(24px); |
| border-top: 1px solid var(--border); |
| display: flex; justify-content: space-around; align-items: center; |
| padding: 12px 10px 24px; z-index: 9999; |
| } |
| .bottom-nav-item { |
| display: flex; flex-direction: column; align-items: center; gap: 4px; |
| color: var(--text-muted); text-decoration: none; font-size: 9px; |
| font-family: 'Space Mono', monospace; font-weight: 700; |
| transition: all 0.2s; padding: 8px 16px; border-radius: 14px; |
| } |
| .bottom-nav-item i { font-size: 20px; font-style: normal; margin-bottom: 2px; } |
| .bottom-nav-item.active { background: rgba(88,166,255,0.1); color: var(--accent); } |
| .bottom-nav-item.active i { color: var(--accent); } |
| |
| </style> |
| </head> |
| <body> |
|
|
| <div class="progress-bar" id="progress-bar"></div> |
|
|
| <header style="background:transparent; border-bottom:none; margin-top:14px; padding:10px 24px;"> |
| <div class="logo"> |
| <div style="background:var(--accent-bg);padding:8px;border-radius:12px;border:1px solid var(--accent);font-size:20px;">π</div> |
| <div> |
| <div class="logo-text" style="font-size:16px; font-weight:700; color:var(--accent);">GitPulse</div> |
| <div class="logo-sub" style="font-size:9px;">GITHUB TALENT INTELLIGENCE</div> |
| </div> |
| </div> |
| <div class="header-actions"> |
| <a href="/" class="btn-back" style="background:var(--surface2); border:none; padding:8px 14px; font-weight:700; color:var(--text); text-decoration:none; border-radius:12px;">β Go Back</a> |
| </div> |
| </header> |
|
|
|
|
|
|
| |
| <div class="hero" id="hero"> |
| <div class="skeleton skeleton-avatar"></div> |
| <div class="skeleton skeleton-title"></div> |
| <div class="skeleton skeleton-text"></div> |
| <div class="skeleton skeleton-text" style="width:40%;"></div> |
| </div> |
|
|
| |
| <div class="insight-tabs"> |
| <div class="insight-tab-bar"> |
| <button class="insight-tab active" onclick="switchTab('persona')">π§ Persona</button> |
| <button class="insight-tab" onclick="switchTab('ai')">β¨ AI Summary</button> |
| <button class="insight-tab" onclick="switchTab('radar')">π― Skill Radar</button> |
| <button class="insight-tab" onclick="switchTab('repos')">π Repo Dive</button> |
| <button class="insight-tab" onclick="switchTab('similar')">π₯ Similar Devs</button> |
| <button class="insight-tab" onclick="switchTab('heatmap')">π
Heatmap</button> |
| <button class="insight-tab" onclick="switchTab('email')">π§ Draft Email</button> |
| </div> |
| </div> |
|
|
| <main> |
|
|
| |
| <div class="section active" id="tab-persona"> |
| <div class="section-header"> |
| <div> |
| <div class="section-title">π§ DEVELOPER PERSONA</div> |
| <div class="section-desc">Behavioral archetype from recent commit patterns</div> |
| </div> |
| <button class="btn-analyze" id="persona-btn" onclick="runPersona()">Run Analysis</button> |
| </div> |
| <div id="persona-result"> |
| <div class="result-placeholder">Click "Run Analysis" to detect this developer's coding archetype.</div> |
| </div> |
| </div> |
|
|
| |
| <div class="section" id="tab-ai"> |
| <div class="section-header"> |
| <div> |
| <div class="section-title">β¨ AI CANDIDATE SUMMARY</div> |
| <div class="section-desc">AI-generated professional overview powered by Gemini</div> |
| </div> |
| <button class="btn-analyze" id="ai-btn" onclick="runAISummary()">Generate Summary</button> |
| </div> |
| <div id="ai-result"> |
| <div class="result-placeholder">Click "Generate Summary" to get an AI-written overview of this candidate.</div> |
| </div> |
| </div> |
|
|
| |
| <div class="section" id="tab-radar"> |
| <div class="section-header"> |
| <div> |
| <div class="section-title">π― SKILL RADAR</div> |
| <div class="section-desc">Strength across 6 software domains based on repo languages and topics</div> |
| </div> |
| <button class="btn-analyze" id="radar-btn" onclick="runRadar()">Generate Radar</button> |
| </div> |
| <div id="radar-result"> |
| <div class="result-placeholder">Click "Generate Radar" to see this developer's skill distribution across Frontend, Backend, ML, DevOps, Mobile, and Open Source.</div> |
| </div> |
| </div> |
|
|
| |
| <div class="section" id="tab-repos"> |
| <div class="section-header"> |
| <div> |
| <div class="section-title">π REPO DEEP DIVE</div> |
| <div class="section-desc">Top repositories with stars, language, and last activity</div> |
| </div> |
| <button class="btn-analyze" id="repos-btn" onclick="runRepoDive()">Load Repos</button> |
| </div> <div id="repos-result"> |
| <div class="result-placeholder">Click "Load Repos" to see this developer's top repositories.</div> |
| </div> |
| </div> |
|
|
| |
| <div class="section" id="tab-similar"> |
| <div class="section-header"> |
| <div> |
| <div class="section-title">π₯ SIMILAR DEVELOPERS</div> |
| <div class="section-desc">Developers with the same tech stack</div> |
| </div> |
| <button class="btn-analyze" id="similar-btn" onclick="runSimilar()">Find Similar</button> |
| </div> |
| <div id="similar-result"> |
| <div class="result-placeholder">Click "Find Similar" to discover developers with a matching tech stack.</div> |
| </div> |
| </div> |
|
|
| |
| <div class="section" id="tab-email"> |
| <div class="section-header"> |
| <div> |
| <div class="section-title">π§ OUTREACH EMAIL DRAFT</div> |
| <div class="section-desc">AI-written personalized cold email for this candidate</div> |
| </div> |
| <button class="btn-analyze" id="email-btn" onclick="runEmailDraft()">Draft Email</button> |
| </div> |
| <div id="email-result"> |
| <div class="result-placeholder">Click "Draft Email" to generate a personalized outreach email using this developer's GitHub activity.</div> |
| </div> |
| </div> |
|
|
| |
| <div class="section" id="tab-heatmap"> |
| <div class="section-header"> |
| <div> |
| <div class="section-title">π
CONTRIBUTION HEATMAP</div> |
| <div class="section-desc">52-week activity calendar from public GitHub events</div> |
| </div> |
| <button class="btn-analyze" id="heatmap-btn" onclick="runHeatmap()">Load Heatmap</button> |
| </div> |
| <div id="heatmap-result"> |
| <div class="result-placeholder">Click "Load Heatmap" to see this developer's activity over the past year.</div> |
| </div> |
| </div> |
|
|
| </main> |
|
|
| <div class="toast" id="toast"></div> |
|
|
| <script> |
| const USERNAME = "{{ username }}"; |
| |
| // ββ Theme ββ |
| function setTheme(t){ |
| document.documentElement.setAttribute('data-theme',t); |
| localStorage.setItem('gh_theme',t); |
| ['dark','navy','light'].forEach(x=>{ const el=document.getElementById('pill-'+x); if(el) el.classList.toggle('active',x===t); }); |
| } |
| (()=>{ setTheme(localStorage.getItem('gh_theme')||'dark'); })(); |
| |
| // ββ Insight Tabs ββ |
| const TABS=['persona','ai','radar','repos','similar','heatmap','email']; |
| function switchTab(id){ |
| TABS.forEach(t=>{ |
| document.getElementById('tab-'+t).classList.toggle('active',t===id); |
| document.querySelectorAll('.insight-tab').forEach((btn,i)=>btn.classList.toggle('active',TABS[i]===id)); |
| }); |
| } |
| // sync tab buttons on load |
| document.querySelectorAll('.insight-tab').forEach((btn,i)=>btn.setAttribute('data-tab',TABS[i])); |
| |
| // ββ Load Profile ββ |
| async function loadProfile(){ |
| document.getElementById('progress-bar').classList.add('loading'); |
| try{ |
| const res=await fetch(`/api/user/${USERNAME}`); |
| if(!res.ok) throw new Error(); |
| const u=await res.json(); |
| if(u.error) throw new Error(u.error); |
| document.getElementById('hero').innerHTML=` |
| <img class="hero-avatar" src="${u.avatar_url}" alt="${u.login}"/> |
| <div class="hero-name">${u.name||u.login}</div> |
| <div class="hero-username">@${u.login}</div> |
| ${u.bio?`<div class="hero-bio">${u.bio}</div>`:''} |
| <div class="hero-meta"> |
| ${u.location?`<div class="hero-stat">π <span>${u.location}</span></div>`:''} |
| <div class="hero-stat">π <span>${u.public_repos}</span> repos</div> |
| <div class="hero-stat">π₯ <span>${u.followers.toLocaleString()}</span> followers</div> |
| ${u.company?`<div class="hero-stat">π’ <span>${u.company}</span></div>`:''} |
| </div> |
| <a class="btn-github" href="${u.html_url}" target="_blank">π VIEW ON GITHUB</a>`; |
| }catch(e){ |
| document.getElementById('hero').innerHTML=` |
| <div class="hero-username">@${USERNAME}</div> |
| <a class="btn-github" href="https://github.com/${USERNAME}" target="_blank">π VIEW ON GITHUB</a>`; |
| } |
| document.getElementById('progress-bar').classList.remove('loading'); |
| } |
| |
| // ββ Persona ββ |
| async function runPersona(){ |
| const btn=document.getElementById('persona-btn'), result=document.getElementById('persona-result'); |
| btn.disabled=true; btn.textContent='Analyzing...'; |
| result.innerHTML=`<div class="spinner-inline"><div class="spin-circle"></div> SCANNING COMMITS...</div>`; |
| try{ |
| const res=await fetch(`/api/analyze_persona/${USERNAME}`); |
| const data=await res.json(); |
| const descriptions={'π The Exterminator':'Relentless bug hunter. Spends time fixing issues, patching problems, and resolving edge cases.','π The Documenter':'The team\'s knowledge keeper. Commits full of READMEs, docs, and cleanup.','ποΈ The Architect':'Thinks in systems. Loves to refactor, initialize projects, and build solid foundations.','π The Shipper':'Ships fast and often. High-volume commit activity β keeps the product moving.','π» The Coder':'A versatile generalist with a balanced commit style.'}; |
| const icon=data.persona.split(' ')[0]; |
| const label=data.persona.replace(icon+' ',''); |
| result.innerHTML=`<div class="result-box"><div class="persona-card"><div class="persona-icon">${icon}</div><div><div class="persona-name">${label}</div><div class="persona-desc">${descriptions[data.persona]||'A unique developer with a distinctive coding style.'}</div></div></div></div>`; |
| btn.textContent='β Done'; showToast(`${icon} ${label}`); |
| }catch(e){ result.innerHTML=`<div class="error-msg">β Could not analyze persona.</div>`; btn.disabled=false; btn.textContent='Run Analysis'; } |
| } |
| |
| // ββ AI Summary ββ |
| async function runAISummary(){ |
| const btn=document.getElementById('ai-btn'), result=document.getElementById('ai-result'); |
| btn.disabled=true; btn.textContent='Generating...'; |
| result.innerHTML=`<div class="result-box"><div class="ai-summary-card"><div class="ai-label">β¨ AI SUMMARY</div><div class="skeleton skeleton-text" style="width:100%;"></div><div class="skeleton skeleton-text" style="width:90%;"></div><div class="skeleton skeleton-text" style="width:95%;"></div><div id="stream-text" style="display:none;margin-top:10px;"></div></div></div>`; |
| |
| try{ |
| const res=await fetch(`/api/ai_summary/${USERNAME}`); |
| if (!res.ok) throw new Error(); |
| |
| const streamText = document.getElementById('stream-text'); |
| |
| const contentType = res.headers.get("content-type"); |
| if (contentType && contentType.includes("application/json")) { |
| // Gemini JSON Fallback |
| const data=await res.json(); |
| result.innerHTML=`<div class="result-box"><div class="ai-summary-card"><div class="ai-label">β¨ GEMINI AI SUMMARY</div>${data.ai_summary}</div></div>`; |
| } else { |
| // Groq Text Streaming |
| result.innerHTML=`<div class="result-box"><div class="ai-summary-card"><div class="ai-label">β‘ GROQ LLaMA-3 SUMMARY</div><div id="stream-text"></div></div></div>`; |
| const reader = res.body.getReader(); |
| const decoder = new TextDecoder("utf-8"); |
| const streamContainer = document.getElementById('stream-text'); |
| |
| while (true) { |
| const { done, value } = await reader.read(); |
| if (done) break; |
| streamContainer.innerHTML += decoder.decode(value, {stream: true}); |
| } |
| } |
| btn.textContent='β Done'; showToast('β¨ Summary generated!'); |
| }catch(e){ result.innerHTML=`<div class="error-msg">β Could not generate summary.</div>`; btn.disabled=false; btn.textContent='Generate Summary'; } |
| } |
| |
| // ββ Repo Deep Dive ββ |
| async function runRepoDive(){ |
| const btn=document.getElementById('repos-btn'), result=document.getElementById('repos-result'); |
| btn.disabled=true; btn.textContent='Loading...'; |
| result.innerHTML=`<div class="spinner-inline"><div class="spin-circle"></div> FETCHING REPOS...</div>`; |
| try{ |
| const res=await fetch(`/api/repos/${USERNAME}`); |
| const data=await res.json(); |
| if(!data.repos||!data.repos.length) throw new Error('No repos found.'); |
| result.innerHTML=`<div class="result-box"><div class="repo-grid">${data.repos.map(r=>` |
| <div class="repo-card"> |
| <div class="repo-card-name"><a href="${r.url}" target="_blank" style="color:var(--accent);text-decoration:none;">${r.name}</a></div> |
| <div class="repo-card-desc">${r.description||'No description.'}</div> |
| <div class="repo-meta"> |
| ${r.language?`<span>π» <span>${r.language}</span></span>`:''} |
| <span>β <span>${r.stars.toLocaleString()}</span></span> |
| <span>π΄ <span>${r.forks}</span></span> |
| <span>π
<span>${r.updated}</span></span> |
| </div> |
| <button onclick="viewPipeline('${USERNAME}','${r.name}','${(r.description||'').replace(/'/g,"\\'")}',this)" style="margin-top:10px;padding:6px 12px;background:transparent;border:1px solid rgba(251,191,36,0.4);border-radius:6px;cursor:pointer;font-size:10px;font-family:'Space Mono',monospace;color:#fbbf24;transition:all 0.2s;width:100%;" onmouseover="this.style.background='rgba(251,191,36,0.08)'" onmouseout="this.style.background='transparent'">ποΈ View Pipeline</button> |
| </div>`).join('')}</div></div>`; |
| btn.textContent='β Done'; showToast(`π ${data.repos.length} repos loaded`); |
| }catch(e){ result.innerHTML=`<div class="error-msg">β ${e.message}</div>`; btn.disabled=false; btn.textContent='Load Repos'; } |
| } |
| |
| // ββ Similar Devs ββ |
| async function runSimilar(){ |
| const btn=document.getElementById('similar-btn'), result=document.getElementById('similar-result'); |
| btn.disabled=true; btn.textContent='Searching...'; |
| result.innerHTML=`<div class="spinner-inline"><div class="spin-circle"></div> ANALYZING TECH STACK...</div>`; |
| try{ |
| const res=await fetch(`/api/similar_candidates/${USERNAME}`); |
| const data=await res.json(); |
| if(data.error) throw new Error(data.error); |
| const users=data.similar_users||[]; |
| if(!users.length) throw new Error('No similar candidates found.'); |
| result.innerHTML=`<div class="result-box"><div class="top-lang-badge">π·οΈ ${data.top_language}</div><div class="similar-grid">${users.map(u=>`<a class="similar-card" href="${u.html_url||'https://github.com/'+u.login}" target="_blank"><img src="${u.avatar_url}" alt="${u.login}"/><div><div class="similar-card-name">${u.name||u.login}</div><div class="similar-card-handle">@${u.login}</div></div></a>`).join('')}</div></div>`; |
| btn.textContent='β Done'; showToast('π₯ Similar devs found!'); |
| }catch(e){ result.innerHTML=`<div class="error-msg">β ${e.message}</div>`; btn.disabled=false; btn.textContent='Find Similar'; } |
| } |
| |
| // ββ Email Draft ββ |
| async function runEmailDraft(){ |
| const btn=document.getElementById('email-btn'), result=document.getElementById('email-result'); |
| btn.disabled=true; btn.textContent='Drafting...'; |
| result.innerHTML=`<div class="spinner-inline"><div class="spin-circle"></div> WRITING EMAIL...</div>`; |
| try{ |
| const res=await fetch(`/api/draft_email/${USERNAME}`); |
| const data=await res.json(); |
| result.innerHTML=`<div class="result-box"><div class="email-box" id="email-content">${data.email}</div><button class="btn-copy-email" onclick="copyEmail()">π Copy Email</button></div>`; |
| btn.textContent='β Done'; showToast('π§ Email drafted!'); |
| }catch(e){ result.innerHTML=`<div class="error-msg">β Could not draft email.</div>`; btn.disabled=false; btn.textContent='Draft Email'; } |
| } |
| |
| function copyEmail(){ |
| const text=document.getElementById('email-content').innerText; |
| navigator.clipboard.writeText(text).then(()=>showToast('π Email copied!')); |
| } |
| |
| // ββ Pipeline / Architecture viewer (reuses /api/architecture) ββ |
| async function viewPipeline(owner, repo, description, btn) { |
| // open a simple overlay β same styling as index.html arch modal |
| let overlay = document.getElementById('arch-overlay-analyze'); |
| if (!overlay) { |
| overlay = document.createElement('div'); |
| overlay.id = 'arch-overlay-analyze'; |
| overlay.style.cssText = 'display:none;position:fixed;inset:0;background:rgba(0,0,0,0.8);z-index:3000;align-items:center;justify-content:center;padding:16px;'; |
| overlay.innerHTML = ` |
| <div onclick="event.stopPropagation()" style="background:var(--surface);border:1px solid var(--border);border-radius:14px;width:100%;max-width:820px;max-height:90vh;display:flex;flex-direction:column;box-shadow:0 32px 80px rgba(0,0,0,0.7);overflow:hidden;"> |
| <div style="display:flex;align-items:center;justify-content:space-between;padding:15px 20px;border-bottom:1px solid var(--border);"> |
| <div style="display:flex;align-items:center;gap:9px;"> |
| <span style="font-size:20px;">ποΈ</span> |
| <div> |
| <div style="font-family:'Space Mono',monospace;font-size:12px;font-weight:700;color:var(--accent);" id="arch-a-title">Pipeline</div> |
| <div style="font-size:10px;color:var(--text-muted);font-family:'Space Mono',monospace;">AI-generated architecture</div> |
| </div> |
| </div> |
| <button onclick="document.getElementById('arch-overlay-analyze').style.display='none';document.body.style.overflow='';" style="padding:6px 13px;background:transparent;border:1px solid var(--border2);border-radius:6px;color:var(--text-muted);font-family:'Space Mono',monospace;font-size:10px;cursor:pointer;">β CLOSE</button> |
| </div> |
| <div id="arch-a-body" style="flex:1;overflow-y:auto;padding:22px;"></div> |
| </div>`; |
| overlay.addEventListener('click', e => { if(e.target===overlay){ overlay.style.display='none'; document.body.style.overflow=''; }}); |
| document.body.appendChild(overlay); |
| } |
| |
| document.getElementById('arch-a-title').textContent = `${owner}/${repo}`; |
| document.getElementById('arch-a-body').innerHTML = `<div style="display:flex;flex-direction:column;align-items:center;gap:12px;padding:50px;font-family:'Space Mono',monospace;font-size:11px;color:var(--text-muted);"><div style="width:24px;height:24px;border:2px solid var(--border);border-top-color:#fbbf24;border-radius:50%;animation:spin 0.8s linear infinite;"></div>GEMINI IS ANALYSING THE REPO...</div>`; |
| overlay.style.display = 'flex'; |
| document.body.style.overflow = 'hidden'; |
| btn.disabled = true; btn.textContent = 'β³ Analysing...'; |
| |
| try { |
| const res = await fetch(`/api/architecture/${owner}/${repo}`); |
| const data = await res.json(); |
| if (data.error) throw new Error(data.error); |
| |
| let html = '<div style="display:flex;flex-direction:column;gap:0;">'; |
| (data.stages||[]).forEach((stage, i) => { |
| const cols = stage.nodes.length <= 1 ? '1fr' : stage.nodes.length === 2 ? '1fr 1fr' : '1fr 1fr 1fr'; |
| html += `<div style="display:grid;grid-template-columns:${cols};gap:12px;margin-bottom:8px;">`; |
| stage.nodes.forEach(n => { |
| const colors = {input:'rgba(88,166,255,0.4)',process:'rgba(167,139,250,0.4)',model:'rgba(251,191,36,0.4)',output:'rgba(52,211,153,0.4)',infra:'rgba(248,113,133,0.4)'}; |
| const bg = {input:'rgba(88,166,255,0.05)',process:'rgba(167,139,250,0.05)',model:'rgba(251,191,36,0.05)',output:'rgba(52,211,153,0.05)',infra:'rgba(248,113,133,0.05)'}; |
| const lc = {input:'#58a6ff',process:'#c4b5fd',model:'#fbbf24',output:'#34d399',infra:'#fb7185'}; |
| html += `<div style="background:${bg[n.type]||'var(--surface2)'};border:1px solid ${colors[n.type]||'var(--border)'};border-radius:10px;padding:14px 16px;"> |
| <div style="font-size:20px;margin-bottom:7px;">${n.icon||'π¦'}</div> |
| <div style="font-family:'Space Mono',monospace;font-size:9px;color:${lc[n.type]||'var(--text-muted)'};letter-spacing:0.1em;margin-bottom:4px;">${n.layer||''}</div> |
| <div style="font-size:13px;font-weight:600;color:var(--text);margin-bottom:5px;">${n.title||''}</div> |
| <div style="font-size:11px;color:var(--text-muted);line-height:1.5;">${n.description||''}</div> |
| ${n.tools&&n.tools.length?`<div style="display:flex;flex-wrap:wrap;gap:4px;margin-top:8px;">${n.tools.map(t=>`<span style="padding:2px 7px;border-radius:10px;font-size:9px;font-family:'Space Mono',monospace;background:var(--accent-bg);border:1px solid var(--border2);color:var(--accent);">${t}</span>`).join('')}</div>`:''} |
| </div>`; |
| }); |
| html += '</div>'; |
| if (i < (data.stages||[]).length - 1) html += `<div style="text-align:center;padding:4px 0;color:var(--text-muted);font-size:16px;">β</div>`; |
| }); |
| html += '</div>'; |
| |
| if (data.summary) { |
| html += `<div style="background:var(--surface2);border:1px solid var(--border);border-radius:10px;padding:15px 17px;margin-top:18px;"> |
| <div style="font-family:'Space Mono',monospace;font-size:9px;color:var(--text-muted);letter-spacing:0.12em;margin-bottom:8px;">π§ AI ANALYSIS</div> |
| <div style="font-size:13px;color:var(--text);line-height:1.7;">${data.summary}</div> |
| ${data.tech_stack&&data.tech_stack.length?`<div style="display:flex;flex-wrap:wrap;gap:6px;margin-top:12px;">${data.tech_stack.map(t=>`<span style="padding:3px 10px;border-radius:20px;font-size:10px;font-family:'Space Mono',monospace;border:1px solid var(--border2);color:var(--text-muted);">${t}</span>`).join('')}</div>`:''} |
| </div>`; |
| } |
| |
| document.getElementById('arch-a-body').innerHTML = html; |
| showToast('ποΈ Pipeline loaded!'); |
| } catch(e) { |
| document.getElementById('arch-a-body').innerHTML = `<div style="text-align:center;padding:40px;font-family:'Space Mono',monospace;font-size:12px;color:#f85149;">β ${e.message||'Analysis failed. Check GEMINI_API_KEY.'}</div>`; |
| } |
| btn.disabled = false; btn.textContent = 'ποΈ View Pipeline'; |
| } |
| |
| // ββ Skill Radar ββ |
| let radarChart = null; |
| async function runRadar(){ |
| const btn=document.getElementById('radar-btn'), result=document.getElementById('radar-result'); |
| btn.disabled=true; btn.textContent='Generating...'; |
| result.innerHTML=`<div class="spinner-inline"><div class="spin-circle"></div> ANALYSING REPOSITORIES...</div>`; |
| try{ |
| const res=await fetch(`/api/skill_radar/${USERNAME}`); |
| const data=await res.json(); |
| if(data.error) throw new Error(data.error); |
| |
| const scores=data.scores; |
| const labels=Object.keys(scores); |
| const values=Object.values(scores); |
| const isDark=document.documentElement.getAttribute('data-theme')!=='light'; |
| const accentColor= document.documentElement.getAttribute('data-theme')==='navy' ? '#64ffda' : isDark ? '#58a6ff' : '#0969da'; |
| |
| result.innerHTML=` |
| <div class="result-box"> |
| <div style="display:flex;flex-wrap:wrap;gap:20px;align-items:flex-start;"> |
| <div style="flex:1;min-width:260px;max-width:340px;margin:0 auto;"> |
| <canvas id="radar-canvas" style="max-height:300px;"></canvas> |
| </div> |
| <div style="flex:1;min-width:200px;display:flex;flex-direction:column;gap:10px;padding-top:10px;"> |
| ${labels.map((l,i)=>{ |
| const v=values[i]; |
| const bar_colors=['#58a6ff','#34d399','#fbbf24','#a78bfa','#fb7185','#38bdf8']; |
| return `<div> |
| <div style="display:flex;justify-content:space-between;margin-bottom:4px;font-size:12px;"> |
| <span style="color:var(--text);font-weight:500;">${l}</span> |
| <span style="font-family:'Space Mono',monospace;font-size:10px;color:${bar_colors[i]};">${v}/100</span> |
| </div> |
| <div style="height:6px;background:var(--surface2);border-radius:4px;overflow:hidden;"> |
| <div style="height:100%;width:${v}%;background:${bar_colors[i]};border-radius:4px;transition:width 0.8s ease;"></div> |
| </div> |
| </div>`; |
| }).join('')} |
| <div style="margin-top:10px;padding:12px;background:var(--surface2);border:1px solid var(--border);border-radius:8px;font-size:12px;color:var(--text-muted);line-height:1.6;"> |
| ${data.summary||''} |
| </div> |
| </div> |
| </div> |
| </div>`; |
| |
| if(radarChart) radarChart.destroy(); |
| const ctx=document.getElementById('radar-canvas').getContext('2d'); |
| radarChart=new Chart(ctx,{ |
| type:'radar', |
| data:{ |
| labels, |
| datasets:[{ |
| label:'Skill Score', |
| data:values, |
| backgroundColor:accentColor.replace(')',',0.15)').replace('rgb','rgba').replace('#','rgba(').replace(/rgba\(([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2}),/i,(m,r,g,b)=>`rgba(${parseInt(r,16)},${parseInt(g,16)},${parseInt(b,16)},`), |
| borderColor:accentColor, |
| borderWidth:2, |
| pointBackgroundColor:accentColor, |
| pointBorderColor:accentColor, |
| pointRadius:4, |
| }] |
| }, |
| options:{ |
| responsive:true, |
| plugins:{legend:{display:false}}, |
| scales:{r:{ |
| min:0,max:100, |
| ticks:{stepSize:25,color:isDark?'#8b949e':'#57606a',font:{family:'Space Mono',size:9},backdropColor:'transparent'}, |
| grid:{color:isDark?'rgba(255,255,255,0.08)':'rgba(0,0,0,0.08)'}, |
| pointLabels:{color:isDark?'#e2e8f0':'#1a1f2e',font:{family:'Space Mono',size:10}}, |
| angleLines:{color:isDark?'rgba(255,255,255,0.06)':'rgba(0,0,0,0.06)'}, |
| }} |
| } |
| }); |
| btn.textContent='β Done'; showToast('π― Skill radar ready!'); |
| }catch(e){ |
| result.innerHTML=`<div class="error-msg">β ${e.message}</div>`; |
| btn.disabled=false; btn.textContent='Generate Radar'; |
| } |
| } |
| |
| // ββ Contribution Heatmap ββ |
| async function runHeatmap(){ |
| const btn=document.getElementById('heatmap-btn'), result=document.getElementById('heatmap-result'); |
| btn.disabled=true; btn.textContent='Loading...'; |
| result.innerHTML=`<div class="spinner-inline"><div class="spin-circle"></div> FETCHING ACTIVITY DATA...</div>`; |
| try{ |
| const res=await fetch(`/api/heatmap/${USERNAME}`); |
| const data=await res.json(); |
| if(!data.grid) throw new Error('No data'); |
| |
| const grid=data.grid; |
| const maxCount=Math.max(...grid.map(d=>d.count),1); |
| |
| // group by week |
| const weeks=[]; |
| let week=[]; |
| // pad start so first day aligns to correct weekday |
| const firstDay=new Date(grid[0].date).getDay(); |
| for(let i=0;i<firstDay;i++) week.push(null); |
| grid.forEach(d=>{ |
| week.push(d); |
| if(week.length===7){ weeks.push(week); week=[]; } |
| }); |
| if(week.length) weeks.push(week); |
| |
| const months=['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; |
| const days=['S','M','T','W','T','F','S']; |
| |
| const intensity=c=>{ |
| if(c===0) return 'var(--surface2)'; |
| const p=c/maxCount; |
| if(p<0.25) return 'rgba(52,211,153,0.25)'; |
| if(p<0.5) return 'rgba(52,211,153,0.5)'; |
| if(p<0.75) return 'rgba(52,211,153,0.75)'; |
| return '#34d399'; |
| }; |
| |
| let html=` |
| <div class="result-box"> |
| <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:14px;"> |
| <div style="font-family:'Space Mono',monospace;font-size:10px;color:var(--text-muted);"> |
| π
${data.total_contributions} contributions in the last year |
| </div> |
| <div style="display:flex;align-items:center;gap:6px;font-family:'Space Mono',monospace;font-size:9px;color:var(--text-muted);"> |
| Less |
| ${[0,0.25,0.5,0.75,1].map(p=>`<div style="width:11px;height:11px;border-radius:2px;background:${p===0?'var(--surface2)':`rgba(52,211,153,${p})`};border:1px solid var(--border2);"></div>`).join('')} |
| More |
| </div> |
| </div> |
| <div style="overflow-x:auto;padding-bottom:6px;"> |
| <div style="display:flex;gap:3px;min-width:fit-content;"> |
| <div style="display:flex;flex-direction:column;gap:3px;margin-right:4px;padding-top:18px;"> |
| ${days.map(d=>`<div style="height:11px;font-family:'Space Mono',monospace;font-size:8px;color:var(--text-muted);line-height:11px;">${d}</div>`).join('')} |
| </div> |
| ${weeks.map(w=>` |
| <div style="display:flex;flex-direction:column;gap:3px;"> |
| <div style="height:14px;"></div> |
| ${w.map(d=>d?` |
| <div title="${d.date}: ${d.count} commits" |
| style="width:11px;height:11px;border-radius:2px;background:${intensity(d.count)};border:1px solid var(--border2);cursor:default;transition:transform 0.1s;" |
| onmouseover="this.style.transform='scale(1.4)'" |
| onmouseout="this.style.transform='scale(1)'"> |
| </div>` : `<div style="width:11px;height:11px;"></div>`).join('')} |
| </div>`).join('')} |
| </div> |
| </div> |
| </div>`; |
| |
| result.innerHTML=html; |
| btn.textContent='β Done'; showToast('π
Heatmap loaded!'); |
| }catch(e){ |
| result.innerHTML=`<div class="error-msg">β Could not load heatmap. ${e.message}</div>`; |
| btn.disabled=false; btn.textContent='Load Heatmap'; |
| } |
| } |
| |
| // ββ PDF Export ββ |
| async function exportPDF(){ |
| const btn=document.getElementById('pdf-btn'), result=document.getElementById('pdf-result'); |
| btn.disabled=true; btn.textContent='Generating...'; |
| result.innerHTML=`<div class="spinner-inline"><div class="spin-circle"></div> BUILDING PDF REPORT...</div>`; |
| try{ |
| const res=await fetch(`/api/export_pdf/${USERNAME}`); |
| if(!res.ok){ |
| const err=await res.json(); |
| throw new Error(err.detail||'PDF generation failed'); |
| } |
| const blob=await res.blob(); |
| const url=URL.createObjectURL(blob); |
| const a=document.createElement('a'); |
| a.href=url; a.download=`${USERNAME}_profile.pdf`; a.click(); |
| URL.revokeObjectURL(url); |
| result.innerHTML=` |
| <div class="result-box"> |
| <div style="display:flex;align-items:center;gap:14px;padding:18px;background:var(--surface2);border:1px solid var(--border);border-radius:10px;"> |
| <div style="font-size:36px;">π</div> |
| <div> |
| <div style="font-size:14px;font-weight:600;margin-bottom:4px;">Report downloaded!</div> |
| <div style="font-size:12px;color:var(--text-muted);">${USERNAME}_profile.pdf includes profile, stats, and top repositories.</div> |
| <button onclick="exportPDF()" style="margin-top:10px;padding:6px 14px;background:var(--accent-bg);border:1px solid var(--accent2);border-radius:6px;color:var(--accent);font-family:'Space Mono',monospace;font-size:10px;cursor:pointer;">β¬ Download Again</button> |
| </div> |
| </div> |
| </div>`; |
| btn.textContent='β Done'; showToast('π PDF downloaded!'); |
| }catch(e){ |
| result.innerHTML=`<div class="error-msg">β ${e.message}. Make sure <code>reportlab</code> is installed: <code>pip install reportlab</code></div>`; |
| btn.disabled=false; btn.textContent='Download PDF'; |
| } |
| } |
| |
| // ββ Toast ββ |
| let toastTimer; |
| function showToast(msg){ const t=document.getElementById('toast'); t.textContent=msg; t.classList.add('show'); clearTimeout(toastTimer); toastTimer=setTimeout(()=>t.classList.remove('show'),2800); } |
| |
| loadProfile(); |
| </script> |
|
|
| <nav class="bottom-nav"> |
| <a class="bottom-nav-item active" href="/"> |
| <i>π</i> |
| <span>HOME</span> |
| </a> |
| <a class="bottom-nav-item" href="/jd-match"> |
| <i>π―</i> |
| <span>JD MATCH</span> |
| </a> |
| <a class="bottom-nav-item" href="/market-trends"> |
| <i>π</i> |
| <span>TRENDS</span> |
| </a> |
| <a class="bottom-nav-item" href="/role-analyzer"> |
| <i>π</i> |
| <span>ANALYZE</span> |
| </a> |
| </nav> |
|
|
| </body> |
| </html> |
|
|