Skip to content

Commit 6c69973

Browse files
authored
Create profile.html
1 parent 5b37807 commit 6c69973

1 file changed

Lines changed: 385 additions & 0 deletions

File tree

profile.html

Lines changed: 385 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,385 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>dev_profile</title>
7+
<meta id="og-title" property="og:title" content="dev_profile" />
8+
<meta id="og-desc" property="og:description" content="Developer profile" />
9+
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Syne:wght@400;600;800&display=swap" rel="stylesheet" />
10+
<style>
11+
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
12+
:root{
13+
--bg:#0a0c10;--surface:#10141c;--surface2:#161b26;--border:#1e2535;
14+
--green:#00ff88;--amber:#f5a623;--red:#ff4455;--blue:#4a9eff;
15+
--text:#e2e8f0;--text-muted:#64748b;--text-dim:#334155;
16+
--font-mono:'JetBrains Mono',monospace;--font-display:'Syne',sans-serif;
17+
}
18+
body{background:var(--bg);color:var(--text);font-family:var(--font-mono);font-size:13px;min-height:100vh}
19+
body::before{content:'';position:fixed;inset:0;background:repeating-linear-gradient(0deg,transparent,transparent 2px,rgba(0,255,136,0.01) 2px,rgba(0,255,136,0.01) 4px);pointer-events:none;z-index:0}
20+
21+
.page{max-width:860px;margin:0 auto;padding:40px 24px;position:relative;z-index:1}
22+
23+
/* states */
24+
#loading,#not-found,#search-screen{display:none;min-height:60vh;align-items:center;justify-content:center;flex-direction:column;gap:14px;text-align:center}
25+
#loading.active,#not-found.active,#search-screen.active{display:flex}
26+
#profile-content{display:none}
27+
#profile-content.active{display:block}
28+
29+
.loader-bar{width:180px;height:2px;background:var(--border);overflow:hidden}
30+
.loader-bar::after{content:'';display:block;height:100%;width:40%;background:var(--green);animation:slide 1s ease-in-out infinite alternate}
31+
@keyframes slide{from{transform:translateX(-100%)}to{transform:translateX(350%)}}
32+
.loader-text{color:var(--green);font-size:11px;letter-spacing:1px}
33+
.nf-code{font-family:var(--font-display);font-size:72px;font-weight:800;color:var(--green);opacity:.15;line-height:1}
34+
.nf-msg{font-size:13px;color:var(--text-muted)}
35+
36+
/* search */
37+
.search-box{border:1px solid var(--green);padding:40px 48px;width:440px;position:relative;background:rgba(0,255,136,0.02)}
38+
.s-corner{position:absolute;width:12px;height:12px;border-color:var(--green);border-style:solid}
39+
.s-corner.tl{top:-1px;left:-1px;border-width:2px 0 0 2px}
40+
.s-corner.tr{top:-1px;right:-1px;border-width:2px 2px 0 0}
41+
.s-corner.bl{bottom:-1px;left:-1px;border-width:0 0 2px 2px}
42+
.s-corner.br{bottom:-1px;right:-1px;border-width:0 2px 2px 0}
43+
.s-title{font-family:var(--font-display);font-size:26px;font-weight:800;color:var(--green);margin-bottom:4px}
44+
.s-sub{font-size:11px;color:var(--text-muted);margin-bottom:24px}
45+
.s-row{display:flex;gap:8px}
46+
.s-input{flex:1;background:var(--surface2);border:1px solid var(--border);color:var(--text);font-family:var(--font-mono);font-size:13px;padding:10px 13px;outline:none;transition:border-color .2s}
47+
.s-input:focus{border-color:var(--green)}
48+
.s-btn{background:var(--green);color:#000;border:none;font-family:var(--font-mono);font-size:12px;font-weight:700;padding:0 18px;cursor:pointer;letter-spacing:.5px;transition:opacity .2s}
49+
.s-btn:hover{opacity:.85}
50+
51+
/* profile header */
52+
.profile-header{display:flex;align-items:flex-start;gap:24px;margin-bottom:28px;padding-bottom:24px;border-bottom:1px solid var(--border)}
53+
.avatar{width:80px;height:80px;border-radius:50%;border:2px solid var(--green);overflow:hidden;flex-shrink:0}
54+
.avatar img{width:100%;height:100%;object-fit:cover}
55+
.profile-info{flex:1;min-width:0}
56+
.profile-name{font-family:var(--font-display);font-size:26px;font-weight:800;color:var(--green);line-height:1.1;margin-bottom:2px}
57+
.profile-login{font-size:12px;color:var(--text-muted);margin-bottom:8px;display:flex;align-items:center;gap:6px}
58+
.online-dot{width:6px;height:6px;background:var(--green);border-radius:50%;display:inline-block;animation:pulse 2s infinite}
59+
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.3}}
60+
.profile-bio{font-size:12px;color:var(--text);line-height:1.65;margin-bottom:10px;max-width:500px}
61+
.profile-meta{display:flex;gap:14px;flex-wrap:wrap}
62+
.meta-item{font-size:11px;color:var(--text-muted)}
63+
.meta-item a{color:var(--blue);text-decoration:none}
64+
.profile-actions{display:flex;gap:8px;flex-shrink:0;align-items:flex-start}
65+
.btn-gh{background:var(--green);color:#000;border:none;font-family:var(--font-mono);font-size:11px;font-weight:700;padding:8px 14px;cursor:pointer;letter-spacing:.5px;text-transform:uppercase;text-decoration:none;display:inline-block;transition:opacity .2s}
66+
.btn-gh:hover{opacity:.85}
67+
.btn-copy{background:transparent;border:1px solid var(--border);color:var(--text-muted);font-family:var(--font-mono);font-size:11px;padding:8px 14px;cursor:pointer;letter-spacing:.5px;text-transform:uppercase;transition:all .2s}
68+
.btn-copy:hover{border-color:var(--green);color:var(--green)}
69+
70+
/* stat grid */
71+
.stat-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-bottom:14px}
72+
.stat-card{background:var(--surface2);border:1px solid var(--border);padding:14px;text-align:center}
73+
.stat-val{font-family:var(--font-display);font-size:26px;font-weight:800;color:var(--green);display:block;line-height:1}
74+
.stat-label{font-size:10px;text-transform:uppercase;letter-spacing:1px;color:var(--text-muted);margin-top:4px;display:block}
75+
76+
/* section */
77+
.section{background:var(--surface);border:1px solid var(--border);padding:20px;margin-bottom:14px;position:relative}
78+
.section::before{content:'';position:absolute;top:0;left:0;width:3px;height:100%;background:var(--green);opacity:.5}
79+
.section-title{font-size:10px;text-transform:uppercase;letter-spacing:1.5px;color:var(--text-muted);margin-bottom:14px;display:flex;align-items:center;gap:6px}
80+
.section-icon{width:5px;height:5px;background:var(--green);display:inline-block}
81+
82+
/* lang bars */
83+
.lang-bar-item{display:flex;align-items:center;gap:10px;margin-bottom:10px}
84+
.lang-name{font-size:11px;color:var(--text-muted);width:80px;flex-shrink:0}
85+
.lang-bar-bg{flex:1;height:5px;background:var(--surface2)}
86+
.lang-bar-fill{height:100%;transition:width 1.2s ease}
87+
.lang-pct{font-size:11px;color:var(--text-muted);width:34px;text-align:right;flex-shrink:0}
88+
89+
/* repo grid */
90+
.repo-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}
91+
.repo-card{background:var(--surface2);border:1px solid var(--border);padding:14px;cursor:pointer;transition:border-color .2s;text-decoration:none;display:block}
92+
.repo-card:hover{border-color:var(--green)}
93+
.repo-name{font-size:12px;color:var(--blue);font-weight:500;margin-bottom:4px}
94+
.repo-desc{font-size:11px;color:var(--text-muted);line-height:1.5;margin-bottom:8px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;min-height:2em}
95+
.repo-meta{display:flex;align-items:center;gap:10px;font-size:11px;color:var(--text-muted)}
96+
.lang-dot{width:7px;height:7px;border-radius:50%;display:inline-block;margin-right:3px}
97+
.stars{color:var(--amber)}
98+
99+
/* heatmap */
100+
.heatmap-wrap{overflow-x:auto;padding-bottom:4px}
101+
.heatmap-grid{display:flex;gap:3px;min-width:fit-content}
102+
.heatmap-week{display:flex;flex-direction:column;gap:3px}
103+
.heatmap-cell{width:11px;height:11px;border-radius:2px;flex-shrink:0}
104+
.heatmap-cell[data-count="0"]{background:#111720}
105+
.heatmap-cell[data-level="1"]{background:#004422}
106+
.heatmap-cell[data-level="2"]{background:#006633}
107+
.heatmap-cell[data-level="3"]{background:#00aa55}
108+
.heatmap-cell[data-level="4"]{background:#00ff88}
109+
.heatmap-footer{display:flex;align-items:center;gap:8px;margin-top:8px;font-size:10px;color:var(--text-muted)}
110+
.heatmap-legend{display:flex;gap:3px;align-items:center}
111+
112+
/* footer */
113+
.profile-footer{margin-top:24px;padding-top:16px;border-top:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;font-size:10px;color:var(--text-dim)}
114+
.footer-brand{color:var(--green);text-decoration:none;font-weight:700;letter-spacing:.5px}
115+
116+
@media(max-width:640px){
117+
.profile-header{flex-direction:column}
118+
.stat-grid{grid-template-columns:repeat(2,1fr)}
119+
.repo-grid{grid-template-columns:1fr}
120+
.search-box{width:calc(100vw - 40px);padding:28px 20px}
121+
}
122+
</style>
123+
</head>
124+
<body>
125+
<div class="page">
126+
127+
<!-- Loading -->
128+
<div id="loading" class="active">
129+
<div class="loader-text">// fetching profile...</div>
130+
<div class="loader-bar"></div>
131+
</div>
132+
133+
<!-- Not found -->
134+
<div id="not-found">
135+
<div class="nf-code">404</div>
136+
<div class="nf-msg">profile not found</div>
137+
<a href="profile.html" style="color:var(--blue);font-size:12px;margin-top:8px">← search profiles</a>
138+
</div>
139+
140+
<!-- Search (no slug) -->
141+
<div id="search-screen">
142+
<div class="search-box">
143+
<div class="s-corner tl"></div><div class="s-corner tr"></div>
144+
<div class="s-corner bl"></div><div class="s-corner br"></div>
145+
<div class="s-title">dev_profile</div>
146+
<div class="s-sub">// public developer profiles</div>
147+
<div class="s-row">
148+
<input class="s-input" id="slug-input" placeholder="paste profile slug or url..." autocomplete="off" spellcheck="false" />
149+
<button class="s-btn" onclick="goToSlug()">view →</button>
150+
</div>
151+
</div>
152+
</div>
153+
154+
<!-- Profile content -->
155+
<div id="profile-content">
156+
157+
<!-- Header -->
158+
<div class="profile-header">
159+
<div class="avatar"><img id="p-avatar" src="" alt="" /></div>
160+
<div class="profile-info">
161+
<div class="profile-name" id="p-name"></div>
162+
<div class="profile-login">
163+
<span class="online-dot"></span>
164+
<span id="p-login"></span>
165+
</div>
166+
<div class="profile-bio" id="p-bio"></div>
167+
<div class="profile-meta" id="p-meta"></div>
168+
</div>
169+
<div class="profile-actions">
170+
<a id="p-gh-link" href="#" target="_blank" class="btn-gh">github ↗</a>
171+
<button class="btn-copy" onclick="copyURL()">copy url</button>
172+
</div>
173+
</div>
174+
175+
<!-- Stats -->
176+
<div class="stat-grid">
177+
<div class="stat-card"><span class="stat-val" id="p-repos"></span><span class="stat-label">repos</span></div>
178+
<div class="stat-card"><span class="stat-val" id="p-stars"></span><span class="stat-label">stars</span></div>
179+
<div class="stat-card"><span class="stat-val" id="p-followers"></span><span class="stat-label">followers</span></div>
180+
<div class="stat-card"><span class="stat-val" id="p-forks"></span><span class="stat-label">forks</span></div>
181+
</div>
182+
183+
<!-- Heatmap -->
184+
<div class="section">
185+
<div class="section-title"><span class="section-icon"></span>contribution activity (last 6 months)</div>
186+
<div class="heatmap-wrap">
187+
<div class="heatmap-grid" id="p-heatmap"></div>
188+
</div>
189+
<div class="heatmap-footer">
190+
<span>less</span>
191+
<div class="heatmap-legend">
192+
<div class="heatmap-cell" data-count="0"></div>
193+
<div class="heatmap-cell" data-level="1"></div>
194+
<div class="heatmap-cell" data-level="2"></div>
195+
<div class="heatmap-cell" data-level="3"></div>
196+
<div class="heatmap-cell" data-level="4"></div>
197+
</div>
198+
<span>more</span>
199+
<span style="margin-left:auto;color:var(--green)" id="p-commits"></span>
200+
</div>
201+
</div>
202+
203+
<!-- Languages -->
204+
<div class="section">
205+
<div class="section-title"><span class="section-icon"></span>language breakdown</div>
206+
<div id="p-langs"></div>
207+
</div>
208+
209+
<!-- Top repos -->
210+
<div class="section">
211+
<div class="section-title"><span class="section-icon"></span>top repositories</div>
212+
<div class="repo-grid" id="p-repos-list"></div>
213+
</div>
214+
215+
<!-- Footer -->
216+
<div class="profile-footer">
217+
<span>generated by <a class="footer-brand" href="https://dev0root6.github.io/dev-dashboard/">dev_dashboard</a></span>
218+
<span>data from github api · updates on login</span>
219+
</div>
220+
221+
</div>
222+
</div>
223+
224+
<script>
225+
const API_BASE = 'https://dev-dashboard-api.devsekarindhu18.workers.dev';
226+
227+
const LANG_COLORS = {
228+
Python:'#3572A5',JavaScript:'#f1e05a',TypeScript:'#2b7489',
229+
Go:'#00ADD8',C:'#555555','C++':'#f34b7d',Rust:'#dea584',
230+
Shell:'#89e051',HTML:'#e34c26',CSS:'#563d7c',Java:'#b07219',
231+
Ruby:'#701516',Swift:'#ffac45',Kotlin:'#F18E33',default:'#00ff88'
232+
};
233+
function lc(l){return LANG_COLORS[l]||LANG_COLORS.default}
234+
function fmtNum(n){return n>=1000?(n/1000).toFixed(1)+'k':String(n)}
235+
236+
function show(id){
237+
['loading','not-found','search-screen','profile-content'].forEach(s=>{
238+
const el=document.getElementById(s);
239+
el.classList.remove('active');
240+
if(s==='profile-content') el.style.display='none';
241+
});
242+
const target=document.getElementById(id);
243+
target.classList.add('active');
244+
if(id==='profile-content') target.style.display='block';
245+
}
246+
247+
async function loadProfile(slug){
248+
show('loading');
249+
try{
250+
const res=await fetch(`${API_BASE}/public/${slug}`);
251+
if(!res.ok){ show('not-found'); return; }
252+
const d=await res.json();
253+
if(d.error){ show('not-found'); return; }
254+
renderProfile(d);
255+
show('profile-content');
256+
257+
// update page title and meta
258+
document.title=`${d.name||d.login} — dev_profile`;
259+
document.getElementById('og-title').content=`${d.name||d.login} on dev_dashboard`;
260+
document.getElementById('og-desc').content=d.bio||`@${d.login}${d.public_repos} repos · ${fmtNum(d.total_stars)} stars`;
261+
}catch(e){
262+
show('not-found');
263+
}
264+
}
265+
266+
function renderProfile(d){
267+
document.getElementById('p-avatar').src=d.avatar_url||'';
268+
document.getElementById('p-name').textContent=d.name||d.login;
269+
document.getElementById('p-login').textContent=`@${d.login}`;
270+
document.getElementById('p-gh-link').href=`https://github.com/${d.login}`;
271+
272+
const bio=document.getElementById('p-bio');
273+
bio.textContent=d.bio||'';
274+
bio.style.display=d.bio?'block':'none';
275+
276+
// meta items
277+
const metas=[];
278+
if(d.location) metas.push(`📍 ${d.location}`);
279+
if(d.blog) metas.push(`<a href="${d.blog.startsWith('http')?d.blog:'https://'+d.blog}" target="_blank" rel="noopener">${d.blog}</a>`);
280+
if(d.twitter_username) metas.push(`@${d.twitter_username}`);
281+
if(d.created_at) metas.push(`joined ${new Date(d.created_at).getFullYear()}`);
282+
document.getElementById('p-meta').innerHTML=metas.map(m=>`<span class="meta-item">${m}</span>`).join('');
283+
284+
// stats
285+
document.getElementById('p-repos').textContent=d.public_repos||0;
286+
document.getElementById('p-stars').textContent=fmtNum(d.total_stars||0);
287+
document.getElementById('p-followers').textContent=fmtNum(d.followers||0);
288+
document.getElementById('p-forks').textContent=fmtNum(d.total_forks||0);
289+
290+
renderHeatmap(d.heatmap||{});
291+
renderLangs(d.languages||{});
292+
renderRepos(d.top_repos||[]);
293+
}
294+
295+
function renderHeatmap(heatmap){
296+
const today=new Date(), weeks=26;
297+
const startDate=new Date(today);
298+
startDate.setDate(startDate.getDate()-weeks*7+1);
299+
const container=document.getElementById('p-heatmap');
300+
container.innerHTML='';
301+
let total=0;
302+
303+
for(let w=0;w<weeks;w++){
304+
const weekDiv=document.createElement('div');
305+
weekDiv.className='heatmap-week';
306+
for(let d=0;d<7;d++){
307+
const date=new Date(startDate);
308+
date.setDate(startDate.getDate()+w*7+d);
309+
const cell=document.createElement('div');
310+
cell.className='heatmap-cell';
311+
if(date>today){cell.style.opacity='0';weekDiv.appendChild(cell);continue}
312+
const key=date.toISOString().split('T')[0];
313+
const count=heatmap[key]||0;
314+
total+=count;
315+
if(count===0)cell.setAttribute('data-count','0');
316+
else if(count<=2)cell.setAttribute('data-level','1');
317+
else if(count<=5)cell.setAttribute('data-level','2');
318+
else if(count<=10)cell.setAttribute('data-level','3');
319+
else cell.setAttribute('data-level','4');
320+
cell.title=`${key}: ${count} commit${count!==1?'s':''}`;
321+
weekDiv.appendChild(cell);
322+
}
323+
container.appendChild(weekDiv);
324+
}
325+
document.getElementById('p-commits').textContent=`${total} commits`;
326+
}
327+
328+
function renderLangs(langMap){
329+
const sorted=Object.entries(langMap).sort((a,b)=>b[1]-a[1]).slice(0,8);
330+
const total=sorted.reduce((a,b)=>a+b[1],0);
331+
document.getElementById('p-langs').innerHTML=sorted.map(([l,c])=>{
332+
const pct=Math.round(c/total*100);
333+
return`<div class="lang-bar-item">
334+
<span class="lang-name">${l}</span>
335+
<div class="lang-bar-bg"><div class="lang-bar-fill" style="width:${pct}%;background:${lc(l)}"></div></div>
336+
<span class="lang-pct">${pct}%</span>
337+
</div>`;
338+
}).join('');
339+
}
340+
341+
function renderRepos(repos){
342+
document.getElementById('p-repos-list').innerHTML=repos.map(r=>`
343+
<a class="repo-card" href="${r.html_url}" target="_blank" rel="noopener">
344+
<div class="repo-name">${r.name} ↗</div>
345+
<div class="repo-desc">${r.description||'no description'}</div>
346+
<div class="repo-meta">
347+
${r.language?`<span><span class="lang-dot" style="background:${lc(r.language)}"></span>${r.language}</span>`:''}
348+
<span class="stars">★ ${fmtNum(r.stargazers_count)}</span>
349+
<span>⑂ ${fmtNum(r.forks_count)}</span>
350+
</div>
351+
</a>`).join('');
352+
}
353+
354+
function copyURL(){
355+
navigator.clipboard.writeText(window.location.href).then(()=>{
356+
const btn=document.querySelector('.btn-copy');
357+
btn.textContent='copied!';
358+
btn.style.borderColor='var(--green)';
359+
btn.style.color='var(--green)';
360+
setTimeout(()=>{btn.textContent='copy url';btn.style.borderColor='';btn.style.color='';},2000);
361+
});
362+
}
363+
364+
function goToSlug(){
365+
const val=document.getElementById('slug-input').value.trim();
366+
if(!val)return;
367+
// support pasting full URL or just slug
368+
const match=val.match(/[?&]u=([a-z0-9-]+)/)||val.match(/^([a-z0-9-]+)$/);
369+
if(match) window.location.href=`profile.html?u=${match[1]}`;
370+
}
371+
372+
// Init
373+
window.addEventListener('DOMContentLoaded',()=>{
374+
const params=new URLSearchParams(window.location.search);
375+
const slug=params.get('u');
376+
if(slug){
377+
loadProfile(slug);
378+
} else {
379+
show('search-screen');
380+
}
381+
document.getElementById('slug-input')?.addEventListener('keydown',e=>{if(e.key==='Enter')goToSlug()});
382+
});
383+
</script>
384+
</body>
385+
</html>

0 commit comments

Comments
 (0)