Overlays: fit the trefoil on the right point, and add the outer yantra (lotus rings, bhupura)

The lobe-meeting point is the image of the triple point where the T4 apex sits on the T6 base,
not the T0 apex. Outer layer adds the 1, 1.2, 1.4, 1.5 circles, the 8 and 16 petals and the
bhupura square with its four gates, all carried through both inversions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01161aC97vUnBnyZRBGyJeHy
This commit is contained in:
teahubxyzandClaude Opus 5.5 committed 2026-10-23 20:22:32 +00:00
1 parent 4f76794ed1
commit eb0a2ec2ef
1 file changed
+14 -7
+14 -7
View File
@@ -40,6 +40,7 @@ input[type=range]{width:100%;accent-color:var(--fit)}
<button id="l-orig" aria-pressed="true" style="--sw:var(--fg)">yantra</button>
<button id="l-unit" aria-pressed="false" style="--sw:var(--ring)">inverse · rim</button>
<button id="l-fit" aria-pressed="true" style="--sw:var(--fit)">inverse · ρ</button>
<button id="l-outer" aria-pressed="true" style="--sw:var(--muted)">outer</button>
</div>
<div class="row">
<label class="sl" for="rho" style="flex:1;min-width:220px">ρ <input id="rho" type="range" min="-1" max="0.6" step="0.001" value="-0.31"> <span class="val" id="rv"></span></label>
@@ -65,10 +66,15 @@ function seg(a,b,n=500){const o=[];for(let i=0;i<=n;i++){const t=i/n;o.push([a[0
const sides=[];T.forEach(([lx,by,ay,rx,ty])=>{const A=P(lx,by),B=P(150,ay),C=P(rx,by);[[A,B],[B,C],[C,A]].forEach(([a,b])=>sides.push({up:!!ty,pts:seg(a,b)}))});
// Fit: send the outer downward triangle's vertices (where two outer lobes meet) onto the
// side midpoints of the outer upward triangle, which lie on the same three rays: rho^2 = |V||M|.
const V0=P(150,250), M1=P(150,174.24660560764943);
const V0=P(150,160.66014976539617), M1=P(150,174.24660560764943);
// outer yantra: lotus rings, petals, bhupura (square with four gates), in yantra units (rim = 1)
const circ=(r,n=360)=>Array.from({length:n+1},(_,i)=>{const a=i/n*Math.PI*2;return[r*Math.cos(a),r*Math.sin(a)]});
function petals(r0,r1,k){const o=[];for(let i=0;i<k;i++){const a0=i/k*Math.PI*2,a1=(i+1)/k*Math.PI*2,am=(a0+a1)/2,pts=[];for(let j=0;j<=40;j++){const t=j/40;const a=a0+(am-a0)*t;const r=r0+(r1-r0)*Math.sin(t*Math.PI/2);pts.push([r*Math.cos(a),r*Math.sin(a)])}for(let j=0;j<=40;j++){const t=j/40;const a=am+(a1-am)*t;const r=r1-(r1-r0)*(1-Math.cos(t*Math.PI/2));pts.push([r*Math.cos(a),r*Math.sin(a)])}o.push(pts)}return o}
function bhupura(){const q=[[1.6,-1.6],[1.6,-0.6],[1.73,-0.6],[1.73,-1.2],[1.97,-1.2],[1.97,1.2],[1.73,1.2],[1.73,0.6],[1.6,0.6],[1.6,1.6]];const corners=[];for(let k=0;k<4;k++){const c=Math.cos(k*Math.PI/2),s=Math.sin(k*Math.PI/2);q.forEach(([x,y])=>corners.push([x*c-y*s,x*s+y*c]))}corners.push(corners[0]);const o=[];for(let i=0;i<corners.length-1;i++)o.push(...seg(corners[i],corners[i+1],120).slice(i?1:0));return o}
const OUTER=[circ(1.2),circ(1.4),circ(1.5),...petals(1,1.2,8),...petals(1.2,1.4,16),bhupura()];
const RHO_FIT=Math.sqrt(Math.hypot(...V0)*Math.hypot(...M1));
const inv=(p,r2)=>{const r=p[0]*p[0]+p[1]*p[1];return r<1e-12?null:[r2*p[0]/r,r2*p[1]/r]};
let L={orig:true,unit:false,fit:true},rho=RHO_FIT,zoom=1,pan=[0,0];
let L={orig:true,unit:false,fit:true,outer:true},rho=RHO_FIT,zoom=1,pan=[0,0];
const cv=document.getElementById('c'),ctx=cv.getContext('2d');
function size(){const d=Math.min(devicePixelRatio,2);cv.width=innerWidth*d;cv.height=innerHeight*d;ctx.setTransform(d,0,0,d,0,0);draw()}
function draw(){
@@ -77,18 +83,19 @@ function draw(){
const X=p=>cx+p[0]*s,Y=p=>cy-p[1]*s;
const ring=(r,c)=>{ctx.strokeStyle=c;ctx.globalAlpha=.8;ctx.lineWidth=1;ctx.setLineDash([4,4]);ctx.beginPath();ctx.arc(cx,cy,r*s,0,Math.PI*2);ctx.stroke();ctx.setLineDash([]);ctx.globalAlpha=1};
const path=(pts,c,a,lw)=>{ctx.strokeStyle=c;ctx.globalAlpha=a;ctx.lineWidth=lw;ctx.beginPath();let pen=false;for(const p of pts){if(!p||Math.abs(p[0])>200||Math.abs(p[1])>200){pen=false;continue}pen?ctx.lineTo(X(p),Y(p)):ctx.moveTo(X(p),Y(p));pen=true}ctx.stroke();ctx.globalAlpha=1};
if(L.unit){ring(1,col.ring);sides.forEach(sd=>path(sd.pts.map(p=>inv(p,1)),col.ring,.7,1.1))}
if(L.fit){ring(rho,col.fit);sides.forEach(sd=>path(sd.pts.map(p=>inv(p,rho*rho)),col.fit,.85,1.2))}
if(L.orig)sides.forEach(sd=>path(sd.pts,sd.up?col.up:col.down,1,1.3));
const O=L.outer?OUTER:[];
if(L.unit){ring(1,col.ring);sides.forEach(sd=>path(sd.pts.map(p=>inv(p,1)),col.ring,.7,1.1));O.forEach(o=>path(o.map(p=>inv(p,1)),col.ring,.4,1))}
if(L.fit){ring(rho,col.fit);sides.forEach(sd=>path(sd.pts.map(p=>inv(p,rho*rho)),col.fit,.85,1.2));O.forEach(o=>path(o.map(p=>inv(p,rho*rho)),col.fit,.45,1))}
if(L.orig){sides.forEach(sd=>path(sd.pts,sd.up?col.up:col.down,1,1.3));O.forEach(o=>path(o,col.fg,.45,1));path(circ(1),col.fg,.6,1)}
if(L.fit&&Math.abs(rho-RHO_FIT)<1e-3){ctx.fillStyle=col.fit;const A=Math.atan2(M1[1],M1[0]),m=Math.hypot(...M1);for(let k=0;k<3;k++){const a=A+k*2*Math.PI/3;ctx.beginPath();ctx.arc(X([m*Math.cos(a),0]),Y([0,m*Math.sin(a)]),3.5,0,7);ctx.fill()}}
ctx.fillStyle=col.bindu;ctx.beginPath();ctx.arc(cx,cy,2.4,0,Math.PI*2);ctx.fill();
document.getElementById('rv').textContent=rho.toFixed(4);
document.getElementById('note').textContent=
'Yantra in gold and pink; its inversion in the rim (ρ = 1) in violet; its inversion in a circle of radius ρ in teal. '+
(Math.abs(rho-RHO_FIT)<1e-3?`Fit: ρ = ${RHO_FIT.toFixed(4)} sends the points where two outer lobes meet onto the side midpoints of the outer upward triangle (teal dots).`:'Fit trefoil snaps ρ so the lobe meeting points land on the outer triangle’s side midpoints.');
(Math.abs(rho-RHO_FIT)<1e-3?`Fit: ρ = ${RHO_FIT.toFixed(4)}. Two big lobes and one inner lobe meet at the image of a triple point (the T4 apex on the T6 base); this ρ lands it on the side midpoints of the outer upward triangle (teal dots).`:'Fit trefoil snaps ρ so the lobe meeting points land on the outer triangle’s side midpoints.');
}
const $=id=>document.getElementById(id);
['orig','unit','fit'].forEach(k=>$('l-'+k).onclick=e=>{L[k]=!L[k];e.currentTarget.setAttribute('aria-pressed',L[k]);draw()});
['orig','unit','fit','outer'].forEach(k=>$('l-'+k).onclick=e=>{L[k]=!L[k];e.currentTarget.setAttribute('aria-pressed',L[k]);draw()});
$('rho').oninput=e=>{rho=Math.pow(10,+e.target.value);draw()};
$('snap').onclick=()=>{rho=RHO_FIT;$('rho').value=Math.log10(rho);if(!L.fit){L.fit=true;$('l-fit').setAttribute('aria-pressed','true')}draw()};
$('rho').value=Math.log10(RHO_FIT);