Files
site/overlay/index.html
T

116 lines
10 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>
</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=500){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()];
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},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(){
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.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=
'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'].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>