diff --git a/frontend/js/phys-fx.js b/frontend/js/phys-fx.js index 8da557c..1c8fd79 100644 --- a/frontend/js/phys-fx.js +++ b/frontend/js/phys-fx.js @@ -2025,119 +2025,128 @@ class EnergyLevels { update(){} render(){ if (!this.el) return; - const W = this.W, H = this.H; - const padTop = 36, padBot = 30; + const W = this.W; + /* Внутренняя высота увеличена для читаемости */ + const H = Math.max(this.H, 440); + const padTop = 36, padBot = 32; const bandW = 16, bandLeft = 4; - const leftLine = 170, rightLine = W - 80; + const leftLine = 170, rightLine = W - 82; const nMax = 6; const nFrom = this.n_from, nTo = this.n_to; function E(n){ return -13.6 / (n * n); } + + /* Разрывная шкала: + — верхняя зона (70% высоты): n=2..6 + ионизация, линейная по E + — нижняя зона (30% высоты): только n=1, сжата с маркером разрыва */ + const upperH = Math.round((H - padTop - padBot) * 0.72); + const splitY = padTop + upperH; /* граница зон в пикселях */ + const y1fixed = H - padBot - 16; /* фиксированная y для n=1 */ + const E2 = E(2), maxE = 0.4; + function yE(En){ - const minE = -14.2, maxE = 0.4; - return padTop + (maxE - En) / (maxE - minE) * (H - padTop - padBot); + if (En <= E2 - 0.05){ + /* Нижняя зона: линейная E(1)→E(2) → y1fixed→splitY */ + const t = (En - E(1)) / (E2 - E(1)); + return y1fixed - t * (y1fixed - splitY); + } + /* Верхняя зона: линейная E(2)→maxE → splitY→padTop */ + return padTop + (maxE - En) / (maxE - E2) * upperH; } - /* SVG открытие: белый фон, рамка */ + /* SVG */ let svg = ''; svg += ''; /* Заголовок */ - svg += 'Eₙ = −13,6/n² эВ'; + svg += 'Eₙ = −13,6 / n² эВ (атом водорода)'; - /* Серии — цветные полосы между уровнями */ - const seriesBands = [ - { n1: 1, n2: 2, fill: '#ede9fe', textFill: '#6d28d9', label: 'Лайман' }, - { n1: 2, n2: 3, fill: '#dbeafe', textFill: '#1d4ed8', label: 'Бальмер' }, - { n1: 3, n2: 6, fill: '#d1fae5', textFill: '#065f46', label: 'Пашен' }, + /* Цветные полосы серий */ + const seriesData = [ + { yTop: yE(E2), yBot: y1fixed, fill: '#ede9fe', tc: '#6d28d9', label: 'Лайман' }, + { yTop: yE(E(3)), yBot: yE(E2), fill: '#dbeafe', tc: '#1d4ed8', label: 'Бальмер' }, + { yTop: padTop, yBot: yE(E(3)),fill: '#d1fae5', tc: '#065f46', label: 'Пашен' }, ]; - for (const b of seriesBands){ - const yTop = yE(E(b.n2)); - const yBot = yE(E(b.n1)); - const bx = bandLeft; - const bh = yBot - yTop; - svg += ''; - /* Вертикальная метка внутри полосы */ - const ymid = ((yTop + yBot) / 2).toFixed(1); - svg += '' + b.label + ''; + for (const b of seriesData){ + const bh = b.yBot - b.yTop; + if (bh < 6) continue; + svg += ''; + const ym = ((b.yTop + b.yBot) / 2).toFixed(1); + const bx2 = bandLeft + bandW / 2; + svg += '' + b.label + ''; } - /* Вертикальная ось слева от уровней */ + /* Вертикальная ось — верхняя часть */ const axisX = leftLine - 3; - svg += ''; + svg += ''; + /* Ось нижней части */ + svg += ''; - /* Линия ионизации E=0 */ + /* Маркер разрыва оси */ + const bMid = (splitY + splitY + 22) / 2; + for (const dy of [-5, 5]){ + const my = bMid + dy; + svg += ''; + } + /* Подпись разрыва */ + svg += '∿ масштаб'; + + /* Линия ионизации */ const y0 = yE(0); - svg += ''; - svg += 'E = 0 (ионизация)'; + svg += ''; + svg += 'E = 0 (ионизация)'; /* Уровни n=1..6 */ for (let n = 1; n <= nMax; n++){ const En = E(n); const yL = yE(En); - const isFrom = (n === nFrom); - const isTo = (n === nTo); + const isFrom = (n === nFrom), isTo = (n === nTo); const active = isFrom || isTo; - const lineColor = isFrom ? '#4f46e5' : isTo ? '#0284c7' : '#94a3b8'; + const lc = isFrom ? '#4f46e5' : isTo ? '#0284c7' : '#94a3b8'; const sw = active ? 2.4 : 1; - - /* Glow под активными */ if (active){ - svg += ''; + svg += ''; } - svg += ''; - - /* Метка n= слева от оси */ - const labelX = axisX - 4; - const labelY = yL.toFixed(1); - const labelColor = isFrom ? '#4f46e5' : isTo ? '#0284c7' : '#475569'; - const labelFW = active ? '800' : '500'; + svg += ''; + /* Метка n= */ + const lx = axisX - 4; + const textC = isFrom ? '#4f46e5' : isTo ? '#0284c7' : '#475569'; if (active){ - svg += ''; + svg += ''; } - svg += 'n=' + n + ''; - - /* Значения энергии справа */ - const eColor = active ? '#334155' : '#94a3b8'; - const eFW = active ? '600' : '400'; - svg += '' + En.toFixed(2) + 'эВ'; + svg += 'n=' + n + ''; + /* Значение энергии */ + const ec = active ? '#334155' : '#94a3b8'; + svg += '' + En.toFixed(2) + ' эВ'; } /* Стрелка перехода */ if (nFrom !== nTo){ const Ef = E(nFrom), Et = E(nTo); const yf = yE(Ef), yt = yE(Et); - const emission = Ef > Et; /* испускание: падаем вниз по энергии */ - const arrowColor = emission ? '#ef4444' : '#16a34a'; + const emission = Ef > Et; + const ac = emission ? '#ef4444' : '#16a34a'; const xT = leftLine + (rightLine - leftLine) * 0.42; - - /* Линия стрелки */ - svg += ''; - - /* Наконечник в точке назначения yt */ - const dir = yt < yf ? -1 : 1; /* направление стрелки */ - const tip = yt.toFixed(1); - const tipBase = (yt + 10 * dir).toFixed(1); - svg += ''; - + svg += ''; + const dir = yt < yf ? -1 : 1; + svg += ''; /* Info-box */ const dE = Math.abs(Ef - Et); const lam = 1240 / dE; - const boxW = 88, boxH = lam >= 380 && lam <= 700 ? 48 : 40; + const bxW = 94, bxH = lam >= 380 && lam <= 700 ? 50 : 42; const yMid = (yf + yt) / 2; - let bx = xT + 10; - if (bx + boxW > W - 4) bx = xT - boxW - 10; - const boxFill = emission ? '#fff5f5' : '#f0fdf4'; - const boxStroke = emission ? '#fca5a5' : '#86efac'; - svg += ''; - svg += 'hν = ' + dE.toFixed(3) + ' эВ'; - svg += 'λ ≈ ' + lam.toFixed(0) + ' нм'; - /* Цветовая метка для видимого диапазона */ + let bxLeft = xT + 12; + if (bxLeft + bxW > W - 4) bxLeft = xT - bxW - 12; + const bf = emission ? '#fff5f5' : '#f0fdf4'; + const bs = emission ? '#fca5a5' : '#86efac'; + svg += ''; + svg += 'hν = ' + dE.toFixed(3) + ' эВ'; + svg += 'λ ≈ ' + lam.toFixed(0) + ' нм'; if (lam >= 380 && lam <= 700){ - const hue = Math.round(270 - (lam - 380) / (700 - 380) * 270); - const cy = yMid - boxH / 2 + 39; - svg += ''; - svg += ''; + const hue = Math.round(270 - (lam - 380) / 320 * 270); + const cy = yMid - bxH / 2 + 40; + svg += ''; + svg += ''; } }