Skip to content

Commit dc087a2

Browse files
authored
trs -> tre
0 parents  commit dc087a2

File tree

1 file changed

+153
-0
lines changed

1 file changed

+153
-0
lines changed

index.html

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1" />
6+
<title>Teleology → Recursion → Sonata</title>
7+
<style>
8+
:root{ --bg:#111; --text:#eee; --muted:#bbb; --ink:#9ad1ff; }
9+
*{ box-sizing:border-box }
10+
html,body{ height:100% }
11+
body{
12+
margin:0; background:var(--bg); color:var(--text);
13+
font:16px/1.5 "Courier New", Courier, monospace;
14+
display:flex; flex-direction:column; align-items:center; justify-content:center;
15+
padding:1rem; gap:1rem;
16+
}
17+
h1{
18+
font-size:clamp(1rem,2.6vw,1.4rem); letter-spacing:.1em; margin:0; text-align:center; color:#ddd;
19+
}
20+
a{ color:#ddd; text-decoration:none; transition:color .25s ease }
21+
a:hover{ color:var(--ink); text-decoration:underline }
22+
canvas{ width:min(80vmin,900px); height:auto; aspect-ratio:4/1.6; display:block }
23+
footer{ color:#777; font-size:clamp(.7rem,1.5vw,.85rem) }
24+
</style>
25+
</head>
26+
<body>
27+
<h1>
28+
<a href="https://claude.ai/public/artifacts/4ec0a53e-03b8-4462-ab92-ac9c9b65e162" target="_blank" rel="noopener">Teleology</a>
29+
<a href="https://abikesa.github.io/buddhism/" target="_blank" rel="noopener">Recursion</a>
30+
<a href="https://ukb-pyro.github.io/inf/" target="_blank" rel="noopener">Sonata</a>
31+
</h1>
32+
33+
<canvas id="stage" aria-label="Smooth morph: line → circle → infinity → line"></canvas>
34+
35+
<footer>© 2025 Ukubona LLC</footer>
36+
37+
<script>
38+
(function(){
39+
const canvas = document.getElementById('stage');
40+
const ctx = canvas.getContext('2d');
41+
42+
// --- sizing with HiDPI crispness ---
43+
function fit(){
44+
const cssW = canvas.getBoundingClientRect().width;
45+
const cssH = cssW / (4/1.6);
46+
const dpr = Math.max(1, window.devicePixelRatio || 1);
47+
canvas.width = Math.floor(cssW * dpr);
48+
canvas.height = Math.floor(cssH * dpr);
49+
ctx.setTransform(dpr,0,0,dpr,0,0); // draw in CSS pixels
50+
}
51+
52+
// parametric shapes sampled at uniform t in [0,1)
53+
const N = 900; // many points for imperceptible transitions
54+
const T = 8_000; // ms per morph leg (long & smooth)
55+
const CYCLE = T * 3; // line->circle, circle->∞, ∞->line
56+
const TWO_PI = Math.PI * 2;
57+
58+
// ease: very gentle S-curve (cosine)
59+
const ease = t => 0.5 - 0.5 * Math.cos(Math.PI * t);
60+
61+
// base sampler returning arrays of [x,y]
62+
function sample(fn){
63+
const pts = new Array(N);
64+
for(let i=0;i<N;i++){
65+
const t = i / N;
66+
pts[i] = fn(t);
67+
}
68+
return pts;
69+
}
70+
71+
// shapes centered at (0,0) with radius a, later scaled to canvas
72+
const line = sample(t => [ (t*2-1), 0 ]); // horizontal segment [-1,1]×{0}
73+
const circle = sample(t => [ Math.cos(TWO_PI*t), Math.sin(TWO_PI*t) ]); // unit circle
74+
// Lemniscate of Gerono (sideways ∞), vertically softened to better match circle
75+
const lemni = sample(t => {
76+
const th = TWO_PI * t;
77+
const x = Math.cos(th);
78+
const y = 0.60 * Math.sin(th) * Math.cos(th); // 0.5*sin(2θ) scaled
79+
return [x, y];
80+
});
81+
82+
// interpolate between two shapes, pointwise
83+
function blend(a, b, m){
84+
const out = new Array(N);
85+
for(let i=0;i<N;i++){
86+
const x = a[i][0] + (b[i][0] - a[i][0]) * m;
87+
const y = a[i][1] + (b[i][1] - a[i][1]) * m;
88+
out[i] = [x, y];
89+
}
90+
return out;
91+
}
92+
93+
// subtle "breathing" on radius to avoid mechanical feel
94+
function radius(now, base){
95+
return base * (1 + 0.02 * Math.sin(now/3000) + 0.01 * Math.cos(now/7000));
96+
}
97+
98+
function draw(now){
99+
fit();
100+
const w = canvas.width / (window.devicePixelRatio||1);
101+
const h = canvas.height / (window.devicePixelRatio||1);
102+
const cx = w/2, cy = h/2;
103+
const scale = Math.min(w, h) * 0.38;
104+
105+
// which leg of the cycle?
106+
const t = (now % CYCLE);
107+
const leg = Math.floor(t / T); // 0,1,2
108+
const local = (t % T) / T; // 0..1 within leg
109+
const m = ease(local); // eased morph amount
110+
111+
// choose endpoints
112+
let A, B;
113+
if(leg === 0){ A = line; B = circle; }
114+
else if(leg === 1){ A = circle; B = lemni; }
115+
else { A = lemni; B = line; }
116+
117+
// blend and render
118+
const pts = blend(A, B, m);
119+
const r = radius(now, scale);
120+
121+
ctx.clearRect(0,0,w,h);
122+
ctx.save();
123+
ctx.translate(cx, cy);
124+
125+
ctx.beginPath();
126+
for(let i=0;i<N;i++){
127+
const x = pts[i][0] * r;
128+
const y = pts[i][1] * r;
129+
if(i===0) ctx.moveTo(x, y);
130+
else ctx.lineTo(x, y);
131+
}
132+
// for circle/lemni, gently close; for the line it’s effectively a straight segment
133+
ctx.closePath();
134+
135+
ctx.lineWidth = Math.max(2, Math.min(6, w*0.006));
136+
ctx.lineJoin = "round";
137+
ctx.lineCap = "round";
138+
ctx.strokeStyle = "#9ad1ff";
139+
ctx.shadowColor = "rgba(154,209,255,.25)";
140+
ctx.shadowBlur = 8;
141+
ctx.stroke();
142+
143+
ctx.restore();
144+
requestAnimationFrame(draw);
145+
}
146+
147+
fit();
148+
requestAnimationFrame(draw);
149+
window.addEventListener('resize', fit, {passive:true});
150+
})();
151+
</script>
152+
</body>
153+
</html>

0 commit comments

Comments
 (0)