Skip to content

Instantly share code, notes, and snippets.

@aborruso
Created July 6, 2026 17:56
Show Gist options
  • Select an option

  • Save aborruso/7457d994fd4237c96cda03edf801e2a3 to your computer and use it in GitHub Desktop.

Select an option

Save aborruso/7457d994fd4237c96cda03edf801e2a3 to your computer and use it in GitHub Desktop.
<!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">$&nbsp;❯</span>
<button id="run">./auguri.sh &nbsp;▶</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