Files
Learn_System/frontend/js/labs/periodic.js
T
Maxim Dolgolyov ea2526dc73 feat(labs): 4 школьные хим. симы + визуальная прокачка лаборатории
4 НОВЫЕ СИМЫ (школьная программа 8-11 классов):

Органика (organic.js, 1545 строк):
- Конструктор молекул: drag атомов C/H/O/N/Cl/S, валентности, click-pair bonds
- Авто-определение класса: алкан/алкен/алкин/спирт/альдегид/кислота/эфир/амин/аромат
- IUPAC-имена для C1-C10
- Гомологические ряды: 7 рядов с slider количества углеродов, M, T_кип, T_пл
- 6 качественных реакций: Br₂ вода, KMnO₄, Ag₂O/NH₃ (серебряное зеркало), Cu(OH)₂, FeCl₃, I₂

Периодическая таблица (periodic.js, 118 элементов):
- Стандартный вид 18×9 + лантаноиды/актиноиды
- Карточка элемента: Z, M, конфигурация, степени окисления, ЭО, ρ, T_пл/T_кип
- Боровская модель электронных оболочек (анимированная)
- Подсветка: 11 типов / s/p/d/f-блоки / без подсветки
- Графики свойств по периоду/группе (ЭО, M, плотность, T_пл/T_кип)
- Поиск по символу/имени/Z/массе

Качественный анализ (qualanalysis.js, 24 иона):
- 15 катионов: Na/K/NH₄/Mg/Ca/Ba/Al/Fe²⁺/Fe³⁺/Cu/Ag/Pb/Zn/H/OH
- 10 анионов: Cl/Br/I/SO₄/SO₃/CO₃/NO₃/PO₄/S²/CH₃COO
- 9 реактивов + пламя
- 2 режима: «определи ион» и «неизвестное вещество» с логом наблюдений
- Анимация капли, осадка с цветом, газовых пузырей, пламени

Растворы (solutions.js, 4 режима):
- Калькулятор: m_в, m_р-ра, ρ, T → ω, ν, C_М, C_Н с понятной логикой пересчёта
- Разбавление с before/after визуализацией
- Смешивание двух растворов с правилом рычага
- Кривые растворимости 8 веществ + задача перекристаллизации
- 15 пресетов веществ (NaCl, NaOH, H₂SO₄, CuSO₄·5H₂O, глюкоза, сахароза, ...)

ВИЗУАЛЬНАЯ ПРОКАЧКА (_chem_visuals.js, helper file):

12 функций школьной лабораторной графики:
- drawErlenmeyer / drawBeaker / drawBurette / drawTube — proper SVG-paths со шкалой
- drawSpiritLamp — стеклянный резервуар + фитиль + анимированное пламя
- animateGasBubbles / animatePrecipitateFall — анимация продуктов
- drawProductLabel — fade-in/out стрелка ↑/↓ с подписью
- drawEduTooltip — bubble с пояснением реакции
- drawDeskBackground / drawVesselShadow — лабораторный фон
- drawPHStrip — pH-индикаторная полоса с маркером

Прокачено 6 chem-сим: chemsandbox, flask, titration, electrolysis, ionexchange, redox
Каждая получила: фон парты, тени под колбами, анимированные стрелки продуктов,
educational tooltips из поля 'why' реакции. Спиртовка с пламенем в flask.
pH-полоса в titration.

Каталог теперь: 39 симуляций (было 35 + 4 новых).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 13:08:35 +03:00

751 lines
56 KiB
JavaScript
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.
'use strict';
/* ══════════════════════════════════════════════════════════════
PeriodicTableSim — Периодическая таблица (118 элементов)
Режимы: стандартный вид, подсветка по типам/блокам,
графики свойств, боровские оболочки, поиск элементов
══════════════════════════════════════════════════════════════ */
/* ── Element data ───────────────────────────────────────────── */
const ELEMENTS = [
{ Z:1, symbol:'H', name:'Водород', mass:1.008, group:1, period:1, block:'s', config:'1s¹', oxStates:[-1,+1], En:2.20, density:0.0899, melt:14.01, boil:20.28, type:'nonmetal', discovered:1766, by:'Кавендиш' },
{ Z:2, symbol:'He', name:'Гелий', mass:4.003, group:18, period:1, block:'s', config:'1s²', oxStates:[0], En:null, density:0.1786, melt:0.95, boil:4.22, type:'noble', discovered:1868, by:'Жансен' },
{ Z:3, symbol:'Li', name:'Литий', mass:6.941, group:1, period:2, block:'s', config:'[He]2s¹', oxStates:[+1], En:0.98, density:0.534, melt:453.65, boil:1603, type:'alkali', discovered:1817, by:'Арфведсон' },
{ Z:4, symbol:'Be', name:'Бериллий', mass:9.012, group:2, period:2, block:'s', config:'[He]2s²', oxStates:[+2], En:1.57, density:1.85, melt:1560, boil:2742, type:'alkaline', discovered:1798, by:'Воклен' },
{ Z:5, symbol:'B', name:'Бор', mass:10.811, group:13, period:2, block:'p', config:'[He]2s²2p¹', oxStates:[+3], En:2.04, density:2.34, melt:2349, boil:4200, type:'metalloid', discovered:1808, by:'Гей-Люссак' },
{ Z:6, symbol:'C', name:'Углерод', mass:12.011, group:14, period:2, block:'p', config:'[He]2s²2p²', oxStates:[-4,+4], En:2.55, density:2.267, melt:3823, boil:4098, type:'nonmetal', discovered:null, by:'Древний мир' },
{ Z:7, symbol:'N', name:'Азот', mass:14.007, group:15, period:2, block:'p', config:'[He]2s²2p³', oxStates:[-3,+5], En:3.04, density:1.251, melt:63.15, boil:77.36, type:'nonmetal', discovered:1772, by:'Резерфорд' },
{ Z:8, symbol:'O', name:'Кислород', mass:15.999, group:16, period:2, block:'p', config:'[He]2s²2p⁴', oxStates:[-2], En:3.44, density:1.429, melt:54.36, boil:90.20, type:'nonmetal', discovered:1774, by:'Пристли' },
{ Z:9, symbol:'F', name:'Фтор', mass:18.998, group:17, period:2, block:'p', config:'[He]2s²2p⁵', oxStates:[-1], En:3.98, density:1.696, melt:53.48, boil:85.03, type:'halogen', discovered:1886, by:'Муассан' },
{ Z:10, symbol:'Ne', name:'Неон', mass:20.180, group:18, period:2, block:'p', config:'[He]2s²2p⁶', oxStates:[0], En:null, density:0.9002, melt:24.56, boil:27.07, type:'noble', discovered:1898, by:'Рамзай' },
{ Z:11, symbol:'Na', name:'Натрий', mass:22.990, group:1, period:3, block:'s', config:'[Ne]3s¹', oxStates:[+1], En:0.93, density:0.971, melt:370.87, boil:1156, type:'alkali', discovered:1807, by:'Дэви' },
{ Z:12, symbol:'Mg', name:'Магний', mass:24.305, group:2, period:3, block:'s', config:'[Ne]3s²', oxStates:[+2], En:1.31, density:1.738, melt:923, boil:1363, type:'alkaline', discovered:1755, by:'Блэк' },
{ Z:13, symbol:'Al', name:'Алюминий', mass:26.982, group:13, period:3, block:'p', config:'[Ne]3s²3p¹', oxStates:[+3], En:1.61, density:2.70, melt:933.47, boil:2792, type:'posttransition',discovered:1825, by:'Эрстед' },
{ Z:14, symbol:'Si', name:'Кремний', mass:28.086, group:14, period:3, block:'p', config:'[Ne]3s²3p²', oxStates:[-4,+4], En:1.90, density:2.329, melt:1687, boil:3538, type:'metalloid', discovered:1824, by:'Берцелиус' },
{ Z:15, symbol:'P', name:'Фосфор', mass:30.974, group:15, period:3, block:'p', config:'[Ne]3s²3p³', oxStates:[-3,+5], En:2.19, density:1.823, melt:317.30, boil:553.65, type:'nonmetal', discovered:1669, by:'Бранд' },
{ Z:16, symbol:'S', name:'Сера', mass:32.065, group:16, period:3, block:'p', config:'[Ne]3s²3p⁴', oxStates:[-2,+6], En:2.58, density:2.07, melt:388.36, boil:717.87, type:'nonmetal', discovered:null, by:'Древний мир' },
{ Z:17, symbol:'Cl', name:'Хлор', mass:35.453, group:17, period:3, block:'p', config:'[Ne]3s²3p⁵', oxStates:[-1,+7], En:3.16, density:3.214, melt:171.65, boil:239.11, type:'halogen', discovered:1774, by:'Шееле' },
{ Z:18, symbol:'Ar', name:'Аргон', mass:39.948, group:18, period:3, block:'p', config:'[Ne]3s²3p⁶', oxStates:[0], En:null, density:1.784, melt:83.80, boil:87.30, type:'noble', discovered:1894, by:'Рэлей' },
{ Z:19, symbol:'K', name:'Калий', mass:39.098, group:1, period:4, block:'s', config:'[Ar]4s¹', oxStates:[+1], En:0.82, density:0.862, melt:336.53, boil:1032, type:'alkali', discovered:1807, by:'Дэви' },
{ Z:20, symbol:'Ca', name:'Кальций', mass:40.078, group:2, period:4, block:'s', config:'[Ar]4s²', oxStates:[+2], En:1.00, density:1.55, melt:1115, boil:1757, type:'alkaline', discovered:1808, by:'Дэви' },
{ Z:21, symbol:'Sc', name:'Скандий', mass:44.956, group:3, period:4, block:'d', config:'[Ar]3d¹4s²', oxStates:[+3], En:1.36, density:2.985, melt:1814, boil:3109, type:'transition', discovered:1879, by:'Нильсон' },
{ Z:22, symbol:'Ti', name:'Титан', mass:47.867, group:4, period:4, block:'d', config:'[Ar]3d²4s²', oxStates:[+4], En:1.54, density:4.507, melt:1941, boil:3560, type:'transition', discovered:1791, by:'Грегор' },
{ Z:23, symbol:'V', name:'Ванадий', mass:50.942, group:5, period:4, block:'d', config:'[Ar]3d³4s²', oxStates:[+5], En:1.63, density:6.11, melt:2183, boil:3680, type:'transition', discovered:1830, by:'Сефстрём' },
{ Z:24, symbol:'Cr', name:'Хром', mass:51.996, group:6, period:4, block:'d', config:'[Ar]3d⁵4s¹', oxStates:[+3,+6], En:1.66, density:7.19, melt:2180, boil:2944, type:'transition', discovered:1798, by:'Воклен' },
{ Z:25, symbol:'Mn', name:'Марганец', mass:54.938, group:7, period:4, block:'d', config:'[Ar]3d⁵4s²', oxStates:[+2,+7], En:1.55, density:7.21, melt:1519, boil:2334, type:'transition', discovered:1774, by:'Ган' },
{ Z:26, symbol:'Fe', name:'Железо', mass:55.845, group:8, period:4, block:'d', config:'[Ar]3d⁶4s²', oxStates:[+2,+3], En:1.83, density:7.874, melt:1811, boil:3134, type:'transition', discovered:null, by:'Древний мир' },
{ Z:27, symbol:'Co', name:'Кобальт', mass:58.933, group:9, period:4, block:'d', config:'[Ar]3d⁷4s²', oxStates:[+2,+3], En:1.88, density:8.90, melt:1768, boil:3200, type:'transition', discovered:1735, by:'Брандт' },
{ Z:28, symbol:'Ni', name:'Никель', mass:58.693, group:10, period:4, block:'d', config:'[Ar]3d⁸4s²', oxStates:[+2], En:1.91, density:8.908, melt:1728, boil:3186, type:'transition', discovered:1751, by:'Кронстедт' },
{ Z:29, symbol:'Cu', name:'Медь', mass:63.546, group:11, period:4, block:'d', config:'[Ar]3d¹⁰4s¹', oxStates:[+1,+2], En:1.90, density:8.96, melt:1357.77,boil:2835, type:'transition', discovered:null, by:'Древний мир' },
{ Z:30, symbol:'Zn', name:'Цинк', mass:65.38, group:12, period:4, block:'d', config:'[Ar]3d¹⁰4s²', oxStates:[+2], En:1.65, density:7.14, melt:692.68, boil:1180, type:'transition', discovered:1746, by:'Марграф' },
{ Z:31, symbol:'Ga', name:'Галлий', mass:69.723, group:13, period:4, block:'p', config:'[Ar]3d¹⁰4s²4p¹', oxStates:[+3], En:1.81, density:5.91, melt:302.91, boil:2477, type:'posttransition',discovered:1875, by:'Де Буабодран' },
{ Z:32, symbol:'Ge', name:'Германий', mass:72.630, group:14, period:4, block:'p', config:'[Ar]3d¹⁰4s²4p²', oxStates:[+4], En:2.01, density:5.323, melt:1211.40,boil:3106, type:'metalloid', discovered:1886, by:'Винклер' },
{ Z:33, symbol:'As', name:'Мышьяк', mass:74.922, group:15, period:4, block:'p', config:'[Ar]3d¹⁰4s²4p³', oxStates:[-3,+5], En:2.18, density:5.776, melt:1090, boil:887, type:'metalloid', discovered:1250, by:'Альберт Великий' },
{ Z:34, symbol:'Se', name:'Селен', mass:78.971, group:16, period:4, block:'p', config:'[Ar]3d¹⁰4s²4p⁴', oxStates:[-2,+6], En:2.55, density:4.809, melt:493.65, boil:958, type:'nonmetal', discovered:1817, by:'Берцелиус' },
{ Z:35, symbol:'Br', name:'Бром', mass:79.904, group:17, period:4, block:'p', config:'[Ar]3d¹⁰4s²4p⁵', oxStates:[-1,+5], En:2.96, density:3.122, melt:265.95, boil:332.00, type:'halogen', discovered:1826, by:'Балар' },
{ Z:36, symbol:'Kr', name:'Криптон', mass:83.798, group:18, period:4, block:'p', config:'[Ar]3d¹⁰4s²4p⁶', oxStates:[0], En:3.00, density:3.749, melt:115.79, boil:119.93, type:'noble', discovered:1898, by:'Рамзай' },
{ Z:37, symbol:'Rb', name:'Рубидий', mass:85.468, group:1, period:5, block:'s', config:'[Kr]5s¹', oxStates:[+1], En:0.82, density:1.532, melt:312.46, boil:961, type:'alkali', discovered:1861, by:'Бунзен' },
{ Z:38, symbol:'Sr', name:'Стронций', mass:87.62, group:2, period:5, block:'s', config:'[Kr]5s²', oxStates:[+2], En:0.95, density:2.64, melt:1050, boil:1655, type:'alkaline', discovered:1790, by:'Кроуфорд' },
{ Z:39, symbol:'Y', name:'Иттрий', mass:88.906, group:3, period:5, block:'d', config:'[Kr]4d¹5s²', oxStates:[+3], En:1.22, density:4.472, melt:1799, boil:3609, type:'transition', discovered:1794, by:'Гадолин' },
{ Z:40, symbol:'Zr', name:'Цирконий', mass:91.224, group:4, period:5, block:'d', config:'[Kr]4d²5s²', oxStates:[+4], En:1.33, density:6.52, melt:2128, boil:4682, type:'transition', discovered:1789, by:'Клапрот' },
{ Z:41, symbol:'Nb', name:'Ниобий', mass:92.906, group:5, period:5, block:'d', config:'[Kr]4d⁴5s¹', oxStates:[+5], En:1.6, density:8.57, melt:2750, boil:5017, type:'transition', discovered:1801, by:'Хатчетт' },
{ Z:42, symbol:'Mo', name:'Молибден', mass:95.95, group:6, period:5, block:'d', config:'[Kr]4d⁵5s¹', oxStates:[+6], En:2.16, density:10.28, melt:2896, boil:4912, type:'transition', discovered:1781, by:'Шееле' },
{ Z:43, symbol:'Tc', name:'Технеций', mass:98, group:7, period:5, block:'d', config:'[Kr]4d⁵5s²', oxStates:[+7], En:1.9, density:11.50, melt:2430, boil:4538, type:'transition', discovered:1937, by:'Перье' },
{ Z:44, symbol:'Ru', name:'Рутений', mass:101.07, group:8, period:5, block:'d', config:'[Kr]4d⁷5s¹', oxStates:[+4], En:2.2, density:12.45, melt:2607, boil:4423, type:'transition', discovered:1844, by:'Клаус' },
{ Z:45, symbol:'Rh', name:'Родий', mass:102.906, group:9, period:5, block:'d', config:'[Kr]4d⁸5s¹', oxStates:[+3], En:2.28, density:12.41, melt:2237, boil:3968, type:'transition', discovered:1803, by:'Воластон' },
{ Z:46, symbol:'Pd', name:'Палладий', mass:106.42, group:10, period:5, block:'d', config:'[Kr]4d¹⁰', oxStates:[+2], En:2.20, density:12.023, melt:1828.05,boil:3236, type:'transition', discovered:1803, by:'Воластон' },
{ Z:47, symbol:'Ag', name:'Серебро', mass:107.868, group:11, period:5, block:'d', config:'[Kr]4d¹⁰5s¹', oxStates:[+1], En:1.93, density:10.49, melt:1234.93,boil:2435, type:'transition', discovered:null, by:'Древний мир' },
{ Z:48, symbol:'Cd', name:'Кадмий', mass:112.414, group:12, period:5, block:'d', config:'[Kr]4d¹⁰5s²', oxStates:[+2], En:1.69, density:8.65, melt:594.22, boil:1040, type:'transition', discovered:1817, by:'Штромейер' },
{ Z:49, symbol:'In', name:'Индий', mass:114.818, group:13, period:5, block:'p', config:'[Kr]4d¹⁰5s²5p¹', oxStates:[+3], En:1.78, density:7.31, melt:429.75, boil:2345, type:'posttransition',discovered:1863, by:'Рейх' },
{ Z:50, symbol:'Sn', name:'Олово', mass:118.710, group:14, period:5, block:'p', config:'[Kr]4d¹⁰5s²5p²', oxStates:[+2,+4], En:1.96, density:7.287, melt:505.08, boil:2875, type:'posttransition',discovered:null, by:'Древний мир' },
{ Z:51, symbol:'Sb', name:'Сурьма', mass:121.760, group:15, period:5, block:'p', config:'[Kr]4d¹⁰5s²5p³', oxStates:[-3,+5], En:2.05, density:6.697, melt:903.78, boil:1860, type:'metalloid', discovered:null, by:'Древний мир' },
{ Z:52, symbol:'Te', name:'Теллур', mass:127.60, group:16, period:5, block:'p', config:'[Kr]4d¹⁰5s²5p⁴', oxStates:[-2,+6], En:2.1, density:6.24, melt:722.66, boil:1261, type:'metalloid', discovered:1782, by:'фон Райхенштайн' },
{ Z:53, symbol:'I', name:'Йод', mass:126.904, group:17, period:5, block:'p', config:'[Kr]4d¹⁰5s²5p⁵', oxStates:[-1,+7], En:2.66, density:4.933, melt:386.85, boil:457.55, type:'halogen', discovered:1811, by:'Куртуа' },
{ Z:54, symbol:'Xe', name:'Ксенон', mass:131.293, group:18, period:5, block:'p', config:'[Kr]4d¹⁰5s²5p⁶', oxStates:[0], En:2.6, density:5.894, melt:161.40, boil:165.05, type:'noble', discovered:1898, by:'Рамзай' },
{ Z:55, symbol:'Cs', name:'Цезий', mass:132.905, group:1, period:6, block:'s', config:'[Xe]6s¹', oxStates:[+1], En:0.79, density:1.873, melt:301.59, boil:944, type:'alkali', discovered:1860, by:'Бунзен' },
{ Z:56, symbol:'Ba', name:'Барий', mass:137.327, group:2, period:6, block:'s', config:'[Xe]6s²', oxStates:[+2], En:0.89, density:3.594, melt:1000, boil:2170, type:'alkaline', discovered:1808, by:'Дэви' },
{ Z:57, symbol:'La', name:'Лантан', mass:138.905, group:3, period:6, block:'f', config:'[Xe]5d¹6s²', oxStates:[+3], En:1.10, density:6.162, melt:1193, boil:3737, type:'lanthanide', discovered:1839, by:'Мосандер' },
{ Z:58, symbol:'Ce', name:'Церий', mass:140.116, group:null,period:6, block:'f', config:'[Xe]4f¹5d¹6s²', oxStates:[+3,+4], En:1.12, density:6.770, melt:1068, boil:3716, type:'lanthanide', discovered:1803, by:'Берцелиус' },
{ Z:59, symbol:'Pr', name:'Празеодим', mass:140.908, group:null,period:6, block:'f', config:'[Xe]4f³6s²', oxStates:[+3], En:1.13, density:6.77, melt:1208, boil:3793, type:'lanthanide', discovered:1885, by:'фон Вельсбах' },
{ Z:60, symbol:'Nd', name:'Неодим', mass:144.242, group:null,period:6, block:'f', config:'[Xe]4f⁴6s²', oxStates:[+3], En:1.14, density:7.01, melt:1297, boil:3347, type:'lanthanide', discovered:1885, by:'фон Вельсбах' },
{ Z:61, symbol:'Pm', name:'Прометий', mass:145, group:null,period:6, block:'f', config:'[Xe]4f⁵6s²', oxStates:[+3], En:1.13, density:7.26, melt:1315, boil:3273, type:'lanthanide', discovered:1945, by:'Маринский' },
{ Z:62, symbol:'Sm', name:'Самарий', mass:150.36, group:null,period:6, block:'f', config:'[Xe]4f⁶6s²', oxStates:[+2,+3], En:1.17, density:7.52, melt:1345, boil:2067, type:'lanthanide', discovered:1879, by:'Буабодран' },
{ Z:63, symbol:'Eu', name:'Европий', mass:151.964, group:null,period:6, block:'f', config:'[Xe]4f⁷6s²', oxStates:[+2,+3], En:1.20, density:5.244, melt:1099, boil:1802, type:'lanthanide', discovered:1901, by:'Демарсе' },
{ Z:64, symbol:'Gd', name:'Гадолиний', mass:157.25, group:null,period:6, block:'f', config:'[Xe]4f⁷5d¹6s²', oxStates:[+3], En:1.20, density:7.90, melt:1585, boil:3546, type:'lanthanide', discovered:1880, by:'Мариньяк' },
{ Z:65, symbol:'Tb', name:'Тербий', mass:158.925, group:null,period:6, block:'f', config:'[Xe]4f⁹6s²', oxStates:[+3], En:1.10, density:8.23, melt:1629, boil:3503, type:'lanthanide', discovered:1843, by:'Мосандер' },
{ Z:66, symbol:'Dy', name:'Диспрозий', mass:162.500, group:null,period:6, block:'f', config:'[Xe]4f¹⁰6s²', oxStates:[+3], En:1.22, density:8.540, melt:1680, boil:2840, type:'lanthanide', discovered:1886, by:'Буабодран' },
{ Z:67, symbol:'Ho', name:'Гольмий', mass:164.930, group:null,period:6, block:'f', config:'[Xe]4f¹¹6s²', oxStates:[+3], En:1.23, density:8.795, melt:1734, boil:2993, type:'lanthanide', discovered:1878, by:'Клеве' },
{ Z:68, symbol:'Er', name:'Эрбий', mass:167.259, group:null,period:6, block:'f', config:'[Xe]4f¹²6s²', oxStates:[+3], En:1.24, density:9.066, melt:1802, boil:3141, type:'lanthanide', discovered:1843, by:'Мосандер' },
{ Z:69, symbol:'Tm', name:'Тулий', mass:168.934, group:null,period:6, block:'f', config:'[Xe]4f¹³6s²', oxStates:[+3], En:1.25, density:9.32, melt:1818, boil:2223, type:'lanthanide', discovered:1879, by:'Клеве' },
{ Z:70, symbol:'Yb', name:'Иттербий', mass:173.054, group:null,period:6, block:'f', config:'[Xe]4f¹⁴6s²', oxStates:[+2,+3], En:1.10, density:6.90, melt:1097, boil:1469, type:'lanthanide', discovered:1878, by:'Мариньяк' },
{ Z:71, symbol:'Lu', name:'Лютеций', mass:174.967, group:3, period:6, block:'d', config:'[Xe]4f¹⁴5d¹6s²', oxStates:[+3], En:1.27, density:9.841, melt:1925, boil:3675, type:'lanthanide', discovered:1907, by:'Урбен' },
{ Z:72, symbol:'Hf', name:'Гафний', mass:178.49, group:4, period:6, block:'d', config:'[Xe]4f¹⁴5d²6s²', oxStates:[+4], En:1.3, density:13.31, melt:2506, boil:4876, type:'transition', discovered:1923, by:'Костер' },
{ Z:73, symbol:'Ta', name:'Тантал', mass:180.948, group:5, period:6, block:'d', config:'[Xe]4f¹⁴5d³6s²', oxStates:[+5], En:1.5, density:16.69, melt:3290, boil:5731, type:'transition', discovered:1802, by:'Экеберг' },
{ Z:74, symbol:'W', name:'Вольфрам', mass:183.84, group:6, period:6, block:'d', config:'[Xe]4f¹⁴5d⁴6s²', oxStates:[+6], En:2.36, density:19.25, melt:3695, boil:5828, type:'transition', discovered:1783, by:'Братья дель Риo' },
{ Z:75, symbol:'Re', name:'Рений', mass:186.207, group:7, period:6, block:'d', config:'[Xe]4f¹⁴5d⁵6s²', oxStates:[+7], En:1.9, density:21.02, melt:3459, boil:5869, type:'transition', discovered:1925, by:'Ноддак' },
{ Z:76, symbol:'Os', name:'Осмий', mass:190.23, group:8, period:6, block:'d', config:'[Xe]4f¹⁴5d⁶6s²', oxStates:[+4], En:2.2, density:22.59, melt:3306, boil:5285, type:'transition', discovered:1803, by:'Теннант' },
{ Z:77, symbol:'Ir', name:'Иридий', mass:192.217, group:9, period:6, block:'d', config:'[Xe]4f¹⁴5d⁷6s²', oxStates:[+4], En:2.20, density:22.56, melt:2719, boil:4701, type:'transition', discovered:1803, by:'Теннант' },
{ Z:78, symbol:'Pt', name:'Платина', mass:195.084, group:10, period:6, block:'d', config:'[Xe]4f¹⁴5d⁹6s¹', oxStates:[+2,+4], En:2.28, density:21.45, melt:2041.4, boil:4098, type:'transition', discovered:1735, by:'де Улоа' },
{ Z:79, symbol:'Au', name:'Золото', mass:196.967, group:11, period:6, block:'d', config:'[Xe]4f¹⁴5d¹⁰6s¹',oxStates:[+1,+3], En:2.54, density:19.30, melt:1337.33,boil:3129, type:'transition', discovered:null, by:'Древний мир' },
{ Z:80, symbol:'Hg', name:'Ртуть', mass:200.592, group:12, period:6, block:'d', config:'[Xe]4f¹⁴5d¹⁰6s²',oxStates:[+1,+2], En:2.00, density:13.534, melt:234.32, boil:629.88, type:'transition', discovered:null, by:'Древний мир' },
{ Z:81, symbol:'Tl', name:'Таллий', mass:204.38, group:13, period:6, block:'p', config:'[Xe]4f¹⁴5d¹⁰6s²6p¹',oxStates:[+1,+3],En:1.62,density:11.85, melt:577, boil:1746, type:'posttransition',discovered:1861, by:'Крукс' },
{ Z:82, symbol:'Pb', name:'Свинец', mass:207.2, group:14, period:6, block:'p', config:'[Xe]4f¹⁴5d¹⁰6s²6p²',oxStates:[+2,+4],En:2.33,density:11.34, melt:600.61, boil:2022, type:'posttransition',discovered:null, by:'Древний мир' },
{ Z:83, symbol:'Bi', name:'Висмут', mass:208.980, group:15, period:6, block:'p', config:'[Xe]4f¹⁴5d¹⁰6s²6p³',oxStates:[+3], En:2.02,density:9.747, melt:544.55, boil:1837, type:'posttransition',discovered:1753, by:'Жоффруа' },
{ Z:84, symbol:'Po', name:'Полоний', mass:209, group:16, period:6, block:'p', config:'[Xe]4f¹⁴5d¹⁰6s²6p⁴',oxStates:[+4], En:2.0, density:9.32, melt:527, boil:1235, type:'metalloid', discovered:1898, by:'Кюри' },
{ Z:85, symbol:'At', name:'Астат', mass:210, group:17, period:6, block:'p', config:'[Xe]4f¹⁴5d¹⁰6s²6p⁵',oxStates:[-1,+1],En:2.2, density:null, melt:575, boil:null, type:'halogen', discovered:1940, by:'Корсон' },
{ Z:86, symbol:'Rn', name:'Радон', mass:222, group:18, period:6, block:'p', config:'[Xe]4f¹⁴5d¹⁰6s²6p⁶',oxStates:[0], En:null,density:9.73, melt:202, boil:211.45, type:'noble', discovered:1900, by:'Дорн' },
{ Z:87, symbol:'Fr', name:'Франций', mass:223, group:1, period:7, block:'s', config:'[Rn]7s¹', oxStates:[+1], En:0.7, density:null, melt:300, boil:950, type:'alkali', discovered:1939, by:'Перей' },
{ Z:88, symbol:'Ra', name:'Радий', mass:226, group:2, period:7, block:'s', config:'[Rn]7s²', oxStates:[+2], En:0.9, density:5.0, melt:973, boil:2010, type:'alkaline', discovered:1898, by:'Кюри' },
{ Z:89, symbol:'Ac', name:'Актиний', mass:227, group:3, period:7, block:'f', config:'[Rn]6d¹7s²', oxStates:[+3], En:1.1, density:10.07, melt:1323, boil:3471, type:'actinide', discovered:1899, by:'Дебьерн' },
{ Z:90, symbol:'Th', name:'Торий', mass:232.038, group:null,period:7, block:'f', config:'[Rn]6d²7s²', oxStates:[+4], En:1.3, density:11.72, melt:2115, boil:5061, type:'actinide', discovered:1828, by:'Берцелиус' },
{ Z:91, symbol:'Pa', name:'Протактиний', mass:231.036, group:null,period:7, block:'f', config:'[Rn]5f²6d¹7s²', oxStates:[+5], En:1.5, density:15.37, melt:1841, boil:4300, type:'actinide', discovered:1913, by:'Фаянс' },
{ Z:92, symbol:'U', name:'Уран', mass:238.029, group:null,period:7, block:'f', config:'[Rn]5f³6d¹7s²', oxStates:[+6], En:1.38, density:19.05, melt:1405.3, boil:4404, type:'actinide', discovered:1789, by:'Клапрот' },
{ Z:93, symbol:'Np', name:'Нептуний', mass:237, group:null,period:7, block:'f', config:'[Rn]5f⁴6d¹7s²', oxStates:[+5], En:1.36, density:20.25, melt:913, boil:4273, type:'actinide', discovered:1940, by:'МакМиллан' },
{ Z:94, symbol:'Pu', name:'Плутоний', mass:244, group:null,period:7, block:'f', config:'[Rn]5f⁶7s²', oxStates:[+4], En:1.28, density:19.84, melt:912.5, boil:3501, type:'actinide', discovered:1940, by:'Сиборг' },
{ Z:95, symbol:'Am', name:'Америций', mass:243, group:null,period:7, block:'f', config:'[Rn]5f⁷7s²', oxStates:[+3], En:1.3, density:13.67, melt:1449, boil:2880, type:'actinide', discovered:1944, by:'Сиборг' },
{ Z:96, symbol:'Cm', name:'Кюрий', mass:247, group:null,period:7, block:'f', config:'[Rn]5f⁷6d¹7s²', oxStates:[+3], En:1.3, density:13.51, melt:1613, boil:3383, type:'actinide', discovered:1944, by:'Сиборг' },
{ Z:97, symbol:'Bk', name:'Берклий', mass:247, group:null,period:7, block:'f', config:'[Rn]5f⁹7s²', oxStates:[+3], En:1.3, density:14.79, melt:1259, boil:null, type:'actinide', discovered:1949, by:'Сиборг' },
{ Z:98, symbol:'Cf', name:'Калифорний', mass:251, group:null,period:7, block:'f', config:'[Rn]5f¹⁰7s²', oxStates:[+3], En:1.3, density:15.1, melt:1173, boil:null, type:'actinide', discovered:1950, by:'Сиборг' },
{ Z:99, symbol:'Es', name:'Эйнштейний', mass:252, group:null,period:7, block:'f', config:'[Rn]5f¹¹7s²', oxStates:[+3], En:1.3, density:null, melt:1133, boil:null, type:'actinide', discovered:1952, by:'Гиорсо' },
{ Z:100,symbol:'Fm', name:'Фермий', mass:257, group:null,period:7, block:'f', config:'[Rn]5f¹²7s²', oxStates:[+3], En:1.3, density:null, melt:1800, boil:null, type:'actinide', discovered:1952, by:'Гиорсо' },
{ Z:101,symbol:'Md', name:'Менделевий', mass:258, group:null,period:7, block:'f', config:'[Rn]5f¹³7s²', oxStates:[+3], En:1.3, density:null, melt:1100, boil:null, type:'actinide', discovered:1955, by:'Гиорсо' },
{ Z:102,symbol:'No', name:'Нобелий', mass:259, group:null,period:7, block:'f', config:'[Rn]5f¹⁴7s²', oxStates:[+2], En:1.3, density:null, melt:1100, boil:null, type:'actinide', discovered:1958, by:'Флёров' },
{ Z:103,symbol:'Lr', name:'Лоуренсий', mass:266, group:3, period:7, block:'d', config:'[Rn]5f¹⁴7s²7p¹', oxStates:[+3], En:1.3, density:null, melt:1900, boil:null, type:'actinide', discovered:1961, by:'Гиорсо' },
{ Z:104,symbol:'Rf', name:'Резерфордий', mass:267, group:4, period:7, block:'d', config:'[Rn]5f¹⁴6d²7s²', oxStates:[+4], En:null, density:null, melt:null, boil:null, type:'transition', discovered:1964, by:'Флёров' },
{ Z:105,symbol:'Db', name:'Дубний', mass:268, group:5, period:7, block:'d', config:'[Rn]5f¹⁴6d³7s²', oxStates:[+5], En:null, density:null, melt:null, boil:null, type:'transition', discovered:1968, by:'Флёров' },
{ Z:106,symbol:'Sg', name:'Сиборгий', mass:269, group:6, period:7, block:'d', config:'[Rn]5f¹⁴6d⁴7s²', oxStates:[+6], En:null, density:null, melt:null, boil:null, type:'transition', discovered:1974, by:'Флёров' },
{ Z:107,symbol:'Bh', name:'Борий', mass:270, group:7, period:7, block:'d', config:'[Rn]5f¹⁴6d⁵7s²', oxStates:[+7], En:null, density:null, melt:null, boil:null, type:'transition', discovered:1981, by:'ГСИ' },
{ Z:108,symbol:'Hs', name:'Хассий', mass:277, group:8, period:7, block:'d', config:'[Rn]5f¹⁴6d⁶7s²', oxStates:[+8], En:null, density:null, melt:null, boil:null, type:'transition', discovered:1984, by:'ГСИ' },
{ Z:109,symbol:'Mt', name:'Мейтнерий', mass:278, group:9, period:7, block:'d', config:'[Rn]5f¹⁴6d⁷7s²', oxStates:[null], En:null, density:null, melt:null, boil:null, type:'transition', discovered:1982, by:'ГСИ' },
{ Z:110,symbol:'Ds', name:'Дармштадтий', mass:281, group:10, period:7, block:'d', config:'[Rn]5f¹⁴6d⁸7s²', oxStates:[null], En:null, density:null, melt:null, boil:null, type:'transition', discovered:1994, by:'ГСИ' },
{ Z:111,symbol:'Rg', name:'Рентгений', mass:282, group:11, period:7, block:'d', config:'[Rn]5f¹⁴6d⁹7s²', oxStates:[null], En:null, density:null, melt:null, boil:null, type:'transition', discovered:1994, by:'ГСИ' },
{ Z:112,symbol:'Cn', name:'Коперниций', mass:285, group:12, period:7, block:'d', config:'[Rn]5f¹⁴6d¹⁰7s²',oxStates:[null], En:null, density:null, melt:null, boil:null, type:'transition', discovered:1996, by:'ГСИ' },
{ Z:113,symbol:'Nh', name:'Нихоний', mass:286, group:13, period:7, block:'p', config:'[Rn]5f¹⁴6d¹⁰7s²7p¹',oxStates:[null],En:null, density:null, melt:null, boil:null, type:'posttransition',discovered:2004, by:'РИКЕН' },
{ Z:114,symbol:'Fl', name:'Флеровий', mass:289, group:14, period:7, block:'p', config:'[Rn]5f¹⁴6d¹⁰7s²7p²',oxStates:[null],En:null, density:null, melt:null, boil:null, type:'posttransition',discovered:1998, by:'Флёров' },
{ Z:115,symbol:'Mc', name:'Московий', mass:290, group:15, period:7, block:'p', config:'[Rn]5f¹⁴6d¹⁰7s²7p³',oxStates:[null],En:null, density:null, melt:null, boil:null, type:'posttransition',discovered:2003, by:'Флёров' },
{ Z:116,symbol:'Lv', name:'Ливерморий', mass:293, group:16, period:7, block:'p', config:'[Rn]5f¹⁴6d¹⁰7s²7p⁴',oxStates:[null],En:null, density:null, melt:null, boil:null, type:'posttransition',discovered:2000, by:'Флёров' },
{ Z:117,symbol:'Ts', name:'Теннессин', mass:294, group:17, period:7, block:'p', config:'[Rn]5f¹⁴6d¹⁰7s²7p⁵',oxStates:[null],En:null, density:null, melt:null, boil:null, type:'halogen', discovered:2010, by:'Флёров' },
{ Z:118,symbol:'Og', name:'Оганессон', mass:294, group:18, period:7, block:'p', config:'[Rn]5f¹⁴6d¹⁰7s²7p⁶',oxStates:[null],En:null, density:null, melt:null, boil:null, type:'noble', discovered:2002, by:'Флёров' },
];
/* ── Colour palette per type ──────────────────────────────── */
const TYPE_COLORS = {
alkali: '#EF476F',
alkaline: '#FF6B35',
transition: '#7B8EF7',
posttransition:'#06D6E0',
metalloid: '#7BF5A4',
nonmetal: '#FFD166',
halogen: '#C77DFF',
noble: '#A8DADC',
lanthanide: '#9B5DE5',
actinide: '#F15BB5',
metal: '#7B8EF7',
};
const TYPE_LABELS = {
alkali: 'Щелочные металлы',
alkaline: 'Щёлочноземельные',
transition: 'Переходные металлы',
posttransition:'Постпереходные',
metalloid: 'Металлоиды',
nonmetal: 'Неметаллы',
halogen: 'Галогены',
noble: 'Благородные газы',
lanthanide: 'Лантаноиды',
actinide: 'Актиноиды',
};
const BLOCK_COLORS = {
s: '#EF476F',
p: '#06D6E0',
d: '#7B8EF7',
f: '#9B5DE5',
};
/* ── Электронные оболочки (K,L,M,N,O,P,Q) ── */
const SHELL_CAPACITY = [2, 8, 18, 32, 32, 18, 8];
function getShellFill(Z) {
const caps = SHELL_CAPACITY;
const shells = [];
let rem = Z;
for (let i = 0; i < caps.length && rem > 0; i++) {
const n = Math.min(rem, caps[i]);
shells.push(n);
rem -= n;
}
return shells;
}
/* ── Layout helpers ──────────────────────────────────────────── */
/* Standard 18-column layout: returns {col, row} for each element */
function getCell(el) {
if (el.type === 'lanthanide' && el.Z !== 57 && el.Z !== 71) {
return { col: el.Z - 57 + 3, row: 9 }; // lanthanide row
}
if (el.type === 'actinide' && el.Z !== 89 && el.Z !== 103) {
return { col: el.Z - 89 + 3, row: 10 }; // actinide row
}
const g = el.group;
const p = el.period;
if (!g) return null;
return { col: g, row: p };
}
/* ══════════════════════════════════════════════════════════════
CLASS
══════════════════════════════════════════════════════════════ */
class PeriodicTableSim {
constructor(wrap) {
this._wrap = wrap;
this._mode = 'type'; // type | block | none
this._selected = null; // element Z
this._searchQ = '';
this._highlighted = new Set(); // Zs matching search
this._propKey = 'En'; // property for chart
this._chartBy = 'period';
this._chartN = 2; // period number or group number
this._bohrZ = null; // Z for Bohr shell panel
this._bohrRaf = null;
this._bohrAngle = 0;
// build
this._buildUI();
this._buildTable();
this._updateCard(null);
// chart defaults
this._drawChart();
}
/* ─────────────────────────────────────────────────────
UI BUILD
───────────────────────────────────────────────────── */
_buildUI() {
this._wrap.innerHTML = '';
this._wrap.style.cssText = 'display:flex;flex-direction:column;height:100%;min-height:0;background:#0D0D1A;overflow:hidden;';
/* top toolbar */
const toolbar = document.createElement('div');
toolbar.style.cssText = 'display:flex;align-items:center;gap:8px;padding:8px 12px;border-bottom:1px solid rgba(255,255,255,0.08);flex-wrap:wrap;flex-shrink:0;';
toolbar.innerHTML = `
<span style="font-size:.72rem;font-weight:700;color:rgba(255,255,255,0.4);text-transform:uppercase;letter-spacing:.07em">Режим:</span>
<button class="ptbl-mode-btn active" data-m="type">По типу</button>
<button class="ptbl-mode-btn" data-m="block">По блоку</button>
<button class="ptbl-mode-btn" data-m="none">Без подсветки</button>
<span style="margin-left:8px;font-size:.72rem;font-weight:700;color:rgba(255,255,255,0.4);text-transform:uppercase;letter-spacing:.07em">Поиск:</span>
<input id="ptbl-search" type="text" placeholder="Символ / название / Z / масса"
style="padding:4px 8px;border-radius:6px;border:1px solid rgba(255,255,255,0.15);background:rgba(255,255,255,0.06);color:#fff;font-size:.78rem;width:200px;outline:none;">
<button id="ptbl-search-clear" style="padding:3px 7px;border-radius:5px;border:1px solid rgba(255,255,255,0.15);background:transparent;color:#aaa;font-size:.72rem;cursor:pointer">
<svg class="ic" viewBox="0 0 24 24" style="width:12px;height:12px"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
</button>`;
this._wrap.appendChild(toolbar);
/* mode buttons */
toolbar.querySelectorAll('.ptbl-mode-btn').forEach(btn => {
btn.style.cssText = 'padding:4px 10px;border-radius:6px;border:1px solid rgba(255,255,255,0.15);background:transparent;color:#aaa;font-size:.75rem;cursor:pointer;transition:all .15s';
btn.addEventListener('click', () => {
toolbar.querySelectorAll('.ptbl-mode-btn').forEach(b => { b.style.background='transparent'; b.style.color='#aaa'; b.classList.remove('active'); });
btn.style.background='rgba(155,93,229,0.25)'; btn.style.color='#fff'; btn.classList.add('active');
this._mode = btn.dataset.m;
if (window.LabFX) LabFX.sound.play('whoosh', { pitch: 1.2, volume: 0.25 });
this._colorTable();
});
});
/* set initial active style */
toolbar.querySelector('.ptbl-mode-btn.active').style.background = 'rgba(155,93,229,0.25)';
toolbar.querySelector('.ptbl-mode-btn.active').style.color = '#fff';
/* search */
const si = toolbar.querySelector('#ptbl-search');
si.addEventListener('input', () => { this._searchQ = si.value.trim().toLowerCase(); this._applySearch(); });
toolbar.querySelector('#ptbl-search-clear').addEventListener('click', () => { si.value=''; this._searchQ=''; this._applySearch(); });
/* main area: table + right panel */
const main = document.createElement('div');
main.style.cssText = 'display:flex;flex:1;min-height:0;overflow:hidden;';
this._wrap.appendChild(main);
/* left: table + legend */
const leftCol = document.createElement('div');
leftCol.style.cssText = 'flex:1;display:flex;flex-direction:column;min-width:0;overflow:auto;padding:8px 4px 8px 8px;';
main.appendChild(leftCol);
/* table grid container */
this._tableEl = document.createElement('div');
this._tableEl.id = 'ptbl-grid';
this._tableEl.style.cssText = 'display:grid;grid-template-columns:repeat(18,1fr);gap:2px;min-width:540px;';
leftCol.appendChild(this._tableEl);
/* gap filler row */
const gapDiv = document.createElement('div');
gapDiv.style.cssText = 'height:8px;';
leftCol.appendChild(gapDiv);
/* f-block (lanthanide/actinide) */
this._fblockEl = document.createElement('div');
this._fblockEl.id = 'ptbl-fblock';
this._fblockEl.style.cssText = 'display:grid;grid-template-columns:repeat(15,1fr);gap:2px;min-width:540px;margin-left:calc(2*100%/18 + 4px);';
leftCol.appendChild(this._fblockEl);
/* legend */
this._legendEl = document.createElement('div');
this._legendEl.style.cssText = 'display:flex;flex-wrap:wrap;gap:6px;margin-top:10px;min-width:540px;';
leftCol.appendChild(this._legendEl);
/* right panel */
const rightCol = document.createElement('div');
rightCol.style.cssText = 'width:260px;flex-shrink:0;display:flex;flex-direction:column;border-left:1px solid rgba(255,255,255,0.07);overflow:hidden;';
main.appendChild(rightCol);
/* element card */
this._cardEl = document.createElement('div');
this._cardEl.id = 'ptbl-card';
this._cardEl.style.cssText = 'flex:1;overflow-y:auto;padding:12px 10px 8px;font-size:.78rem;color:#ccc;';
rightCol.appendChild(this._cardEl);
/* Bohr shells canvas */
const bohrWrap = document.createElement('div');
bohrWrap.style.cssText = 'height:150px;flex-shrink:0;border-top:1px solid rgba(255,255,255,0.07);background:rgba(0,0,0,0.3);position:relative;';
this._bohrCanvas = document.createElement('canvas');
this._bohrCanvas.style.cssText = 'width:100%;height:100%;';
bohrWrap.appendChild(this._bohrCanvas);
rightCol.appendChild(bohrWrap);
/* chart panel */
const chartPan = document.createElement('div');
chartPan.style.cssText = 'flex-shrink:0;border-top:1px solid rgba(255,255,255,0.07);padding:8px;background:rgba(0,0,0,0.2);';
chartPan.innerHTML = `
<div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap;margin-bottom:6px;">
<span style="font-size:.7rem;color:rgba(255,255,255,0.4)">Свойство:</span>
<select id="ptbl-prop-sel" style="background:#1a1a2e;border:1px solid rgba(255,255,255,0.15);color:#ccc;border-radius:5px;padding:2px 5px;font-size:.72rem;">
<option value="En">ЭО (Полинг)</option>
<option value="mass">Масса</option>
<option value="melt">T плавл. (K)</option>
<option value="boil">T кип. (K)</option>
<option value="density">Плотность</option>
</select>
<select id="ptbl-by-sel" style="background:#1a1a2e;border:1px solid rgba(255,255,255,0.15);color:#ccc;border-radius:5px;padding:2px 5px;font-size:.72rem;">
<option value="period">По периоду</option>
<option value="group">По группе</option>
</select>
<select id="ptbl-n-sel" style="background:#1a1a2e;border:1px solid rgba(255,255,255,0.15);color:#ccc;border-radius:5px;padding:2px 5px;font-size:.72rem;">
${[1,2,3,4,5,6,7].map(n=>`<option value="${n}" ${n===2?'selected':''}>№ ${n}</option>`).join('')}
</select>
</div>
<canvas id="ptbl-chart" style="width:100%;height:90px;display:block;"></canvas>`;
rightCol.appendChild(chartPan);
chartPan.querySelector('#ptbl-prop-sel').addEventListener('change', e => { this._propKey=e.target.value; this._drawChart(); });
chartPan.querySelector('#ptbl-by-sel').addEventListener('change', e => {
this._chartBy=e.target.value;
const nSel = chartPan.querySelector('#ptbl-n-sel');
if (this._chartBy === 'group') {
nSel.innerHTML = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18].map(n=>`<option value="${n}" ${n===1?'selected':''}>Гр. ${n}</option>`).join('');
this._chartN = 1;
} else {
nSel.innerHTML = [1,2,3,4,5,6,7].map(n=>`<option value="${n}" ${n===2?'selected':''}>№ ${n}</option>`).join('');
this._chartN = 2;
}
this._drawChart();
});
chartPan.querySelector('#ptbl-n-sel').addEventListener('change', e => { this._chartN=+e.target.value; this._drawChart(); });
this._chartCanvas = chartPan.querySelector('#ptbl-chart');
new ResizeObserver(() => this._drawChart()).observe(this._chartCanvas);
new ResizeObserver(() => this._drawBohr()).observe(this._bohrCanvas);
}
/* ─────────────────────────────────────────────────────
TABLE BUILD
───────────────────────────────────────────────────── */
_buildTable() {
/* create placeholder grid for rows 1-7 × cols 1-18 */
const cells = {}; // 'row,col' → div
for (let r = 1; r <= 7; r++) {
for (let c = 1; c <= 18; c++) {
const d = document.createElement('div');
d.style.cssText = 'aspect-ratio:1;border-radius:4px;';
cells[`${r},${c}`] = d;
this._tableEl.appendChild(d);
}
}
/* f-block rows */
const fCells = { 9: {}, 10: {} }; // lanthanides / actinides
for (let fc = 1; fc <= 15; fc++) {
for (const fr of [9, 10]) {
const d = document.createElement('div');
d.style.cssText = 'aspect-ratio:1;border-radius:4px;';
fCells[fr][fc] = d;
this._fblockEl.appendChild(d);
}
}
/* place elements */
this._cellMap = {}; // Z → div
for (const el of ELEMENTS) {
const pos = getCell(el);
if (!pos) continue;
let div;
if (pos.row <= 7) {
div = cells[`${pos.row},${pos.col}`];
} else {
const fCol = pos.col - 2; // 3..17 → 1..15
div = fCells[pos.row][fCol];
}
if (!div) continue;
this._cellMap[el.Z] = div;
div.dataset.z = el.Z;
div.title = `${el.name} (${el.symbol})`;
div.style.cssText += 'cursor:pointer;display:flex;flex-direction:column;align-items:center;justify-content:center;transition:filter .12s,transform .12s;position:relative;overflow:hidden;';
div.innerHTML = `
<span style="font-size:.55em;opacity:.7;line-height:1">${el.Z}</span>
<span style="font-size:.85em;font-weight:800;line-height:1.1">${el.symbol}</span>
<span style="font-size:.42em;opacity:.65;line-height:1.2;text-align:center;max-width:90%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis">${el.name}</span>`;
div.addEventListener('mouseenter', () => { div.style.filter='brightness(1.4)'; div.style.transform='scale(1.12)'; div.style.zIndex='10'; });
div.addEventListener('mouseleave', () => { div.style.filter=''; div.style.transform=''; div.style.zIndex=''; });
div.addEventListener('click', () => this._selectElement(el.Z));
}
this._colorTable();
this._buildLegend();
}
_colorTable() {
for (const el of ELEMENTS) {
const div = this._cellMap[el.Z];
if (!div) continue;
let bg, textC = '#fff';
if (this._mode === 'type') {
bg = TYPE_COLORS[el.type] || '#555';
} else if (this._mode === 'block') {
bg = BLOCK_COLORS[el.block] || '#555';
} else {
bg = '#2a2a3e';
}
div.style.background = bg + '33'; // 20% opacity base
div.style.border = `1px solid ${bg}88`;
div.style.color = '#fff';
if (this._highlighted.size > 0) {
if (this._highlighted.has(el.Z)) {
div.style.background = bg + 'cc';
div.style.border = `2px solid ${bg}`;
div.style.boxShadow = `0 0 8px ${bg}99`;
} else {
div.style.opacity = '0.25';
}
} else {
div.style.opacity = '1';
div.style.boxShadow = '';
}
}
this._buildLegend();
}
_buildLegend() {
this._legendEl.innerHTML = '';
const map = this._mode === 'block' ? BLOCK_COLORS : TYPE_COLORS;
const labels = this._mode === 'block' ? { s:'s-блок', p:'p-блок', d:'d-блок', f:'f-блок' } : TYPE_LABELS;
for (const [k, col] of Object.entries(map)) {
if (!labels[k]) continue;
const d = document.createElement('div');
d.style.cssText = `display:flex;align-items:center;gap:4px;font-size:.68rem;color:#bbb;cursor:pointer;`;
d.innerHTML = `<span style="width:12px;height:12px;border-radius:3px;background:${col};display:inline-block;flex-shrink:0;"></span>${labels[k]}`;
/* highlight on hover */
d.addEventListener('mouseenter', () => this._highlightType(k));
d.addEventListener('mouseleave', () => this._unhighlightType());
this._legendEl.appendChild(d);
}
}
_highlightType(key) {
for (const el of ELEMENTS) {
const div = this._cellMap[el.Z];
if (!div) continue;
const match = this._mode === 'block' ? el.block === key : el.type === key;
if (match) {
div.style.filter = 'brightness(1.5)';
div.style.transform = 'scale(1.05)';
div.style.zIndex = '5';
} else {
div.style.opacity = '0.2';
}
}
}
_unhighlightType() {
for (const el of ELEMENTS) {
const div = this._cellMap[el.Z];
if (!div) continue;
div.style.filter = '';
div.style.transform = '';
div.style.zIndex = '';
div.style.opacity = this._highlighted.has(el.Z) ? '1' : (this._highlighted.size > 0 ? '0.25' : '1');
}
}
/* ─────────────────────────────────────────────────────
SELECT / SEARCH
───────────────────────────────────────────────────── */
_selectElement(Z) {
this._selected = Z;
if (window.LabFX) LabFX.sound.play('chime', { pitch: 0.8 + Z * 0.008, volume: 0.25 });
/* highlight selected cell */
for (const el of ELEMENTS) {
const div = this._cellMap[el.Z];
if (!div) continue;
if (el.Z === Z) {
div.style.outline = '2px solid #fff';
div.style.outlineOffset = '1px';
} else {
div.style.outline = '';
div.style.outlineOffset = '';
}
}
this._updateCard(ELEMENTS.find(e => e.Z === Z));
this._bohrZ = Z;
this._startBohr();
}
_applySearch() {
this._highlighted.clear();
if (this._searchQ) {
for (const el of ELEMENTS) {
const q = this._searchQ;
if (
el.symbol.toLowerCase() === q ||
el.name.toLowerCase().includes(q) ||
String(el.Z) === q ||
String(el.mass).startsWith(q)
) {
this._highlighted.add(el.Z);
}
}
}
this._colorTable();
/* auto-select if single result */
if (this._highlighted.size === 1) {
this._selectElement([...this._highlighted][0]);
}
}
/* ─────────────────────────────────────────────────────
ELEMENT CARD
───────────────────────────────────────────────────── */
_updateCard(el) {
if (!el) {
this._cardEl.innerHTML = `<div style="color:rgba(255,255,255,0.25);font-size:.8rem;text-align:center;padding:20px 0">Кликните на элемент</div>`;
this._bohrZ = null;
cancelAnimationFrame(this._bohrRaf);
this._bohrRaf = null;
this._drawBohr();
return;
}
const col = TYPE_COLORS[el.type] || '#888';
const ox = el.oxStates && el.oxStates[0] !== null ? el.oxStates.map(s => (s > 0 ? '+' : '') + s).join(', ') : '—';
const fmt = v => v !== null && v !== undefined ? v : '—';
this._cardEl.innerHTML = `
<div style="text-align:center;margin-bottom:10px;">
<div style="font-size:2.5rem;font-weight:900;color:${col};line-height:1">${el.symbol}</div>
<div style="font-size:.95rem;font-weight:700;color:#fff;margin-top:2px">${el.name}</div>
<div style="font-size:.72rem;color:rgba(255,255,255,0.4)">Z = ${el.Z} · ${el.mass} а.е.м.</div>
<div style="display:inline-block;margin-top:6px;padding:2px 8px;border-radius:12px;background:${col}22;border:1px solid ${col}55;font-size:.68rem;color:${col}">${TYPE_LABELS[el.type] || el.type}</div>
</div>
<table style="width:100%;border-collapse:collapse;font-size:.74rem;">
${this._row('Конфигурация', `<code style="font-size:.72rem;color:${col}">${el.config}</code>`)}
${this._row('Блок', el.block + '-блок')}
${this._row('Период / Группа', `${el.period} / ${el.group || '—'}`)}
${this._row('Ст. окисления', ox)}
${this._row('ЭО (Полинг)', fmt(el.En))}
${this._row('Плотность, г/см³', fmt(el.density))}
${this._row('T<sub>пл</sub>, K', fmt(el.melt))}
${this._row('T<sub>кип</sub>, K', fmt(el.boil))}
${this._row('Открыт', el.discovered ? `${el.discovered}, ${el.by}` : el.by)}
</table>`;
}
_row(label, val) {
return `<tr>
<td style="padding:3px 4px 3px 0;color:rgba(255,255,255,0.45);border-bottom:1px solid rgba(255,255,255,0.05);white-space:nowrap">${label}</td>
<td style="padding:3px 0 3px 4px;color:#ddd;border-bottom:1px solid rgba(255,255,255,0.05)">${val}</td>
</tr>`;
}
/* ─────────────────────────────────────────────────────
BOHR SHELLS ANIMATION
───────────────────────────────────────────────────── */
_startBohr() {
cancelAnimationFrame(this._bohrRaf);
this._bohrAngle = 0;
this._animBohr();
}
_animBohr() {
this._bohrAngle += 0.018;
this._drawBohr();
this._bohrRaf = requestAnimationFrame(() => this._animBohr());
}
_drawBohr() {
const canvas = this._bohrCanvas;
const dpr = window.devicePixelRatio || 1;
const W = canvas.offsetWidth || 240;
const H = canvas.offsetHeight || 150;
canvas.width = W * dpr;
canvas.height = H * dpr;
const ctx = canvas.getContext('2d');
ctx.scale(dpr, dpr);
ctx.clearRect(0, 0, W, H);
if (!this._bohrZ) return;
const el = ELEMENTS.find(e => e.Z === this._bohrZ);
if (!el) return;
const shells = getShellFill(el.Z);
const cx = W / 2, cy = H / 2;
const maxR = Math.min(W, H) * 0.44;
const nShells = shells.length;
const col = TYPE_COLORS[el.type] || '#7B8EF7';
/* nucleus */
ctx.beginPath();
ctx.arc(cx, cy, nShells > 0 ? 5 + nShells * 1.5 : 6, 0, Math.PI * 2);
ctx.fillStyle = col;
ctx.fill();
shells.forEach((count, i) => {
const r = maxR * (i + 1) / nShells;
/* orbit ring */
ctx.beginPath();
ctx.arc(cx, cy, r, 0, Math.PI * 2);
ctx.strokeStyle = 'rgba(255,255,255,0.12)';
ctx.lineWidth = 1;
ctx.stroke();
/* electrons */
const speed = 1 - i * 0.12; // inner shells faster
for (let e = 0; e < count; e++) {
const a = this._bohrAngle * speed + (2 * Math.PI * e) / count;
const ex = cx + r * Math.cos(a);
const ey = cy + r * Math.sin(a);
ctx.beginPath();
ctx.arc(ex, ey, 2.5, 0, Math.PI * 2);
ctx.fillStyle = '#06D6E0';
ctx.fill();
}
});
/* label */
ctx.font = `700 10px Manrope,sans-serif`;
ctx.fillStyle = 'rgba(255,255,255,0.5)';
ctx.textAlign = 'center';
ctx.fillText(shells.join(','), cx, H - 4);
}
/* ─────────────────────────────────────────────────────
PROPERTY CHART
───────────────────────────────────────────────────── */
_drawChart() {
const canvas = this._chartCanvas;
if (!canvas) return;
const dpr = window.devicePixelRatio || 1;
const W = canvas.offsetWidth || 240;
const H = canvas.offsetHeight || 90;
canvas.width = W * dpr;
canvas.height = H * dpr;
const ctx = canvas.getContext('2d');
ctx.scale(dpr, dpr);
ctx.clearRect(0, 0, W, H);
/* filter elements */
let els;
if (this._chartBy === 'period') {
els = ELEMENTS.filter(e => e.period === this._chartN && e.group !== null);
els.sort((a, b) => a.group - b.group);
} else {
els = ELEMENTS.filter(e => e.group === this._chartN);
els.sort((a, b) => a.period - b.period);
}
const vals = els.map(e => e[this._propKey]);
const validVals = vals.filter(v => v !== null && v !== undefined && isFinite(v));
if (validVals.length < 2) {
ctx.font = '11px Manrope,sans-serif';
ctx.fillStyle = 'rgba(255,255,255,0.3)';
ctx.textAlign = 'center';
ctx.fillText('Нет данных', W / 2, H / 2);
return;
}
const minV = Math.min(...validVals);
const maxV = Math.max(...validVals);
const pad = { t: 10, r: 8, b: 20, l: 8 };
const gW = W - pad.l - pad.r;
const gH = H - pad.t - pad.b;
/* axes */
ctx.strokeStyle = 'rgba(255,255,255,0.1)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(pad.l, pad.t);
ctx.lineTo(pad.l, pad.t + gH);
ctx.lineTo(pad.l + gW, pad.t + gH);
ctx.stroke();
/* line */
ctx.beginPath();
let first = true;
const points = [];
els.forEach((el, i) => {
const v = el[this._propKey];
if (v === null || v === undefined || !isFinite(v)) return;
const x = pad.l + (i / Math.max(els.length - 1, 1)) * gW;
const y = pad.t + gH - ((v - minV) / (maxV - minV || 1)) * gH;
points.push({ x, y, el });
if (first) { ctx.moveTo(x, y); first = false; } else ctx.lineTo(x, y);
});
ctx.strokeStyle = '#9B5DE5';
ctx.lineWidth = 1.5;
ctx.stroke();
/* markers */
points.forEach(({ x, y, el }) => {
const col = TYPE_COLORS[el.type] || '#7B8EF7';
ctx.beginPath();
ctx.arc(x, y, 3, 0, Math.PI * 2);
ctx.fillStyle = col;
ctx.fill();
});
/* x labels (symbols) */
ctx.font = '8px Manrope,sans-serif';
ctx.fillStyle = 'rgba(255,255,255,0.35)';
ctx.textAlign = 'center';
/* show at most 18 labels */
const step = Math.ceil(els.length / 18);
els.forEach((el, i) => {
if (i % step === 0) {
const x = pad.l + (i / Math.max(els.length - 1, 1)) * gW;
ctx.fillText(el.symbol, x, H - 4);
}
});
}
/* ─────────────────────────────────────────────────────
LIFECYCLE
───────────────────────────────────────────────────── */
stop() {
cancelAnimationFrame(this._bohrRaf);
this._bohrRaf = null;
}
}
/* ── global opener ─────────────────────────────────────────── */
var periodicSim = null;
function _openPeriodic() {
document.getElementById('sim-periodic').style.display = 'flex';
if (!periodicSim) {
periodicSim = new PeriodicTableSim(document.getElementById('periodic-wrap'));
}
}