lamp: the zeta ball projected from the touch point, u = 1/(4(s − ½))
Chart centred on ∞ with the same frame as the s-picture (poles ±½, third strut ±i/2, |u| = ½ fixed). Domain colouring of ζ, ξ and χ = ζ(s)/ζ(1−s) in a worker (Euler–Maclaurin + reflection), zoom, the dial as a plain rotation, the first 200 nontrivial zeros and the trivial zeros marked, the strip's edges as two kissing circles, and a cross-section of both projections. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FAqjv3n4yABxvJPJHFRhVA
This commit is contained in:
1 parent
73adb40542
commit
d4638f0de2
1 file changed
+304
+304
@@ -0,0 +1,304 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
<title>Lamp at ½</title>
|
||||
<meta name="description" content="The zeta ball projected from the other pole: lamp at the touch point ½, screen at ∞. Same frame, the dial a plain rotation, and every zero falls into the centre.">
|
||||
<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>
|
||||
/* Same dark look as the other explorers. Controls bar, then a round lens (the u-plane) beside a cross-section and key, then notes. */
|
||||
:root{--bg:#0b0a12;--panel:#15131f;--line:#2a2638;--fg:#e9e4f2;--muted:#9a93ad;--up:#f0b44c;--down:#e2559a;--bindu:#ff3b4e;--teal:#4fd1c5;
|
||||
--display:"Cormorant Garamond",Georgia,serif;--mono:"JetBrains Mono",ui-monospace,monospace;color-scheme:dark}
|
||||
html,body{margin:0}
|
||||
body{background:var(--bg);color:var(--fg);font-family:var(--mono);font-size:13.5px;line-height:1.6;
|
||||
padding:calc(28px + env(safe-area-inset-top,0px)) max(16px,env(safe-area-inset-right,0px)) calc(48px + env(safe-area-inset-bottom,0px)) max(16px,env(safe-area-inset-left,0px))}
|
||||
main{max-width:1040px;margin:0 auto;display:flex;flex-direction:column;gap:18px}
|
||||
.eyebrow{font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--muted)}
|
||||
.eyebrow a{color:var(--muted)}
|
||||
h1{font-family:var(--display);font-weight:500;font-size:clamp(30px,5.5vw,46px);line-height:1.05;margin:.15em 0 0;text-wrap:balance}
|
||||
h1 i{color:var(--down)}
|
||||
.lede{color:var(--muted);max-width:72ch;margin:0}
|
||||
.lede b{color:var(--fg);font-weight:500}
|
||||
.bar{display:flex;flex-wrap:wrap;gap:10px 18px;align-items:center;padding:12px 14px;border:1px solid var(--line);border-radius:6px;background:var(--panel)}
|
||||
.seg{display:inline-flex;border:1px solid var(--line);border-radius:5px;overflow:hidden}
|
||||
.seg button{background:transparent;color:var(--fg);border:0;padding:6px 11px;font:inherit;cursor:pointer}
|
||||
.seg button+button{border-left:1px solid var(--line)}
|
||||
.seg button[aria-pressed="true"]{background:var(--up);color:#1a1408}
|
||||
label.k{color:var(--muted);font-size:11.5px;letter-spacing:.06em;text-transform:uppercase;margin-right:6px}
|
||||
.chk{display:inline-flex;gap:6px;align-items:center;cursor:pointer}
|
||||
input[type=range]{accent-color:var(--up);width:150px;vertical-align:middle}
|
||||
.row{display:grid;grid-template-columns:1.35fr 1fr;gap:14px;align-items:start}
|
||||
.pane{border:1px solid var(--line);border-radius:6px;background:var(--panel);overflow:hidden}
|
||||
.pane h2{font-family:var(--display);font-weight:500;font-size:20px;margin:0;padding:8px 12px 0}
|
||||
.pane p.sub{margin:0;padding:0 12px 8px;color:var(--muted);font-size:12px;min-height:1.6em}
|
||||
.lens{position:relative;aspect-ratio:1;margin:0 12px 12px;border-radius:50%;overflow:hidden;background:#000;box-shadow:0 0 0 1px var(--line)}
|
||||
.lens canvas{position:absolute;inset:0;width:100%;height:100%;transform-origin:50% 50%}
|
||||
.busy{position:absolute;left:50%;bottom:10px;transform:translateX(-50%);font-size:11px;color:var(--up);opacity:0;transition:opacity .2s;pointer-events:none}
|
||||
.busy.on{opacity:1}
|
||||
.read{padding:0 12px 10px;color:var(--muted);font-size:12px;min-height:1.6em}
|
||||
.read b{color:var(--fg);font-weight:500}
|
||||
.side{display:flex;flex-direction:column;gap:14px}
|
||||
.card{border:1px solid var(--line);border-radius:6px;background:var(--panel);padding:14px 16px;color:var(--muted);font-size:12.5px}
|
||||
.card h3{font-family:var(--display);font-weight:500;font-size:19px;color:var(--fg);margin:0 0 6px}
|
||||
.card p{margin:0 0 8px}
|
||||
.card p:last-child{margin-bottom:0}
|
||||
.card b{color:var(--fg);font-weight:500}
|
||||
.card svg{width:100%;height:auto;display:block;background:#000;border-radius:4px;margin:4px 0 8px}
|
||||
.notes{display:grid;grid-template-columns:1fr 1fr;gap:14px}
|
||||
.dot{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:5px;vertical-align:middle}
|
||||
.line{display:inline-block;width:14px;height:2px;margin-right:5px;vertical-align:middle}
|
||||
@media (max-width:760px){.row,.notes{grid-template-columns:1fr}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<header>
|
||||
<div class="eyebrow">maths · <a href="../">two poles</a> · <a href="../zeta/">zeta ball</a> · <a href="../strut/">strut</a> · <a href="../dial/">dial</a> · <a href="../flow/">flow</a> · lamp at ½</div>
|
||||
<h1>Lamp at <i>½</i></h1>
|
||||
</header>
|
||||
<p class="lede">The zeta ball, projected from the other pole. Put the lamp at the touch point ½ and the screen at the top, tangent at ∞: the chart is <b>u = ¼ / (s − ½)</b>. The frame does not move — the poles still sit at ±½, the third strut at ±i/2, the circle |u| = ½ is where both charts agree — only the two ends of the ½–∞ strut have swapped. The old lamp, ∞, is now the centre; the old touch point, ½, is now at infinity. In this chart the functional equation is u ↦ −u and the whole dial is a rotation about the centre. And every zero, trivial and critical, falls into the origin: the trivial ones along the real axis from one side, the critical ones along the strut from both.</p>
|
||||
|
||||
<div class="bar" role="group" aria-label="controls">
|
||||
<span><label class="k">paint</label>
|
||||
<span class="seg"><button data-fn="0" aria-pressed="true">ζ</button><button data-fn="1" aria-pressed="false">ξ</button><button data-fn="2" aria-pressed="false">χ = ζ(s)/ζ(1−s)</button></span></span>
|
||||
<span><label class="k">zoom</label><input type="range" id="zoom" min="0" max="100" step="1" value="0"> <span id="zoomRead">±0.60</span></span>
|
||||
<span><label class="k">dial</label><input type="range" id="dial" min="0" max="360" step="1" value="0"> <span id="dialRead">0°</span>
|
||||
<span class="seg" style="margin-left:6px"><button data-stop="0">0</button><button data-stop="90">¼</button><button data-stop="180">½</button><button data-stop="270">¾</button></span></span>
|
||||
<label class="chk"><input type="checkbox" id="zeros" checked> zeros</label>
|
||||
<label class="chk"><input type="checkbox" id="frame" checked> frame</label>
|
||||
<label class="chk"><input type="checkbox" id="grid"> the ball's latitude & longitude</label>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<section class="pane">
|
||||
<h2 id="title">u = ¼ / (s − ½), centred on ∞</h2>
|
||||
<p class="sub" id="sub">window ±0.60 · strut gold, mirror white, poles teal, zeros white</p>
|
||||
<div class="lens" id="lens">
|
||||
<canvas id="paint"></canvas>
|
||||
<canvas id="marks"></canvas>
|
||||
<canvas id="fixed"></canvas>
|
||||
<span class="busy" id="busy">painting</span>
|
||||
</div>
|
||||
<p class="read" id="read">hover for u and s</p>
|
||||
</section>
|
||||
<div class="side">
|
||||
<div class="card">
|
||||
<h3>Cross-section through the real axis</h3>
|
||||
<svg id="xs" viewBox="0 0 100 58" aria-label="cross-section of the ball with both projections"></svg>
|
||||
<p>The ball of diameter ½ on the s-plane. <b>Old lamp</b> at the top, ∞, casting onto the floor: the poles land on 0 and 1. <b>New lamp</b> at the touch point ½, casting onto the ceiling: the same poles land directly above, at u = ∓½, and ∞ itself lands at the centre. The trivial zeros, bunched near the top of the ball, fall onto the ceiling at −1/(8n+2): −0.1, −0.056, −0.038, … into the centre from the left.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>Key</h3>
|
||||
<p><span class="line" style="background:var(--up)"></span>the strut: critical line Re s = ½, now the imaginary axis</p>
|
||||
<p><span class="line" style="background:#ddd"></span>the mirror: real axis of s, still the real axis</p>
|
||||
<p><span class="dot" style="background:var(--teal)"></span>the ball's poles s = 0, 1 at u = ∓½ (ζ's pole is the right one)</p>
|
||||
<p><span class="dot" style="background:#fff"></span>nontrivial zeros at u = ∓i/(4t)</p>
|
||||
<p><span class="dot" style="background:var(--teal);width:6px;height:6px"></span>trivial zeros at u = −1/(8n+2)</p>
|
||||
<p><span class="dot" style="background:var(--down)"></span>poles of χ at u = +1/(8n+2), the mirror images of its zeros</p>
|
||||
<p><span class="line" style="background:#777"></span>the circle |u| = ½: the ball's horizontal equator, the one circle both charts draw in the same place</p>
|
||||
<p><span class="line" style="background:var(--teal);opacity:.6"></span>the strip's edges Re s = 0 and Re s = 1: two circles of diameter ½ kissing at the centre. The critical strip is everything outside them, pinched to nothing at the lamp</p>
|
||||
<p>Hue is the argument of the value, brightness its size. The dial turns the ball about the ½–∞ strut; the chart's own crosshair stays put.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="notes">
|
||||
<div class="card">
|
||||
<h3>Same frame, other lamp</h3>
|
||||
<p>Stereographic projection from the top of a ball of diameter d onto the floor, and from the bottom onto the ceiling, differ by z ↦ d²/z̄. With d = ½ that is u = ¼/(s − ½), read from inside the ball so the mirror comes out the right way round. Points on the circle |s − ½| = ½ — through 0, 1 and ½ ± i/2 — are the ball's horizontal equator and land in the same place under both lamps. Everything else trades near for far: the small disc around ½ becomes the whole outside of the lens, and the whole far field of s becomes the small disc around the centre.</p>
|
||||
<p>So this is the chart centred on the point the s-plane does not have. The real origin ½ has gone to infinity and the imaginary origin ∞ sits at 0.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>The dial is a rotation</h3>
|
||||
<p>s ↦ 1 − s is u ↦ −u, a point reflection through the centre. Turning the ball by θ about the ½–∞ strut is u ↦ e<sup>−iθ</sup>u, a rotation of the plane. The quarter turn is the Wick rotation: it lays the strut, with every nontrivial zero on it, along the real axis. No Möbius map, no moving circles. Switch the paint to ξ and the half turn leaves the picture exactly as it was; with ζ it does not, and χ is the difference.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>The zeros fall in</h3>
|
||||
<p>Nontrivial zeros ½ ± it sit at u = ∓i/(4t): on the strut, approaching the centre from above and below. Trivial zeros −2n sit at u = −1/(8n + 2): on the mirror, approaching from the left only — the positive real axis carries ζ(2), ζ(3), … → 1 and no zeros at all. One-sided along the real line, two-sided along the imaginary one: the real line reaches the lamp as a ray, the imaginary line as a circle through it. Both sequences converge to a point of this chart. In the s-chart that point is missing, which is the only reason they looked like they ran away.</p>
|
||||
<p>The strip itself is the outside of two kissing circles, the images of Re s = 0 and Re s = 1. Zoom in and the strip narrows like a horn, its two edges tangent to the strut at the centre: in this chart the strip has no width at the lamp. That is a fact about the chart, not about the zeros — Davenport–Heilbronn's off-line zeros would fall into the same cusp — but it is why, seen from ∞, on the line and near the line are the same thing.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>χ, what the half turn leaves over</h3>
|
||||
<p>χ(s) = ζ(s)/ζ(1 − s) = 2<sup>s</sup>π<sup>s−1</sup> sin(πs/2) Γ(1 − s): the gamma factor by itself. Every nontrivial zero cancels out of it. What remains lives on the mirror — zeros at u = −½, −1/(8n+2) on the left, poles at u = +½, +1/(8n+2) on the right, each the point reflection of the other, since χ(s)χ(1 − s) = 1. On the strut |χ| = 1 exactly: pure phase, winding as the Riemann–Siegel θ(t). ζ on the line is that phase times a real amplitude, Hardy's Z. Fold χ in and the picture becomes ξ, symmetric under the half turn.</p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script type="text/plain" id="wsrc">
|
||||
const TAU=2*Math.PI,LN2=Math.LN2,LNPI=Math.log(Math.PI),HALF_LN_TAU=0.5*Math.log(TAU);
|
||||
const BC=[1/12,-1/720,1/30240,-1/1209600,1/47900160,-691/1307674368000,1/74724249600,-3617/10670622842880000,43867/5109094217170944000,-174611/802857662698291200000];
|
||||
// Hurwitz zeta(s,a) by Euler-Maclaurin; N grows with |s| so the Bernoulli tail converges
|
||||
function hurwitz(sr,si,a,out){
|
||||
const N=Math.min(560,Math.max(14,Math.ceil(Math.hypot(sr,si)/Math.PI)+12));
|
||||
let re=0,im=0;
|
||||
for(let n=0;n<N;n++){const l=Math.log(n+a),m=Math.exp(-sr*l),t=-si*l;re+=m*Math.cos(t);im+=m*Math.sin(t);}
|
||||
const lN=Math.log(N+a),m0=Math.exp(-sr*lN),t0=-si*lN,nr=m0*Math.cos(t0),ni=m0*Math.sin(t0);
|
||||
re+=nr/2;im+=ni/2;
|
||||
const ar=(N+a)*nr,ai=(N+a)*ni,br=sr-1,bi=si,d=br*br+bi*bi;
|
||||
re+=(ar*br+ai*bi)/d;im+=(ai*br-ar*bi)/d;
|
||||
let pr=sr,pi=si,sc=1/(N+a);
|
||||
for(let k=1;k<=BC.length;k++){
|
||||
const tr=pr*nr-pi*ni,ti=pr*ni+pi*nr,c=BC[k-1]*sc;re+=c*tr;im+=c*ti;
|
||||
if(Math.abs(c)*Math.hypot(tr,ti)<1e-16*Math.hypot(re,im))break;
|
||||
const xr=sr+2*k-1,yr=sr+2*k,qr=xr*yr-si*si,qi=si*(xr+yr);
|
||||
const npr=pr*qr-pi*qi;pi=pr*qi+pi*qr;pr=npr;sc/=(N+a)*(N+a);
|
||||
}
|
||||
out[0]=re;out[1]=im;
|
||||
}
|
||||
function zetaEM(sr,si,out){hurwitz(sr,si,1,out);}
|
||||
function lnGamma(zr,zi,out){ // Stirling with shift; Re z should not be very negative (callers reflect first)
|
||||
let accR=0,accI=0;
|
||||
while(zr<9){accR+=0.5*Math.log(zr*zr+zi*zi);accI+=Math.atan2(zi,zr);zr+=1;}
|
||||
const lr=0.5*Math.log(zr*zr+zi*zi),li=Math.atan2(zi,zr);
|
||||
let r=(zr-0.5)*lr-zi*li-zr+HALF_LN_TAU,i=(zr-0.5)*li+zi*lr-zi;
|
||||
const d=zr*zr+zi*zi,ir=zr/d,ii=-zi/d,i2r=ir*ir-ii*ii,i2i=2*ir*ii;let pr=ir,pi=ii;
|
||||
const cs=[1/12,-1/360,1/1260,-1/1680];
|
||||
for(let j=0;j<4;j++){r+=cs[j]*pr;i+=cs[j]*pi;const t=pr*i2r-pi*i2i;pi=pr*i2i+pi*i2r;pr=t;}
|
||||
out[0]=r-accR;out[1]=i-accI;
|
||||
}
|
||||
function logSin(x,y,out){
|
||||
if(y>15){out[0]=-LN2+y;out[1]=Math.PI/2-x;return;}
|
||||
if(y<-15){out[0]=-LN2-y;out[1]=x-Math.PI/2;return;}
|
||||
const re=Math.sin(x)*Math.cosh(y),im=Math.cos(x)*Math.sinh(y);
|
||||
out[0]=0.5*Math.log(re*re+im*im);out[1]=Math.atan2(im,re);
|
||||
}
|
||||
const T1=[0,0],T2=[0,0],T3=[0,0],T4=[0,0],T5=[0,0];
|
||||
// log chi(s), chi = 2^s pi^(s-1) sin(pi s/2) Gamma(1-s), for Re s <= 1/2 (Gamma argument then has Re >= 1/2)
|
||||
function logChiLeft(sr,si,out){
|
||||
logSin(Math.PI*sr/2,Math.PI*si/2,T1);lnGamma(1-sr,-si,T2);
|
||||
out[0]=sr*LN2+(sr-1)*LNPI+T1[0]+T2[0];
|
||||
out[1]=si*(LN2+LNPI)+T1[1]+T2[1];
|
||||
}
|
||||
function logChi(sr,si,out){ // chi(s) chi(1-s) = 1
|
||||
if(sr<=0.5){logChiLeft(sr,si,out);return;}
|
||||
logChiLeft(1-sr,-si,out);out[0]=-out[0];out[1]=-out[1];
|
||||
}
|
||||
function logZeta(sr,si,out){
|
||||
if(sr>=0.5){zetaEM(sr,si,T1);out[0]=0.5*Math.log(T1[0]*T1[0]+T1[1]*T1[1]);out[1]=Math.atan2(T1[1],T1[0]);return;}
|
||||
logChiLeft(sr,si,T3);zetaEM(1-sr,-si,T1);
|
||||
out[0]=T3[0]+0.5*Math.log(T1[0]*T1[0]+T1[1]*T1[1]);
|
||||
out[1]=T3[1]+Math.atan2(T1[1],T1[0]);
|
||||
}
|
||||
function logXi(sr,si,out){
|
||||
if(sr<0.5){sr=1-sr;si=-si;}
|
||||
const pr=0.5*(sr*(sr-1)-si*si),pi=0.5*si*(2*sr-1);
|
||||
lnGamma(sr/2,si/2,T4);logZeta(sr,si,T5);
|
||||
out[0]=0.5*Math.log(pr*pr+pi*pi)-sr/2*LNPI+T4[0]+T5[0];
|
||||
out[1]=Math.atan2(pi,pr)-si/2*LNPI+T4[1]+T5[1];
|
||||
}
|
||||
function hsv(h,s,v,buf,o){const i=Math.floor(h*6),f=h*6-i,p=v*(1-s),q=v*(1-f*s),t=v*(1-(1-f)*s);let r,g,b;
|
||||
switch(((i%6)+6)%6){case 0:r=v;g=t;b=p;break;case 1:r=q;g=v;b=p;break;case 2:r=p;g=v;b=t;break;case 3:r=p;g=q;b=v;break;case 4:r=t;g=p;b=v;break;default:r=v;g=p;b=q;}
|
||||
buf[o]=r*255;buf[o+1]=g*255;buf[o+2]=b*255;buf[o+3]=255;}
|
||||
const L=[0,0];
|
||||
function paint(sr,si,fn,buf,o){
|
||||
if(!isFinite(sr)||!isFinite(si)||Math.hypot(sr,si)>1700){buf[o]=buf[o+1]=buf[o+2]=40;buf[o+3]=255;return;}
|
||||
if(fn===1)logXi(sr,si,L);else if(fn===2)logChi(sr,si,L);else logZeta(sr,si,L);
|
||||
const scale=fn===1?4:fn===2?2:1;
|
||||
if(!isFinite(L[0])){const v=L[0]<0?0:255;buf[o]=buf[o+1]=buf[o+2]=v;buf[o+3]=255;return;}
|
||||
let h=(L[1]/TAU)%1;if(h<0)h+=1;
|
||||
const v=0.14+0.74*(0.5+Math.atan(0.7*L[0]/scale)/Math.PI);hsv(h,0.62,v,buf,o);
|
||||
}
|
||||
onmessage=(e)=>{
|
||||
const m=e.data,n=m.n,buf=new Uint8ClampedArray(n*n*4);
|
||||
for(let j=0;j<n;j++)for(let i=0;i<n;i++){
|
||||
const x=(i/(n-1)-0.5)*2*m.hw,y=-(j/(n-1)-0.5)*2*m.hw;
|
||||
const d=4*(x*x+y*y); // s = 1/2 + 1/(4u)
|
||||
paint(0.5+x/d,-y/d,m.fn,buf,(j*n+i)*4);
|
||||
}
|
||||
postMessage({id:m.id,n,buf},[buf.buffer]);
|
||||
};
|
||||
</script>
|
||||
<script>
|
||||
(()=>{
|
||||
const $=id=>document.getElementById(id);
|
||||
const ZEROS=[14.1347251,21.0220396,25.0108576,30.4248761,32.9350616,37.5861782,40.918719,43.3270733,48.0051509,49.7738325,52.9703215,56.4462477,59.347044,60.8317785,65.112544,67.0798105,69.5464017,72.0671577,75.7046907,77.1448401,79.337375,82.9103809,84.735493,87.4252746,88.8091112,92.4918993,94.651344,95.8706342,98.8311942,101.317851,103.725538,105.4466231,107.1686112,111.0295355,111.8746592,114.3202209,116.2266803,118.7907829,121.370125,122.9468293,124.2568186,127.5166839,129.5787042,131.0876885,133.4977372,134.7565098,138.1160421,139.736209,141.1237074,143.1118458,146.0009825,147.4227653,150.0535204,150.9252576,153.0246938,156.1129093,157.5975918,158.8499882,161.1889641,163.0307097,165.5370692,167.18444,169.0945154,169.9119765,173.4115365,174.7541915,176.4414343,178.3774078,179.916484,182.2070785,184.8744678,185.5987837,187.2289226,189.4161587,192.0266564,193.0797266,195.2653967,196.8764818,198.0153097,201.2647519,202.4935945,204.1896718,205.3946972,207.9062589,209.5765097,211.6908626,213.3479194,214.5470448,216.1695385,219.0675963,220.7149188,221.4307056,224.0070003,224.9833247,227.4214443,229.3374133,231.2501887,231.9872353,233.6934042,236.5242297,237.7698205,239.5554776,241.0491578,242.8232719,244.0708985,247.1369901,248.1019901,249.5736896,251.0149478,253.0699867,255.3062565,256.3807137,258.6104395,259.874407,260.8050845,263.5738939,265.5578518,266.6149738,267.9219151,269.970449,271.4940556,273.4596092,275.5874926,276.4520495,278.2507435,279.2292509,282.4651148,283.2111857,284.835964,286.6674454,287.9119205,289.5798549,291.8462913,293.5584341,294.9653696,295.5732549,297.9792771,299.8403261,301.6493255,302.6967496,304.8643713,305.7289126,307.2194961,310.1094631,311.1651415,312.4278012,313.9852857,315.4756161,317.7348059,318.8531043,321.1601343,322.1445587,323.4669696,324.8628661,327.4439013,329.0330717,329.9532397,331.4744676,333.6453785,334.2113548,336.8418504,338.3399929,339.8582167,341.0422611,342.0548775,344.6617029,346.3478706,347.2726776,349.3162609,350.4084193,351.878649,353.4889005,356.017575,357.1513023,357.9526851,359.743755,361.2893617,363.3313306,364.7360241,366.2127103,367.9935755,368.9684381,370.0509192,373.0619284,373.8648739,375.8259128,376.3240922,378.4366802,379.8729753,381.4844686,383.4435294,384.9561168,385.8613008,387.2228902,388.8461284,391.4560836,392.2450833,393.4277438,395.58287,396.3818542];
|
||||
const st={fn:0,zoom:0,theta:0,zeros:true,frame:true,grid:false};
|
||||
const hw=()=>0.6*Math.pow(10,-st.zoom/100*1.78); // ±0.60 down to ±0.01
|
||||
const src=$("wsrc").textContent,W=new Worker(URL.createObjectURL(new Blob([src],{type:"text/javascript"})));
|
||||
let jobId=0;const N_LO=150,N_HI=440;
|
||||
function draw(hi){const id=++jobId;$("busy").classList.add("on");W.postMessage({id,n:hi?N_HI:N_LO,fn:st.fn,hw:hw()});}
|
||||
W.onmessage=(e)=>{const m=e.data;if(m.id!==jobId)return;const c=$("paint");c.width=c.height=m.n;c.getContext("2d").putImageData(new ImageData(m.buf,m.n,m.n),0,0);$("busy").classList.remove("on");};
|
||||
function sizeOf(c){const S=Math.round(c.clientWidth*devicePixelRatio);if(c.width!==S){c.width=c.height=S;}return S;}
|
||||
const px=(x,y,S)=>[(x/(2*hw())+0.5)*S,(0.5-y/(2*hw()))*S];
|
||||
function marks(){ // everything attached to the ball: turns with the dial
|
||||
const c=$("marks"),S=sizeOf(c),g=c.getContext("2d");g.clearRect(0,0,S,S);const dpr=devicePixelRatio,h=hw();
|
||||
const circ=(cx,cy,r,col,wd)=>{g.strokeStyle=col;g.lineWidth=wd*dpr;g.beginPath();const[X,Y]=px(cx,cy,S);g.arc(X,Y,r/(2*h)*S,0,7);g.stroke();};
|
||||
const seg=(x0,y0,x1,y1,col,wd)=>{g.strokeStyle=col;g.lineWidth=wd*dpr;g.beginPath();let[a,b]=px(x0,y0,S);g.moveTo(a,b);[a,b]=px(x1,y1,S);g.lineTo(a,b);g.stroke();};
|
||||
const dot=(x,y,col,r)=>{const[X,Y]=px(x,y,S);if(X<-5||Y<-5||X>S+5||Y>S+5)return;g.fillStyle=col;g.beginPath();g.arc(X,Y,r*dpr,0,7);g.fill();};
|
||||
const ring=(x,y,col,r)=>{const[X,Y]=px(x,y,S);if(X<-5||Y<-5||X>S+5||Y>S+5)return;g.strokeStyle=col;g.lineWidth=1.1*dpr;g.beginPath();g.arc(X,Y,r*dpr,0,7);g.stroke();};
|
||||
if(st.grid){ // bipolar grid, foci ±1/2: the ball's own latitude (Apollonian circles) and longitude (circles through the poles)
|
||||
const a=0.5;
|
||||
for(let m=1;m<=10;m++){const k=Math.pow(2,m/2),cx=a*(k*k+1)/(k*k-1),r=2*a*k/(k*k-1);circ(cx,0,r,"rgba(160,150,190,.28)",0.8);circ(-cx,0,r,"rgba(160,150,190,.28)",0.8);}
|
||||
for(let m=1;m<12;m++){if(m===6)continue;const al=m*Math.PI/12,cy=a/Math.tan(al),r=Math.hypot(a,cy);circ(0,cy,r,"rgba(160,150,190,.28)",0.8);}
|
||||
}
|
||||
if(st.frame){
|
||||
seg(-3,0,3,0,"rgba(230,230,230,.55)",1); // mirror: real axis of s
|
||||
seg(0,-3,0,3,"rgba(240,180,76,.7)",1); // strut: critical line
|
||||
g.setLineDash([3*dpr,3*dpr]);circ(0.25,0,0.25,"rgba(79,209,197,.55)",0.9);circ(-0.25,0,0.25,"rgba(79,209,197,.55)",0.9);g.setLineDash([]); // Re s = 1 and Re s = 0: the strip's edges, kissing at the lamp
|
||||
dot(-0.5,0,"#4fd1c5",3.4);dot(0.5,0,"#4fd1c5",3.4); // poles of the ball, s = 0 and s = 1
|
||||
dot(0,0.5,"#9a93ad",2.6);dot(0,-0.5,"#9a93ad",2.6); // third strut, s = 1/2 ∓ i/2
|
||||
}
|
||||
if(st.zeros){ // rings, thinned so that at any zoom neighbours stay ≥ 5px apart
|
||||
const gap=5*dpr,scale=S/(2*h);let last=1e9;
|
||||
for(const t of ZEROS){const u=1/(4*t),p=u*scale;if(last-p<gap)continue;last=p;ring(0,-u,"#fff",2.6);ring(0,u,"#fff",2.6);}
|
||||
if(st.fn!==1){last=1e9;for(let n=1;n<=200;n++){const u=1/(8*n+2),p=u*scale;if(last-p<gap)continue;last=p;ring(-u,0,"#4fd1c5",2.2);if(st.fn===2)ring(u,0,"#e2559a",2.2);}}
|
||||
if(st.fn===2){ring(0.5,0,"#e2559a",4.4);ring(-0.5,0,"#4fd1c5",4.4);}
|
||||
}
|
||||
}
|
||||
function fixed(){ // the chart's own crosshair and the circle both charts share
|
||||
const c=$("fixed"),S=sizeOf(c),g=c.getContext("2d");g.clearRect(0,0,S,S);const dpr=devicePixelRatio,h=hw();
|
||||
g.strokeStyle="rgba(255,255,255,.14)";g.lineWidth=dpr;g.beginPath();g.moveTo(0,S/2);g.lineTo(S,S/2);g.moveTo(S/2,0);g.lineTo(S/2,S);g.stroke();
|
||||
g.strokeStyle="rgba(140,140,150,.7)";g.lineWidth=1.1*dpr;g.setLineDash([3*dpr,4*dpr]);g.beginPath();g.arc(S/2,S/2,0.5/(2*h)*S,0,7);g.stroke();g.setLineDash([]);
|
||||
g.fillStyle="#f0b44c";g.beginPath();g.arc(S/2,S/2,3*dpr,0,7);g.fill(); // the centre: s = ∞, the old lamp
|
||||
g.fillStyle="#9a93ad";g.font=`${11*dpr}px JetBrains Mono`;g.textAlign="center";
|
||||
g.fillText("∞",S/2+9*dpr,S/2-6*dpr);
|
||||
g.textAlign="left";g.fillText("½ is out past the rim",S/2+10*dpr,S-30*dpr);
|
||||
}
|
||||
function turn(){const r=`rotate(${st.theta}deg)`;$("paint").style.transform=r;$("marks").style.transform=r;}
|
||||
function all(){draw(false);clearTimeout(all._t);all._t=setTimeout(()=>draw(true),220);marks();fixed();
|
||||
$("sub").textContent=`window ±${hw().toFixed(2)} · `+(st.fn===2?"χ: zeros left, poles right, pure phase on the strut":st.fn===1?"ξ: symmetric under the half turn":"strut gold, mirror white, poles teal, zeros white");}
|
||||
document.querySelectorAll("[data-fn]").forEach(b=>b.onclick=()=>{st.fn=+b.dataset.fn;document.querySelectorAll("[data-fn]").forEach(x=>x.setAttribute("aria-pressed",x===b));all();});
|
||||
document.querySelectorAll("[data-stop]").forEach(b=>b.onclick=()=>{st.theta=+b.dataset.stop;$("dial").value=st.theta;$("dialRead").textContent=st.theta+"°";turn();});
|
||||
$("zoom").oninput=e=>{st.zoom=+e.target.value;$("zoomRead").textContent="±"+hw().toFixed(2);marks();fixed();clearTimeout($("zoom")._t);$("zoom")._t=setTimeout(all,120);};
|
||||
$("dial").oninput=e=>{st.theta=+e.target.value;$("dialRead").textContent=st.theta+"°";turn();};
|
||||
$("zeros").onchange=e=>{st.zeros=e.target.checked;marks();};
|
||||
$("frame").onchange=e=>{st.frame=e.target.checked;marks();};
|
||||
$("grid").onchange=e=>{st.grid=e.target.checked;marks();};
|
||||
$("lens").onmousemove=e=>{const r=$("lens").getBoundingClientRect(),h=hw();
|
||||
let x=((e.clientX-r.left)/r.width-0.5)*2*h,y=-((e.clientY-r.top)/r.height-0.5)*2*h;
|
||||
const th=st.theta*Math.PI/180,xr=x*Math.cos(th)-y*Math.sin(th),yr=x*Math.sin(th)+y*Math.cos(th); // undo the turn: screen → painted u
|
||||
const d=4*(xr*xr+yr*yr),sr=0.5+xr/d,si=-yr/d,f=v=>Math.abs(v)<1e-4?v.toExponential(2):v.toFixed(4);
|
||||
$("read").innerHTML=`u = <b>${f(xr)} ${yr<0?"−":"+"} ${f(Math.abs(yr))}i</b> s = ½ + ¼/u = <b>${f(sr)} ${si<0?"−":"+"} ${f(Math.abs(si))}i</b>`;};
|
||||
$("lens").onmouseleave=()=>{$("read").textContent="hover for u and s";};
|
||||
// cross-section: ball of diameter 1/2 resting at 1/2; floor y=0 (s), ceiling y=1/2 (u). SVG units: x' = 20 + 60x, y' = 48 - 60y
|
||||
(function xs(){const svg=$("xs"),X=x=>20+60*x,Y=y=>48-60*y,out=[];
|
||||
const el=(t,a,inner="")=>out.push(`<${t} ${Object.entries(a).map(([k,v])=>`${k}="${v}"`).join(" ")}>${inner}</${t}>`);
|
||||
el("line",{x1:X(-0.3),y1:Y(0),x2:X(1.3),y2:Y(0),stroke:"#ddd","stroke-width":.5});
|
||||
el("line",{x1:X(-0.3),y1:Y(0.5),x2:X(1.3),y2:Y(0.5),stroke:"#f0b44c","stroke-width":.5});
|
||||
el("circle",{cx:X(0.5),cy:Y(0.25),r:15,fill:"none",stroke:"#9a93ad","stroke-width":.6});
|
||||
const P0=[0.25,0.25],P1=[0.75,0.25],S=[0.5,0],Nn=[0.5,0.5];
|
||||
// old lamp: from N to the floor (dashed, muted)
|
||||
for(const P of[P0,P1]){const t=0.5/(0.5-P[1]);const fx=0.5+(P[0]-0.5)*t;el("line",{x1:X(Nn[0]),y1:Y(Nn[1]),x2:X(fx),y2:Y(0),stroke:"#777","stroke-width":.45,"stroke-dasharray":"1.2 1.2"});}
|
||||
// new lamp: from S through the poles to the ceiling (gold)
|
||||
for(const P of[P0,P1]){const t=0.5/P[1];const cx=0.5+(P[0]-0.5)*t;el("line",{x1:X(S[0]),y1:Y(S[1]),x2:X(cx),y2:Y(0.5),stroke:"#f0b44c","stroke-width":.6});}
|
||||
el("line",{x1:X(0.5),y1:Y(0),x2:X(0.5),y2:Y(0.5),stroke:"#f0b44c","stroke-width":.4,"stroke-dasharray":"1 1.4"});
|
||||
// trivial zeros on the ball's left arc, and their fall onto the ceiling
|
||||
for(let n=1;n<=5;n++){const ph=2*Math.atan(4*n+1),bx=0.5-0.25*Math.sin(ph),by=0.25-0.25*Math.cos(ph),cx=0.5-1/(8*n+2);
|
||||
el("line",{x1:X(S[0]),y1:Y(S[1]),x2:X(cx),y2:Y(0.5),stroke:"#4fd1c5","stroke-width":.35,opacity:.8});
|
||||
el("circle",{cx:X(bx),cy:Y(by),r:.9,fill:"#4fd1c5"});el("circle",{cx:X(cx),cy:Y(0.5),r:.8,fill:"#4fd1c5"});}
|
||||
for(const P of[P0,P1])el("circle",{cx:X(P[0]),cy:Y(P[1]),r:1.1,fill:"#4fd1c5"});
|
||||
el("circle",{cx:X(0.5),cy:Y(0),r:1.3,fill:"#f0b44c"});el("circle",{cx:X(0.5),cy:Y(0.5),r:1.1,fill:"#e9e4f2"});
|
||||
const tx=(x,y,s,a={})=>el("text",{x:X(x),y:Y(y),fill:a.fill||"#9a93ad","font-size":a.fs||3.1,"text-anchor":a.anchor||"middle","font-family":"JetBrains Mono, monospace"},s);
|
||||
tx(0,-0.055,"0");tx(0.5,-0.055,"½");tx(1,-0.055,"1");tx(1.26,-0.055,"s",{fill:"#ddd"});
|
||||
tx(0,0.55,"−½");tx(0.5,0.55,"0");tx(1,0.55,"½");tx(1.26,0.55,"u",{fill:"#f0b44c"});
|
||||
tx(0.5,-0.125,"new lamp · the touch point ½",{fill:"#f0b44c",fs:2.8});tx(0.5,0.63,"old lamp · ∞ · lands at the centre",{fill:"#e9e4f2",fs:2.8});
|
||||
tx(0.19,0.24,"0",{fill:"#4fd1c5",anchor:"end"});tx(0.81,0.24,"1",{fill:"#4fd1c5",anchor:"start"});
|
||||
tx(0.34,0.44,"−2, −4, …",{fill:"#4fd1c5",fs:2.6,anchor:"end"});
|
||||
svg.innerHTML=out.join("");})();
|
||||
addEventListener("resize",()=>{marks();fixed();});
|
||||
all();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user