Add power-ladder scene to opening paragraph
This commit is contained in:
parent
83cdeb5fce
commit
9d81de447d
2 changed files with 109 additions and 0 deletions
|
|
@ -10,6 +10,10 @@ draft = true
|
|||
|
||||
Take an antenna. Point it at the Moon. Transmit. A microscopic fraction of what you sent makes it across 384,000 km of empty space, smacks into the pocked gray surface, scatters off in every direction because the Moon is a deeply unimpressive mirror, and a microscopic fraction of that comes back. A few kilowatts leave; a few attowatts return. On the other side of the planet, someone pointed at the same Moon hears it.
|
||||
|
||||
<div id="scene-scale"></div>
|
||||
|
||||
A kilowatt is 1000 watts. An attowatt is 10<sup>-18</sup> watts. That's twenty-one decades between what you put in and what eventually comes out the other end, which is roughly the difference between the Sun's output and a single LED in somebody's living room.
|
||||
|
||||
This is moonbounce. As a method of having a radio contact it is absurd in a way that very little else in the hobby manages. You can sit a non-ham at a reasonable HF station and explain how a signal gets from Texas to Argentina, and if they squint they'll accept it. Try to explain moonbounce with a straight face and you look slightly unwell.
|
||||
|
||||
It does, annoyingly, just work. You need respectable antennas, respectable power, and a computer willing to spend about a minute per transmission sifting what came back from what didn't.
|
||||
|
|
|
|||
|
|
@ -133,6 +133,110 @@
|
|||
ctx.restore();
|
||||
}
|
||||
|
||||
// ---------- Scene 0: kW to aW power ladder ----------
|
||||
function sceneScale() {
|
||||
const s = scene('scene-scale', {
|
||||
height: 230,
|
||||
caption: 'A logarithmic ladder from 1 kW on the right to 1 aW on the left. Each tick is a factor of ten. There are twenty-one of them, and the pulse that starts out at the transmitter has to fall all the way to the far end before anyone can decode it.',
|
||||
});
|
||||
if (!s) return;
|
||||
|
||||
const marks = [
|
||||
{ p: 1e3, label: '1 kW — your transmitter', color: C.red },
|
||||
{ p: 1, label: '1 W — a handheld HT', color: C.orange },
|
||||
{ p: 1e-3, label: '1 mW — an indicator LED', color: C.yellow },
|
||||
{ p: 1e-6, label: '1 μW', color: C.green },
|
||||
{ p: 1e-9, label: '1 nW — strong Wi-Fi at the receiver', color: C.cyan },
|
||||
{ p: 1e-12, label: '1 pW', color: C.blue },
|
||||
{ p: 1e-15, label: '1 fW — weak HF DX at the antenna', color: '#b199e4' },
|
||||
{ p: 1e-18, label: '1 aW — what comes back from the Moon', color: C.magenta },
|
||||
];
|
||||
|
||||
const start = performance.now();
|
||||
function draw() {
|
||||
const { w, h } = s.getSize();
|
||||
const ctx = s.ctx;
|
||||
clear(ctx, w, h);
|
||||
|
||||
const m = { l: 30, r: 30, t: 70, b: 70 };
|
||||
const pW = w - m.l - m.r;
|
||||
const barY = m.t + 20;
|
||||
const barH = 14;
|
||||
|
||||
// log scale: logP from -18 to +3
|
||||
const xOf = logP => m.l + (logP + 18) / 21 * pW;
|
||||
|
||||
// Gradient bar
|
||||
const grad = ctx.createLinearGradient(m.l, 0, m.l + pW, 0);
|
||||
grad.addColorStop(0, 'rgba(199, 141, 224, 0.22)');
|
||||
grad.addColorStop(0.5, 'rgba(109, 197, 211, 0.30)');
|
||||
grad.addColorStop(1, 'rgba(229, 128, 137, 0.55)');
|
||||
ctx.fillStyle = grad;
|
||||
if (ctx.roundRect) { ctx.beginPath(); ctx.roundRect(m.l, barY, pW, barH, 4); ctx.fill(); }
|
||||
else ctx.fillRect(m.l, barY, pW, barH);
|
||||
ctx.strokeStyle = C.grid;
|
||||
ctx.lineWidth = 1;
|
||||
if (ctx.roundRect) { ctx.beginPath(); ctx.roundRect(m.l, barY, pW, barH, 4); ctx.stroke(); }
|
||||
else ctx.strokeRect(m.l, barY, pW, barH);
|
||||
|
||||
// Minor ticks at every decade (22 positions from -18..+3)
|
||||
for (let d = -18; d <= 3; d++) {
|
||||
const x = xOf(d);
|
||||
const important = marks.some(mk => Math.abs(Math.log10(mk.p) - d) < 0.01);
|
||||
ctx.strokeStyle = important ? C.fg : C.dim;
|
||||
ctx.lineWidth = important ? 1.5 : 0.75;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, barY - (important ? 10 : 5));
|
||||
ctx.lineTo(x, barY + barH + (important ? 10 : 5));
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Labels — alternate above / below to avoid collisions
|
||||
marks.forEach((mk, i) => {
|
||||
const x = xOf(Math.log10(mk.p));
|
||||
const above = i % 2 === 0;
|
||||
const y = above ? barY - 18 : barY + barH + 22;
|
||||
pill(ctx, mk.label, x, y, mk.color, 'center');
|
||||
});
|
||||
|
||||
// Animated pulse from kW (right) to aW (left) every 6 s
|
||||
const t = ((performance.now() - start) / 1000) % 6;
|
||||
const pulseLogP = 3 - (t / 6) * 21;
|
||||
const pulseX = xOf(pulseLogP);
|
||||
const pulseY = barY + barH / 2;
|
||||
// Visual radius shrinks *slightly* (log-visible), not exponentially or it becomes invisible
|
||||
const visualR = 3 + 12 * (pulseLogP + 18) / 21;
|
||||
// Glow
|
||||
const glow = ctx.createRadialGradient(pulseX, pulseY, 0, pulseX, pulseY, visualR * 3);
|
||||
glow.addColorStop(0, 'rgba(226, 195, 125, 0.9)');
|
||||
glow.addColorStop(0.5, 'rgba(226, 195, 125, 0.2)');
|
||||
glow.addColorStop(1, 'rgba(226, 195, 125, 0)');
|
||||
ctx.fillStyle = glow;
|
||||
ctx.beginPath(); ctx.arc(pulseX, pulseY, visualR * 3, 0, Math.PI * 2); ctx.fill();
|
||||
ctx.fillStyle = C.yellow;
|
||||
ctx.beginPath(); ctx.arc(pulseX, pulseY, visualR, 0, Math.PI * 2); ctx.fill();
|
||||
|
||||
// Current-power readout riding the pulse
|
||||
const currentW = Math.pow(10, pulseLogP);
|
||||
let currentStr;
|
||||
if (currentW >= 1) currentStr = `${currentW.toFixed(0)} W`;
|
||||
else if (currentW >= 1e-3) currentStr = `${(currentW * 1e3).toFixed(1)} mW`;
|
||||
else if (currentW >= 1e-6) currentStr = `${(currentW * 1e6).toFixed(1)} μW`;
|
||||
else if (currentW >= 1e-9) currentStr = `${(currentW * 1e9).toFixed(1)} nW`;
|
||||
else if (currentW >= 1e-12) currentStr = `${(currentW * 1e12).toFixed(1)} pW`;
|
||||
else if (currentW >= 1e-15) currentStr = `${(currentW * 1e15).toFixed(1)} fW`;
|
||||
else currentStr = `${(currentW * 1e18).toFixed(1)} aW`;
|
||||
pill(ctx, currentStr, pulseX, pulseY + 42, C.yellow, 'center');
|
||||
|
||||
// Title
|
||||
text(ctx, 'power out vs power back: 21 orders of magnitude', w / 2, 22, C.fg, 'center', 'alphabetic', 13);
|
||||
text(ctx, '(+30 dBW on one end, −180 dBW on the other)', w / 2, 40, C.dim, 'center', 'alphabetic', 11);
|
||||
|
||||
requestAnimationFrame(draw);
|
||||
}
|
||||
requestAnimationFrame(draw);
|
||||
}
|
||||
|
||||
// ---------- Scene 1: Earth-Moon geometry ----------
|
||||
function sceneGeometry() {
|
||||
const s = scene('scene-geometry', {
|
||||
|
|
@ -1155,6 +1259,7 @@
|
|||
|
||||
// Init after DOM
|
||||
function init() {
|
||||
sceneScale();
|
||||
sceneGeometry();
|
||||
sceneDistance();
|
||||
scenePathloss();
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue