Files
site/inversion/index.html
T

94 lines
7.3 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 Inversion</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;
--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}
.seg{display:flex;border:1px solid var(--line);border-radius:999px;overflow:hidden;width:max-content;max-width:100%}
.seg button{background:transparent;color:var(--muted);border:0;font:inherit;font-size:11px;padding:7px 12px;cursor:pointer;letter-spacing:.05em}
.seg button[aria-pressed="true"]{background:var(--panel);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(--down)}
.val{font-variant-numeric:tabular-nums;color:var(--fg);min-width:3.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>inverted</i></h1><div class="sub">z ↦ z / |z|² in the outer circle · drag to pan · pinch or scroll to zoom</div></header>
<div class="tray"><div class="inner">
<div class="seg" role="group" aria-label="Show">
<button id="s-orig" aria-pressed="false">original</button>
<button id="s-inv" aria-pressed="false">inverted</button>
<button id="s-both" aria-pressed="true">both</button>
</div>
<label class="sl" for="zoom">zoom <input id="zoom" type="range" min="-1.2" max="1.6" step="0.01" value="-0.5"> <span class="val" id="zv"></span></label>
<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'),fg:css('--fg')};
function seg(a,b,n=400){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)}))});
const inv=([x,y])=>{const r=x*x+y*y;return r<1e-12?null:[x/r,y/r]};
let show='both',zoom=Math.pow(10,-0.5),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.46+pan[1],s=Math.min(w,h)*0.36*zoom;
ctx.fillStyle=css('--bg');ctx.fillRect(0,0,w,h);
const X=p=>cx+p[0]*s,Y=p=>cy-p[1]*s;
ctx.strokeStyle=col.ring;ctx.lineWidth=1.2;ctx.setLineDash([4,4]);ctx.beginPath();ctx.arc(cx,cy,s,0,Math.PI*2);ctx.stroke();ctx.setLineDash([]);
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])>1e4||Math.abs(p[1])>1e4){pen=false;continue}pen?ctx.lineTo(X(p),Y(p)):ctx.moveTo(X(p),Y(p));pen=true}ctx.stroke();ctx.globalAlpha=1};
if(show!=='inv')sides.forEach(sd=>path(sd.pts,sd.up?col.up:col.down,show==='both'?.85:1,1.1));
if(show!=='orig')sides.forEach(sd=>path(sd.pts.map(inv),sd.up?col.up:col.down,1,1.1));
ctx.fillStyle=col.bindu;ctx.beginPath();ctx.arc(cx,cy,2.4,0,Math.PI*2);ctx.fill();
document.getElementById('zv').textContent=zoom<1?zoom.toFixed(2)+'×':zoom.toFixed(1)+'×';
document.getElementById('note').textContent=show==='orig'
?'The flat yantra inside the circle of inversion.'
:'Every straight side becomes a circle arc through the bindu. The small inner triangles map far outside; the outer triangles fold just beyond the ring, and the bindu itself goes to ∞.';
}
const $=id=>document.getElementById(id);
function setShow(v){show=v;['orig','inv','both'].forEach(k=>$('s-'+k).setAttribute('aria-pressed',k===v));draw()}
$('s-orig').onclick=()=>setShow('orig');$('s-inv').onclick=()=>setShow('inv');$('s-both').onclick=()=>setShow('both');
$('zoom').oninput=e=>{zoom=Math.pow(10,+e.target.value);draw()};
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(.06,zoom*d/pinch));$('zoom').value=Math.log10(zoom);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(.06,zoom*Math.exp(-e.deltaY*0.0015)));$('zoom').value=Math.log10(zoom);draw()},{passive:false});
addEventListener('resize',size);size();
</script>
</body>
</html>