Files
site/overlay/index.html
T

125 lines
12 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>