Compare commits

...
5 Commits
Author SHA1 Message Date
ayushya c8f7e04f07 Merge pull request 'Noo site mobile only' (#2) from noo-site-mobile-only into main
Deploy noo (noo.ayushya.dev) / deploy (push) Successful in 15s
Reviewed-on: #2
2026-10-05 22:53:37 -04:00
Ayushya AmitabhandClaude Sonnet 5.5 e156712304 noo site: add Android and Google Play icons to the early-testing buttons
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-05 22:52:41 -04:00
Ayushya AmitabhandClaude Sonnet 5.5 d789ff1700 noo site: mention only iOS and Android (phone and tablet)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-05 22:47:14 -04:00
ayushya 1ac6774670 Merge pull request 'Rework portfolio as a bento grid' (#1) from portfolio-bento-grid into main
Deploy portfolio (ayushya.dev) / deploy (push) Successful in 15s
Reviewed-on: #1
2026-10-05 22:40:56 -04:00
Ayushya AmitabhandClaude Sonnet 5.5 99b4151fa6 Rework portfolio as a bento grid
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-05 22:34:08 -04:00
3 changed files with 233 additions and 404 deletions
+2
View File
@@ -78,4 +78,6 @@
<symbol id="i-wallpaper" viewBox="0 0 24 24"><circle cx="8" cy="9" r="2"/><path d="M20 16 15.29 11.3a2 2 0 0 0-2.83 0L5 19"/><rect x="3" y="3" width="18" height="18" rx="2"/></symbol> <symbol id="i-wallpaper" viewBox="0 0 24 24"><circle cx="8" cy="9" r="2"/><path d="M20 16 15.29 11.3a2 2 0 0 0-2.83 0L5 19"/><rect x="3" y="3" width="18" height="18" rx="2"/></symbol>
<symbol id="i-bell" viewBox="0 0 24 24"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/></symbol> <symbol id="i-bell" viewBox="0 0 24 24"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/></symbol>
<symbol id="i-mail" viewBox="0 0 24 24"><rect width="20" height="16" x="2" y="4" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/></symbol> <symbol id="i-mail" viewBox="0 0 24 24"><rect width="20" height="16" x="2" y="4" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/></symbol>
<symbol id="i-android" viewBox="0 0 24 24"><path fill="currentColor" stroke="none" fill-rule="evenodd" d="M4.5 17a7.5 7.5 0 0 1 15 0Zm4.1-3.6a.95.95 0 1 0 1.9 0 .95.95 0 1 0-1.9 0Zm4.9 0a.95.95 0 1 0 1.9 0 .95.95 0 1 0-1.9 0Z"/><path fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" d="M8.2 9.4 6.7 6.8M15.8 9.4l1.5-2.6"/></symbol>
<symbol id="i-google-play" viewBox="0 0 24 24"><path fill="#00A0FF" stroke="none" d="M4 2 13 12 4 22Z"/><path fill="#00E076" stroke="none" d="M4 2 17.2 10 13 12Z"/><path fill="#FF3A44" stroke="none" d="M4 22 17.2 14 13 12Z"/><path fill="#FFD400" stroke="none" d="M17.2 10 20.5 12 17.2 14 13 12Z"/></symbol>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

+3 -5
View File
@@ -358,14 +358,12 @@
<section class="section section-cta" id="get"> <section class="section section-cta" id="get">
<div class="wrap cta-inner"> <div class="wrap cta-inner">
<h2>Bring your Nextcloud with you.</h2> <h2>Bring your Nextcloud with you.</h2>
<p class="section-sub">Free. Noo is in early testing on Android: join to get the app, then point it at your server and go.</p> <p class="section-sub">Free, for Android and iOS phones and tablets. Noo is in early testing on Android: join to get the app, then point it at your server and go.</p>
<div class="hero-actions cta-actions"> <div class="hero-actions cta-actions">
<a class="btn btn-primary btn-lg" href="https://play.google.com/apps/testing/dev.ayushya.noo" rel="noopener">Join early testing</a> <a class="btn btn-primary btn-lg" href="https://play.google.com/apps/testing/dev.ayushya.noo" rel="noopener"><svg class="i" width="22" height="22" aria-hidden="true"><use href="icons.svg#i-android"/></svg>Join early testing</a>
<a class="btn btn-outline btn-lg" href="https://play.google.com/store/apps/details?id=dev.ayushya.noo" rel="noopener">Already a tester? Open in Google Play</a> <a class="btn btn-outline btn-lg" href="https://play.google.com/store/apps/details?id=dev.ayushya.noo" rel="noopener"><svg class="i" width="22" height="22" aria-hidden="true"><use href="icons.svg#i-google-play"/></svg>Already a tester? Open in Google Play</a>
</div> </div>
<ul class="soon-list" aria-label="Coming soon"> <ul class="soon-list" aria-label="Coming soon">
<li>macOS <span>coming soon</span></li>
<li>Windows <span>coming soon</span></li>
<li>iOS <span>coming soon</span></li> <li>iOS <span>coming soon</span></li>
</ul> </ul>
<p class="oss-note"> <p class="oss-note">
+228 -399
View File
@@ -1,399 +1,228 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Ayushya.dev</title> <title>Ayushya.dev</title>
<link rel="icon" type="image/svg+xml" href="/favicon.svg"> <link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
<style> <style>
:root{ :root{
--bg:#fafaf9; --fg:#141414; --muted:#6b6b68; --accent:#2563eb; --bg:#fafaf9; --fg:#141414; --muted:#6b6b68; --accent:#2563eb;
--border:#141414; --card-bg:#ffffff; --divider:#dcdcd8; --limb:#c7c7c2; --border:#141414; --card-bg:#ffffff; --divider:#dcdcd8; --limb:#c7c7c2;
} --accent-bg:#2563eb; --accent-fg:#ffffff;
@media (prefers-color-scheme: dark){ }
:root{ @media (prefers-color-scheme: dark){
--bg:#0b0d0f; --fg:#f2f2f0; --muted:#9a9a95; --accent:#64a1fb; :root{
--border:#f2f2f0; --card-bg:#141719; --divider:#2b2e31; --limb:#33373b; --bg:#0b0d0f; --fg:#f2f2f0; --muted:#9a9a95; --accent:#64a1fb;
} --border:#f2f2f0; --card-bg:#141719; --divider:#2b2e31; --limb:#33373b;
} --accent-bg:#64a1fb; --accent-fg:#0b0d0f;
*{box-sizing:border-box;} }
html,body{margin:0;padding:0;} }
body{background:var(--bg);color:var(--fg);font-family:'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;} *{box-sizing:border-box;}
a{color:inherit;text-decoration:none;} html,body{margin:0;padding:0;}
h1,h2,h3,p{margin:0;} body{background:var(--bg);color:var(--fg);font-family:'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;}
button{font-family:inherit;} a{color:inherit;text-decoration:none;}
h1,h2,h3,p{margin:0;}
.page{width:100%;min-height:100vh;display:flex;flex-direction:column;}
.layout-grid{display:grid;grid-template-columns:1fr 190px 1fr;gap:48px;max-width:1180px;width:100%;margin:0 auto;padding:72px 32px 56px;align-items:start;} .page{max-width:1120px;margin:0 auto;padding:40px 24px 56px;}
.name-box{border:1px solid var(--border);border-radius:14px;padding:26px 30px;width:100%;} /* Bento grid: 4 columns, 160px rows */
.name-box h1{font-size:26px;font-weight:700;letter-spacing:0.03em;text-align:center;} .bento{display:grid;grid-template-columns:repeat(4,1fr);grid-auto-rows:160px;gap:16px;}
.tile{position:relative;overflow:hidden;background:var(--card-bg);border:1px solid var(--divider);border-radius:20px;padding:22px;display:flex;flex-direction:column;justify-content:space-between;transition:border-color .15s ease,transform .15s ease;}
.left-col{display:flex;flex-direction:column;gap:32px;min-width:0;} a.tile:hover{border-color:var(--border);transform:translateY(-2px);}
.projects-block{display:flex;flex-direction:column;gap:16px;margin-top:48px;} a.tile:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}
.stack-row{display:flex;align-items:center;gap:14px;position:relative;} .label{font-size:11px;letter-spacing:0.14em;color:var(--muted);}
.arrow{font-size:18px;color:var(--accent);align-self:flex-end;transition:transform .15s ease;}
.link-btn{display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--accent);color:var(--accent);border-radius:8px;padding:11px 20px;font-size:13px;font-weight:600;letter-spacing:0.06em;font-family:inherit;background:transparent;cursor:pointer;transition:background .15s ease,color .15s ease;} a.tile:hover .arrow{transform:translate(3px,-3px);}
.link-btn:hover{background:var(--accent);color:var(--card-bg);}
.hero{grid-column:span 2;grid-row:span 2;border-color:var(--border);}
.card-link{display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--accent);color:var(--accent);border-radius:8px;padding:9px 18px;font-size:12px;font-weight:700;letter-spacing:0.06em;transition:background .15s ease,color .15s ease;align-self:flex-start;} .hero h1{font-size:clamp(28px,4.4vw,48px);font-weight:700;letter-spacing:0.02em;line-height:1.05;}
.card-link:hover{background:var(--accent);color:var(--card-bg);} .hero h1 span{color:var(--accent);}
.hero p.tagline{font-size:13px;line-height:1.6;color:var(--muted);max-width:34ch;}
.nav-btn{width:40px;height:40px;min-width:40px;min-height:40px;border-radius:50%;border:1px solid var(--border);background:var(--card-bg);color:var(--fg);display:flex;align-items:center;justify-content:center;cursor:pointer;padding:0;transition:background .15s ease,color .15s ease;}
.nav-btn:hover{background:var(--fg);color:var(--bg);} .robot-tile{grid-column:span 1;grid-row:span 2;align-items:center;justify-content:center;}
.nav-btn:hover .tri{border-bottom-color:var(--bg);} .project{grid-column:span 1;grid-row:span 1;gap:10px;justify-content:flex-start;}
.nav-btn:hover .tri-down{border-top-color:var(--bg);} .project.wide{grid-column:span 2;}
.nav-btn[data-disabled="true"]{opacity:0.32;pointer-events:none;} .avatar{width:40px;height:40px;border-radius:50%;border:1px solid var(--accent);display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:var(--accent);flex-shrink:0;}
.tri{width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:8px solid var(--accent);transition:border-color .15s ease;} .project h3{font-size:13px;font-weight:700;letter-spacing:0.04em;}
.tri-down{width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;border-top:8px solid var(--accent);transition:border-color .15s ease;} .project p.desc{font-size:12px;line-height:1.5;color:var(--muted);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;}
.nav-col{position:absolute;right:-58px;top:50%;transform:translateY(-50%);display:flex;flex-direction:column;gap:10px;flex-shrink:0;} .project .arrow{position:absolute;top:18px;right:20px;}
.stack-scroll{scrollbar-width:none;-ms-overflow-style:none;} .social{align-items:flex-start;}
.stack-scroll::-webkit-scrollbar{display:none;width:0;height:0;} .social .name{font-size:15px;font-weight:700;letter-spacing:0.06em;}
.stack-card-top{display:flex;flex-direction:column;gap:14px;} .accent-tile{background:var(--accent-bg);color:var(--accent-fg);border-color:var(--accent-bg);}
.avatar{width:52px;height:52px;border-radius:50%;border:1px solid var(--accent);display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:700;color:var(--accent);flex-shrink:0;} .accent-tile .label{color:inherit;opacity:.75;}
.card-text{display:flex;flex-direction:column;gap:6px;} .accent-tile .arrow{color:inherit;}
.card-text h3{font-size:15px;font-weight:700;letter-spacing:0.02em;} .email{grid-column:span 2;}
.desc-clamp{display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;font-size:13px;line-height:1.5;color:var(--muted);} .email .addr{font-size:clamp(14px,2.2vw,20px);font-weight:700;letter-spacing:0.04em;word-break:break-all;}
.projects-label{text-align:center;font-size:11px;letter-spacing:0.14em;color:var(--muted);} .terminal{grid-column:span 2;font-size:12px;color:var(--muted);}
.marquee-wrap{overflow:hidden;-webkit-mask-image:linear-gradient(90deg,transparent,#000 10%,#000 90%,transparent);mask-image:linear-gradient(90deg,transparent,#000 10%,#000 90%,transparent);letter-spacing:0.08em;}
.mid-col{display:flex;flex-direction:column;align-items:stretch;gap:16px;height:100%;align-self:stretch;} .marquee-track{display:inline-block;white-space:nowrap;animation:scrollLeft 20s linear infinite;}
.divider-v{width:1px;flex:1 1 auto;background:var(--divider);margin:8px auto;} @keyframes scrollLeft{from{transform:translateX(0);}to{transform:translateX(-50%);}}
.email-link{font-size:12px;letter-spacing:0.06em;color:var(--muted);text-align:center;} .prompt{color:var(--fg);}
.prompt b{color:var(--accent);}
.robot-col{display:flex;flex-direction:column;align-items:center;gap:28px;padding-top:6px;} .cursor{display:inline-block;width:8px;height:14px;background:var(--accent);vertical-align:-2px;animation:blink 1s steps(1) infinite;}
.marquee-wrap{overflow:hidden;-webkit-mask-image:linear-gradient(90deg,transparent,#000 12%,#000 88%,transparent);mask-image:linear-gradient(90deg,transparent,#000 12%,#000 88%,transparent);width:100%;max-width:260px;font-size:12px;letter-spacing:0.08em;color:var(--muted);} @keyframes blink{50%{opacity:0;}}
.marquee-track{display:inline-block;white-space:nowrap;animation:scrollLeft 16s linear infinite;}
@keyframes scrollLeft{from{transform:translateX(0);}to{transform:translateX(-50%);}} /* Robot (scaled to fit the tile) */
.robot{display:flex;flex-direction:column;align-items:center;transform:scale(.62);transform-origin:center;}
.robot{display:flex;flex-direction:column;align-items:center;} .antenna-wire{width:2px;height:20px;background:var(--fg);}
.antenna-wire{width:2px;height:20px;background:var(--fg);} .antenna-seg-1{width:2px;height:12px;background:var(--fg);transform:rotate(28deg) translateX(3px);}
.antenna-seg-1{width:2px;height:12px;background:var(--fg);transform:rotate(28deg) translateX(3px);} .antenna-seg-2{width:2px;height:12px;background:var(--fg);transform:rotate(-28deg) translateX(-3px);margin-top:-2px;}
.antenna-seg-2{width:2px;height:12px;background:var(--fg);transform:rotate(-28deg) translateX(-3px);margin-top:-2px;} .antenna-tip{width:10px;height:10px;border-radius:50%;background:var(--fg);margin-top:2px;}
.antenna-tip{width:10px;height:10px;border-radius:50%;background:var(--fg);margin-top:2px;} .head{width:140px;height:118px;margin-top:14px;border:2px solid var(--fg);border-radius:30px;background:var(--card-bg);display:flex;align-items:center;justify-content:center;gap:20px;}
.head{width:140px;height:118px;margin-top:14px;border:2px solid var(--fg);border-radius:30px;background:var(--card-bg);display:flex;align-items:center;justify-content:center;gap:20px;} .eye-ball{width:34px;height:34px;border-radius:50%;background:var(--card-bg);border:2px solid var(--fg);position:relative;flex-shrink:0;}
.eye-ball{width:34px;height:34px;border-radius:50%;background:var(--card-bg);border:2px solid var(--fg);position:relative;flex-shrink:0;} .pupil{position:absolute;top:50%;left:50%;width:13px;height:13px;border-radius:50%;background:var(--fg);margin-top:-6.5px;margin-left:-6.5px;transition:transform .06s linear;}
.pupil{position:absolute;top:50%;left:50%;width:13px;height:13px;border-radius:50%;background:var(--fg);margin-top:-6.5px;margin-left:-6.5px;transition:transform .06s linear;} .neck-gap{height:16px;}
.neck-gap{height:16px;} .body-row{display:flex;align-items:flex-start;gap:10px;}
.body-row{display:flex;align-items:flex-start;gap:10px;} .arm{width:16px;height:118px;border-radius:10px;background:var(--limb);margin-top:10px;}
.arm{width:16px;height:118px;border-radius:10px;background:var(--limb);margin-top:10px;} .torso{width:150px;height:150px;border:2px solid var(--fg);border-radius:26px;background:var(--card-bg);}
.torso{width:150px;height:150px;border:2px solid var(--fg);border-radius:26px;background:var(--card-bg);} .leg-gap{height:10px;}
.leg-gap{height:10px;} .legs-row{display:flex;gap:26px;}
.legs-row{display:flex;gap:26px;} .leg{width:20px;height:78px;border-radius:10px;background:var(--limb);}
.leg{width:20px;height:78px;border-radius:10px;background:var(--limb);}
@media (max-width: 880px){
@media (max-width: 880px){ .bento{grid-template-columns:repeat(2,1fr);}
.layout-grid{grid-template-columns:1fr !important;} .hero{grid-column:span 2;grid-row:span 1;}
.divider-v{display:none !important;} .robot-tile{grid-column:span 1;grid-row:span 2;}
.robot-col{padding-top:0 !important;} .project.wide{grid-column:span 2;}
.stack-row{flex-direction:column;align-items:stretch;} }
.nav-col{flex-direction:row !important;justify-content:center;position:static;transform:none;right:auto;top:auto;} @media (max-width: 520px){
.tri{transform:rotate(-90deg);} .page{padding:24px 16px 40px;}
.tri-down{transform:rotate(-90deg);} .bento{grid-auto-rows:minmax(150px,auto);gap:12px;}
} .robot-tile{grid-column:span 2;grid-row:span 1;min-height:260px;}
</style> .project,.project.wide,.social,.email,.terminal{grid-column:span 2;}
</head> .social{grid-column:span 1;}
<body> }
<div class="page"> @media (prefers-reduced-motion: reduce){
<div class="layout-grid"> .marquee-track,.cursor{animation:none;}
a.tile,.arrow{transition:none;}
<div class="left-col"> }
<div class="name-box"><h1>AYUSHYA.DEV</h1></div> </style>
</head>
<div class="projects-block"> <body>
<div class="stack-row"> <main class="page">
<div class="stack-scroll" id="stackScroll"> <div class="bento">
<div class="spacer" id="spacer" style="position:relative;">
<div class="layer" id="layer" style="position:absolute;top:0;left:0;"> <section class="tile hero">
<!-- cards injected by JS --> <p class="label">PORTFOLIO</p>
</div> <div>
</div> <h1>AYUSHYA<span>.DEV</span></h1>
</div> </div>
<p class="tagline">Building apps, tools, and small corners of the web.</p>
<div class="nav-col"> </section>
<button class="nav-btn" id="prevBtn" aria-label="Show previous project" type="button">
<span class="tri"></span> <div class="tile robot-tile" aria-hidden="true">
</button> <div class="robot">
<button class="nav-btn" id="nextBtn" aria-label="Show next project" type="button"> <div class="antenna-wire"></div>
<span class="tri-down"></span> <div class="antenna-seg-1"></div>
</button> <div class="antenna-seg-2"></div>
</div> <div class="antenna-tip"></div>
</div> <div class="head">
<div class="eye-ball" id="eyeLeft"><div class="pupil" id="pupilLeft"></div></div>
<p class="projects-label">PROJECTS</p> <div class="eye-ball" id="eyeRight"><div class="pupil" id="pupilRight"></div></div>
</div> </div>
</div> <div class="neck-gap"></div>
<div class="body-row">
<div class="mid-col"> <div class="arm"></div>
<a class="link-btn" href="https://github.com/ayushyamitabh/" target="_blank" rel="noopener noreferrer">GITHUB</a> <div class="torso"></div>
<a class="link-btn" href="https://linkedin.com/in/ayushyamitabh" target="_blank" rel="noopener noreferrer">LINKEDIN</a> <div class="arm"></div>
<div class="divider-v"></div> </div>
<a class="email-link" href="mailto:hello@ayushya.dev">HELLO@AYUSHYA.DEV</a> <div class="leg-gap"></div>
</div> <div class="legs-row">
<div class="leg"></div>
<div class="robot-col"> <div class="leg"></div>
<div class="marquee-wrap"> </div>
<span class="marquee-track" id="marquee"></span> </div>
</div> </div>
<div class="robot"> <a class="tile social accent-tile" href="https://github.com/ayushyamitabh/" target="_blank" rel="noopener noreferrer">
<div class="antenna-wire"></div> <span class="label">CODE</span>
<div class="antenna-seg-1"></div> <span class="name">GITHUB</span>
<div class="antenna-seg-2"></div> <span class="arrow">&#8599;</span>
<div class="antenna-tip"></div> </a>
<div class="head"> <a class="tile social" href="https://linkedin.com/in/ayushyamitabh" target="_blank" rel="noopener noreferrer">
<div class="eye-ball" id="eyeLeft"><div class="pupil" id="pupilLeft"></div></div> <span class="label">NETWORK</span>
<div class="eye-ball" id="eyeRight"><div class="pupil" id="pupilRight"></div></div> <span class="name">LINKEDIN</span>
</div> <span class="arrow">&#8599;</span>
</a>
<div class="neck-gap"></div>
<a class="tile project wide" href="https://noo.ayushya.dev" target="_blank" rel="noopener noreferrer">
<div class="body-row"> <div class="avatar">N</div>
<div class="arm"></div> <h3>NOO</h3>
<div class="torso"></div> <p class="desc">A fast, native Nextcloud client built with Flutter and Material You &mdash; files, photos, activity, shares, and trash in one themed app.</p>
<div class="arm"></div> <span class="arrow">&#8599;</span>
</div> </a>
<div class="leg-gap"></div> <a class="tile project" href="https://watchparty-archive.web.app/" target="_blank" rel="noopener noreferrer">
<div class="avatar">WP</div>
<div class="legs-row"> <h3>CHROME WATCH PARTY</h3>
<div class="leg"></div> <p class="desc">Syncs streamed video across devices, with chat, GIFs, and free group calls.</p>
<div class="leg"></div> <span class="arrow">&#8599;</span>
</div> </a>
</div>
</div> <a class="tile project" href="https://vaxandme-archive.web.app/" target="_blank" rel="noopener noreferrer">
<div class="avatar">V&amp;M</div>
</div> <h3>VAX &amp; ME</h3>
</div> <p class="desc">Explains expected COVID-19 vaccine side effects, built from FDA data.</p>
<span class="arrow">&#8599;</span>
<script> </a>
(function () {
var PROJECTS = [ <div class="tile terminal" aria-hidden="true">
{ initials: 'N', name: 'NOO', desc: 'A fast, native Nextcloud client built with Flutter and Material You \u2014 files, photos, activity, shares, and trash in one themed app.', link: 'https://noo.ayushya.dev' }, <p class="label">STATUS</p>
{ initials: 'WP', name: 'CHROME WATCH PARTY', desc: 'Syncs streamed video across devices so you can watch with your favorite people, with chat, GIFs, and free group calls.', link: 'https://watchparty-archive.web.app/' }, <div class="marquee-wrap"><span class="marquee-track" id="marquee"></span></div>
{ initials: 'V&M', name: 'VAX & ME', desc: 'Explains the expected side effects of COVID-19 vaccines, built from FDA data on Pfizer, Moderna, and J&J.', link: 'https://vaxandme-archive.web.app/' }, <p class="prompt"><b>&gt;</b> hello, world <span class="cursor"></span></p>
]; </div>
var COUNT = PROJECTS.length; <a class="tile email accent-tile" href="mailto:hello@ayushya.dev">
var PEEK_SCALE = 0.9; <span class="label">SAY HELLO</span>
var MOBILE_BREAKPOINT = 880; <span class="addr">HELLO@AYUSHYA.DEV</span>
var MOBILE_CONTAINER_HEIGHT = 260; <span class="arrow">&#8599;</span>
var DESKTOP_STEP = 160; </a>
var DESKTOP_VIEW = 318;
var DESKTOP_CARD = 250; </div>
</main>
var stackScroll = document.getElementById('stackScroll');
var spacer = document.getElementById('spacer'); <script>
var layer = document.getElementById('layer'); (function () {
var prevBtn = document.getElementById('prevBtn'); var binary = (function (len) {
var nextBtn = document.getElementById('nextBtn'); var out = '';
for (var i = 0; i < len; i++) {
var cardEls = []; out += Math.random() < 0.5 ? '0' : '1';
var isMobile = window.matchMedia('(max-width: ' + MOBILE_BREAKPOINT + 'px)').matches; if (i % 4 === 3) out += ' ';
var containerWidth = 320; }
var scrollPos = 0; return out;
})(80);
function escapeHtml(str) { document.getElementById('marquee').textContent = binary + ' ' + binary;
var div = document.createElement('div');
div.textContent = str; var pairs = [
return div.innerHTML; [document.getElementById('eyeLeft'), document.getElementById('pupilLeft')],
} [document.getElementById('eyeRight'), document.getElementById('pupilRight')]
];
function buildCards() { var MAX_OFFSET = 8;
PROJECTS.forEach(function (proj) { var rafPending = false;
var card = document.createElement('div');
card.innerHTML = function updatePupils(clientX, clientY) {
'<div class="stack-card-top">' + pairs.forEach(function (pair) {
'<div class="avatar">' + escapeHtml(proj.initials) + '</div>' + var rect = pair[0].getBoundingClientRect();
'<div class="card-text">' + var cx = rect.left + rect.width / 2;
'<h3>' + escapeHtml(proj.name) + '</h3>' + var cy = rect.top + rect.height / 2;
'<p class="desc-clamp">' + escapeHtml(proj.desc) + '</p>' + var angle = Math.atan2(clientY - cy, clientX - cx);
'</div>' + pair[1].style.transform = 'translate(' + (Math.cos(angle) * MAX_OFFSET) + 'px, ' + (Math.sin(angle) * MAX_OFFSET) + 'px)';
'</div>' + });
'<a class="card-link" href="' + proj.link + '" target="_blank" rel="noopener noreferrer">LINK</a>'; }
layer.appendChild(card);
cardEls.push(card); window.addEventListener('mousemove', function (e) {
}); if (rafPending) return;
} rafPending = true;
var x = e.clientX, y = e.clientY;
function getGeometry() { requestAnimationFrame(function () { rafPending = false; updatePupils(x, y); });
if (isMobile) { });
var cw = containerWidth; })();
var peek = Math.max(16, Math.round(cw * 0.1)); </script>
var cardSize = Math.round(Math.max(cw - peek * 2, 120)); </body>
var step = Math.round(cardSize * 0.64); </html>
return { axisProp: 'left', sizeProp: 'width', crossSizeProp: 'height', peek: peek, cardSize: cardSize, step: step, viewSize: cw };
}
return { axisProp: 'top', sizeProp: 'height', crossSizeProp: 'width', peek: 34, cardSize: DESKTOP_CARD, step: DESKTOP_STEP, viewSize: DESKTOP_VIEW };
}
function lerp(a, b, t) { return a + (b - a) * t; }
var EPS = 1.05;
function render() {
var g = getGeometry();
var maxScroll = (COUNT - 1) * g.step;
var clampedPos = Math.max(0, Math.min(scrollPos, maxScroll));
var progress = clampedPos / g.step;
if (isMobile) {
stackScroll.style.cssText = 'position:relative;height:' + MOBILE_CONTAINER_HEIGHT + 'px;width:100%;overflow-x:auto;overflow-y:hidden;min-width:0;-webkit-overflow-scrolling:touch;';
spacer.style.cssText = 'position:relative;width:' + (g.viewSize + maxScroll) + 'px;height:100%;';
layer.style.cssText = 'position:absolute;top:0;left:0;width:' + g.viewSize + 'px;height:100%;transform:translateX(' + clampedPos + 'px);';
} else {
stackScroll.style.cssText = 'position:relative;height:' + g.viewSize + 'px;flex:1 1 auto;min-width:0;overflow-y:auto;overflow-x:hidden;';
spacer.style.cssText = 'position:relative;height:' + (g.viewSize + maxScroll) + 'px;width:100%;';
layer.style.cssText = 'position:absolute;top:0;left:0;width:100%;height:' + g.viewSize + 'px;transform:translateY(' + clampedPos + 'px);';
}
for (var i = 0; i < COUNT; i++) {
var delta = i - progress;
var pos, scale, opacity;
if (delta < -EPS || delta > EPS) {
pos = g.peek; scale = PEEK_SCALE; opacity = 0;
} else if (delta <= 0) {
var t1 = Math.min(-delta, 1);
pos = lerp(g.peek, 0, t1);
scale = lerp(1, PEEK_SCALE, t1);
opacity = lerp(1, 0.55, t1);
} else {
var t2 = Math.min(delta, 1);
pos = lerp(g.peek, g.peek * 2, t2);
scale = lerp(1, PEEK_SCALE, t2);
opacity = lerp(1, 0.55, t2);
}
var zIndex = Math.round((1 - Math.min(Math.abs(delta), 1)) * 100);
var pointerEvents = Math.abs(delta) < 0.05 ? 'auto' : 'none';
var otherAxis = isMobile ? 'top:0;' : 'left:0;';
var sizeStyle = g.sizeProp + ':' + g.cardSize + 'px; ' + g.crossSizeProp + ':100%;';
cardEls[i].style.cssText =
'position:absolute; ' + g.axisProp + ':' + pos + 'px; ' + otherAxis + ' ' + sizeStyle +
' z-index:' + zIndex + '; opacity:' + opacity + '; pointer-events:' + pointerEvents + '; transform: scale(' + scale + ');' +
' padding:24px; background:var(--card-bg); border:1px solid var(--border); border-radius:16px; display:flex; flex-direction:column; justify-content:space-between; gap:14px; box-shadow:0 10px 24px rgba(0,0,0,0.12);';
}
var activeIndex = Math.round(progress);
prevBtn.setAttribute('data-disabled', activeIndex <= 0 ? 'true' : 'false');
nextBtn.setAttribute('data-disabled', activeIndex >= COUNT - 1 ? 'true' : 'false');
}
function getStep() {
return getGeometry().step;
}
function nextCard() {
var step = getStep();
var active = Math.round(scrollPos / step);
var target = Math.min(active + 1, COUNT - 1) * step;
if (isMobile) stackScroll.scrollTo({ left: target, behavior: 'smooth' });
else stackScroll.scrollTo({ top: target, behavior: 'smooth' });
}
function prevCard() {
var step = getStep();
var active = Math.round(scrollPos / step);
var target = Math.max(active - 1, 0) * step;
if (isMobile) stackScroll.scrollTo({ left: target, behavior: 'smooth' });
else stackScroll.scrollTo({ top: target, behavior: 'smooth' });
}
function measure() {
var rect = stackScroll.getBoundingClientRect();
if (rect.width > 0) containerWidth = rect.width;
render();
}
buildCards();
measure();
prevBtn.addEventListener('click', prevCard);
nextBtn.addEventListener('click', nextCard);
var scrollRafPending = false;
var snapTimer = null;
function snapToNearest() {
var step = getStep();
var maxIndex = COUNT - 1;
var nearest = Math.max(0, Math.min(Math.round(scrollPos / step), maxIndex));
var target = nearest * step;
if (Math.abs(scrollPos - target) > 1) {
if (isMobile) stackScroll.scrollTo({ left: target, behavior: 'smooth' });
else stackScroll.scrollTo({ top: target, behavior: 'smooth' });
}
}
stackScroll.addEventListener('scroll', function () {
if (scrollRafPending) return;
scrollRafPending = true;
requestAnimationFrame(function () {
scrollRafPending = false;
scrollPos = isMobile ? stackScroll.scrollLeft : stackScroll.scrollTop;
render();
});
clearTimeout(snapTimer);
snapTimer = setTimeout(snapToNearest, 130);
});
var mq = window.matchMedia('(max-width: ' + MOBILE_BREAKPOINT + 'px)');
function handleMqChange(e) {
stackScroll.scrollTo({ top: 0, left: 0 });
isMobile = e.matches;
scrollPos = 0;
measure();
}
if (mq.addEventListener) mq.addEventListener('change', handleMqChange);
else if (mq.addListener) mq.addListener(handleMqChange);
window.addEventListener('resize', measure);
if (typeof ResizeObserver !== 'undefined') {
new ResizeObserver(measure).observe(stackScroll);
}
var binary = (function (len) {
var out = '';
for (var i = 0; i < len; i++) {
out += Math.random() < 0.5 ? '0' : '1';
if (i % 4 === 3) out += ' ';
}
return out;
})(80);
document.getElementById('marquee').textContent = binary + ' ' + binary;
var eyeLeft = document.getElementById('eyeLeft');
var eyeRight = document.getElementById('eyeRight');
var pupilLeft = document.getElementById('pupilLeft');
var pupilRight = document.getElementById('pupilRight');
var MAX_OFFSET = 8;
var rafPending = false;
function updatePupils(clientX, clientY) {
[[eyeLeft, pupilLeft], [eyeRight, pupilRight]].forEach(function (pair) {
var eye = pair[0], pupil = pair[1];
var rect = eye.getBoundingClientRect();
var cx = rect.left + rect.width / 2;
var cy = rect.top + rect.height / 2;
var angle = Math.atan2(clientY - cy, clientX - cx);
pupil.style.transform = 'translate(' + (Math.cos(angle) * MAX_OFFSET) + 'px, ' + (Math.sin(angle) * MAX_OFFSET) + 'px)';
});
}
window.addEventListener('mousemove', function (e) {
if (rafPending) return;
rafPending = true;
var clientX = e.clientX, clientY = e.clientY;
requestAnimationFrame(function () {
rafPending = false;
updatePupils(clientX, clientY);
});
});
})();
</script>
</body>
</html>