Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> | |
| <title>Open Windows</title> | |
| <meta name="description" content="A generative visualization: sheer curtains dancing in the smoothest breeze at an open window, golden hour, dust in the light. The style is the seed. It does not end."> | |
| <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=Cormorant+Garamond:ital,wght@0,300;1,300;1,400&display=swap" rel="stylesheet"> | |
| <style> | |
| :root{--ink:#f6ead2;--dim:#c9a878} | |
| *{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent;user-select:none} | |
| html,body{height:100%;overflow:hidden;background:#160b12} | |
| canvas{position:fixed;inset:0;width:100%;height:100%;display:block} | |
| #veil{position:fixed;inset:0;z-index:2;pointer-events:none;display:flex;flex-direction:column;align-items:center;justify-content:flex-end;padding:0 24px 8vh;text-align:center} | |
| #ded{font-family:'Cormorant Garamond',Georgia,serif;font-style:italic;font-weight:300; | |
| font-size:clamp(16px,3vw,25px);line-height:1.5;color:var(--ink);max-width:32rem; | |
| text-shadow:0 2px 30px rgba(20,8,10,.6);opacity:0;transition:opacity 4s ease 1.5s} | |
| #ded.in{opacity:.92} | |
| #ded .t{font-size:.7em;letter-spacing:.4em;text-transform:uppercase;color:var(--dim);display:block;margin-bottom:14px;font-style:normal} | |
| #sig{position:fixed;left:0;right:0;bottom:14px;z-index:2;text-align:center;font-family:'Cormorant Garamond',serif; | |
| font-style:italic;font-size:12px;color:var(--dim);opacity:0;transition:opacity 3s ease 5s} | |
| #sig.in{opacity:.55} | |
| @media(prefers-reduced-motion:reduce){#ded,#sig{transition:none;opacity:.9}} | |
| </style> | |
| </head> | |
| <body> | |
| <canvas id="c"></canvas> | |
| <div id="veil"> | |
| <div id="ded"><span class="t">the seed</span>when the smoothest breeze dances with the curtains at your eternal love’s apartment during an open-windows session</div> | |
| </div> | |
| <div id="sig">🜂 the style is the seed · it does not end</div> | |
| <script> | |
| (function(){ | |
| ; | |
| var cv=document.getElementById('c'), ctx=cv.getContext('2d'); | |
| var W=0,H=0,DPR=Math.min(2,window.devicePixelRatio||1); | |
| var reduce=matchMedia('(prefers-reduced-motion: reduce)').matches; | |
| // ── the seed: hash the phrase into deterministic parameters ───────────────── | |
| // the style is the seed — the same words are always the same breeze. | |
| var SEED_STR="when the smoothest breeze dances with the curtains at your eternal love's apartment during an open-windows session"; | |
| function hash(s){ var h=2166136261>>>0; for(var i=0;i<s.length;i++){ h^=s.charCodeAt(i); h=Math.imul(h,16777619)>>>0; } return h; } | |
| function rng(seed){ var s=seed>>>0; return function(){ s^=s<<13; s^=s>>>17; s^=s<<5; s>>>=0; return s/4294967296; }; } | |
| var SEED=hash(SEED_STR), rnd=rng(SEED); | |
| // breeze = sum of a few low sines (the *smoothest* breeze — no harsh noise), seeded phases | |
| var BREEZE=[]; for(var i=0;i<4;i++) BREEZE.push({f:0.5+i*0.42, sp:0.16+i*0.075, ph:rnd()*6.283, a:1/(i+1)}); | |
| // palette hue nudged a touch by the seed, kept in golden-rose | |
| var HUE=(rnd()*16-8); | |
| function resize(){ W=innerWidth;H=innerHeight; cv.width=Math.floor(W*DPR);cv.height=Math.floor(H*DPR); ctx.setTransform(DPR,0,0,DPR,0,0); build(); } | |
| addEventListener('resize',resize); | |
| // ── the smoothest breeze: a smooth flow field over (y, time) ──────────────── | |
| function breeze(y, t){ | |
| var s=0; for(var i=0;i<BREEZE.length;i++){ var b=BREEZE[i]; s+=b.a*Math.sin(t*b.sp + y*b.f*2.6 + b.ph); } | |
| return s; // ~ -2..2, very smooth | |
| } | |
| // drape: 0 at the rod (anchored), growing toward the free hanging hem | |
| function drape(y){ return Math.pow(y,1.5); } | |
| // ── dust motes in the light ───────────────────────────────────────────────── | |
| var motes=[]; | |
| function build(){ | |
| motes=[]; var n=Math.round((W*H)/26000)+24; | |
| for(var i=0;i<n;i++) motes.push({ x:rnd(), y:rnd(), z:0.3+rnd()*0.7, ph:rnd()*6.283, sp:0.1+rnd()*0.25 }); | |
| } | |
| // warm golden-hour ground | |
| function background(t){ | |
| var g=ctx.createLinearGradient(0,0,0,H); | |
| g.addColorStop(0, 'hsl('+(324+HUE)+',34%,10%)'); // warm plum dusk (ceiling) | |
| g.addColorStop(0.42,'hsl('+(16+HUE)+',46%,20%)'); // deep rust | |
| g.addColorStop(0.72,'hsl('+(30+HUE)+',62%,34%)'); // amber | |
| g.addColorStop(1, 'hsl('+(26+HUE)+',48%,16%)'); // warm floor shadow | |
| ctx.fillStyle=g; ctx.fillRect(0,0,W,H); | |
| // the window's warm light: a soft glow entering from upper-centre-right | |
| var lx=W*0.62, ly=H*0.30, r=Math.max(W,H)*0.75; | |
| var breath=0.5+0.5*Math.sin(t*0.14); | |
| var lg=ctx.createRadialGradient(lx,ly,0,lx,ly,r); | |
| lg.addColorStop(0,'hsla('+(44+HUE)+',95%,72%,'+(0.34+0.10*breath)+')'); | |
| lg.addColorStop(0.4,'hsla('+(38+HUE)+',90%,60%,0.10)'); | |
| lg.addColorStop(1,'hsla('+(38+HUE)+',90%,60%,0)'); | |
| ctx.fillStyle=lg; ctx.fillRect(0,0,W,H); | |
| } | |
| // gentle god-rays swaying from the window | |
| function rays(t){ | |
| ctx.save(); ctx.globalCompositeOperation='lighter'; | |
| var ox=W*0.62, oy=-H*0.15; | |
| for(var i=0;i<5;i++){ | |
| var sway=Math.sin(t*0.1+i*0.9)*0.05; | |
| var a=(-0.35+i*0.16)+sway; | |
| var w=W*(0.05+i*0.012); | |
| var x1=ox+Math.tan(a)*H*1.3; | |
| var grd=ctx.createLinearGradient(ox,oy,x1,H*1.2); | |
| var al=0.05+0.02*Math.sin(t*0.2+i); | |
| grd.addColorStop(0,'hsla('+(46+HUE)+',95%,74%,'+al+')'); | |
| grd.addColorStop(1,'hsla('+(46+HUE)+',95%,74%,0)'); | |
| ctx.fillStyle=grd; | |
| ctx.beginPath(); ctx.moveTo(ox-w,oy); ctx.lineTo(ox+w,oy); ctx.lineTo(x1+w*2.4,H*1.2); ctx.lineTo(x1-w*2.4,H*1.2); ctx.closePath(); ctx.fill(); | |
| } | |
| ctx.restore(); | |
| } | |
| // two soft glows, breathing together — the eternal love, felt not shown | |
| function beloveds(t){ | |
| ctx.save(); ctx.globalCompositeOperation='lighter'; | |
| var pulse=0.5+0.5*Math.sin(t*0.22); // one shared heartbeat | |
| var cx=W*0.5, cy=H*0.66, sep=Math.min(W,H)*0.05*(0.9+0.14*Math.sin(t*0.09)); | |
| [-1,1].forEach(function(s,i){ | |
| var x=cx+s*sep, y=cy+Math.sin(t*0.11+i)* (H*0.006); | |
| var r=Math.min(W,H)*(0.09+0.02*pulse); | |
| var g=ctx.createRadialGradient(x,y,0,x,y,r); | |
| g.addColorStop(0,'hsla('+(34+HUE)+',90%,72%,'+(0.10+0.06*pulse)+')'); | |
| g.addColorStop(1,'hsla('+(34+HUE)+',90%,72%,0)'); | |
| ctx.fillStyle=g; ctx.beginPath(); ctx.arc(x,y,r,0,7); ctx.fill(); | |
| }); | |
| ctx.restore(); | |
| } | |
| // a sheer curtain panel: overlapping fold-ribbons displaced by the breeze | |
| function curtain(panelX, panelW, t, phase, front){ | |
| var folds=Math.max(8,Math.round(panelW/34)); | |
| var foldW=panelW/folds; | |
| var steps=26, top=-H*0.02, bottom=H*1.02; | |
| ctx.save(); | |
| ctx.globalCompositeOperation='lighter'; | |
| for(var f=0;f<folds;f++){ | |
| var baseX=panelX + f*foldW + foldW*0.5; | |
| var fph=phase + f*0.5; | |
| // ribbon path (two edges wobbling with the breeze) | |
| ctx.beginPath(); | |
| var pts=[]; | |
| for(var k=0;k<=steps;k++){ | |
| var yy=k/steps, y=top+(bottom-top)*yy; | |
| var dx=breeze(yy+fph*0.13, t+fph)*drape(yy)* (panelW*0.055) + Math.sin(t*0.3+fph)*foldW*0.15*drape(yy); | |
| pts.push([baseX+dx, y]); | |
| } | |
| // width of this ribbon pulses subtly (cloth thickness catching light) | |
| var half=foldW*(0.62+0.14*Math.sin(t*0.4+fph)); | |
| ctx.moveTo(pts[0][0]-half,pts[0][1]); | |
| for(var k=1;k<pts.length;k++) ctx.lineTo(pts[k][0]-half,pts[k][1]); | |
| for(var k=pts.length-1;k>=0;k--) ctx.lineTo(pts[k][0]+half,pts[k][1]); | |
| ctx.closePath(); | |
| // vertical gradient: honey-lit near the top-light, cooler cream below; folds shade by index | |
| var lit=0.5+0.5*Math.sin(f*0.9+t*0.25); // which folds catch the light now | |
| var g=ctx.createLinearGradient(baseX,top,baseX,bottom); | |
| var aTop=(front?0.11:0.07)*(0.6+0.6*lit); | |
| g.addColorStop(0,'hsla('+(44+HUE)+',80%,88%,'+aTop+')'); | |
| g.addColorStop(0.5,'hsla('+(38+HUE)+',70%,82%,'+(aTop*0.8)+')'); | |
| g.addColorStop(1,'hsla('+(28+HUE)+',55%,72%,'+(aTop*0.5)+')'); | |
| ctx.fillStyle=g; ctx.fill(); | |
| } | |
| ctx.restore(); | |
| } | |
| // window frame — a soft dark silhouette so the light reads as "a window" | |
| function frame(){ | |
| ctx.save(); | |
| var wx=W*0.30, wy=H*0.06, ww=W*0.62, wh=H*0.78; | |
| // outer wall darkening around the opening | |
| ctx.fillStyle='rgba(14,7,10,0.0)'; | |
| // mullion bars (thin, warm-dark) | |
| ctx.strokeStyle='hsla('+(20+HUE)+',35%,14%,0.55)'; ctx.lineWidth=Math.max(3,W*0.006); | |
| ctx.strokeRect(wx,wy,ww,wh); | |
| ctx.beginPath(); ctx.moveTo(wx+ww*0.5,wy); ctx.lineTo(wx+ww*0.5,wy+wh); // vertical mullion | |
| ctx.moveTo(wx,wy+wh*0.5); ctx.lineTo(wx+ww,wy+wh*0.5); // horizontal mullion | |
| ctx.stroke(); | |
| ctx.restore(); | |
| } | |
| function motesDraw(t){ | |
| ctx.save(); ctx.globalCompositeOperation='lighter'; | |
| for(var i=0;i<motes.length;i++){ var m=motes[i]; | |
| var y=(m.y + t*0.006*m.sp)%1; // drift slowly upward | |
| var x=m.x + breeze(y, t)*0.012*m.z + Math.sin(t*m.sp+m.ph)*0.006; | |
| var tw=0.4+0.6*Math.abs(Math.sin(t*m.sp*2+m.ph)); | |
| var px=((x%1)+1)%1*W, py=y*H, r=(0.6+m.z*1.6); | |
| ctx.fillStyle='hsla('+(46+HUE)+',90%,'+(80)+'%,'+(0.06+0.20*tw*m.z)+')'; | |
| ctx.beginPath(); ctx.arc(px,py,r,0,7); ctx.fill(); | |
| } | |
| ctx.restore(); | |
| } | |
| function vignette(){ | |
| var g=ctx.createRadialGradient(W*0.5,H*0.5,Math.min(W,H)*0.35,W*0.5,H*0.55,Math.max(W,H)*0.72); | |
| g.addColorStop(0,'rgba(0,0,0,0)'); g.addColorStop(1,'hsla('+(340+HUE)+',40%,6%,0.62)'); | |
| ctx.fillStyle=g; ctx.fillRect(0,0,W,H); | |
| } | |
| var t0=performance.now(), tStatic=8.0; | |
| function frameLoop(now){ | |
| var t=reduce? tStatic : (now-t0)/1000; | |
| background(t); | |
| rays(t); | |
| // two sheer layers for depth: a farther panel, then the nearer one | |
| curtain(W*0.30, W*0.40, t, 3.1, false); | |
| beloveds(t); | |
| motesDraw(t); | |
| curtain(W*0.30, W*0.62, t, 0.0, true); | |
| frame(); | |
| vignette(); | |
| if(!reduce) requestAnimationFrame(frameLoop); | |
| } | |
| resize(); | |
| requestAnimationFrame(frameLoop); | |
| // let the scene settle, then breathe the words in | |
| setTimeout(function(){ document.getElementById('ded').classList.add('in'); document.getElementById('sig').classList.add('in'); }, 200); | |
| })(); | |
| </script> | |
| </body> | |
| </html> | |