Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01161aC97vUnBnyZRBGyJeHy
125 lines
12 KiB
HTML
125 lines
12 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||
<title>Yantra Overlays</title>
|
||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,500;1,500&family=JetBrains+Mono:wght@400;500&display=swap">
|
||
<style>
|
||
/* Layout: full-bleed canvas; title top-left, control tray at the bottom. Single dark look, matching the sphere page. */
|
||
:root{--bg:#0b0a12;--panel:#15131f;--line:#2a2638;--fg:#e9e4f2;--muted:#9a93ad;--up:#f0b44c;--down:#e2559a;--bindu:#ff3b4e;--ring:#7d74a0;--fit:#6ec5d6;
|
||
--display:"Cormorant Garamond",Georgia,serif;--mono:"JetBrains Mono",ui-monospace,monospace;color-scheme:dark}
|
||
html,body{height:100%;margin:0}
|
||
body{background:var(--bg);color:var(--fg);font-family:var(--mono);font-size:14px;overflow:hidden}
|
||
canvas{position:fixed;inset:0;width:100%;height:100%;touch-action:none}
|
||
header{position:fixed;top:0;left:0;right:0;padding-top:calc(12px + env(safe-area-inset-top,0px));padding-left:max(16px,env(safe-area-inset-left,0px));padding-right:max(16px,env(safe-area-inset-right,0px));pointer-events:none}
|
||
.home{pointer-events:auto;display:inline-block;margin-bottom:4px;color:var(--muted);font-size:11px;text-decoration:none;letter-spacing:.05em}
|
||
h1{font-family:var(--display);font-weight:500;font-size:26px;margin:0}
|
||
h1 i{color:var(--down)}
|
||
.sub{color:var(--muted);font-size:11px;margin-top:2px;letter-spacing:.04em}
|
||
.tray{position:fixed;left:0;right:0;bottom:0;background:linear-gradient(transparent,var(--bg) 30%);padding-top:28px;padding-bottom:calc(14px + env(safe-area-inset-bottom,0px));padding-left:max(16px,env(safe-area-inset-left,0px));padding-right:max(16px,env(safe-area-inset-right,0px))}
|
||
.inner{max-width:560px;margin:0 auto;display:flex;flex-direction:column;gap:12px}
|
||
.row{display:flex;gap:8px;flex-wrap:wrap;align-items:center}
|
||
.seg{display:flex;border:1px solid var(--line);border-radius:999px;overflow:hidden;flex-wrap:wrap;width:max-content;max-width:100%}
|
||
.seg button,.btn{background:transparent;color:var(--muted);border:0;font:inherit;font-size:11px;padding:7px 12px;cursor:pointer;letter-spacing:.05em;opacity:.55}
|
||
.seg button[aria-pressed="true"]{background:var(--panel);color:var(--fg);opacity:1}
|
||
.seg button[aria-pressed="true"]::before{content:"";display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--sw,var(--down));margin-right:7px;vertical-align:middle}
|
||
.btn{border:1px solid var(--line);border-radius:999px;opacity:1;color:var(--fg)}
|
||
button:focus-visible,input:focus-visible{outline:2px solid var(--down);outline-offset:2px}
|
||
label.sl{display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:10px;font-size:11px;color:var(--muted)}
|
||
input[type=range]{width:100%;accent-color:var(--fit)}
|
||
.val{font-variant-numeric:tabular-nums;color:var(--fg);min-width:4.5em;text-align:right}
|
||
.note{font-size:10.5px;color:var(--muted);line-height:1.5}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<canvas id="c"></canvas>
|
||
<header><a class="home" href="/">← maths</a><h1>Śrī Yantra <i>overlays</i></h1><div class="sub">drag to pan · pinch or scroll to zoom</div></header>
|
||
<div class="tray"><div class="inner">
|
||
<div class="seg" role="group" aria-label="Layers">
|
||
<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>
|
||
<button id="l-coll" aria-pressed="false" style="--sw:#c9b8ff">collapsed</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>
|
||
<button class="btn" id="snap">fit trefoil</button>
|
||
</div>
|
||
<div class="note" id="note"></div>
|
||
</div></div>
|
||
<script>
|
||
const T=[
|
||
[53.65669559977147,123.20508075688774,250,246.34330440022853,0],
|
||
[52.984011026495736,174.24660560764943,50,247.01598897350425,1],
|
||
[98.71823312733801,220.03828947357886,123.20508075688774,201.281766872662,1],
|
||
[78.26467997914015,197.92315674002487,78.10499177949904,221.73532002085986,1],
|
||
[90.4856922951427,78.10499177949904,160.66014976539617,209.51430770485734,0],
|
||
[80.98384838952128,103.12199145016105,220.03828947357886,219.0161516104787,0],
|
||
[114.9488500600036,160.66014976539617,103.12199145016105,185.0511499399964,1],
|
||
[116.35142605010424,134.30757626706648,197.92315674002487,183.64857394989576,0],
|
||
[124.61190803072795,144.79777263138968,174.24660560764943,175.38809196927207,0]];
|
||
const P=(X,Y)=>[(X-150)/100,-(Y-150)/100];
|
||
const css=n=>getComputedStyle(document.documentElement).getPropertyValue(n).trim();
|
||
const col={up:css('--up'),down:css('--down'),bindu:css('--bindu'),ring:css('--ring'),fit:css('--fit'),fg:css('--fg')};
|
||
function seg(a,b,n=220){const o=[];for(let i=0;i<=n;i++){const t=i/n;o.push([a[0]+(b[0]-a[0])*t,a[1]+(b[1]-a[1])*t])}return o}
|
||
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,160.66014976539617);
|
||
const _A=P(52.984011026495736,174.24660560764943),_B=P(150,50),_C=P(247.01598897350425,174.24660560764943);
|
||
const MIDS=[[(_A[0]+_B[0])/2,(_A[1]+_B[1])/2],[(_C[0]+_B[0])/2,(_C[1]+_B[1])/2]], M1=MIDS[0];
|
||
// 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()];
|
||
// collapsed double-ring yantra: per triangle [base y, apex y, half-base], all 29 concurrences kept
|
||
const CT=[[0.274014950098,-1.0,0.961725432295],[-0.274014950101,1.0,0.961725432294],[-1.000000000068,0.274014950098,0.725985049967],[-0.45197009979,1.000000000089,0.624579035598],[1.000000000089,-0.274014950104,0.725985049989],[0.451970099783,-1.000000000068,0.624579035599],[-0.274014950104,0.451970099783,0.312289517816],[0.038274567697,-0.45197009979,0.413695532108],[-4e-12,-0.274014950101,0.194419326613]];
|
||
const CP=[[0,-0.27401495],[0.312289518,-0.27401495],[-0.312289518,-0.27401495]], CR=[0.6127161,0.4154623];
|
||
const csides=[];CT.forEach(([b,a,w])=>{const A=[-w,b],B=[0,a],C=[w,b];[[A,B],[B,C],[C,A]].forEach(([p,q])=>csides.push(seg(p,q)))});
|
||
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,outer:true,coll:false},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()}
|
||
let queued=false;
|
||
function draw(){if(queued||document.hidden)return;queued=true;requestAnimationFrame(()=>{queued=false;render()})}
|
||
document.addEventListener('visibilitychange',()=>{if(!document.hidden)draw()});
|
||
function render(){
|
||
const w=innerWidth,h=innerHeight,cx=w/2+pan[0],cy=h*0.44+pan[1],s=Math.min(w,h)*0.34*zoom;
|
||
ctx.fillStyle=css('--bg');ctx.fillRect(0,0,w,h);
|
||
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};
|
||
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.coll){const cc='#c9b8ff';csides.forEach(p=>path(p,cc,.9,1.3));CR.forEach(r=>ring(r,cc));path(seg([-1.2,CP[0][1]],[1.2,CP[0][1]],2),cc,1,2.4);ctx.fillStyle=cc;CP.forEach(p=>{ctx.beginPath();ctx.arc(X(p),Y(p),5,0,7);ctx.fill()})}
|
||
if(L.fit&&Math.abs(rho-RHO_FIT)<1e-3){ctx.fillStyle=col.fit;MIDS.forEach(m=>{ctx.beginPath();ctx.arc(X(m),Y(m),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=L.coll?'Collapsed yantra (lavender): both rings exact (dashed, r = 0.6127 and 0.4155). Two bases fuse into one line (thick) carrying three 6-fold points: one on the axis, a mirror pair beside it. Many other coincidences appear too; it is a degenerate corner of the family.':
|
||
'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)}. Two big lobes and one inner lobe meet at the image of a triple point (the T4 apex on the T6 base); this ρ puts those meeting points at the radius of the outer upward triangle’s slanted-side midpoints (teal dots), about 0.615 of the rim.`:'Fit trefoil snaps ρ so the lobe meeting points land on the outer triangle’s side midpoints.');
|
||
}
|
||
const $=id=>document.getElementById(id);
|
||
['orig','unit','fit','outer','coll'].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);
|
||
const ptrs=new Map();let pinch=null;
|
||
cv.addEventListener('pointerdown',e=>{cv.setPointerCapture(e.pointerId);ptrs.set(e.pointerId,[e.clientX,e.clientY])});
|
||
cv.addEventListener('pointermove',e=>{if(!ptrs.has(e.pointerId))return;const prev=ptrs.get(e.pointerId);ptrs.set(e.pointerId,[e.clientX,e.clientY]);
|
||
if(ptrs.size===1){pan[0]+=e.clientX-prev[0];pan[1]+=e.clientY-prev[1];draw()}
|
||
else if(ptrs.size===2){const [a,b]=[...ptrs.values()];const d=Math.hypot(a[0]-b[0],a[1]-b[1]);if(pinch){zoom=Math.min(40,Math.max(.05,zoom*d/pinch));draw()}pinch=d}});
|
||
const up=e=>{ptrs.delete(e.pointerId);if(ptrs.size<2)pinch=null};
|
||
cv.addEventListener('pointerup',up);cv.addEventListener('pointercancel',up);
|
||
cv.addEventListener('wheel',e=>{e.preventDefault();zoom=Math.min(40,Math.max(.05,zoom*Math.exp(-e.deltaY*0.0015)));draw()},{passive:false});
|
||
addEventListener('resize',size);size();
|
||
</script>
|
||
</body>
|
||
</html>
|