|
4 | 4 | <meta charset="utf-8" /> |
5 | 5 | <meta name="viewport" content="width=device-width,initial-scale=1" /> |
6 | 6 | <title>Bismay • Portfolio</title> |
7 | | - <meta name="description" content="Portfolio — Bismay • minimal black & white UI with interactive 3D-style project cards" /> |
| 7 | + <meta name="description" content="Bismay-exe portfolio — PNG-sequence hero + auto-fetched GitHub projects" /> |
8 | 8 | <style> |
9 | | - :root{--bg:#fff;--fg:#000;--muted:#666;--accent:#000} |
| 9 | + :root{--bg:#fff;--fg:#000;--muted:#666} |
10 | 10 | *{box-sizing:border-box} |
11 | | - html,body{height:100%;margin:0;background:var(--bg);color:var(--fg);font-family:Inter,system-ui,Arial,Helvetica,sans-serif} |
| 11 | + html,body{height:100%;margin:0;background:var(--bg);color:var(--fg);font-family:Inter,system-ui,Arial,Helvetica,sans-serif;-webkit-font-smoothing:antialiased} |
12 | 12 | a{color:inherit;text-decoration:none} |
| 13 | + |
| 14 | + /* Hero (PNG sequence) */ |
| 15 | + #hero{height:100vh; display:grid; place-items:center; position:relative; overflow:hidden} |
| 16 | + canvas#sequence{max-width:90vw; max-height:85vh; display:block} |
| 17 | + #loading{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);font-family:monospace;color:var(--muted)} |
| 18 | + |
| 19 | + /* content */ |
13 | 20 | .container{max-width:1100px;margin:0 auto;padding:48px 24px} |
| 21 | + .hero-copy{position:absolute;top:36px;left:36px;z-index:20} |
| 22 | + .title{font-weight:800;font-size:28px} |
| 23 | + .subtitle{color:var(--muted);margin-top:6px} |
| 24 | + |
| 25 | + /* Projects */ |
| 26 | + section#projects{padding:56px 0} |
| 27 | + h2{font-size:20px;margin:0 0 10px} |
| 28 | + p.lead{color:var(--muted);margin-top:6px} |
14 | 29 |
|
15 | | - /* Hero */ |
16 | | - .hero{display:flex;align-items:center;gap:28px;flex-wrap:wrap} |
17 | | - .title{font-weight:800;font-size:clamp(28px,6vw,54px);line-height:1} |
18 | | - .subtitle{color:var(--muted);margin-top:8px;font-size:16px} |
19 | | - |
20 | | - /* Animated words */ |
21 | | - .animated-words{display:inline-block;margin-left:6px;vertical-align:middle} |
22 | | - .animated-words span{display:inline-block;opacity:0;transform:translateY(6px);animation:wordIn .9s forwards;} |
23 | | - .animated-words span:nth-child(1){animation-delay:.1s} |
24 | | - .animated-words span:nth-child(2){animation-delay:.35s} |
25 | | - .animated-words span:nth-child(3){animation-delay:.6s} |
26 | | - @keyframes wordIn{to{opacity:1;transform:none}} |
27 | | - |
28 | | - /* Grid */ |
29 | | - .grid{display:grid;grid-template-columns:repeat(3,1fr);gap:24px;margin-top:40px} |
| 30 | + .grid{display:grid;grid-template-columns:repeat(3,1fr);gap:20px;margin-top:24px} |
30 | 31 | @media(max-width:980px){.grid{grid-template-columns:repeat(2,1fr)}} |
31 | 32 | @media(max-width:640px){.grid{grid-template-columns:1fr}} |
32 | 33 |
|
33 | | - /* Card */ |
34 | | - .card{background:#fff;border:1px solid rgba(0,0,0,0.06);border-radius:14px;padding:18px;box-shadow:0 10px 30px rgba(0,0,0,0.06);cursor:pointer;transform-style:preserve-3d;transition:transform .25s ease, box-shadow .25s ease} |
35 | | - .card:hover{box-shadow:0 22px 60px rgba(0,0,0,0.12);} |
| 34 | + .card{background:var(--bg);border:1px solid rgba(0,0,0,0.06);border-radius:12px;padding:14px;box-shadow:0 8px 30px rgba(0,0,0,0.04);cursor:pointer;transform-style:preserve-3d;transition:transform .22s ease, box-shadow .22s ease} |
| 35 | + .card:hover{box-shadow:0 26px 60px rgba(0,0,0,0.08)} |
36 | 36 | .card-inner{transform-style:preserve-3d;transition:transform .18s ease} |
37 | | - .card-thumb{height:140px;border-radius:10px;overflow:hidden;background:linear-gradient(180deg,#f6f6f6,#e9e9e9);display:flex;align-items:center;justify-content:center;color:var(--muted);font-weight:700} |
| 37 | + .card-thumb{height:120px;border-radius:8px;overflow:hidden;background:linear-gradient(180deg,#fafafa,#f0f0f0);display:flex;align-items:center;justify-content:center;color:var(--muted);font-weight:700} |
38 | 38 | .card-title{font-weight:700;margin-top:12px} |
39 | | - .card-desc{color:var(--muted);font-size:14px;margin-top:8px} |
40 | | - .card-meta{display:flex;gap:10px;align-items:center;margin-top:14px;font-size:13px;color:var(--muted)} |
| 39 | + .card-desc{color:var(--muted);font-size:14px;margin-top:8px;min-height:44px} |
| 40 | + .card-meta{display:flex;gap:8px;align-items:center;margin-top:10px;font-size:13px;color:var(--muted);flex-wrap:wrap} |
| 41 | + .badge{display:inline-block;padding:4px 8px;border-radius:999px;background:#fff;color:#000;font-size:12px;margin:2px;box-shadow:0 1px 3px rgba(0,0,0,0.12)} |
| 42 | + .meta-right{margin-left:auto;font-weight:600} |
41 | 43 |
|
42 | | - /* 3D hover perspective */ |
43 | | - .card-3d{perspective:900px} |
| 44 | + footer{padding:36px 0;color:var(--muted);text-align:center} |
44 | 45 |
|
45 | | - /* Footer */ |
46 | | - footer{margin-top:56px;padding:24px 0;color:var(--muted);text-align:center} |
| 46 | + /* help overlay (draggable desktop only) */ |
| 47 | + #help-overlay{position:fixed;top:20px;right:20px;background:rgba(0,0,0,0.8);color:#fff;padding:12px 14px;border-radius:10px;font-size:13px;z-index:999;cursor:grab} |
| 48 | + #help-overlay:active{cursor:grabbing} |
47 | 49 | </style> |
48 | 50 | </head> |
49 | 51 | <body> |
50 | | - <div class="container"> |
51 | | - <header class="hero"> |
52 | | - <div> |
53 | | - <div class="title">Bismay • <span style="opacity:.8">Developer</span></div> |
54 | | - <div class="subtitle">Minimal, modern projects — <span class="animated-words"><span>Design</span><span>Code</span><span>Ship</span></span></div> |
55 | | - </div> |
56 | | - <div style="margin-left:auto;text-align:right"> |
57 | | - <div style="font-weight:700">GitHub</div> |
58 | | - <div style="color:var(--muted)"><a href="https://github.com/Bismay-exe" target="_blank" rel="noopener">github.com/Bismay-exe</a></div> |
59 | | - </div> |
60 | | - </header> |
61 | | - |
62 | | - <section aria-labelledby="projects"> |
63 | | - <h2 id="projects" style="margin-top:48px">Selected projects</h2> |
64 | | - <p style="color:var(--muted)">Projects pulled from the public GitHub profile. Click any card to view the repo.</p> |
65 | | - |
66 | | - <div class="grid" id="projects-grid"> |
67 | | - <!-- Cards will be injected here --> |
68 | | - </div> |
| 52 | + <header id="hero"> |
| 53 | + <canvas id="sequence"></canvas> |
| 54 | + <div id="loading">Loading...</div> |
| 55 | + <div class="hero-copy"> |
| 56 | + <div class="title">Bismay • Developer</div> |
| 57 | + <div class="subtitle">Interactive portfolio — scroll to explore</div> |
| 58 | + </div> |
| 59 | + </header> |
| 60 | + |
| 61 | + <main class="container"> |
| 62 | + <section id="projects"> |
| 63 | + <h2>Projects</h2> |
| 64 | + <p class="lead">All public repos from <strong>Bismay-exe</strong> (pulled live from GitHub)</p> |
| 65 | + <div class="grid" id="projects-grid"></div> |
69 | 66 | </section> |
| 67 | + <footer>© <span id="year"></span> Bismay • Built with HTML/CSS/JS</footer> |
| 68 | + </main> |
70 | 69 |
|
71 | | - <footer> |
72 | | - © <span id="year"></span> Bismay • Built with plain HTML/CSS/JS |
73 | | - </footer> |
74 | | - </div> |
| 70 | + <div id="help-overlay">Controls: Scroll to spin • Drag (desktop) • Keys: ← → space R L H</div> |
75 | 71 |
|
76 | 72 | <script> |
77 | | - // Project list sourced from GitHub profile (public repos); see README for live links. |
78 | | - const projects = [ |
79 | | - {name:'Beautiful-Notes', desc:'Sleek notes app with glass UI', url:'https://github.com/Bismay-exe/Beautiful-Notes'}, |
80 | | - {name:'5GTile', desc:'5G toggle / Tile project (Kotlin)', url:'https://github.com/Bismay-exe/5GTile'}, |
81 | | - {name:'Mechanical-Calculator', desc:'All-in-one calculator with themes', url:'https://github.com/Bismay-exe/Mechanical-Calculator'}, |
82 | | - {name:'My-Heavenly-Archive', desc:'Media gallery with filters & lightbox', url:'https://github.com/Bismay-exe/My-Heavenly-Archive'}, |
83 | | - {name:'Crypto-converter', desc:'Realtime crypto ↔ INR/USD/EUR converter', url:'https://github.com/Bismay-exe/Crypto-converter'}, |
84 | | - {name:'login-page', desc:'Simple minimal login page template', url:'https://github.com/Bismay-exe/login-page'}, |
85 | | - {name:'Bismay-exe', desc:'Personal profile & readme repo', url:'https://github.com/Bismay-exe/Bismay-exe'}, |
86 | | - {name:'Beautiful-Notes-Showcase', desc:'(extra) project showcase', url:'https://github.com/Bismay-exe/Beautiful-Notes-Showcase'} |
87 | | - ]; |
88 | | - |
89 | | - // Build cards |
90 | | - const grid = document.getElementById('projects-grid'); |
91 | | - projects.forEach((p, idx) => { |
92 | | - const col = document.createElement('a'); |
93 | | - col.className = 'card card-3d'; |
94 | | - col.href = p.url; col.target = '_blank'; col.rel = 'noopener'; |
95 | | - |
96 | | - const inner = document.createElement('div'); inner.className = 'card-inner'; |
97 | | - const thumb = document.createElement('div'); thumb.className = 'card-thumb'; thumb.textContent = p.name; |
98 | | - const title = document.createElement('div'); title.className = 'card-title'; title.textContent = p.name.replace(/[-_]/g,' '); |
99 | | - const desc = document.createElement('div'); desc.className = 'card-desc'; desc.textContent = p.desc; |
100 | | - const meta = document.createElement('div'); meta.className = 'card-meta'; meta.innerHTML = `<span>GitHub</span><span>•</span><span>View Repo</span>`; |
101 | | - |
102 | | - inner.appendChild(thumb); inner.appendChild(title); inner.appendChild(desc); inner.appendChild(meta); |
103 | | - col.appendChild(inner); |
104 | | - grid.appendChild(col); |
105 | | - |
106 | | - // 3D hover effect: mousemove on card |
107 | | - col.addEventListener('mousemove', (e)=>{ |
108 | | - const rect = col.getBoundingClientRect(); |
109 | | - const x = (e.clientX - rect.left) / rect.width - 0.5; // -0.5 -> 0.5 |
110 | | - const y = (e.clientY - rect.top) / rect.height - 0.5; |
111 | | - const tiltX = (y * -1) * 12; // rotateX |
112 | | - const tiltY = x * 12; // rotateY |
113 | | - inner.style.transform = `rotateX(${tiltX}deg) rotateY(${tiltY}deg) translateZ(8px)`; |
114 | | - }); |
115 | | - col.addEventListener('mouseleave', ()=>{ |
116 | | - inner.style.transform = 'rotateX(0deg) rotateY(0deg) translateZ(0px)'; |
117 | | - }); |
118 | | - |
119 | | - // subtle entrance animation stagger |
120 | | - inner.style.opacity = 0; inner.style.transform += ' translateY(10px)'; |
121 | | - setTimeout(()=>{ inner.style.transition='transform .5s ease, opacity .5s ease'; inner.style.opacity=1; inner.style.transform='none'; }, idx*80); |
| 73 | + // ========== PNG SEQUENCE HERO =========== |
| 74 | + const canvas = document.getElementById('sequence'); |
| 75 | + const ctx = canvas.getContext('2d'); |
| 76 | + let W = window.innerWidth, H = window.innerHeight; |
| 77 | + function resize(){ W = window.innerWidth; H = window.innerHeight; canvas.width = W; canvas.height = H; } |
| 78 | + window.addEventListener('resize', resize); resize(); |
| 79 | + |
| 80 | + const TOTAL = 300; // male0001.png ... male0300.png |
| 81 | + const cache = {}; |
| 82 | + const buffer = 8; |
| 83 | + let current = 1, velocity = 0, firstLoaded=false; |
| 84 | + let scale = 1; |
| 85 | + let tiltX=0, tiltY=0; |
| 86 | + |
| 87 | + function frameFile(n){ return `male${String(n).padStart(4,'0')}.png`; } |
| 88 | + |
| 89 | + function loadFrame(n, cb){ |
| 90 | + if (n < 1) n += TOTAL; if (n > TOTAL) n -= TOTAL; |
| 91 | + if (cache[n]) { if(cb) cb(cache[n]); return; } |
| 92 | + const i = new Image(); i.src = frameFile(n); |
| 93 | + i.onload = ()=>{ cache[n] = i; if(!firstLoaded){ firstLoaded=true; document.getElementById('loading').style.display='none'; } if(cb) cb(i); }; |
| 94 | + i.onerror = ()=>{ /* ignore */ }; |
| 95 | + } |
| 96 | + |
| 97 | + // load first frame |
| 98 | + loadFrame(1, (img)=>{ draw(1); preload(1); }); |
| 99 | + function preload(center){ for(let off=-buffer; off<=buffer; off++){ let idx = Math.round(center) + off; if(idx<1) idx+=TOTAL; if(idx>TOTAL) idx-=TOTAL; loadFrame(idx); } } |
| 100 | + |
| 101 | + function draw(frame){ const img = cache[Math.round(frame)]; if(!img) return; ctx.clearRect(0,0,W,H); const iw = img.width*scale; const ih = img.height*scale; const x = (W - iw)/2 + tiltX*30; const y = (H - ih)/2 + tiltY*30; ctx.drawImage(img, x, y, iw, ih); } |
| 102 | + |
| 103 | + function update(){ |
| 104 | + current += velocity; velocity *= 0.92; |
| 105 | + if(current > TOTAL) current = 1; if(current < 1) current = TOTAL; |
| 106 | + const idx = Math.round(current); |
| 107 | + if(cache[idx]) draw(current); |
| 108 | + preload(idx); |
| 109 | + requestAnimationFrame(update); |
| 110 | + } |
| 111 | + requestAnimationFrame(update); |
| 112 | + |
| 113 | + // wheel -> velocity |
| 114 | + window.addEventListener('wheel', (e)=>{ velocity += Math.sign(e.deltaY) * 2.2; }, {passive:true}); |
| 115 | + // mouse drag |
| 116 | + let lastY=0; window.addEventListener('mousedown', (e)=>{ lastY = e.clientY; }); |
| 117 | + window.addEventListener('mousemove', (e)=>{ if(e.buttons){ const d = e.clientY - lastY; velocity += d*0.06; lastY = e.clientY; } tiltX = (e.clientX / W - 0.5); tiltY = (e.clientY / H - 0.5); }); |
| 118 | + // touch swipe |
| 119 | + let tx=0, ty=0, tstart=0, lastTap=0, tapTimeout=null; let pinchDist=null; |
| 120 | + window.addEventListener('touchstart',(e)=>{ if(e.touches.length===1){ tx=e.touches[0].clientX; ty=e.touches[0].clientY; tstart=Date.now(); } if(e.touches.length===2){ pinchDist = Math.hypot(e.touches[0].clientX-e.touches[1].clientX, e.touches[0].clientY-e.touches[1].clientY); } }); |
| 121 | + window.addEventListener('touchmove',(e)=>{ if(e.touches.length===1){ const dx = e.touches[0].clientX - tx; const dy = e.touches[0].clientY - ty; velocity += -dx*0.04; tx = e.touches[0].clientX; ty = e.touches[0].clientY; tiltX = (tx / W - 0.5); tiltY = (ty / H - 0.5); } else if(e.touches.length===2){ const dist = Math.hypot(e.touches[0].clientX-e.touches[1].clientX, e.touches[0].clientY-e.touches[1].clientY); if(pinchDist){ const delta = dist - pinchDist; scale = Math.min(2, Math.max(0.5, scale + delta*0.003)); } pinchDist = dist; } }, {passive:false}); |
| 122 | + window.addEventListener('touchend',(e)=>{ if(e.touches.length===0 && e.changedTouches.length===1){ const dt = Date.now()-tstart; if(dt<200) { /* quick flick */ velocity *= 2.2; } // tap/double-tap handling |
| 123 | + const now = Date.now(); const gap = now - lastTap; if(gap<300 && gap>0){ // double tap -> stop |
| 124 | + velocity = 0; clearTimeout(tapTimeout); |
| 125 | + } else { |
| 126 | + tapTimeout = setTimeout(()=>{ /* single tap -> reset */ current = 1; },250); |
| 127 | + } lastTap = now; |
| 128 | + } if(e.touches.length<2) pinchDist = null; }); |
| 129 | + |
| 130 | + // keyboard controls |
| 131 | + window.addEventListener('keydown',(e)=>{ |
| 132 | + if(e.key==='ArrowRight') velocity += 3; |
| 133 | + if(e.key==='ArrowLeft') velocity -= 3; |
| 134 | + if(e.key===' ') velocity = 0; |
| 135 | + if(e.key.toLowerCase()==='r') current = 1; |
| 136 | + if(e.key.toLowerCase()==='l') current = TOTAL; |
| 137 | + if(e.key==='+') scale = Math.min(2, scale+0.1); |
| 138 | + if(e.key==='-') scale = Math.max(0.5, scale-0.1); |
| 139 | + if(e.key.toLowerCase()==='h'){ const o=document.getElementById('help-overlay'); o.style.opacity=(o.style.opacity==='0'? '1':'0'); } |
122 | 140 | }); |
123 | 141 |
|
124 | | - // small accessibility: keyboard focus styling |
125 | | - document.addEventListener('keydown', (e)=>{ |
126 | | - if(e.key === 'Tab') document.body.classList.add('show-focus'); |
127 | | - }); |
| 142 | + // ========== Portfolio: auto-fetch all repos =========== |
| 143 | + const owner = 'Bismay-exe'; |
| 144 | + const grid = document.getElementById('projects-grid'); |
| 145 | + |
| 146 | + async function fetchAllRepos(){ |
| 147 | + let repos = []; |
| 148 | + let page = 1; |
| 149 | + while(true){ |
| 150 | + const res = await fetch(`https://api.github.com/users/${owner}/repos?per_page=100&page=${page}`); |
| 151 | + if(!res.ok) break; |
| 152 | + const chunk = await res.json(); |
| 153 | + if(chunk.length===0) break; |
| 154 | + repos = repos.concat(chunk); |
| 155 | + if(chunk.length<100) break; page++; |
| 156 | + } |
| 157 | + // sort by updated desc |
| 158 | + repos.sort((a,b)=> new Date(b.updated_at) - new Date(a.updated_at)); |
| 159 | + return repos; |
| 160 | + } |
| 161 | + |
| 162 | + async function fetchReadme(owner, repo){ |
| 163 | + try{ |
| 164 | + const res = await fetch(`https://api.github.com/repos/${owner}/${repo}/readme`); |
| 165 | + if(!res.ok) return null; |
| 166 | + const data = await res.json(); |
| 167 | + const content = atob(data.content); |
| 168 | + const first = content.split('\n\n').find(p=>p.trim().length>0); |
| 169 | + return first ? first.replace(/\n/g,' ') : null; |
| 170 | + }catch(e){return null} |
| 171 | + } |
| 172 | + |
| 173 | + async function fetchTopics(owner, repo){ |
| 174 | + try{ |
| 175 | + const res = await fetch(`https://api.github.com/repos/${owner}/${repo}/topics`, {headers: {'Accept':'application/vnd.github.mercy-preview+json'}}); |
| 176 | + if(!res.ok) return []; |
| 177 | + const j = await res.json(); return j.names||[]; |
| 178 | + }catch(e){return []} |
| 179 | + } |
| 180 | + |
| 181 | + async function fetchRepo(owner, repo){ |
| 182 | + try{ const res = await fetch(`https://api.github.com/repos/${owner}/${repo}`); if(!res.ok) return null; return await res.json(); }catch(e){return null} |
| 183 | + } |
| 184 | + |
| 185 | + function createCard(repo){ |
| 186 | + const link = document.createElement('a'); link.className='card card-3d'; link.href=repo.html_url; link.target='_blank'; link.rel='noopener'; |
| 187 | + const inner = document.createElement('div'); inner.className='card-inner'; |
| 188 | + const thumb = document.createElement('div'); thumb.className='card-thumb'; thumb.textContent = repo.name; |
| 189 | + const title = document.createElement('div'); title.className='card-title'; title.textContent = repo.name.replace(/[-_]/g,' '); |
| 190 | + const desc = document.createElement('div'); desc.className='card-desc'; desc.textContent = '⏳ Loading project info...'; |
| 191 | + const meta = document.createElement('div'); meta.className='card-meta'; meta.textContent = ''; |
| 192 | + inner.append(thumb,title,desc,meta); link.append(inner); grid.append(link); |
| 193 | + |
| 194 | + // hover tilt |
| 195 | + link.addEventListener('mousemove', (e)=>{ |
| 196 | + const rect = link.getBoundingClientRect(); const x = (e.clientX - rect.left)/rect.width - 0.5; const y = (e.clientY - rect.top)/rect.height - 0.5; const rx = (-y)*12; const ry = x*12; inner.style.transform = `rotateX(${rx}deg) rotateY(${ry}deg) translateZ(8px)`; |
| 197 | + }); |
| 198 | + link.addEventListener('mouseleave', ()=>{ inner.style.transform='none'; }); |
| 199 | + |
| 200 | + return {link,inner,thumb,title,desc,meta}; |
| 201 | + } |
| 202 | + |
| 203 | + (async ()=>{ |
| 204 | + const repos = await fetchAllRepos(); |
| 205 | + if(!repos || repos.length===0){ grid.innerHTML='<div style="color:var(--muted)">No public repos found.</div>'; return; } |
| 206 | + for(const r of repos){ |
| 207 | + const cardEls = createCard(r); |
| 208 | + // fetch details concurrently |
| 209 | + (async ()=>{ |
| 210 | + const [readme, topics, repoData] = await Promise.all([fetchReadme(owner, r.name), fetchTopics(owner, r.name), fetchRepo(owner, r.name)]); |
| 211 | + cardEls.desc.textContent = readme || '⚡ Project details coming soon...'; |
| 212 | + // badges |
| 213 | + if(topics && topics.length){ cardEls.meta.innerHTML = topics.map(t=>`<span class="badge">${t}</span>`).join(' '); } |
| 214 | + // stats + live |
| 215 | + let stats = ''; |
| 216 | + if(repoData){ stats += `⭐ ${repoData.stargazers_count} 🍴 ${repoData.forks_count} 🕒 ${new Date(repoData.updated_at).toLocaleDateString()}`; } |
| 217 | + // live demo |
| 218 | + let live = ''; |
| 219 | + if(repoData && (repoData.has_pages || repoData.homepage)){ |
| 220 | + const liveUrl = repoData.homepage ? repoData.homepage : `https://${owner}.github.io/${r.name}/`; |
| 221 | + live = ` <a style="margin-left:8px;font-weight:600" href="${liveUrl}" target="_blank" rel="noopener">Live</a>`; |
| 222 | + } |
| 223 | + if(cardEls.meta.innerHTML) cardEls.meta.innerHTML += '<div style="flex:1"></div>' + `<div class="meta-right">${stats}${live}</div>`; else cardEls.meta.innerHTML = `<div class="meta-right">${stats}${live}</div>`; |
| 224 | + })(); |
| 225 | + } |
| 226 | + })(); |
| 227 | + |
| 228 | + // help overlay drag (desktop only) |
| 229 | + const help = document.getElementById('help-overlay'); let dragging=false,offX=0,offY=0; |
| 230 | + help.addEventListener('mousedown',(e)=>{ dragging=true; offX = e.clientX - help.getBoundingClientRect().left; offY = e.clientY - help.getBoundingClientRect().top; help.style.transition='none'; }); |
| 231 | + window.addEventListener('mousemove',(e)=>{ if(dragging){ help.style.left = (e.clientX - offX) + 'px'; help.style.top = (e.clientY - offY) + 'px'; help.style.right='auto'; } }); |
| 232 | + window.addEventListener('mouseup',()=>{ if(dragging){ dragging=false; help.style.transition='opacity 0.6s ease'; } }); |
128 | 233 |
|
129 | 234 | document.getElementById('year').textContent = new Date().getFullYear(); |
130 | 235 | </script> |
|
0 commit comments