holiday-card-wordpress_1.html
<!-- Holiday card: paste into a WordPress Custom HTML block. Set IMG_URL in the script to your Media Library image URL. --><style>.sgb{--ar:1.25;position:relative;box-sizing:border-box;width:min(100%,calc((100vh - 48px) * var(--ar)),1000px);margin:24px auto}@media (orientation:portrait){.sgb{--ar:.85}}.sgb *{box-sizing:border-box}.sgb-stage{position:relative;overflow:hidden;width:100%;aspect-ratio:var(--ar);border-radius:clamp(14px,2.6vw,28px);background:radial-gradient(circle at 30% 22%,#f1f2ee,#a9b6bb);touch-action:pan-y;cursor:crosshair;-webkit-user-select:none;user-select:none;font-family:Georgia,"Times New Roman",serif}.sgb-stage svg{position:absolute;inset:0;width:100%;height:100%;max-width:none;display:block}.sgb-bar{position:absolute;left:0;right:0;height:0;background:#000;pointer-events:none;top:0}.sgb-bar.b{top:auto;bottom:0}#sgb-hint{position:absolute;left:0;right:0;bottom:5%;text-align:center;color:#4b5558;font-size:clamp(12px,1.6vw,15px);letter-spacing:.02em;transition:opacity .5s;pointer-events:none}#sgb-rec{position:absolute;top:4%;left:4%;display:flex;align-items:center;gap:8px;font:600 clamp(11px,1.5vw,14px)/1 ui-monospace,Menlo,Consolas,monospace;color:#1c2124;opacity:0;transition:opacity .3s,color .4s;pointer-events:none;z-index:3}#sgb-rec i{width:.7em;height:.7em;border-radius:50%;background:#e5372f;animation:sgbblink 1s steps(2,jump-none) infinite}@keyframes sgbblink{50%{opacity:.15}}#sgb-title{position:absolute;left:0;right:0;bottom:7.5%;padding:12% 5% 3.5%;color:#fff;background:linear-gradient(transparent,rgba(0,0,0,.6));opacity:0;pointer-events:none;z-index:2}#sgb-titleH{display:block;font-weight:400;font-size:clamp(22px,5vw,44px);line-height:1.1}#sgb-credit{display:block;margin-top:6px;font-size:clamp(11px,1.5vw,14px);opacity:.8;letter-spacing:.03em}#sgb-replay{position:absolute;top:4%;right:4%;z-index:4;opacity:0;pointer-events:none;transition:opacity .4s;background:rgba(0,0,0,.45);color:#fff;border:1px solid rgba(255,255,255,.35);border-radius:999px;padding:6px 14px;font:inherit;font-size:clamp(11px,1.5vw,13px);cursor:pointer}.sgb.done #sgb-replay{opacity:1;pointer-events:auto}@media (prefers-reduced-motion:reduce){#sgb-rec i{animation:none}}</style><div class="sgb" id="sgb-root"><div class="sgb-stage" id="sgb-stage"><svg id="sgb-s" xmlns="http://www.w3.org/2000/svg"> <defs> <radialGradient id="sgb-body" cx="40%" cy="34%" r="85%" fx="40%" fy="34%"> <stop offset="0" stop-color="#3b3e43"/> <stop offset=".5" stop-color="#0e0f11"/> <stop offset="1" stop-color="#020203"/> </radialGradient> <radialGradient id="sgb-clear" cx="50%" cy="45%" r="75%"> <stop offset="0" stop-color="#fbf9f3"/> <stop offset="1" stop-color="#e2ddd0"/> </radialGradient> <!-- Glass material: rough edge, frost grain lit by the cursor, beveled rim lit by the cursor --> <filter id="sgb-glass" x="-25%" y="-25%" width="150%" height="150%" color-interpolation-filters="sRGB"> <feTurbulence type="fractalNoise" baseFrequency=".8" numOctaves="3" seed="7" result="noise"/> <feDisplacementMap in="SourceGraphic" in2="noise" scale="7" xChannelSelector="R" yChannelSelector="G" result="body"/> <feOffset id="sgb-drift" in="noise" dx="0" dy="0" result="noiseD"/> <feSpecularLighting id="sgb-frost" in="noiseD" surfaceScale="2.8" specularConstant="1" specularExponent="16" lighting-color="#dbe6ec" result="frostSpec"> <fePointLight id="sgb-l1" x="0" y="0" z="120"/> </feSpecularLighting> <feComposite in="frostSpec" in2="body" operator="in" result="frostIn"/> <feGaussianBlur in="body" stdDeviation="7" result="soft"/> <feSpecularLighting id="sgb-rim" in="soft" surfaceScale="10" specularConstant="1.3" specularExponent="26" lighting-color="#ffffff" result="rimSpec"> <fePointLight id="sgb-l2" x="0" y="0" z="80"/> </feSpecularLighting> <feComposite in="rimSpec" in2="body" operator="in" result="rimIn"/> <feMerge><feMergeNode in="body"/><feMergeNode in="frostIn"/><feMergeNode in="rimIn"/></feMerge> </filter> <!-- Feathered, organic reveal edge (not a hard circle) --> <filter id="sgb-soft" filterUnits="userSpaceOnUse" x="0" y="0" width="800" height="600"> <feTurbulence id="sgb-wob" type="fractalNoise" baseFrequency=".011" numOctaves="2" seed="4" result="w"/> <feDisplacementMap id="sgb-wobMap" in="SourceGraphic" in2="w" scale="60" xChannelSelector="R" yChannelSelector="G" result="d"/> <feGaussianBlur id="sgb-wobBlur" in="d" stdDeviation="24"/> </filter> <filter id="sgb-halo" filterUnits="userSpaceOnUse" x="0" y="0" width="800" height="600"><feGaussianBlur id="sgb-haloBlur" stdDeviation="12"/></filter> <filter id="sgb-shadowBlur" x="-50%" y="-200%" width="200%" height="500%"><feGaussianBlur stdDeviation="14"/></filter> <clipPath id="sgb-blobClip"><path id="sgb-clipPath"/></clipPath> <mask id="sgb-revealMask" maskUnits="userSpaceOnUse" x="-200" y="-200" width="3000" height="3000"> <g filter="url(#sgb-soft)"><circle id="sgb-revealCircle" fill="#fff" r="0" cx="0" cy="0"/></g> </mask> </defs> <ellipse id="sgb-shadow" fill="#000" opacity=".28" filter="url(#sgb-shadowBlur)"/> <g filter="url(#sgb-glass)"> <path id="sgb-blob" fill="url(#sgb-body)"/> <g clip-path="url(#sgb-blobClip)"> <g mask="url(#sgb-revealMask)"> <g id="sgb-imgG"><image id="sgb-revealImg" preserveAspectRatio="xMidYMid slice" x="0" y="0" width="800" height="600"/></g> </g> <g filter="url(#sgb-halo)"><circle id="sgb-haloRing" fill="none" stroke="#fff" r="0" opacity="0"/></g> </g> </g></svg><div class="sgb-bar" id="sgb-barT"></div><div class="sgb-bar b" id="sgb-barB"></div><div id="sgb-rec"><i></i><span id="sgb-tc">00:00:00:00</span></div><div id="sgb-title"><span id="sgb-titleH"></span><span id="sgb-credit"></span></div><button id="sgb-replay" type="button">Replay</button><div id="sgb-hint">Hold to pull focus</div></div></div><script>(function(){ var IMG_URL="PASTE_YOUR_IMAGE_URL_HERE", TITLE="Happy Holidays", CREDIT="Cinematography by Scott Strellis"; var $=function(i){return document.getElementById(i)}; var root=$('sgb-root'),stage=$('sgb-stage'),blob=$('sgb-blob'),clip=$('sgb-clipPath'),rc=$('sgb-revealCircle'),img=$('sgb-revealImg'),imgG=$('sgb-imgG'), hint=$('sgb-hint'),halo=$('sgb-haloRing'),shadow=$('sgb-shadow'),body=$('sgb-body'),l1=$('sgb-l1'),l2=$('sgb-l2'),frost=$('sgb-frost'),rim=$('sgb-rim'), drift=$('sgb-drift'),wob=$('sgb-wob'),wobMap=$('sgb-wobMap'),wobBlur=$('sgb-wobBlur'),haloBlur=$('sgb-haloBlur'),soft=$('sgb-soft'),haloF=$('sgb-halo'), barT=$('sgb-barT'),barB=$('sgb-barB'),rec=$('sgb-rec'),tc=$('sgb-tc'),title=$('sgb-title'),replay=$('sgb-replay'); $('sgb-titleH').textContent=TITLE;$('sgb-credit').textContent=CREDIT; img.setAttribute('href',IMG_URL); var reduce=window.matchMedia('(prefers-reduced-motion: reduce)').matches; var W=0,H=0,N=72,HOLD=3000; var m={x:0,y:0,sx:0,sy:0,active:false,pull:0},lt={x:0,y:0}; var pts=[];for(var i=0;i<N;i++)pts.push({ox:0,oy:0,vx:0,vy:0}); var holdMs=0,p=0,done=false,last=performance.now(),rx=0,ry=0,init=false,lastTc=''; function ss(a,b,x){x=Math.max(0,Math.min(1,(x-a)/(b-a)));return x*x*(3-2*x);} function pad(n){return (n<10?'0':'')+n;} function resize(){ W=stage.clientWidth;H=stage.clientHeight; $('sgb-s').setAttribute('viewBox','0 0 '+W+' '+H); [soft,haloF].forEach(function(f){f.setAttribute('width',W);f.setAttribute('height',H);}); img.setAttribute('x',-40);img.setAttribute('y',-40);img.setAttribute('width',W+80);img.setAttribute('height',H+80); if(!init){m.sx=m.x=W/2;m.sy=m.y=H/2;lt.x=W*.35;lt.y=H*.2;rx=W/2;ry=H/2;init=true;} } window.addEventListener('resize',resize); if(window.ResizeObserver)new ResizeObserver(resize).observe(stage); resize(); function move(e){var b=stage.getBoundingClientRect();m.x=e.clientX-b.left;m.y=e.clientY-b.top;m.active=true;} stage.addEventListener('pointermove',move); stage.addEventListener('pointerdown',move); stage.addEventListener('pointerleave',function(){m.active=false;}); stage.addEventListener('pointercancel',function(){m.active=false;}); window.addEventListener('pointerup',function(e){if(e.pointerType!=='mouse')m.active=false;}); replay.addEventListener('click',function(e){e.stopPropagation();done=false;holdMs=0;p=0;m.active=false;root.classList.remove('done');rx=W/2;ry=H/2;}); function frame(now){ var dt=Math.min(64,now-last);last=now;var t=now/1000; var R=Math.min(W,H)*0.27; // hold timer (rack focus): only counts while the cursor rests on the blob var inside=false; if(!done&&m.active){try{inside=blob.isPointInFill(new DOMPoint(m.x,m.y));}catch(e){}} if(!done){ holdMs=Math.max(0,holdMs+(inside?dt:-dt*3.3)); if(holdMs>=HOLD){holdMs=HOLD;done=true;root.classList.add('done');} } p=Math.min(1,holdMs/HOLD); var e=ss(0,1,p), g=ss(.72,1,p); var cover=Math.hypot(W,H)*0.62; m.pull+=((m.active&&!done?1:0)-m.pull)*0.08; var tx=m.active?m.x:W/2,ty=m.active?m.y:H/2; m.sx+=(tx-m.sx)*0.16;m.sy+=(ty-m.sy)*0.16; var cx=W/2+(m.sx-W/2)*0.07*m.pull, cy=H/2+(m.sy-H/2)*0.07*m.pull; var R2=R*(1+p*0.07*Math.sin(t*15)); var Reff=R2+(cover-R2)*g; var sp=(reduce?0.25:1)*(1+p*2.4), amp=(1+p*1.5)*(1-0.5*g); // spring-physics jelly body var P=[]; for(var i=0;i<N;i++){ var a=i/N*Math.PI*2, s=pts[i]; var wv=0.085*Math.sin(3*a+t*0.8*sp)+0.06*Math.sin(5*a-t*1.1*sp+1.3)+0.04*Math.sin(2*a+t*0.6*sp+2)+0.02*Math.sin(9*a+t*1.7*sp)+p*0.014*Math.sin(17*a+t*28); var r=Reff*(1+wv*amp), x=cx+Math.cos(a)*r, y=cy+Math.sin(a)*r; var dx=m.sx-x, dy=m.sy-y, d=Math.hypot(dx,dy), f=d<R2?Math.pow(1-d/R2,2)*0.46*m.pull:0; s.vx+=(dx*f-s.ox)*0.15; s.vy+=(dy*f-s.oy)*0.15; s.vx*=0.76; s.vy*=0.76; s.ox+=s.vx; s.oy+=s.vy; P.push([x+s.ox,y+s.oy]); } var path='M'+P[0][0].toFixed(1)+' '+P[0][1].toFixed(1); for(var j=0;j<N;j++){ var p0=P[(j-1+N)%N],p1=P[j],p2=P[(j+1)%N],p3=P[(j+2)%N]; path+='C'+(p1[0]+(p2[0]-p0[0])/6).toFixed(1)+' '+(p1[1]+(p2[1]-p0[1])/6).toFixed(1)+' '+(p2[0]-(p3[0]-p1[0])/6).toFixed(1)+' '+(p2[1]-(p3[1]-p1[1])/6).toFixed(1)+' '+p2[0].toFixed(1)+' '+p2[1].toFixed(1); } path+='Z'; blob.setAttribute('d',path);clip.setAttribute('d',path); // light follows the cursor; drifts on its own when idle var idleX=cx+Math.cos(t*.45)*R*1.25, idleY=cy-R*.9+Math.sin(t*.45)*R*.35; var lx=m.active?m.sx:idleX, ly=m.active?m.sy:idleY; lt.x+=(lx-lt.x)*0.12;lt.y+=(ly-lt.y)*0.12; l1.setAttribute('x',lt.x);l1.setAttribute('y',lt.y);l1.setAttribute('z',R*.5); l2.setAttribute('x',lt.x);l2.setAttribute('y',lt.y);l2.setAttribute('z',R*.32); frost.setAttribute('specularConstant',((1+p*0.9)*(1-g*.85)).toFixed(2)); frost.setAttribute('specularExponent',(16-p*7).toFixed(1)); rim.setAttribute('specularConstant',((1.3+p*1.1)*(1-g*.85)).toFixed(2)); drift.setAttribute('dx',((cx-W/2)+Math.sin(t*.6)*3).toFixed(1)); drift.setAttribute('dy',((cy-H/2)+Math.cos(t*.5)*3).toFixed(1)); var gx=50+(lt.x-cx)/R*20, gy=50+(lt.y-cy)/R*20; body.setAttribute('cx',gx+'%');body.setAttribute('cy',gy+'%');body.setAttribute('fx',gx+'%');body.setAttribute('fy',gy+'%'); shadow.setAttribute('cx',cx);shadow.setAttribute('cy',cy+R*1.18);shadow.setAttribute('rx',R*.9);shadow.setAttribute('ry',R*.14); shadow.setAttribute('opacity',(.28*(1-g)).toFixed(2)); // feathered, wobbling reveal bloom that starts where you hold if(inside){rx+=(m.sx-rx)*0.2;ry+=(m.sy-ry)*0.2;} if(p>=1){rx+=(cx-rx)*0.1;ry+=(cy-ry)*0.1;} var rr=e*R*3.7+g*Math.hypot(W,H); rc.setAttribute('cx',rx);rc.setAttribute('cy',ry);rc.setAttribute('r',rr.toFixed(1)); wobMap.setAttribute('scale',(R*0.42).toFixed(1)); wobBlur.setAttribute('stdDeviation',(R*0.12+e*R*0.06).toFixed(1)); wob.setAttribute('baseFrequency',(0.010+0.003*Math.sin(t*1.3)).toFixed(4)); halo.setAttribute('cx',rx);halo.setAttribute('cy',ry);halo.setAttribute('r',(e*R*3.7*0.82).toFixed(1)); halo.setAttribute('stroke-width',(R*0.12).toFixed(1)); halo.setAttribute('opacity',(Math.sin(Math.min(1,p*1.05)*Math.PI)*0.5*(1-g)).toFixed(2)); haloBlur.setAttribute('stdDeviation',(R*0.07).toFixed(1)); // photo pulls into focus and dollies in as you hold var sc=1.12-0.12*e, bl=(1-e)*(1-e)*18; imgG.setAttribute('transform','translate('+W/2+' '+H/2+') scale('+sc.toFixed(4)+') translate('+(-W/2)+' '+(-H/2)+')'); img.style.filter=bl>0.05?'blur('+bl.toFixed(1)+'px)':'none'; // camera-style overlays barT.style.height=barB.style.height=(g*7.5).toFixed(2)+'%'; title.style.opacity=ss(.92,1,p).toFixed(2); rec.style.opacity=holdMs>60?1:0; rec.style.color=g>.5?'#fff':'#1c2124'; var ms=Math.min(holdMs,HOLD), tcs='00:00:'+pad(Math.floor(ms/1000))+':'+pad(Math.floor((ms%1000)/1000*24)); if(tcs!==lastTc){tc.textContent=tcs;lastTc=tcs;} hint.style.opacity=(p>0.03||done)?0:1; requestAnimationFrame(frame); } requestAnimationFrame(frame);})();</script>
