Created
July 6, 2026 17:56
-
-
Save aborruso/7457d994fd4237c96cda03edf801e2a3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!doctype html> | |
| <html lang="it"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>run auguri.sh</title> | |
| <style> | |
| :root{ | |
| --ink:#120F1E; | |
| --ink-2:#1A1630; | |
| --surface:#1E1938; | |
| --surface-2:#251F44; | |
| --line:#352C5C; | |
| --text:#ECE8FB; | |
| --muted:#9C93C4; | |
| --amber:#FFC23D; | |
| --coral:#FF5C8A; | |
| --cyan:#54E0CE; | |
| --violet:#8B7BFF; | |
| --ok:#54E0CE; | |
| --mono:ui-monospace,SFMono-Regular,"SF Mono","Cascadia Code","Fira Code",Menlo,Consolas,monospace; | |
| --sans:system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif; | |
| } | |
| *{box-sizing:border-box} | |
| html,body{margin:0} | |
| body{ | |
| background: | |
| radial-gradient(900px 500px at 85% -8%, rgba(139,123,255,.22), transparent 60%), | |
| radial-gradient(760px 480px at 8% 108%, rgba(255,92,138,.16), transparent 60%), | |
| var(--ink); | |
| color:var(--text); | |
| font-family:var(--mono); | |
| line-height:1.55; | |
| min-height:100vh; | |
| -webkit-font-smoothing:antialiased; | |
| overflow-x:hidden; | |
| } | |
| .waves{position:fixed;inset:0;z-index:0;pointer-events:none;opacity:.22} | |
| .waves svg{position:absolute;bottom:-4%;left:-5%;width:110%;height:auto} | |
| .wrap{position:relative;z-index:1;max-width:720px;margin:0 auto; | |
| padding:clamp(20px,6vw,60px) clamp(14px,4vw,24px); | |
| min-height:100vh;display:flex;flex-direction:column;justify-content:center} | |
| .term{ | |
| background:linear-gradient(180deg,var(--ink-2),var(--surface)); | |
| border:1px solid var(--line); | |
| border-radius:14px; | |
| box-shadow:0 30px 80px -30px rgba(0,0,0,.8), inset 0 1px 0 rgba(255,255,255,.03); | |
| overflow:hidden; | |
| } | |
| .bar{display:flex;align-items:center;gap:10px;padding:11px 15px; | |
| background:rgba(255,255,255,.02);border-bottom:1px solid var(--line)} | |
| .dot{width:12px;height:12px;border-radius:50%} | |
| .dot.r{background:#FF5F57}.dot.y{background:var(--amber)}.dot.g{background:#28C840} | |
| .bar .path{margin-left:8px;color:var(--muted);font-size:12.5px;letter-spacing:.02em} | |
| .screen{padding:clamp(20px,4vw,34px)} | |
| .boot{color:var(--muted);font-size:13px;min-height:22px;margin-bottom:20px;white-space:pre-wrap} | |
| .boot .g{color:var(--ok)} | |
| .cur{display:inline-block;width:9px;height:16px;background:var(--amber); | |
| vertical-align:-3px;margin-left:2px;animation:blink 1s steps(1) infinite} | |
| @keyframes blink{50%{opacity:0}} | |
| .id{display:flex;gap:18px;align-items:center;flex-wrap:wrap} | |
| .avatar{position:relative;width:72px;height:72px;border-radius:16px;flex:0 0 auto; | |
| border:1px solid var(--line);box-shadow:0 0 0 4px rgba(255,194,61,.10);overflow:hidden} | |
| .avatar img{width:100%;height:100%;object-fit:cover;display:block} | |
| .avatar .fb{position:absolute;inset:0;display:flex;align-items:center;justify-content:center; | |
| font-size:36px;background:linear-gradient(140deg,#2b2450,#3a2f66)} | |
| .id h1{margin:0;font-family:var(--sans);font-weight:800;letter-spacing:-.02em; | |
| font-size:clamp(24px,6vw,36px)} | |
| .id h1 .at{color:var(--amber)} | |
| .id .role{color:var(--muted);font-size:13.5px;margin-top:4px} | |
| .lede{margin:24px 0 4px;font-size:14.5px;color:var(--text)} | |
| .lede .kw{color:var(--coral)}.lede .str{color:var(--amber)}.lede .cm{color:#6a6392} | |
| .runrow{display:flex;align-items:center;gap:10px;margin-top:20px;flex-wrap:wrap} | |
| .runrow .ps{color:var(--cyan);font-weight:700} | |
| button{font-family:var(--mono);cursor:pointer;border-radius:10px;font-size:14px; | |
| padding:13px 22px;border:1px solid transparent;background:var(--amber);color:#241a00; | |
| font-weight:700;transition:transform .12s, background .2s, box-shadow .2s; | |
| box-shadow:0 0 0 rgba(255,194,61,0)} | |
| button:hover{transform:translateY(-1px);background:#ffce5f;box-shadow:0 10px 30px -12px rgba(255,194,61,.7)} | |
| button:active{transform:translateY(0)} | |
| button:focus-visible{outline:2px solid var(--amber);outline-offset:3px} | |
| .btn-ghost{background:transparent;color:var(--muted);border-color:var(--line);font-weight:400} | |
| .btn-ghost:hover{background:var(--surface-2);color:var(--text);box-shadow:none} | |
| .out{margin-top:16px;font-size:13.5px;color:var(--muted);white-space:pre-wrap;min-height:0} | |
| .out .g{color:var(--ok)} | |
| .reveal{animation:rise .45s ease both} | |
| @keyframes rise{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}} | |
| .hidden{display:none!important} | |
| .finale{text-align:center;padding:6px 0 4px} | |
| .cake{font-size:60px;line-height:1;filter:drop-shadow(0 8px 24px rgba(255,92,138,.5)); | |
| animation:pop .6s cubic-bezier(.2,1.4,.4,1) both} | |
| @keyframes pop{from{transform:scale(0) rotate(-18deg);opacity:0}to{transform:none;opacity:1}} | |
| .hb{font-family:var(--sans);font-weight:800;letter-spacing:-.02em;line-height:1.02; | |
| font-size:clamp(32px,9vw,58px);margin:14px 0 6px; | |
| background:linear-gradient(100deg,var(--amber),var(--coral) 55%,var(--violet)); | |
| -webkit-background-clip:text;background-clip:text;color:transparent} | |
| .hb .nm{display:block} | |
| .finale .msg{color:var(--text);font-size:15px;max-width:520px;margin:18px auto 0} | |
| .finale .msg b{color:var(--amber)} | |
| .finale .quote{font-size:clamp(17px,4.6vw,22px);font-style:italic;color:var(--cyan); | |
| letter-spacing:-.01em;position:relative} | |
| .finale .quote::before{content:"“ ";color:var(--violet)} | |
| .finale .quote::after{content:" ”";color:var(--violet)} | |
| /* ---- gift boxes ---- */ | |
| .gifts{display:flex;flex-wrap:wrap;justify-content:center;gap:12px;margin:26px auto 0;max-width:460px} | |
| .gift{position:relative;width:98px;height:70px;display:flex;align-items:center;justify-content:center} | |
| .gift .box{position:absolute;font-size:40px;transition:opacity .3s, transform .3s} | |
| .gift .loot{position:absolute;display:inline-flex;align-items:center;gap:7px; | |
| padding:8px 13px;border-radius:10px;font-size:13px;font-weight:700;white-space:nowrap; | |
| background:var(--surface-2);border:1px solid var(--line);color:var(--text); | |
| opacity:0;transform:scale(.4) translateY(6px)} | |
| .gift .loot .ic{font-size:16px} | |
| .gift.shake .box{animation:wiggle .5s ease-in-out infinite} | |
| @keyframes wiggle{0%,100%{transform:rotate(-7deg) translateY(0)} | |
| 25%{transform:rotate(6deg) translateY(-3px)}50%{transform:rotate(-5deg) translateY(0)} | |
| 75%{transform:rotate(5deg) translateY(-2px)}} | |
| .gift.open .box{opacity:0;transform:scale(1.5) translateY(-14px)} | |
| .gift.open .loot{opacity:1;transform:none; | |
| transition:opacity .35s .05s, transform .45s .05s cubic-bezier(.2,1.5,.4,1)} | |
| .again{margin-top:26px} | |
| canvas#cf{position:fixed;inset:0;z-index:40;pointer-events:none} | |
| @media (prefers-reduced-motion:reduce){ | |
| *{animation-duration:.001ms!important;animation-iteration-count:1!important} | |
| .cur{animation:none} | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="waves" aria-hidden="true"> | |
| <svg viewBox="0 0 1200 240" preserveAspectRatio="none"> | |
| <path d="M0,120 C200,60 400,180 600,120 C800,60 1000,180 1200,120 L1200,240 L0,240 Z" fill="none" stroke="#8B7BFF" stroke-width="2"/> | |
| <path d="M0,150 C200,90 400,210 600,150 C800,90 1000,210 1200,150" fill="none" stroke="#54E0CE" stroke-width="1.5"/> | |
| <path d="M0,90 C200,30 400,150 600,90 C800,30 1000,150 1200,90" fill="none" stroke="#FF5C8A" stroke-width="1.5"/> | |
| </svg> | |
| </div> | |
| <div class="wrap"> | |
| <div class="term" role="application" aria-label="Terminale auguri per Andrea"> | |
| <div class="bar"> | |
| <span class="dot r"></span><span class="dot y"></span><span class="dot g"></span> | |
| <span class="path">andrea@terminal: ~/auguri</span> | |
| </div> | |
| <div class="screen"> | |
| <!-- HERO --> | |
| <section id="hero"> | |
| <div class="boot" id="boot"></div> | |
| <div class="id reveal" id="idcard" style="opacity:0"> | |
| <div class="avatar"> | |
| <img | |
| src="https://scontent.fqsr1-1.fna.fbcdn.net/v/t39.30808-1/483983858_10161364792103163_6798227594491995912_n.jpg?stp=dst-jpg_tt6&cstp=mx364x364&ctp=s200x200&_nc_cat=100&ccb=1-7&_nc_sid=e99d92&_nc_ohc=aZqaPboGNeEQ7kNvwEZ-IKQ&_nc_zt=24&_nc_ht=scontent.fqsr1-1.fna&oh=00_AQAxd_ryPdIoSnfUffgO7lV_-K7LgwZJ35BXy_Y3NVq1-g&oe=6A516598" | |
| alt="Andrea" | |
| referrerpolicy="no-referrer" | |
| onerror="this.style.display='none';this.nextElementSibling.style.display='flex'"> | |
| <div class="fb" style="display:none" aria-hidden="true">🦆</div> | |
| </div> | |
| <div> | |
| <h1><span class="at">@</span>aborruso</h1> | |
| <div class="role">c'è un comando che aspetta solo di essere eseguito…</div> | |
| </div> | |
| </div> | |
| <p class="lede reveal" id="lede" style="opacity:0"><span class="cm"># premi invio sul comando qui sotto 👇</span></p> | |
| <div class="runrow reveal" id="runrow" style="opacity:0"> | |
| <span class="ps">$ ❯</span> | |
| <button id="run">./auguri.sh ▶</button> | |
| </div> | |
| <div class="out" id="out" aria-live="polite"></div> | |
| </section> | |
| <!-- FINALE --> | |
| <section id="finale-sec" class="hidden"> | |
| <div class="finale"> | |
| <div class="cake">🎂🦆🎉</div> | |
| <div class="hb"><span class="nm">Buon compleanno</span><span class="nm">Andrea!</span></div> | |
| <p class="msg quote">il csv è piatto, la terra no.</p> | |
| <div class="gifts" id="gifts"> | |
| <div class="gift"><span class="box">🎁</span><span class="loot"><span class="ic">🦆</span>DuckDB</span></div> | |
| <div class="gift"><span class="box">🎁</span><span class="loot"><span class="ic">🔧</span>Miller · mlr</span></div> | |
| <div class="gift"><span class="box">🎁</span><span class="loot"><span class="ic">🧮</span>jq</span></div> | |
| <div class="gift"><span class="box">🎁</span><span class="loot"><span class="ic">🗺️</span>GDAL</span></div> | |
| <div class="gift"><span class="box">🎁</span><span class="loot"><span class="ic">📄</span>CSV</span></div> | |
| </div> | |
| <p class="msg"><b>Tantissimi auguri, Andrea!</b> 💛</p> | |
| <div class="again"> | |
| <button class="btn-ghost" id="replay">↺ rilancia i coriandoli</button> | |
| </div> | |
| </div> | |
| </section> | |
| </div> | |
| </div> | |
| </div> | |
| <canvas id="cf"></canvas> | |
| <script> | |
| (function(){ | |
| "use strict"; | |
| var $=function(s){return document.querySelector(s)}; | |
| var reduce=window.matchMedia&&matchMedia("(prefers-reduced-motion: reduce)").matches; | |
| /* ---------- boot typing ---------- */ | |
| var bootEl=$("#boot"); | |
| function reveal(id,d){setTimeout(function(){var e=$("#"+id);if(e)e.style.opacity=1;},d);} | |
| var lines=[ | |
| ["> ","whoami","",false], | |
| ["","andrea","",true], | |
| ["> ","load birthday.module …","",false], | |
| ["ok ","modulo pronto. manca solo un run.","",true] | |
| ]; | |
| function typeBoot(cb){ | |
| if(reduce){ | |
| bootEl.innerHTML='> <span class="g">whoami</span>\n<span class="g">andrea</span>\n> load birthday.module …\n<span class="g">ok</span> modulo pronto. manca solo un run.'; | |
| cb();return; | |
| } | |
| var li=0,buf=""; | |
| function nextLine(){ | |
| if(li>=lines.length){cb();return;} | |
| var pre=lines[li][0],txt=lines[li][1],green=lines[li][3]; | |
| var ci=0,prefix=pre?'<span class="g">'+pre+'</span>':''; | |
| (function typ(){ | |
| var shown=txt.slice(0,ci); | |
| var body=green?'<span class="g">'+shown+'</span>':shown; | |
| bootEl.innerHTML=buf+prefix+body+'<span class="cur"></span>'; | |
| ci++; | |
| if(ci<=txt.length){setTimeout(typ,16+Math.random()*24);} | |
| else{buf+=prefix+(green?'<span class="g">'+txt+'</span>':txt)+"\n";li++;setTimeout(nextLine,170);} | |
| })(); | |
| } | |
| nextLine(); | |
| } | |
| typeBoot(function(){ | |
| bootEl.innerHTML=bootEl.innerHTML.replace('<span class="cur"></span>',''); | |
| ["idcard","lede","runrow"].forEach(function(id,i){reveal(id,200+i*150);}); | |
| }); | |
| /* ---------- run ---------- */ | |
| var hero=$("#hero"),finale=$("#finale-sec"),out=$("#out"),runBtn=$("#run"); | |
| runBtn.addEventListener("click",function(){ | |
| runBtn.disabled=true; | |
| out.innerHTML='<span class="g">executing</span> ./auguri.sh …'; | |
| burst(24); | |
| setTimeout(function(){ | |
| hero.classList.add("hidden"); | |
| finale.classList.remove("hidden"); | |
| finale.scrollIntoView({behavior:reduce?"auto":"smooth",block:"center"}); | |
| celebrate(); | |
| openGifts(); | |
| },reduce?0:650); | |
| }); | |
| $("#replay").addEventListener("click",function(){celebrate();openGifts(true);}); | |
| /* ---------- gift boxes auto-open ---------- */ | |
| var gifts=[].slice.call(document.querySelectorAll("#gifts .gift")); | |
| function openGifts(reset){ | |
| gifts.forEach(function(g){ g.classList.remove("open","shake"); }); | |
| if(reduce){ gifts.forEach(function(g){g.classList.add("open");}); return; } | |
| gifts.forEach(function(g,i){ | |
| setTimeout(function(){ g.classList.add("shake"); }, i*160); | |
| setTimeout(function(){ g.classList.remove("shake"); g.classList.add("open"); burst(14); }, i*160+700); | |
| }); | |
| } | |
| /* ---------- confetti ---------- */ | |
| var cv=$("#cf"),ctx=cv.getContext("2d"),parts=[],raf=null,W,H; | |
| function size(){W=cv.width=innerWidth;H=cv.height=innerHeight;} | |
| addEventListener("resize",size);size(); | |
| var COL=["#FFC23D","#FF5C8A","#54E0CE","#8B7BFF","#ffffff"]; | |
| function rnd(a,b){return a+Math.random()*(b-a);} | |
| function burst(n){ | |
| if(reduce)return; | |
| for(var i=0;i<n;i++){ | |
| parts.push({x:rnd(0,W),y:rnd(-40,H*0.35),vx:rnd(-1.2,1.2),vy:rnd(1.5,4.2), | |
| s:rnd(5,11),c:COL[(Math.random()*COL.length)|0],a:rnd(0,6.28),va:rnd(-.2,.2),life:rnd(80,160)}); | |
| } | |
| if(!raf)loop(); | |
| } | |
| function celebrate(){if(reduce)return;for(var k=0;k<5;k++){setTimeout(function(){burst(50);},k*220);}} | |
| function loop(){ | |
| ctx.clearRect(0,0,W,H); | |
| for(var i=parts.length-1;i>=0;i--){ | |
| var p=parts[i]; | |
| p.x+=p.vx;p.y+=p.vy;p.vy+=0.045;p.a+=p.va;p.life--; | |
| ctx.save();ctx.translate(p.x,p.y);ctx.rotate(p.a); | |
| ctx.fillStyle=p.c;ctx.globalAlpha=Math.max(0,Math.min(1,p.life/60)); | |
| ctx.fillRect(-p.s/2,-p.s/2,p.s,p.s*0.6);ctx.restore(); | |
| if(p.life<=0||p.y>H+30)parts.splice(i,1); | |
| } | |
| if(parts.length){raf=requestAnimationFrame(loop);}else{ctx.clearRect(0,0,W,H);raf=null;} | |
| } | |
| })(); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment