Last active
June 18, 2026 21:12
-
-
Save arivero/16d17064d4c337ddf29bb8b460d131fa to your computer and use it in GitHub Desktop.
nuclear stability and fine structure constant
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!doctype html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |
| <title>EW–QCD Stability-Line Gadget</title> | |
| <style> | |
| :root { | |
| --bg: #f3f6fa; | |
| --panel: #ffffff; | |
| --panel2: #f7f9fc; | |
| --plot: #ffffff; | |
| --plot-field: #fbfcfe; | |
| --ink: #182235; | |
| --muted: #5d687a; | |
| --border: #d7dfe9; | |
| --grid: #dbe2eb; | |
| --axis: #748196; | |
| --valley: #006f9d; | |
| --band: rgba(0, 111, 157, 0.13); | |
| --alpha: #b86000; | |
| --fission: #c4385b; | |
| --proton: #7051bd; | |
| --neutron: #087d59; | |
| --w: #007f9f; | |
| --z: #7045b8; | |
| --vh: #27833d; | |
| --warn: #a45a00; | |
| } | |
| html, body { margin:0; padding:0; background:var(--bg); color:var(--ink); font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji"; } | |
| main { max-width: 1480px; margin: 0 auto; padding: 18px; } | |
| h1 { margin: 0 0 6px; font-size: 1.35rem; font-weight: 700; } | |
| p { color: var(--muted); line-height: 1.42; } | |
| code, .mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace; } | |
| .layout { display: grid; grid-template-columns: minmax(660px, 1.5fr) minmax(380px, 0.95fr); gap: 16px; align-items: start; } | |
| .card { background: var(--panel); border: 1px solid var(--border); border-radius: 14px; box-shadow: 0 10px 28px rgba(31,48,74,0.10); } | |
| .plot-card { padding: 10px; } | |
| #chart { width: 100%; aspect-ratio: 1 / 1; display:block; border-radius: 10px; background: var(--plot); touch-action: none; user-select: none; cursor: crosshair; } | |
| #chart.dragging { cursor: grabbing; } | |
| .side { padding: 14px; } | |
| .controls { display:grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px; } | |
| label { display:block; font-size: 0.78rem; color: var(--muted); margin-bottom: 4px; } | |
| input[type="number"], input[type="range"], select { width:100%; box-sizing:border-box; border: 1px solid #c9d3df; background: var(--panel2); color: var(--ink); border-radius: 8px; padding: 8px; font-size: 0.92rem; } | |
| input[type="checkbox"] { transform: translateY(1px); margin-right: 6px; } | |
| input[type="range"] { padding: 0; } | |
| button { border: 1px solid #b9c7d8; background: #edf3f9; color: var(--ink); border-radius: 9px; padding: 9px 10px; cursor:pointer; font-weight:600; } | |
| button:hover { background: #e2ebf5; } | |
| .row { display:flex; gap:10px; align-items:center; flex-wrap:wrap; margin: 10px 0; } | |
| .row > * { flex: 1; min-width: 130px; } | |
| .small { font-size: 0.84rem; color: var(--muted); } | |
| .warn { color: var(--warn); } | |
| table { width:100%; border-collapse: collapse; margin-top: 10px; font-size: 0.88rem; } | |
| td, th { border-bottom: 1px solid #e0e6ee; padding: 6px 7px; vertical-align: top; } | |
| th { color: var(--muted); text-align:left; font-weight:600; } | |
| td.num { text-align:right; font-variant-numeric: tabular-nums; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; } | |
| .legend { display:flex; flex-wrap:wrap; gap:9px 14px; padding: 8px 2px 0; color: var(--muted); font-size:0.86rem; } | |
| .chip { display:inline-flex; align-items:center; gap:6px; } | |
| .swatch { width:18px; height:3px; border-radius:3px; display:inline-block; } | |
| details { margin-top: 14px; background: #ffffff; border: 1px solid var(--border); border-radius: 12px; padding: 10px 12px; } | |
| summary { cursor:pointer; font-weight: 700; color: var(--ink); } | |
| .formula { overflow-x:auto; background:#f7f9fc; border:1px solid #d7dfe9; border-radius:10px; padding: 10px; color:#263650; } | |
| .footer-note { margin-top: 12px; font-size: 0.82rem; color: var(--muted); } | |
| .drag-note { margin: 0 0 8px; padding: 8px 10px; border: 1px solid #cfd9e5; border-radius: 9px; background: #f7fafc; color: #42516a; font-size: 0.86rem; } | |
| .embed-code { white-space: pre-wrap; overflow-wrap: anywhere; } | |
| body.embed main { max-width: none; padding: 8px; } | |
| body.embed h1, body.embed .intro, body.embed details { display: none; } | |
| body.embed .layout { margin: 0; } | |
| @media (max-width: 1050px) { .layout { grid-template-columns: 1fr; } .controls { grid-template-columns: 1fr 1fr; } } | |
| </style> | |
| </head> | |
| <body> | |
| <main> | |
| <h1>EW–QCD Stability-Line Gadget</h1> | |
| <p class="intro"> | |
| Move the three diagonal mass-number lines <span class="mono">A=N+Z=M/u</span>. The gadget converts | |
| <span class="mono">M_W</span>, <span class="mono">M_Z</span>, and <span class="mono">v/√2</span> into a tree-level electromagnetic coupling, | |
| inserts it into a liquid-drop Coulomb coefficient, and repaints the beta-stability valley plus its first stopping surface. | |
| </p> | |
| <div class="layout"> | |
| <section class="card plot-card"> | |
| <div class="drag-note"><strong>Interactive:</strong> move the three colored diagonal markers—W, Z, and the Higgs scale v/√2—or edit their values in the panel. The circular handle stays under the pointer.</div> | |
| <svg id="chart" viewBox="0 0 900 900" role="img" aria-label="N Z stability chart with interactive electroweak mass markers"></svg> | |
| <div class="legend"> | |
| <span class="chip"><span class="swatch" style="background:var(--w)"></span>interactive W marker</span> | |
| <span class="chip"><span class="swatch" style="background:var(--z)"></span>interactive Z marker</span> | |
| <span class="chip"><span class="swatch" style="background:var(--vh)"></span>interactive v/√2 marker</span> | |
| <span class="chip"><span class="swatch" style="background:var(--valley)"></span>beta-stability valley</span> | |
| <span class="chip"><span class="swatch" style="background:var(--band)"></span>finite-time beta-survival band</span> | |
| <span class="chip"><span class="swatch" style="background:var(--alpha)"></span>S<sub>α</sub>=0 dripline / endpoint</span> | |
| <span class="chip"><span class="swatch" style="background:var(--fission)"></span>fissility X=1 / endpoint</span> | |
| <span class="chip"><span class="swatch" style="background:var(--proton)"></span>S<sub>p</sub>=0 dripline / endpoint</span> | |
| <span class="chip"><span class="swatch" style="background:var(--neutron)"></span>S<sub>n</sub>=0 dripline / endpoint</span> | |
| </div> | |
| </section> | |
| <aside class="card side"> | |
| <div class="controls"> | |
| <div> | |
| <label for="Aw">W line: A<sub>W</sub>=M<sub>W</sub>/u</label> | |
| <input id="Aw" type="number" step="0.001" value="86.288" /> | |
| </div> | |
| <div> | |
| <label for="Az">Z line: A<sub>Z</sub>=M<sub>Z</sub>/u</label> | |
| <input id="Az" type="number" step="0.001" value="97.894" /> | |
| </div> | |
| <div> | |
| <label for="Avh">vev line: A<sub>v/√2</sub>=v/(√2u)</label> | |
| <input id="Avh" type="number" step="0.001" value="186.906" /> | |
| </div> | |
| </div> | |
| <div class="row"> | |
| <button id="reset">Reset PDG-like values</button> | |
| <div> | |
| <label for="deltaMode">beta valley</label> | |
| <select id="deltaMode"> | |
| <option value="atomic" selected>atomic Δβ = mₙ − m_H</option> | |
| <option value="isospin">isospin-clean Δβ = 0</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label for="alphaMode">alpha boundary</label> | |
| <select id="alphaMode"> | |
| <option value="ldpair" selected>self-consistent LD + pairing α</option> | |
| <option value="empirical">empirical α binding, Coulomb-shifted</option> | |
| <option value="off">ignore α decay</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div class="row"> | |
| <div> | |
| <label for="q0">β finite-time threshold Q<sub>0</sub> at physical G<sub>F</sub> [MeV]</label> | |
| <input id="q0" type="range" min="0.05" max="5" step="0.05" value="1" /> | |
| <div class="small">Q<sub>0</sub> = <span id="q0Text">1.00</span> MeV. Band obeys λ<sub>β</sub>∝G<sub>F</sub><sup>2</sup>Q<sup>5</sup>.</div> | |
| </div> | |
| <div> | |
| <label for="showDrips"><input id="showDrips" type="checkbox" checked style="width:auto; margin-right:6px; vertical-align:middle;">paint driplines</label> | |
| <div class="small">Contours S<sub>α</sub>=0, S<sub>p</sub>=0, S<sub>n</sub>=0, and X<sub>fiss</sub>=1.</div> | |
| </div> | |
| <div> | |
| <label for="constants">constants</label> | |
| <button id="toggleConstants">Show / hide model constants</button> | |
| </div> | |
| </div> | |
| <div class="row"> | |
| <div> | |
| <label for="lockAlpha"><input id="lockAlpha" type="checkbox">keep α<sub>em</sub>* fixed when moving v</label> | |
| <div class="small" id="alphaLockText">If enabled, moving or typing the v/√2 line rescales W and Z together, so α*=M<sub>W</sub><sup>2</sup>(1-M<sub>W</sub><sup>2</sup>/M<sub>Z</sub><sup>2</sup>)/(πv<sup>2</sup>) stays fixed. With Δβ and the nuclear coefficients held fixed, that leaves the static stability valley unchanged and changes G<sub>F</sub>, hence beta-decay times and the finite-time survival band.</div> | |
| </div> | |
| <div> | |
| <label for="lockAlphaV"><input id="lockAlphaV" type="checkbox">keep α<sub>em</sub>* and v fixed when moving W or Z</label> | |
| <div class="small" id="alphaVLockText">If enabled, moving W adjusts Z, and moving Z adjusts W, with v held fixed. For a given Z there are two W branches; the gadget keeps the branch closest to the current W.</div> | |
| </div> | |
| <div> | |
| <label for="showMagicGrid"><input id="showMagicGrid" type="checkbox" checked>use magic-number grid instead of uniform grid</label> | |
| <div class="small">Uses grid lines and tick labels at N,Z = 2, 8, 20, 28, 50, 82, 126, 184, rather than evenly spaced ticks.</div> | |
| </div> | |
| </div> | |
| <div id="constantsBox" style="display:none; margin-top:10px;" class="controls"> | |
| <div><label>a<sub>v</sub> [MeV]</label><input id="av" type="number" step="0.01" value="15.75"></div> | |
| <div><label>a<sub>s</sub> [MeV]</label><input id="as" type="number" step="0.01" value="17.80"></div> | |
| <div><label>a<sub>I</sub> [MeV]</label><input id="aI" type="number" step="0.01" value="23.70"></div> | |
| <div><label>a<sub>c,phys</sub> [MeV]</label><input id="ac" type="number" step="0.001" value="0.711"></div> | |
| <div><label>a<sub>p</sub> [MeV]</label><input id="ap" type="number" step="0.01" value="11.18"></div> | |
| <div><label>α<sub>phys</sub><sup>−1</sup></label><input id="ainv" type="number" step="0.0001" value="137.035999084"></div> | |
| </div> | |
| <table id="summary"></table> | |
| <div id="status" class="footer-note"></div> | |
| </aside> | |
| </div> | |
| <details open class="theory-details"> | |
| <summary>Equations used by the gadget</summary> | |
| <div class="formula mono"> | |
| knobs: x=v², y=M_W², z=M_Z²<br> | |
| α*(x,y,z) = y/(πx) · (1 − y/z)<br> | |
| c* = (a_c,phys / α_phys) α*<br><br> | |
| q = (N−Z)/(2Z), D(q)=Δβ+(4a_I+Δβ)q<br> | |
| A(q;c) = [D(q)/c]^{3/2} for endpoint equations<br> | |
| direct valley rendering: Z_β(A;c)=(4a_I+Δβ)A/(8a_I+2cA^{2/3}), N=A−Z_β<br> | |
| Z(q;c) = A/[2(1+q)], N(q;c)=A(1+2q)/[2(1+q)]<br><br> | |
| B(A,I;c)=a_v A − a_s A^{2/3} − c(A−I)²A^{−1/3}/4 − a_I I²/A<br><br> | |
| valley endpoints are intersections of A(q;c) with the contour equations:<br> | |
| alpha dripline: S_α=B(A,I) − B(A−4,I) − B_α(c)=0<br> | |
| fission line: X_fiss=cZ²/(2a_sA)=1, equivalently D(q)^3 − 64a_s²c(1+q)^4=0 on the valley<br> | |
| proton dripline: S_p=B(A,I) − B(A−1,I+1)=0<br> | |
| neutron dripline: S_n=B(A,I) − B(A−1,I−1)=0<br><br> | |
| q_max = min(q_α, q_F, q_p, q_n), Ä=A(q_max;c). | |
| </div> | |
| <p> | |
| The mass parabola at fixed A is moved by the Coulomb coefficient and the neutron-proton mass term. | |
| The drawn valley is parametrized directly by A, not by q, because q becomes a singular coordinate when | |
| c→0 with the atomic beta mass term Δβ>0. The A=1 point is anchored to hydrogen; the liquid-drop formula is | |
| only a smooth heavy-nucleus approximation there. | |
| In this liquid-drop model the static mass parabola at fixed A depends on the strong coefficients, the Coulomb coefficient c, and Δβ, but not directly on G_F. Thus, at fixed α, moving v changes only beta-decay rates <em>provided Δβ and the nuclear coefficients are frozen</em>. Along a more Standard-Model-like trajectory with fixed Yukawa couplings, changing v also changes quark and electron masses and therefore Δβ; this shifts the parabola minimum, although its curvature remains controlled by c and a_I. Direct weak-contact contributions to nuclear masses are neglected here because they are tiny. The blue translucent band is a finite-time diagnostic: | |
| if λ<sub>β</sub>∝G<sub>F</sub><sup>2</sup>Q<sup>5</sup>, then the Q-threshold for a fixed lifetime scales as | |
| Q<sub>τ</sub>∝G<sub>F</sub><sup>−2/5</sup>. For the discrete beta step of a quadratic mass parabola, the displayed half-width is approximately 1/2+Q<sub>τ</sub>/K<sub>Z</sub>. Increasing v weakens G<sub>F</sub> and widens the finite-time beta-survival band. | |
| </p> | |
| </details> | |
| <details class="embed-help"> | |
| <summary>Embed this standalone gadget elsewhere</summary> | |
| <p>This file has no external JavaScript, CSS, fonts, or network dependencies. Put the HTML file on any static host and embed it with an iframe. Add <code>?embed=1</code> to hide the title, introductory text, and explanatory panels.</p> | |
| <div class="formula mono embed-code"><iframe | |
| id="ew-qcd-gadget" | |
| src="/path/ew_qcd_stability_gadget.html?embed=1" | |
| title="EW–QCD stability-line gadget" | |
| loading="lazy" | |
| sandbox="allow-scripts" | |
| style="width:100%; height:1050px; border:0;" | |
| ></iframe></div> | |
| <p>For automatic height, the gadget posts <code>{type: "ew-qcd-gadget-height", height: ...}</code> to its parent. A parent page may listen for that message and update the iframe height after validating the message origin.</p> | |
| <div class="formula mono embed-code">const frame = document.getElementById('ew-qcd-gadget'); | |
| window.addEventListener('message', event => { | |
| if (event.data?.type === 'ew-qcd-gadget-height') { | |
| frame.style.height = `${event.data.height}px`; | |
| } | |
| });</div> | |
| </details> | |
| </main> | |
| <script> | |
| (() => { | |
| const embedMode = new URLSearchParams(window.location.search).get('embed') === '1'; | |
| if (embedMode) document.body.classList.add('embed'); | |
| const svg = document.getElementById('chart'); | |
| const NS = 'http://www.w3.org/2000/svg'; | |
| const U_GEV = 0.93149410242; | |
| const GF_PHYS = 1.1663787e-5; | |
| const B_ALPHA_PHYS = 28.295674; | |
| const DELTA_ATOMIC = 0.782333; // m_n - atomic hydrogen, MeV | |
| const SQRT2 = Math.sqrt(2); | |
| const PI = Math.PI; | |
| const ids = ['Aw','Az','Avh','deltaMode','alphaMode','q0','showDrips','lockAlpha','lockAlphaV','showMagicGrid','av','as','aI','ac','ap','ainv']; | |
| const el = Object.fromEntries(ids.map(id => [id, document.getElementById(id)])); | |
| const summary = document.getElementById('summary'); | |
| const status = document.getElementById('status'); | |
| const q0Text = document.getElementById('q0Text'); | |
| const alphaLockText = document.getElementById('alphaLockText'); | |
| const alphaVLockText = document.getElementById('alphaVLockText'); | |
| const state = { | |
| dragging: null, | |
| dragGeom: null, | |
| dragDom: null, | |
| Aw: +el.Aw.value, | |
| Az: +el.Az.value, | |
| Avh: +el.Avh.value, | |
| lockNotice: '', | |
| handleFraction: { Aw: 0.34, Az: 0.66, Avh: 0.50 } | |
| }; | |
| document.getElementById('reset').onclick = () => { | |
| state.Aw = 80.377 / U_GEV; | |
| state.Az = 91.1876 / U_GEV; | |
| state.Avh = 246.21965 / (SQRT2 * U_GEV); | |
| state.lockNotice = ''; | |
| el.Aw.value = state.Aw.toFixed(3); | |
| el.Az.value = state.Az.toFixed(3); | |
| el.Avh.value = state.Avh.toFixed(3); | |
| update(); | |
| }; | |
| document.getElementById('toggleConstants').onclick = () => { | |
| const box = document.getElementById('constantsBox'); | |
| box.style.display = box.style.display === 'none' ? 'grid' : 'none'; | |
| }; | |
| function setAvhLocked(newAvh) { | |
| const oldAvh = state.Avh; | |
| if (el.lockAlpha.checked && oldAvh > 0 && newAvh > 0) { | |
| const scale = newAvh / oldAvh; | |
| state.Aw *= scale; | |
| state.Az *= scale; | |
| el.Aw.value = state.Aw.toFixed(3); | |
| el.Az.value = state.Az.toFixed(3); | |
| } | |
| state.Avh = newAvh; | |
| el.Avh.value = state.Avh.toFixed(3); | |
| } | |
| function alphaFromStateValues(Aw, Az, Avh) { | |
| if (!(Aw > 0) || !(Az > Aw) || !(Avh > 0)) return NaN; | |
| return (Aw * Aw) / (2 * PI * Avh * Avh) * (1 - (Aw * Aw) / (Az * Az)); | |
| } | |
| function solveZFromAlphaWV(alphaTarget, Aw, Avh) { | |
| const K = 2 * PI * Avh * Avh * alphaTarget; | |
| if (!(alphaTarget >= 0) || !(Aw > 0) || !(Avh > 0) || !(Aw * Aw > K)) return null; | |
| const denom = 1 - K / (Aw * Aw); | |
| if (!(denom > 0)) return null; | |
| const Az = Aw / Math.sqrt(denom); | |
| return (Az > Aw) ? Az : null; | |
| } | |
| function solveWFromAlphaZV(alphaTarget, Az, Avh, preferAw) { | |
| const K = 2 * PI * Avh * Avh * alphaTarget; | |
| if (!(alphaTarget >= 0) || !(Az > 0) || !(Avh > 0)) return null; | |
| const disc = 1 - 4 * K / (Az * Az); | |
| if (!(disc >= 0)) return null; | |
| const s = Math.sqrt(Math.max(0, disc)); | |
| const wLow2 = 0.5 * Az * Az * (1 - s); | |
| const wHigh2 = 0.5 * Az * Az * (1 + s); | |
| const cands = [Math.sqrt(Math.max(0, wLow2)), Math.sqrt(Math.max(0, wHigh2))].filter(w => w > 0 && w < Az); | |
| if (!cands.length) return null; | |
| if (!(preferAw > 0)) return cands[0]; | |
| cands.sort((a,b) => Math.abs(a - preferAw) - Math.abs(b - preferAw)); | |
| return cands[0]; | |
| } | |
| function applyWZLock(changedKey, oldAlpha) { | |
| state.lockNotice = ''; | |
| if (!el.lockAlphaV.checked) return; | |
| if (!(oldAlpha >= 0) || !(state.Avh > 0)) return; | |
| const K = 2 * PI * state.Avh * state.Avh * oldAlpha; | |
| const eps = 1e-9; | |
| if (changedKey === 'Aw') { | |
| const minAw = Math.sqrt(Math.max(K, 0)); | |
| if (!(state.Aw > minAw)) { | |
| state.Aw = minAw * (1 + 1e-6); | |
| el.Aw.value = state.Aw.toFixed(3); | |
| state.lockNotice = 'W reached the lower domain boundary compatible with the locked α and v.'; | |
| } | |
| const AzNew = solveZFromAlphaWV(oldAlpha, state.Aw, state.Avh); | |
| if (AzNew !== null) { | |
| state.Az = Math.max(state.Aw + eps, AzNew); | |
| el.Az.value = state.Az.toFixed(3); | |
| } | |
| } else if (changedKey === 'Az') { | |
| const minAz = 2 * Math.sqrt(Math.max(K, 0)); | |
| if (!(state.Az > minAz)) { | |
| state.Az = minAz * (1 + 1e-6); | |
| el.Az.value = state.Az.toFixed(3); | |
| state.lockNotice = 'Z reached the branch point compatible with the locked α and v.'; | |
| } | |
| const AwNew = solveWFromAlphaZV(oldAlpha, state.Az, state.Avh, state.Aw); | |
| if (AwNew !== null) { | |
| state.Aw = Math.min(state.Az - eps, AwNew); | |
| el.Aw.value = state.Aw.toFixed(3); | |
| } | |
| } | |
| } | |
| for (const id of ids) { | |
| el[id].addEventListener('input', () => { | |
| state.lockNotice = ''; | |
| const oldAlpha = alphaFromStateValues(state.Aw, state.Az, state.Avh); | |
| if (id === 'Aw') { state.Aw = +el.Aw.value; applyWZLock('Aw', oldAlpha); } | |
| if (id === 'Az') { state.Az = +el.Az.value; applyWZLock('Az', oldAlpha); } | |
| if (id === 'Avh') setAvhLocked(+el.Avh.value); | |
| update(); | |
| }); | |
| } | |
| function constants() { | |
| return { | |
| av: +el.av.value, | |
| as: +el.as.value, | |
| aI: +el.aI.value, | |
| acPhys: +el.ac.value, | |
| ap: +el.ap.value, | |
| alphaPhys: 1 / (+el.ainv.value), | |
| delta: el.deltaMode.value === 'atomic' ? DELTA_ATOMIC : 0, | |
| alphaMode: el.alphaMode.value, | |
| showDrips: el.showDrips.checked, | |
| showMagicGrid: el.showMagicGrid.checked, | |
| q0: +el.q0.value | |
| }; | |
| } | |
| function params() { | |
| const C = constants(); | |
| const Aw = state.Aw, Az = state.Az, Avh = state.Avh; | |
| const MW = Aw * U_GEV; | |
| const MZ = Az * U_GEV; | |
| const v = SQRT2 * Avh * U_GEV; | |
| const alpha = (Aw * Aw) / (2 * PI * Avh * Avh) * (1 - (Aw * Aw) / (Az * Az)); | |
| const c = (C.acPhys / C.alphaPhys) * alpha; | |
| const GF = 1 / (SQRT2 * v * v); | |
| return { ...C, Aw, Az, Avh, MW, MZ, v, alpha, c, GF, betaScale: (GF / GF_PHYS) ** 2 }; | |
| } | |
| function D(q, P) { return P.delta + (4 * P.aI + P.delta) * q; } | |
| function ZbetaA(A, P) { | |
| // Continuous beta-stability valley as a function of A. This remains | |
| // regular when c -> 0; the q-parametrization A=[D(q)/c]^(3/2) does not. | |
| if (!(A > 0)) return NaN; | |
| const denom = 8 * P.aI + 2 * P.c * Math.pow(A, 2/3); | |
| if (!(denom > 0)) return NaN; | |
| return ((4 * P.aI + P.delta) * A) / denom; | |
| } | |
| function valleyNZA(A, P) { | |
| let Z = ZbetaA(A, P); | |
| if (!Number.isFinite(Z)) return null; | |
| // The liquid-drop parabola is not a light-nucleus mass table; clamp only | |
| // to keep the display physical near A=1 and under extreme knob values. | |
| Z = Math.max(0, Math.min(A, Z)); | |
| return { A, Z, N: A - Z }; | |
| } | |
| function Aof(q, P) { | |
| const d = D(q, P); | |
| if (!(P.c > 0) || d <= 0) return NaN; | |
| return Math.pow(d / P.c, 1.5); | |
| } | |
| function Iof(q, P) { | |
| const A = Aof(q, P); | |
| return q / (1 + q) * A; | |
| } | |
| function B(A, I, P) { | |
| if (!(A > 0) || Math.abs(I) > A) return NaN; | |
| return P.av * A - P.as * Math.pow(A, 2/3) - (P.c / 4) * (A - I) * (A - I) * Math.pow(A, -1/3) - P.aI * I * I / A; | |
| } | |
| function Balpha(P) { | |
| const kappa = Math.pow(4, 2/3); | |
| if (P.alphaMode === 'off') return NaN; | |
| if (P.alphaMode === 'empirical') return B_ALPHA_PHYS + kappa * (P.acPhys - P.c); | |
| return 4 * P.av - kappa * (P.as + P.c) + P.ap / 2; | |
| } | |
| function fAlpha(q, P) { | |
| if (P.alphaMode === 'off') return NaN; | |
| const A = Aof(q, P), I = Iof(q, P), ba = Balpha(P); | |
| if (!(A > 4) || !(ba > 0)) return NaN; | |
| return B(A, I, P) - B(A - 4, I, P) - ba; | |
| } | |
| function fFission(q, P) { return Math.pow(D(q, P), 3) - 64 * P.as * P.as * P.c * Math.pow(1 + q, 4); } | |
| function fProton(q, P) { | |
| const A = Aof(q, P), I = Iof(q, P); | |
| if (!(A > 1)) return NaN; | |
| return B(A, I, P) - B(A - 1, I + 1, P); | |
| } | |
| function fNeutron(q, P) { | |
| const A = Aof(q, P), I = Iof(q, P); | |
| if (!(A > 1)) return NaN; | |
| return B(A, I, P) - B(A - 1, I - 1, P); | |
| } | |
| function fAlphaNZ(N, Z, P) { | |
| if (P.alphaMode === 'off') return NaN; | |
| const A = N + Z, I = N - Z, ba = Balpha(P); | |
| if (!(N >= 2 && Z >= 2 && A > 4) || !(ba > 0)) return NaN; | |
| return B(A, I, P) - B(A - 4, I, P) - ba; | |
| } | |
| function fProtonNZ(N, Z, P) { | |
| const A = N + Z, I = N - Z; | |
| if (!(Z >= 1 && A > 1)) return NaN; | |
| return B(A, I, P) - B(A - 1, I + 1, P); | |
| } | |
| function fNeutronNZ(N, Z, P) { | |
| const A = N + Z, I = N - Z; | |
| if (!(N >= 1 && A > 1)) return NaN; | |
| return B(A, I, P) - B(A - 1, I - 1, P); | |
| } | |
| function fFissionNZ(N, Z, P) { | |
| const A = N + Z; | |
| if (!(A > 1 && Z > 0)) return NaN; | |
| return (P.c * Z * Z) / (2 * P.as * A) - 1; | |
| } | |
| function rootsInRange(fn, P, qMin=1e-7, qMax=8, steps=900) { | |
| const roots = []; | |
| let qPrev = qMin, fPrev = fn(qPrev, P); | |
| for (let i = 1; i <= steps; i++) { | |
| // Mildly logarithmic grid; gives detail near q=0 and still reaches large q. | |
| const t = i / steps; | |
| const q = qMin * Math.pow(qMax / qMin, t); | |
| const f = fn(q, P); | |
| if (Number.isFinite(fPrev) && Number.isFinite(f) && fPrev * f < 0) { | |
| let lo = qPrev, hi = q, flo = fPrev, fhi = f; | |
| for (let j = 0; j < 60; j++) { | |
| const mid = 0.5 * (lo + hi); | |
| const fm = fn(mid, P); | |
| if (!Number.isFinite(fm)) break; | |
| if (flo * fm <= 0) { hi = mid; fhi = fm; } | |
| else { lo = mid; flo = fm; } | |
| } | |
| roots.push(0.5 * (lo + hi)); | |
| } | |
| qPrev = q; | |
| fPrev = f; | |
| } | |
| return roots.filter(r => Number.isFinite(r) && r > 0); | |
| } | |
| function endpoint(P) { | |
| if (!(P.Az > P.Aw) || !(P.Avh > 0) || !(P.alpha >= 0)) { | |
| return { valid: false, reason: 'Invalid EW knobs: require M_Z > M_W, v > 0, and α* ≥ 0.' }; | |
| } | |
| if (!(P.c > 1e-14)) { | |
| const entries = [ | |
| { key: 'alpha', label: 'alpha', q: NaN, A: Infinity, color: 'var(--alpha)' }, | |
| { key: 'fission', label: 'fission', q: NaN, A: Infinity, color: 'var(--fission)' }, | |
| { key: 'proton', label: 'proton drip', q: NaN, A: Infinity, color: 'var(--proton)' }, | |
| { key: 'neutron', label: 'neutron drip', q: NaN, A: Infinity, color: 'var(--neutron)' } | |
| ]; | |
| return { valid: true, entries, winner: { key: 'unbounded', label: 'unbounded Coulomb-free limit', q: NaN, A: Infinity, color: 'var(--warn)' } }; | |
| } | |
| const alphaRoots = rootsInRange(fAlpha, P, 1e-7, 8); | |
| const alphaRoot = alphaRoots.filter(q => Aof(q, P) > 8).sort((a,b) => b-a)[0]; | |
| const fissionRoot = rootsInRange(fFission, P, 1e-7, 8).sort((a,b) => a-b)[0]; | |
| const protonRoot = rootsInRange(fProton, P, 1e-7, 8).sort((a,b) => a-b)[0]; | |
| const neutronRoot = rootsInRange(fNeutron, P, 1e-7, 8).sort((a,b) => a-b)[0]; | |
| const entries = [ | |
| { key: 'alpha', label: 'alpha', q: alphaRoot, color: 'var(--alpha)' }, | |
| { key: 'fission', label: 'fission', q: fissionRoot, color: 'var(--fission)' }, | |
| { key: 'proton', label: 'proton drip', q: protonRoot, color: 'var(--proton)' }, | |
| { key: 'neutron', label: 'neutron drip', q: neutronRoot, color: 'var(--neutron)' } | |
| ].map(e => ({ ...e, A: Number.isFinite(e.q) ? Aof(e.q, P) : Infinity })); | |
| const finite = entries.filter(e => Number.isFinite(e.A)); | |
| const winner = finite.sort((a,b) => a.A - b.A)[0] || null; | |
| return { valid: true, entries, winner }; | |
| } | |
| function fmt(x, digits=4) { | |
| if (!Number.isFinite(x)) return '∞'; | |
| const ax = Math.abs(x); | |
| if (ax !== 0 && (ax < 1e-3 || ax >= 1e5)) return x.toExponential(3); | |
| return x.toFixed(digits); | |
| } | |
| function fmtInt(x) { return Number.isFinite(x) ? Math.round(x).toString() : '∞'; } | |
| function ptFromNZ(N, Z, geom) { | |
| const x = geom.m.l + (N / geom.dom) * geom.w; | |
| const y = geom.m.t + (1 - Z / geom.dom) * geom.h; | |
| return [x, y]; | |
| } | |
| function NZFromClient(evt, geom) { | |
| const rect = svg.getBoundingClientRect(); | |
| const x = (evt.clientX - rect.left) * (900 / rect.width); | |
| const y = (evt.clientY - rect.top) * (900 / rect.height); | |
| const N = ((x - geom.m.l) / geom.w) * geom.dom; | |
| const Z = (1 - (y - geom.m.t) / geom.h) * geom.dom; | |
| return { x, y, N, Z }; | |
| } | |
| function make(tag, attrs={}, text='') { | |
| const node = document.createElementNS(NS, tag); | |
| for (const [k,v] of Object.entries(attrs)) node.setAttribute(k, v); | |
| if (text) node.textContent = text; | |
| return node; | |
| } | |
| function pathFromPoints(points) { | |
| return points.map((p,i) => (i ? 'L' : 'M') + p[0].toFixed(2) + ',' + p[1].toFixed(2)).join(' '); | |
| } | |
| function lineSegmentForA(A, geom) { | |
| const dom = geom.dom; | |
| let p1, p2; | |
| if (A <= dom) { p1 = ptFromNZ(0, A, geom); p2 = ptFromNZ(A, 0, geom); } | |
| else { p1 = ptFromNZ(A - dom, dom, geom); p2 = ptFromNZ(dom, A - dom, geom); } | |
| return [p1, p2]; | |
| } | |
| function visibleHandleFraction(A, geom, fraction) { | |
| if (!(A > 0)) return 0.5; | |
| const lo = Math.max(0, (A - geom.dom) / A); | |
| const hi = Math.min(1, geom.dom / A); | |
| return Math.max(lo, Math.min(hi, fraction)); | |
| } | |
| function pointerFractionOnLine(pos, A, geom) { | |
| if (!(A > 0)) return 0.5; | |
| // Orthogonal projection onto N+Z=A. During motion A=N_mouse+Z_mouse, | |
| // so this is exactly the pointer position unless a lock has clamped A. | |
| const nProjected = 0.5 * (pos.N - pos.Z + A); | |
| return visibleHandleFraction(A, geom, nProjected / A); | |
| } | |
| function distanceToSegment(px, py, x1, y1, x2, y2) { | |
| const dx = x2 - x1, dy = y2 - y1; | |
| const l2 = dx*dx + dy*dy; | |
| if (l2 === 0) return Math.hypot(px-x1, py-y1); | |
| let t = ((px-x1)*dx + (py-y1)*dy) / l2; | |
| t = Math.max(0, Math.min(1, t)); | |
| return Math.hypot(px - (x1 + t*dx), py - (y1 + t*dy)); | |
| } | |
| function contourPath(fnNZ, P, geom, grid=125) { | |
| const vals = Array.from({ length: grid + 1 }, () => new Float64Array(grid + 1)); | |
| const finite = Array.from({ length: grid + 1 }, () => new Uint8Array(grid + 1)); | |
| for (let i = 0; i <= grid; i++) { | |
| const N = geom.dom * i / grid; | |
| for (let j = 0; j <= grid; j++) { | |
| const Z = geom.dom * j / grid; | |
| const v = fnNZ(N, Z, P); | |
| vals[i][j] = v; | |
| finite[i][j] = Number.isFinite(v) ? 1 : 0; | |
| } | |
| } | |
| let d = ''; | |
| const interp = (a, b) => { | |
| const fa = a.f, fb = b.f; | |
| const t = Math.max(0, Math.min(1, fa / (fa - fb))); | |
| return { N: a.N + t * (b.N - a.N), Z: a.Z + t * (b.Z - a.Z) }; | |
| }; | |
| for (let i = 0; i < grid; i++) { | |
| const N0 = geom.dom * i / grid, N1 = geom.dom * (i + 1) / grid; | |
| for (let j = 0; j < grid; j++) { | |
| const Z0 = geom.dom * j / grid, Z1 = geom.dom * (j + 1) / grid; | |
| const c0 = { N:N0, Z:Z0, f:vals[i][j], ok:finite[i][j] }; | |
| const c1 = { N:N1, Z:Z0, f:vals[i+1][j], ok:finite[i+1][j] }; | |
| const c2 = { N:N1, Z:Z1, f:vals[i+1][j+1], ok:finite[i+1][j+1] }; | |
| const c3 = { N:N0, Z:Z1, f:vals[i][j+1], ok:finite[i][j+1] }; | |
| const cs = [c0,c1,c2,c3]; | |
| const ps = []; | |
| for (let k = 0; k < 4; k++) { | |
| const a = cs[k], b = cs[(k+1)&3]; | |
| if (!a.ok || !b.ok) continue; | |
| if (a.f === 0 && b.f === 0) continue; | |
| if ((a.f <= 0 && b.f >= 0) || (a.f >= 0 && b.f <= 0)) ps.push(interp(a,b)); | |
| } | |
| if (ps.length >= 2) { | |
| for (let k = 0; k + 1 < ps.length; k += 2) { | |
| const pA = ptFromNZ(ps[k].N, ps[k].Z, geom); | |
| const pB = ptFromNZ(ps[k+1].N, ps[k+1].Z, geom); | |
| d += `M${pA[0].toFixed(2)},${pA[1].toFixed(2)}L${pB[0].toFixed(2)},${pB[1].toFixed(2)}`; | |
| } | |
| } | |
| } | |
| } | |
| return d; | |
| } | |
| let lastGeom = null; | |
| function draw(P, E) { | |
| svg.replaceChildren(); | |
| const Awin = E.valid && E.winner ? E.winner.A : 250; | |
| // Do not let a huge endpoint under weak Coulomb zoom the origin away. | |
| // User mass lines still expand the plot; endpoints are capped for display. | |
| const endpointForZoom = Number.isFinite(Awin) ? Math.min(Awin, 1600) : 0; | |
| const autoDom = Math.max(260, 1.18 * Math.max(state.Aw, state.Az, state.Avh, endpointForZoom)); | |
| const dom = state.dragDom || autoDom; | |
| const geom = { dom, m: { l: 58, r: 22, t: 28, b: 58 } }; | |
| geom.w = 900 - geom.m.l - geom.m.r; | |
| geom.h = 900 - geom.m.t - geom.m.b; | |
| lastGeom = geom; | |
| // background | |
| svg.appendChild(make('rect', { x:0, y:0, width:900, height:900, fill:'var(--plot)', rx:10 })); | |
| svg.appendChild(make('rect', { x:geom.m.l, y:geom.m.t, width:geom.w, height:geom.h, fill:'var(--plot-field)', stroke:'var(--border)' })); | |
| // grid and axes | |
| const magicValsAll = [2, 8, 20, 28, 50, 82, 126, 184]; | |
| const tickVals = P.showMagicGrid | |
| ? [0].concat(magicValsAll.filter(v => v <= dom)) | |
| : (() => { | |
| const niceStep = dom <= 300 ? 25 : dom <= 800 ? 50 : dom <= 1600 ? 100 : 250; | |
| const arr = []; | |
| for (let v = 0; v <= dom + 1e-9; v += niceStep) arr.push(v); | |
| return arr; | |
| })(); | |
| for (const v of tickVals) { | |
| const [x0,y0] = ptFromNZ(v, 0, geom); | |
| const [x1,y1] = ptFromNZ(v, dom, geom); | |
| svg.appendChild(make('line', { x1:x0, y1:y0, x2:x1, y2:y1, stroke:'var(--grid)', 'stroke-width':1 })); | |
| if (v > 0) svg.appendChild(make('text', { x:x0, y:geom.m.t+geom.h+22, fill:'var(--muted)', 'font-size':12, 'text-anchor':'middle' }, String(Math.round(v)))); | |
| const [xx0,yy0] = ptFromNZ(0, v, geom); | |
| const [xx1,yy1] = ptFromNZ(dom, v, geom); | |
| svg.appendChild(make('line', { x1:xx0, y1:yy0, x2:xx1, y2:yy1, stroke:'var(--grid)', 'stroke-width':1 })); | |
| if (v > 0) svg.appendChild(make('text', { x:geom.m.l-10, y:yy0+4, fill:'var(--muted)', 'font-size':12, 'text-anchor':'end' }, String(Math.round(v)))); | |
| } | |
| svg.appendChild(make('line', { x1:geom.m.l, y1:geom.m.t+geom.h, x2:geom.m.l+geom.w, y2:geom.m.t+geom.h, stroke:'var(--axis)', 'stroke-width':1.5 })); | |
| svg.appendChild(make('line', { x1:geom.m.l, y1:geom.m.t, x2:geom.m.l, y2:geom.m.t+geom.h, stroke:'var(--axis)', 'stroke-width':1.5 })); | |
| svg.appendChild(make('text', { x:geom.m.l+geom.w/2, y:888, fill:'var(--ink)', 'font-size':15, 'text-anchor':'middle' }, 'N')); | |
| svg.appendChild(make('text', { x:18, y:geom.m.t+geom.h/2, fill:'var(--ink)', 'font-size':15, 'text-anchor':'middle', transform:`rotate(-90 18 ${geom.m.t+geom.h/2})` }, 'Z')); | |
| // N=Z line | |
| const pNZ0 = ptFromNZ(0,0,geom), pNZ1 = ptFromNZ(dom,dom,geom); | |
| svg.appendChild(make('line', { x1:pNZ0[0], y1:pNZ0[1], x2:pNZ1[0], y2:pNZ1[1], stroke:'#b4bfcd', 'stroke-width':1.2, 'stroke-dasharray':'5 5' })); | |
| svg.appendChild(make('text', { x:pNZ1[0]-8, y:pNZ1[1]+15, fill:'#6f7c8f', 'font-size':12, 'text-anchor':'end' }, 'N=Z')); | |
| // Diagonal draggable A lines. | |
| const lines = [ | |
| ['Aw', state.Aw, 'W', 'var(--w)'], | |
| ['Az', state.Az, 'Z', 'var(--z)'], | |
| ['Avh', state.Avh, 'v/√2', 'var(--vh)'] | |
| ]; | |
| for (const [key, A, label, color] of lines) { | |
| if (!(A > 0) || A > 2*dom) continue; | |
| const [p1,p2] = lineSegmentForA(A, geom); | |
| svg.appendChild(make('line', { x1:p1[0], y1:p1[1], x2:p2[0], y2:p2[1], stroke:color, 'stroke-width':15, 'stroke-linecap':'round', opacity:0.001, cursor:'grab', 'data-line':key })); | |
| svg.appendChild(make('line', { x1:p1[0], y1:p1[1], x2:p2[0], y2:p2[1], stroke:color, 'stroke-width':3.2, 'stroke-linecap':'round', 'data-line':key, opacity:0.95, cursor:'grab' })); | |
| const handleT = visibleHandleFraction(A, geom, state.handleFraction[key] ?? 0.5); | |
| const handleN = handleT * A; | |
| const handleZ = (1 - handleT) * A; | |
| const mid = ptFromNZ(handleN, handleZ, geom); | |
| svg.appendChild(make('circle', { cx:mid[0], cy:mid[1], r:8, fill:'#ffffff', stroke:color, 'stroke-width':3, cursor:'grab', 'data-line':key, 'aria-label':`${label} control` })); | |
| const labelText = key === 'Avh' ? `v/√2 (Higgs scale) · A=${A.toFixed(1)}` : `${label} · A=${A.toFixed(1)}`; | |
| const boxWidth = key === 'Avh' ? 190 : 112; | |
| const placeLeft = mid[0] > geom.m.l + 0.68 * geom.w; | |
| const boxX = placeLeft ? mid[0] - boxWidth - 12 : mid[0] + 12; | |
| const textX = boxX + 8; | |
| svg.appendChild(make('rect', { x:boxX, y:mid[1]-25, width:boxWidth, height:24, rx:6, fill:'#ffffff', stroke:color, 'stroke-width':1.2, opacity:0.96, cursor:'grab', 'data-line':key })); | |
| svg.appendChild(make('text', { x:textX, y:mid[1]-8, fill:color, 'font-size':13, 'font-weight':700, cursor:'grab', 'data-line':key }, labelText)); | |
| } | |
| if (!E.valid || !E.winner) { | |
| svg.appendChild(make('text', { x:450, y:450, fill:'var(--warn)', 'font-size':18, 'text-anchor':'middle' }, E.reason || 'No finite endpoint found')); | |
| return; | |
| } | |
| // Full one-particle/alpha driplines as implicit zero contours. | |
| if (P.showDrips) { | |
| const dripDefs = [ | |
| [fAlphaNZ, 'var(--alpha)', '6 5', 2.0], | |
| [fFissionNZ, 'var(--fission)', '3 4', 1.7], | |
| [fProtonNZ, 'var(--proton)', '7 4', 2.0], | |
| [fNeutronNZ, 'var(--neutron)', '7 4', 2.0] | |
| ]; | |
| for (const [fn, color, dash, width] of dripDefs) { | |
| const d = contourPath(fn, P, geom, 115); | |
| if (d) svg.appendChild(make('path', { d, fill:'none', stroke:color, 'stroke-width':width, 'stroke-dasharray':dash, opacity:0.75 })); | |
| } | |
| } | |
| // Beta finite-time survival band and valley, parametrized by A rather than q. | |
| // This is regular in the W -> 0, alpha* -> 0 limit and visibly starts at hydrogen. | |
| const nPts = 620; | |
| const lower = [], upper = [], valley = []; | |
| const Qcrit = P.q0 * Math.pow(GF_PHYS / P.GF, 2/5); | |
| const endpointA = E.winner && Number.isFinite(E.winner.A) ? E.winner.A : Infinity; | |
| const AStop = Math.max(2, Math.min(endpointA, 2 * geom.dom)); | |
| // Physical light anchor: the continuum liquid-drop parabola is not valid at A=1. | |
| // With the atomic beta mass term, A=1 is hydrogen, not the fractional LD point. | |
| if (el.deltaMode.value === 'atomic') valley.push(ptFromNZ(0, 1, geom)); | |
| for (let i = 0; i <= nPts; i++) { | |
| const t = i / nPts; | |
| // Slightly concave sampling: more detail near the origin without losing the tail. | |
| const A = 1 + (AStop - 1) * t * t; | |
| const nz = valleyNZA(A, P); | |
| if (!nz || !Number.isFinite(nz.N) || !Number.isFinite(nz.Z)) continue; | |
| const {N, Z} = nz; | |
| if (N < -1e-6 || Z < -1e-6 || N > geom.dom*1.05 || Z > geom.dom*1.05) continue; | |
| valley.push(ptFromNZ(N, Z, geom)); | |
| const Kz = 2 * P.c * Math.pow(A, -1/3) + 8 * P.aI / A; | |
| const dZ = Math.min(60, 0.5 + Qcrit / Math.max(Kz, 1e-6)); | |
| const Zhi = Math.min(A, Z + dZ), Nhi = A - Zhi; | |
| const Zlo = Math.max(0, Z - dZ), Nlo = A - Zlo; | |
| upper.push(ptFromNZ(Nhi, Zhi, geom)); | |
| lower.push(ptFromNZ(Nlo, Zlo, geom)); | |
| } | |
| if (upper.length > 3) { | |
| const bandPoints = upper.concat(lower.reverse()); | |
| svg.appendChild(make('path', { d: pathFromPoints(bandPoints) + ' Z', fill:'var(--band)', stroke:'none' })); | |
| } | |
| if (valley.length > 2) svg.appendChild(make('path', { d:pathFromPoints(valley), fill:'none', stroke:'var(--valley)', 'stroke-width':3.2, 'stroke-linecap':'round' })); | |
| // Mark the hydrogen anchor explicitly; it is a light-nucleus boundary condition, not LD physics. | |
| if (el.deltaMode.value === 'atomic') { | |
| const hp = ptFromNZ(0, 1, geom); | |
| svg.appendChild(make('circle', { cx:hp[0], cy:hp[1], r:4.5, fill:'var(--ink)', stroke:'#ffffff', 'stroke-width':1.5 })); | |
| svg.appendChild(make('text', { x:hp[0]+7, y:hp[1]-7, fill:'var(--ink)', 'font-size':12, 'font-weight':700 }, 'H')); | |
| } | |
| // Endpoint dots. | |
| for (const ep of E.entries) { | |
| if (!Number.isFinite(ep.A) || !Number.isFinite(ep.q)) continue; | |
| const Z = ep.A / (2*(1+ep.q)); | |
| const N = ep.A - Z; | |
| const [x,y] = ptFromNZ(N, Z, geom); | |
| svg.appendChild(make('circle', { cx:x, cy:y, r: ep === E.winner ? 7 : 5, fill:ep.color, stroke:'#ffffff', 'stroke-width':2 })); | |
| svg.appendChild(make('text', { x:x+8, y:y-8, fill:ep.color, 'font-size':12, 'font-weight':700 }, `${ep.label} A≈${Math.round(ep.A)}`)); | |
| } | |
| } | |
| function updateTable(P, E) { | |
| q0Text.textContent = (+el.q0.value).toFixed(2); | |
| const rows = []; | |
| rows.push(['M_W', `${fmt(P.MW,4)} GeV`, `A_W=${fmt(P.Aw,3)}`]); | |
| rows.push(['M_Z', `${fmt(P.MZ,4)} GeV`, `A_Z=${fmt(P.Az,3)}`]); | |
| rows.push(['v', `${fmt(P.v,4)} GeV`, `A_v=${fmt(SQRT2*P.Avh,3)}, A_{v/√2}=${fmt(P.Avh,3)}`]); | |
| rows.push(['G_F', `${fmt(P.GF,7)} GeV⁻²`, `rate scale ${(P.betaScale).toExponential(3)}`]); | |
| rows.push(['α*', `${fmt(P.alpha,8)}`, `1/α*=${fmt(1/P.alpha,3)}`]); | |
| rows.push(['c*', `${fmt(P.c,4)} MeV`, `c*/a_c,phys=${fmt(P.c/P.acPhys,4)}`]); | |
| rows.push(['Δβ treatment', el.deltaMode.value === 'atomic' ? `${DELTA_ATOMIC.toFixed(6)} MeV` : '0', 'held fixed while the EW mass markers move']); | |
| rows.push(['α lock on v', el.lockAlpha.checked ? 'on' : 'off', el.lockAlpha.checked ? 'changing v rescales W and Z together, leaving α* fixed' : 'moving v changes α* unless W and Z are adjusted manually']); | |
| rows.push(['α,v lock on W/Z', el.lockAlphaV.checked ? 'on' : 'off', el.lockAlphaV.checked ? 'moving W adjusts Z and moving Z adjusts W at fixed v' : 'W and Z move independently']); | |
| rows.push(['W branch at fixed Z', P.Aw < P.Az / SQRT2 ? 'lower-W' : 'upper-W', 'the two branches meet at W=Z/√2']); | |
| rows.push(['grid mode', P.showMagicGrid ? 'magic' : 'uniform', P.showMagicGrid ? 'magic numbers replace the ordinary coordinate grid' : 'ordinary evenly spaced coordinate grid']); | |
| if (E.valid && E.winner) { | |
| for (const ep of E.entries) rows.push([`A_${ep.key}`, fmtInt(ep.A), `q=${fmt(ep.q,4)}`]); | |
| const q = E.winner.q, A = E.winner.A; | |
| if (Number.isFinite(A) && Number.isFinite(q)) { | |
| const Z = A / (2*(1+q)), N = A - Z; | |
| const Kz = 2 * P.c * Math.pow(A, -1/3) + 8 * P.aI / A; | |
| const Qcrit = P.q0 * Math.pow(GF_PHYS / P.GF, 2/5); | |
| const betaHalf = 0.5 + Qcrit / Math.max(Kz, 1e-9); | |
| rows.push(['winner', E.winner.label, `Ä≈${fmt(A,2)}, Z≈${fmt(Z,1)}, N≈${fmt(N,1)}`]); | |
| rows.push(['K_Z at endpoint', `${fmt(Kz,4)} MeV/charge²`, `β-band halfwidth≈${fmt(betaHalf,2)} charge units`]); | |
| } else { | |
| rows.push(['winner', E.winner.label, 'Ä=∞ in this liquid-drop limit']); | |
| rows.push(['near origin', el.deltaMode.value === 'atomic' ? 'anchored at H' : 'N=Z line', 'display is A-parametrized, not q-parametrized']); | |
| } | |
| } | |
| summary.innerHTML = '<tr><th>quantity</th><th class="num">value</th><th>note</th></tr>' + rows.map(r => `<tr><td>${r[0]}</td><td class="num">${r[1]}</td><td>${r[2]}</td></tr>`).join(''); | |
| if (!E.valid) status.innerHTML = `<span class="warn">${E.reason}</span>`; | |
| else status.textContent = (state.lockNotice ? state.lockNotice + ' ' : '') + 'Move any colored diagonal marker, or type a value, to repaint the chart. Endpoints are exact finite differences except fission, which is the usual fissility condition.'; | |
| alphaLockText.textContent = el.lockAlpha.checked ? 'Lock is on: changing the v/√2 line rescales W and Z together, preserving α*. With Δβ and the nuclear coefficients frozen, the static valley stays put while G_F changes beta-decay times and the finite-time survival band.' : 'If enabled, moving or typing the v/√2 line rescales W and Z together, so α*=M_W²(1-M_W²/M_Z²)/(πv²) stays fixed.'; | |
| alphaVLockText.textContent = el.lockAlphaV.checked ? 'Lock is on: v is held fixed while moving W or Z, and the companion line is adjusted to preserve α*. For a given Z the code keeps the W branch closest to the current one.' : 'If enabled, moving W adjusts Z, and moving Z adjusts W, with v held fixed. For a given Z there are two W branches; the gadget keeps the branch closest to the current W.'; | |
| } | |
| function reportHeight() { | |
| if (window.parent === window) return; | |
| requestAnimationFrame(() => { | |
| window.parent.postMessage({ type: 'ew-qcd-gadget-height', height: document.documentElement.scrollHeight }, '*'); | |
| }); | |
| } | |
| function update() { | |
| const P = params(); | |
| const E = endpoint(P); | |
| draw(P, E); | |
| updateTable(P, E); | |
| reportHeight(); | |
| } | |
| svg.addEventListener('pointerdown', (evt) => { | |
| if (!lastGeom) return; | |
| const geom = lastGeom; | |
| const pos = NZFromClient(evt, geom); | |
| let best = null; | |
| const directKey = evt.target && evt.target.getAttribute ? evt.target.getAttribute('data-line') : null; | |
| if (directKey && ['Aw','Az','Avh'].includes(directKey)) { | |
| best = { key: directKey, d: 0 }; | |
| } else { | |
| for (const [key,A] of [['Aw', state.Aw], ['Az', state.Az], ['Avh', state.Avh]]) { | |
| if (!(A > 0) || A > 2*geom.dom) continue; | |
| const [p1,p2] = lineSegmentForA(A, geom); | |
| const d = distanceToSegment(pos.x, pos.y, p1[0], p1[1], p2[0], p2[1]); | |
| if (best === null || d < best.d) best = { key, d }; | |
| } | |
| } | |
| if (best && best.d < 18) { | |
| const selectedA = state[best.key]; | |
| state.handleFraction[best.key] = pointerFractionOnLine(pos, selectedA, geom); | |
| state.dragging = best.key; | |
| svg.classList.add('dragging'); | |
| state.dragGeom = { ...geom, m: { ...geom.m } }; | |
| state.dragDom = geom.dom; | |
| svg.setPointerCapture(evt.pointerId); | |
| evt.preventDefault(); | |
| } | |
| }); | |
| svg.addEventListener('pointermove', (evt) => { | |
| if (!state.dragging || !state.dragGeom) return; | |
| const pos = NZFromClient(evt, state.dragGeom); | |
| let A = pos.N + pos.Z; | |
| A = Math.max(1, Math.min(5000, A)); | |
| state.lockNotice = ''; | |
| const oldAlpha = alphaFromStateValues(state.Aw, state.Az, state.Avh); | |
| if (state.dragging === 'Avh') setAvhLocked(A); | |
| else if (state.dragging === 'Aw') { | |
| state.Aw = A; | |
| el.Aw.value = A.toFixed(3); | |
| applyWZLock('Aw', oldAlpha); | |
| } else if (state.dragging === 'Az') { | |
| state.Az = A; | |
| el.Az.value = A.toFixed(3); | |
| applyWZLock('Az', oldAlpha); | |
| } | |
| const activeA = state[state.dragging]; | |
| state.handleFraction[state.dragging] = pointerFractionOnLine(pos, activeA, state.dragGeom); | |
| update(); | |
| }); | |
| function endDrag() { | |
| if (!state.dragging) return; | |
| state.dragging = null; | |
| svg.classList.remove('dragging'); | |
| state.dragGeom = null; | |
| state.dragDom = null; | |
| update(); | |
| } | |
| svg.addEventListener('pointerup', endDrag); | |
| svg.addEventListener('pointercancel', endDrag); | |
| svg.addEventListener('lostpointercapture', endDrag); | |
| window.addEventListener('resize', update); | |
| update(); | |
| })(); | |
| </script> | |
| </body> | |
| </html> |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
see as https://gisthost.github.io/?16d17064d4c337ddf29bb8b460d131fa