Files
site/overlay/index.html
T

107 lines
8.7 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>
</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,250), M1=P(150,174.24660560764943);
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];
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};
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));
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.');
}
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()});
$('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>