299 lines
21 KiB
HTML
299 lines
21 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
<title>PyNix | Python, Linux, building things</title>
|
|
<meta name="description" content="PyNix (pynix-pro): Python and Linux projects, learning in public.">
|
|
<link rel="icon" href="assets/favicon-32.png" sizes="32x32" type="image/png">
|
|
<link rel="icon" href="assets/favicon.ico" sizes="any">
|
|
<link rel="apple-touch-icon" href="assets/apple-touch-icon.png">
|
|
<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=JetBrains+Mono:wght@400&display=swap" rel="stylesheet">
|
|
<script>document.documentElement.className='js'</script>
|
|
<style>
|
|
:root{--bg:#0f151d;--blue:#9cc2ee;--ice:#dcebf8;--gray:#9aa7b8;--txt:#e8f0fa;--r:28px;--f:'Space Grotesk',system-ui,sans-serif;--m:'JetBrains Mono',ui-monospace,monospace;--line:rgba(156,194,238,.2)}
|
|
*{box-sizing:border-box;margin:0}
|
|
html{scroll-behavior:smooth}
|
|
body{background:var(--bg);color:var(--txt);font:400 1.05rem/1.7 var(--f);overflow-x:hidden}
|
|
body::before{content:"";position:fixed;inset:0;z-index:50;pointer-events:none;opacity:.08;background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence baseFrequency='.8' numOctaves='2'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E")}
|
|
#bar{position:fixed;top:0;left:0;right:0;height:3px;z-index:60;transform:scaleX(0);transform-origin:0 50%;background:linear-gradient(90deg,var(--blue),var(--ice));box-shadow:0 0 12px var(--blue)}
|
|
#cur{position:fixed;left:-260px;top:-260px;width:520px;height:520px;border-radius:50%;pointer-events:none;z-index:1;background:radial-gradient(circle,rgba(156,194,238,.15),transparent 65%);will-change:transform}
|
|
.blob{position:fixed;z-index:0;width:60vmax;height:60vmax;border-radius:50%;pointer-events:none;background:radial-gradient(circle,rgba(110,160,230,.14),transparent 62%);animation:dr 22s ease-in-out infinite alternate}
|
|
.b1{left:-20vmax;top:-15vmax}
|
|
.b2{right:-25vmax;top:45vh;animation-delay:-8s;background:radial-gradient(circle,rgba(220,235,248,.07),transparent 62%)}
|
|
@keyframes dr{to{transform:translate(6vmax,8vmax) scale(1.12)}}
|
|
main{position:relative;z-index:2}
|
|
section{max-width:1100px;margin:0 auto;padding:clamp(80px,14vh,150px) 6vw}
|
|
h2{font-size:clamp(2rem,5.4vw,3.8rem);line-height:1.05;letter-spacing:-.02em;margin-bottom:22px}
|
|
h3{font-size:1.4rem;margin:6px 0 8px}
|
|
p{color:var(--gray);max-width:60ch}
|
|
i{font-style:normal;display:inline-block;min-width:.3em}
|
|
.js [data-s] i{opacity:0}
|
|
[data-s]{width:fit-content;max-width:100%}
|
|
[data-s] i{transition:color .4s,text-shadow .4s,transform .45s cubic-bezier(.22,1,.36,1)}
|
|
[data-s] i.u{color:var(--blue);text-align:center}
|
|
[data-s] i.lk{color:var(--ice);text-shadow:0 0 18px rgba(156,194,238,.95);transform:translateY(-2px) scale(1.06)}
|
|
a:focus-visible{outline:2px solid var(--blue);outline-offset:4px}
|
|
|
|
/* top bar */
|
|
#nav{position:fixed;z-index:55;top:12px;left:50%;translate:-50% 0;width:min(780px,92vw);display:flex;align-items:center;gap:6px;padding:5px;border-radius:999px;border:1px solid rgba(156,194,238,.18);background:rgba(15,21,29,.38);-webkit-backdrop-filter:blur(14px) saturate(140%);backdrop-filter:blur(14px) saturate(140%);box-shadow:0 8px 40px rgba(0,0,0,.25),inset 0 1px 0 rgba(220,235,248,.07)}
|
|
#nav div{display:contents}
|
|
#nav a{flex:1;display:flex;align-items:center;justify-content:center;gap:8px;padding:6px 14px;border-radius:999px;border:1px solid rgba(156,194,238,.14);background:rgba(156,194,238,.05);color:var(--gray);text-decoration:none;font-size:.85rem;line-height:1.5;transition:background .25s,color .25s,border-color .25s,box-shadow .25s}
|
|
#nav a:hover{color:var(--txt);background:rgba(156,194,238,.16);border-color:rgba(156,194,238,.4)}
|
|
#nav a.on{color:#0b1220;background:var(--blue);border-color:transparent;box-shadow:0 0 18px rgba(156,194,238,.35)}
|
|
#nav .br{color:var(--txt);font-weight:700;letter-spacing:.08em}
|
|
.br img{height:20px;width:auto}
|
|
@media(max-width:560px){.br b{display:none}#nav{gap:4px;padding:4px}#nav a{padding:6px 4px;font-size:.75rem}}
|
|
|
|
/* side rail: labels change role as you scroll (past, active, next, later) */
|
|
#rail{position:fixed;inset:0 0 0 auto;width:0;z-index:55;pointer-events:none}
|
|
#rail .rt{position:absolute;right:34px;top:0;white-space:nowrap;text-decoration:none;font:700 1.5rem/1.15 var(--f);letter-spacing:.07em;text-transform:uppercase;color:var(--gray);transform-origin:100% 0;pointer-events:auto;transition:color .35s;will-change:transform}
|
|
#rail .rt.on{color:var(--txt)}
|
|
#rail .rt:hover{color:var(--ice)}
|
|
#rail .rt i{min-width:0;transition:transform .55s cubic-bezier(.22,1,.36,1) calc(var(--j,0)*28ms),opacity .4s calc(var(--j,0)*28ms);transform:translateY(calc(var(--f,0)*var(--d,0px)));opacity:calc(1 - var(--f,0)*.7)}
|
|
#rail .rt.snap i{transition:none}
|
|
#rail .ln{position:absolute;right:0;top:64px;bottom:12px;width:20px;border-right:1px solid var(--line);background:repeating-linear-gradient(to bottom,rgba(156,194,238,.32) 0 1px,transparent 1px 22px) right/9px 100% no-repeat}
|
|
#rail .mk{position:absolute;right:-1px;top:0;width:22px;height:2px;background:var(--blue);box-shadow:0 0 10px var(--blue)}
|
|
#rail .mk em{position:absolute;right:28px;top:-7px;font:.65rem var(--m);font-style:normal;color:var(--blue)}
|
|
@media(max-width:900px){#rail{display:none}}
|
|
|
|
/* hero */
|
|
.hero{min-height:100svh;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding-top:6vh}
|
|
.hero p{margin:0 auto;font-size:1.3rem}
|
|
.logo{position:relative;width:min(520px,84vw);aspect-ratio:1020/640}
|
|
.logo img{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;user-select:none}
|
|
.halo{position:absolute;inset:8% 16%;border-radius:50%;background:radial-gradient(circle,rgba(156,194,238,.42),transparent 68%);animation:pu 4.5s ease-in-out infinite}
|
|
@keyframes pu{50%{transform:scale(1.18);opacity:.55}}
|
|
.l{animation:iL 1.1s .2s cubic-bezier(.2,.8,.2,1) both;translate:calc(var(--px,0)*-16px) calc(var(--py,0)*-10px)}
|
|
.r{animation:iR 1.1s .2s cubic-bezier(.2,.8,.2,1) both;translate:calc(var(--px,0)*16px) calc(var(--py,0)*-10px)}
|
|
.p{animation:iP 1.2s .55s cubic-bezier(.2,.8,.2,1) both;translate:calc(var(--px,0)*8px) calc(var(--py,0)*8px)}
|
|
@keyframes iL{from{opacity:0;transform:translateX(-90px) rotate(-10deg)}}
|
|
@keyframes iR{from{opacity:0;transform:translateX(90px) rotate(10deg)}}
|
|
@keyframes iP{from{opacity:0;transform:translateY(30px) scale(.8)}}
|
|
h1{font-size:clamp(3.2rem,14vw,9.5rem);line-height:1;letter-spacing:.14em;padding-left:.14em;margin:14px 0 8px;color:var(--ice);text-shadow:0 0 36px rgba(156,194,238,.5)}
|
|
.row{display:flex;gap:14px;flex-wrap:wrap;justify-content:center;margin-top:28px}
|
|
.btn{display:inline-block;padding:9px 22px;border-radius:999px;font:500 .9rem var(--f);color:var(--txt);text-decoration:none;border:1px solid rgba(156,194,238,.32);background:rgba(156,194,238,.06);transition:transform .2s ease-out,box-shadow .3s,background .3s}
|
|
.btn:hover{background:rgba(156,194,238,.16);box-shadow:0 0 34px rgba(156,194,238,.35)}
|
|
.btn.f{background:var(--blue);color:#0b1220;border-color:transparent}
|
|
.cue{margin-top:44px;color:var(--gray);font:.8rem var(--m);animation:bob 2.2s ease-in-out infinite}
|
|
@keyframes bob{50%{transform:translateY(8px)}}
|
|
|
|
/* surfaces */
|
|
.g{display:grid;gap:20px;grid-template-columns:repeat(auto-fit,minmax(280px,1fr))}
|
|
.card{position:relative;overflow:hidden;padding:28px;border-radius:var(--r);border:1px solid var(--line);background:linear-gradient(160deg,rgba(156,194,238,.08),rgba(156,194,238,.02));transition:transform .25s ease-out,opacity .5s,translate .7s cubic-bezier(.22,1,.36,1),border-color .3s}
|
|
.card::before{content:"";position:absolute;inset:0;pointer-events:none;opacity:0;transition:opacity .3s;background:radial-gradient(260px circle at var(--mx,50%) var(--my,50%),rgba(156,194,238,.16),transparent 70%)}
|
|
.card:hover{border-color:rgba(156,194,238,.45)}
|
|
.card:hover::before{opacity:1}
|
|
.card>*{position:relative}
|
|
.big{font-size:1.2rem;color:var(--txt);max-width:34ch}
|
|
.more{color:var(--blue);text-decoration:none;font-weight:500}
|
|
.more:hover{text-decoration:underline}
|
|
.t{font:.95rem/1.9 var(--m);color:var(--txt)}
|
|
.tb{display:flex;align-items:center;gap:8px;padding-bottom:14px;margin-bottom:16px;border-bottom:1px solid var(--line)}
|
|
.tb span{width:12px;height:12px;border-radius:50%;flex:none}
|
|
.tb span:nth-child(1){background:#ff7b7b}.tb span:nth-child(2){background:#ffd166}.tb span:nth-child(3){background:#6ee7a8}
|
|
.tb em{margin-left:auto;font:.8rem var(--m);font-style:normal;color:var(--gray)}
|
|
.t b{font-weight:400;color:var(--blue)}
|
|
.t u{text-decoration:none;display:inline-block;width:.6em;height:1.05em;vertical-align:text-bottom;background:var(--blue);animation:bl 1s steps(2) infinite}
|
|
@keyframes bl{50%{opacity:0}}
|
|
.chips{display:flex;flex-wrap:wrap;gap:12px;margin-top:28px}
|
|
.chip{padding:7px 16px;border-radius:999px;border:1px solid var(--line);background:rgba(156,194,238,.05);font:.9rem var(--m);transition:transform .2s,box-shadow .3s,opacity .5s,translate .7s cubic-bezier(.22,1,.36,1)}
|
|
.chip::before{content:"";display:inline-block;width:7px;height:7px;margin-right:10px;border-radius:50%;background:var(--blue);box-shadow:0 0 10px var(--blue)}
|
|
.chip:hover{transform:translateY(-4px);box-shadow:0 0 26px rgba(156,194,238,.3)}
|
|
.tl{margin:34px 0 0 8px;border-left:2px solid var(--line)}
|
|
.tl div{position:relative;padding:0 0 34px 30px}
|
|
.tl div::before{content:"";position:absolute;left:-8px;top:8px;width:14px;height:14px;border-radius:50%;background:var(--blue);box-shadow:0 0 16px var(--blue)}
|
|
.tl b{display:block;font-size:1.3rem}
|
|
.tl span{color:var(--gray)}
|
|
.pills{display:flex;flex-wrap:wrap;gap:10px;margin-top:30px}
|
|
.pill{display:inline-flex;align-items:baseline;gap:10px;padding:9px 20px;font-size:.92rem;border-radius:999px;text-decoration:none;color:var(--txt);border:1px solid var(--line);background:rgba(156,194,238,.05);transition:transform .15s ease-out,box-shadow .3s,border-color .3s,opacity .5s,translate .7s cubic-bezier(.22,1,.36,1)}
|
|
.pill small{color:var(--gray);font:.76rem var(--m)}
|
|
.pill:hover{border-color:rgba(156,194,238,.6);box-shadow:0 0 40px rgba(156,194,238,.28)}
|
|
footer{text-align:center;padding:40px 6vw 70px;color:var(--gray);font:.85rem var(--m)}
|
|
footer a{color:var(--blue);text-decoration:none}
|
|
|
|
/* reveal */
|
|
.js .rv{opacity:0;translate:0 56px;transition:opacity .5s,translate .7s cubic-bezier(.22,1,.36,1)}
|
|
.js .rv.in{opacity:1;translate:0 0}
|
|
.js .card.rv,.js .chip.rv,.js .pill.rv{transition:transform .25s ease-out,box-shadow .3s,border-color .3s,opacity .5s,translate .7s cubic-bezier(.22,1,.36,1)}
|
|
.pz *{animation-play-state:paused!important}
|
|
@media(prefers-reduced-motion:reduce){[data-s] i{opacity:1!important}*{animation:none!important;transition:none!important}.js .rv{opacity:1;translate:none}#cur{display:none}}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="bar"></div>
|
|
<nav id="nav" aria-label="Main"><a class="br" href="#top"><img src="assets/mark.png" alt=""><b>PyNix</b></a><div><a href="#about">About</a><a href="#skills">Skills</a><a href="#work">Work</a><a href="#contact">Contact</a></div></nav><nav id="rail" aria-label="Sections"></nav><div id="cur"></div><div class="blob b1"></div><div class="blob b2"></div>
|
|
<main>
|
|
|
|
<section class="hero" id="top">
|
|
<div class="logo" id="logo">
|
|
<div class="halo"></div>
|
|
<img class="l" src="assets/bracket-left.png" alt="">
|
|
<img class="p" src="assets/penguin.png" alt="PyNix penguin logo">
|
|
<img class="r" src="assets/bracket-right.png" alt="">
|
|
</div>
|
|
<h1 data-s>PYNIX</h1>
|
|
<p>Python. Linux. Building things.</p>
|
|
<div class="row"><a class="btn f" href="#work">See my work</a><a class="btn" href="#contact">Say hi</a></div>
|
|
<div class="cue">scroll down</div>
|
|
</section>
|
|
|
|
<section id="about">
|
|
<div class="g">
|
|
<div class="rv">
|
|
<h2 data-s>Hi, I'm PyNix.</h2>
|
|
<p>I'm a Python developer in progress and a daily Linux user, picking up C along the way. I like building small, clean things and learning in public. This page shows what I'm making.</p>
|
|
</div>
|
|
<div class="card rv">
|
|
<div class="tb"><span></span><span></span><span></span><em>pynix@gentoo: ~</em></div>
|
|
<div class="t">
|
|
<div>$ whoami</div><div><b>pynix</b></div>
|
|
<div>$ cat stack.txt</div><div><b>python, linux, c</b></div>
|
|
<div>$ echo $STATUS</div><div><b>learning in public</b> <u></u></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="skills">
|
|
<h2 data-s>What I work with</h2>
|
|
<p class="rv">Always learning, always adding to the list.</p>
|
|
<div class="chips">
|
|
<span class="chip rv">Python</span><span class="chip rv">Linux</span><span class="chip rv">Gentoo</span><span class="chip rv">C (basics)</span><span class="chip rv">Git</span><span class="chip rv">GitHub</span>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="work">
|
|
<h2 data-s>Things I've built</h2>
|
|
<!-- TODO: swap these three cards for your real projects and repo links -->
|
|
<div class="g">
|
|
<div class="card rv"><p>Website</p><h3>This site</h3><p>Hand-built with HTML, CSS and a little JavaScript. No frameworks, light on your computer.</p><br><a class="more" href="https://github.com/PyNix-Pro">See it on GitHub</a></div>
|
|
<div class="card rv"><p>Python</p><h3>Python experiments</h3><p>Scripts and small tools I write while learning, shared as they come together.</p><br><a class="more" href="https://github.com/PyNix-Pro">Browse the repos</a></div>
|
|
<div class="card rv"><p>Linux</p><h3>Gentoo notes</h3><p>What I learn from running Gentoo every day, and the setups that work for me.</p><br><a class="more" href="https://github.com/PyNix-Pro">Read along</a></div>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="journey">
|
|
<h2 data-s>How I got here</h2>
|
|
<div class="tl">
|
|
<div class="rv"><b>Python</b><span>Wrote my first lines of code. It's still my main language.</span></div>
|
|
<div class="rv"><b>Linux</b><span>Made it my daily system and never looked back.</span></div>
|
|
<div class="rv"><b>Gentoo</b><span>Started compiling my own system to understand how it all fits together.</span></div>
|
|
<div class="rv"><b>PyNix</b><span>Built a home for everything I make and share.</span></div>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="contact">
|
|
<h2 data-s>Let's build something.</h2>
|
|
<p class="rv">Questions, collabs or just saying hi. Pick whichever you like.</p>
|
|
<div class="pills">
|
|
<a class="pill rv" href="https://www.instagram.com/pynix_pro/" target="_blank" rel="noopener">Instagram<small>@pynix_pro</small></a>
|
|
<a class="pill rv" href="https://x.com/PyNix_Pro" target="_blank" rel="noopener">X (Twitter)<small>@PyNix_Pro</small></a>
|
|
<a class="pill rv" href="https://github.com/PyNix-Pro" target="_blank" rel="noopener">GitHub<small>PyNix-Pro</small></a>
|
|
<a class="pill rv" href="https://discord.com/invite/5uWhAnDWzP" target="_blank" rel="noopener">Discord<small>Join the server</small></a>
|
|
<a class="pill rv" href="mailto:[email protected]">Email<small>[email protected]</small></a>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
<footer>© 2026 PyNix. <a href="#">Back to top</a></footer>
|
|
|
|
<script>
|
|
(()=>{
|
|
const $=s=>document.querySelectorAll(s),
|
|
rm=matchMedia('(prefers-reduced-motion:reduce)').matches,
|
|
fine=matchMedia('(hover:hover) and (pointer:fine)').matches;
|
|
|
|
/* split headings into letters for the scramble effect */
|
|
$('[data-s],[data-r]').forEach(e=>{e.setAttribute('aria-label',e.textContent);
|
|
e.innerHTML=[...e.textContent].map(c=>c==' '?' ':`<i aria-hidden="true" data-c="${c}">${c}</i>`).join('')});
|
|
/* decrypt: letters cycle through random characters, slow down like a slot machine, then lock in with a glow pop */
|
|
const POOL='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789#%&*+<>/';
|
|
function dec(e,fast){if(rm||e._r)return;e._r=1;
|
|
const L=[...e.querySelectorAll('i')],t0=performance.now(),m=fast?.6:1,
|
|
lock=L.map((_,j)=>(300+j*45+Math.random()*250)*m),nx=L.map(()=>0);
|
|
L.forEach(c=>{c.style.width=c.getBoundingClientRect().width+'px';c.style.opacity=1;c.classList.add('u')});
|
|
(function f(t){const el=t-t0;let done=1;
|
|
L.forEach((c,j)=>{
|
|
if(el>=lock[j]){if(c.classList.contains('u')){c.textContent=c.dataset.c;c.style.width='';c.classList.remove('u');c.classList.add('lk');setTimeout(()=>c.classList.remove('lk'),380)}}
|
|
else{done=0;if(t>=nx[j]){c.textContent=POOL[Math.random()*POOL.length|0];nx[j]=t+35+Math.max(0,220-(lock[j]-el))*.45}}});
|
|
if(done)e._r=0;else requestAnimationFrame(f)})(t0)}
|
|
$('[data-s]').forEach(e=>e.addEventListener('pointerenter',()=>dec(e,1)));
|
|
|
|
/* reveal on scroll, staggered by position in its row */
|
|
$('.rv').forEach(e=>e.dataset.i=[...e.parentNode.children].indexOf(e)%5);
|
|
const io=new IntersectionObserver(es=>es.forEach(x=>{if(!x.isIntersecting)return;
|
|
const e=x.target;io.unobserve(e);
|
|
setTimeout(()=>{e.classList.add('in');if('s' in e.dataset)dec(e)},(e.dataset.i||0)*70)}),{threshold:.2});
|
|
$('.rv,[data-s]').forEach(e=>io.observe(e));
|
|
|
|
/* scroll progress */
|
|
const bar=document.getElementById('bar');
|
|
addEventListener('scroll',()=>{bar.style.transform=`scaleX(${scrollY/Math.max(1,document.documentElement.scrollHeight-innerHeight)})`},{passive:true});
|
|
const names={top:'PYNIX',about:'About',skills:'Skills',work:'Work',journey:'Journey',contact:'Contact'},
|
|
secs=[...$('main section[id]')],N=secs.length,R=document.getElementById('rail');
|
|
R.innerHTML=secs.map(x=>{const n=names[x.id]||x.id,m=(n.length-1)/2;
|
|
return `<a href="#${x.id}" class="rt">${[...n].map((c,j)=>`<i style="--j:${j};--d:${(10+Math.sin(j*1.3)*4).toFixed(1)}px">${c}</i>`).join('')}</a>`}).join('')
|
|
+'<div class="ln"><span class="mk"><em>0%</em></span></div>';
|
|
const labs=[...R.querySelectorAll('.rt')],ln=R.querySelector('.ln'),mk=R.querySelector('.mk'),pc=R.querySelector('em'),
|
|
sr=15,pt=74,bh=28,cl=(v,a=0,b=1)=>Math.max(a,Math.min(b,v));
|
|
let cur=-1,rq=0,raf=0,lt=0;
|
|
/* springs: crisp with a little overshoot; the "next" label is stiff so it tracks the scroll exactly */
|
|
function loop(t){
|
|
const dt=Math.min(.03,(t-lt)/1e3||.016);lt=t;let busy=0;
|
|
labs.forEach(a=>{const S=a._s,T=a._t,kk=a._p[0],cc=a._p[1];if(rm)Object.assign(S,T);
|
|
for(const n of['x','y','r','s']){const d=T[n]-S[n],v=S['v'+n]||0,w=v+(kk*d-cc*v)*dt;S['v'+n]=w;S[n]+=w*dt;if(Math.abs(d)>.03||Math.abs(w)>.03)busy=1}
|
|
S.r=Math.min(0,S.r);a.style.transform=`translate(${S.x}px,${S.y}px) rotate(${S.r}deg) scale(${S.s})`});
|
|
raf=busy?requestAnimationFrame(loop):0}
|
|
function place(){
|
|
if(getComputedStyle(R).display=='none')return;
|
|
const vh=innerHeight,sy=scrollY,y=sy+vh*.5,dh=document.documentElement.scrollHeight-vh;
|
|
let k=0;secs.forEach((x,i)=>{if(y>=x.offsetTop)k=i});
|
|
/* p = how far through the current section you are: 0 at its start, 1 right when the next one takes over */
|
|
const s0=Math.max(0,secs[k].offsetTop-vh*.5),s1=k<N-1?secs[k+1].offsetTop-vh*.5:s0+1,
|
|
p=k<N-1?cl((sy-s0)/Math.max(1,s1-s0)):0,e=cl(p/.2);
|
|
labs.forEach((a,i)=>{const h=a._h||28,bot=vh-52-(N-i)*sr;let T={x:0,y:bot,r:0,s:.4},f=0,sp=[260,32];
|
|
if(i<k)T.y=pt+i*sr;
|
|
else if(i==k){T.y=pt+k*sr;T.s=1}
|
|
else if(i==k+1){const y1=pt+k*sr+bh+16;T={x:-h*.45*e,y:bot+(y1-bot)*p,r:-90*e,s:.4+.05*e};sp=[900,60]}
|
|
a._t=T;a._p=sp;if(!a._s)a._s={...T};
|
|
a.style.setProperty('--f',f.toFixed(2));a.classList.toggle('on',i==k)});
|
|
if(k!=cur){const first=cur<0,L=labs[k];cur=k;
|
|
if(!first){L.classList.add('snap');L.style.setProperty('--f',1);L.offsetWidth;L.classList.remove('snap');L.style.setProperty('--f',0);
|
|
if(!rm)L.animate([{textShadow:'0 0 0 rgba(156,194,238,0)'},{textShadow:'0 0 28px rgba(156,194,238,1)',offset:.3},{textShadow:'0 0 0 rgba(156,194,238,0)'}],{duration:900,easing:'ease-out'})}
|
|
$('#nav div a').forEach(a=>a.classList.toggle('on',a.hash=='#'+secs[k].id))}
|
|
const pr=dh>0?cl(sy/dh):0;mk.style.transform=`translateY(${pr*ln.offsetHeight}px)`;pc.textContent=Math.round(pr*100)+'%';
|
|
if(!raf){lt=performance.now();raf=requestAnimationFrame(loop)}}
|
|
function meas(){labs.forEach(a=>a._h=a.offsetHeight);place()}
|
|
addEventListener('scroll',()=>{if(!rq){rq=1;requestAnimationFrame(()=>{rq=0;place()})}},{passive:true});
|
|
addEventListener('resize',meas);
|
|
document.fonts.ready.then(meas);
|
|
meas();
|
|
document.addEventListener('visibilitychange',()=>document.body.classList.toggle('pz',document.hidden));
|
|
|
|
if(fine&&!rm){
|
|
/* cursor glow + logo parallax, one rAF-throttled handler */
|
|
const c=document.getElementById('cur'),L=document.getElementById('logo'),k=v=>Math.max(-1,Math.min(1,v));
|
|
let x=0,y=0,q=0;
|
|
addEventListener('pointermove',e=>{x=e.clientX;y=e.clientY;
|
|
if(!q)q=requestAnimationFrame(()=>{q=0;c.style.transform=`translate(${x}px,${y}px)`;
|
|
const r=L.getBoundingClientRect();
|
|
L.style.setProperty('--px',k((x-r.left)/r.width-.5).toFixed(3));
|
|
L.style.setProperty('--py',k((y-r.top)/r.height-.5).toFixed(3))})},{passive:true});
|
|
/* card tilt + spotlight */
|
|
$('.card').forEach(e=>{
|
|
e.addEventListener('pointermove',v=>{const r=e.getBoundingClientRect(),a=(v.clientX-r.left)/r.width,b=(v.clientY-r.top)/r.height;
|
|
e.style.setProperty('--mx',a*100+'%');e.style.setProperty('--my',b*100+'%');
|
|
e.style.transform=`perspective(800px) rotateX(${(.5-b)*7}deg) rotateY(${(a-.5)*7}deg) translateY(-4px)`});
|
|
e.addEventListener('pointerleave',()=>e.style.transform='')});
|
|
/* magnetic buttons */
|
|
$('.pill,.btn').forEach(e=>{
|
|
e.addEventListener('pointermove',v=>{const r=e.getBoundingClientRect();
|
|
e.style.transform=`translate(${(v.clientX-r.left-r.width/2)*.12}px,${(v.clientY-r.top-r.height/2)*.25}px)`});
|
|
e.addEventListener('pointerleave',()=>e.style.transform='')});
|
|
}
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|