@@ -0,0 +1,183 @@
/* chem8_glossary.js — глоссарий учебника «Химия 8».
* Самодостаточный drop-in: словарь терминов + плавающая кнопка + модалка с поиском
* + авто-подсветка терминов в .card-body (tooltip с определением). Стили инжектятся.
* Подключается одним тегом <script src="/js/chem8_glossary.js" defer></script>.
*/
( function ( W ) {
'use strict' ;
var D = W . document ;
/* словарь: термин → {d: определение, see: [связанные]} */
var G = {
'атом' : { d : 'Мельчайшая химически неделимая частица вещества: ядро (протоны и нейтроны) + электроны.' , see : [ 'химический элемент' , 'нуклид' ] } ,
'химический элемент' : { d : 'Вид атомов с одинаковым зарядом ядра (числом протонов).' , see : [ 'атом' ] } ,
'относительная атомная масса' : { d : 'Безразмерная величина $A_r$ — во сколько раз масса атома больше 1/12 массы атома углерода-12.' , see : [ 'относительная молекулярная масса' ] } ,
'относительная молекулярная масса' : { d : 'Сумма относительных атомных масс всех атомов в формуле ($M_r$).' , see : [ 'молярная масса' ] } ,
'простое вещество' : { d : 'Вещество из атомов одного элемента (O₂, Fe).' , see : [ 'сложное вещество' ] } ,
'сложное вещество' : { d : 'Вещество из атомов разных элементов (H₂O, CaCO₃).' , see : [ 'простое вещество' ] } ,
'химическая формула' : { d : 'Запись состава вещества символами элементов с индексами.' , see : [ ] } ,
'химическое количество' : { d : 'Физическая величина $n$ (порция вещества), измеряется в молях.' , see : [ 'моль' , 'постоянная Авогадро' ] } ,
'моль' : { d : 'Единица химического количества: содержит $6{,}02\\cdot10^{23}$ частиц (число Авогадро).' , see : [ 'постоянная Авогадро' ] } ,
'постоянная Авогадро' : { d : '$N_A = 6{,}02\\cdot10^{23}$ частиц/моль — число частиц в 1 моль.' , see : [ 'моль' ] } ,
'молярная масса' : { d : 'Масса 1 моль вещества $M$ (г/моль); численно равна $M_r$.' , see : [ 'относительная молекулярная масса' ] } ,
'молярный объём' : { d : 'Объём 1 моль газа; при н.у . $V_m = 22{,}4$ л/моль.' , see : [ ] } ,
'оксид' : { d : 'Сложное вещество из элемента и кислорода (с.о. −2): основный, кислотный, амфотерный, несолеобразующий.' , see : [ 'основный оксид' , 'кислотный оксид' ] } ,
'основный оксид' : { d : 'Оксид металла, реагирует с кислотами (CaO, Na₂O).' , see : [ 'оксид' ] } ,
'кислотный оксид' : { d : 'Оксид неметалла, реагирует со щелочами (CO₂, SO₃).' , see : [ 'оксид' ] } ,
'амфотерность' : { d : 'Способность вещества проявлять и кислотные, и основные свойства (Zn(OH)₂, Al(OH)₃).' , see : [ 'оксид' , 'основание' ] } ,
'кислота' : { d : 'Вещество с атомами водорода, способными замещаться металлом, и кислотным остатком.' , see : [ 'основность' ] } ,
'основность' : { d : 'Число атомов водорода в кислоте, способных замещаться металлом.' , see : [ 'кислота' ] } ,
'основание' : { d : 'Вещество из металла и гидроксогрупп OH; растворимые — щёлочи.' , see : [ 'щёлочь' , 'нейтрализация' ] } ,
'щёлочь' : { d : 'Растворимое в воде основание (NaOH, KOH, Ba(OH)₂).' , see : [ 'основание' ] } ,
'соль' : { d : 'Вещество из катионов металла и анионов кислотного остатка (NaCl, CaCO₃).' , see : [ 'реакция ионного обмена' ] } ,
'нейтрализация' : { d : 'Реакция кислоты с основанием: соль + вода.' , see : [ 'кислота' , 'основание' ] } ,
'индикатор' : { d : 'Вещество, меняющее окраску в зависимости от среды (лакмус, фенолфталеин, метилоранж).' , see : [ ] } ,
'реакция ионного обмена' : { d : 'Реакция между растворами, идущая до конца при образовании осадка ↓, газа ↑ или воды.' , see : [ 'соль' , 'растворимость' ] } ,
'ряд активности металлов' : { d : 'Ряд металлов по убыванию химической активности; металл вытесняет менее активные.' , see : [ ] } ,
'генетическая связь' : { d : 'Связь между классами веществ через цепочки превращений (металл→оксид→основание→соль).' , see : [ ] } ,
'периодический закон' : { d : 'Свойства элементов периодически зависят от заряда ядра их атомов (Д. И. Менделеев, 1869).' , see : [ 'периодическая система' ] } ,
'периодическая система' : { d : 'Таблица элементов: периоды (строки) и группы (столбцы).' , see : [ 'период' , 'группа' ] } ,
'период' : { d : 'Горизонтальный ряд в ПСХЭ; номер = число электронных слоёв.' , see : [ 'периодическая система' ] } ,
'группа' : { d : 'Вертикальный столбец ПСХЭ; номер = число внешних электронов.' , see : [ 'периодическая система' ] } ,
'нуклид' : { d : 'Вид атомов с определёнными Z (протоны) и N (нейтроны).' , see : [ 'изотопы' , 'массовое число' ] } ,
'массовое число' : { d : 'Число протонов и нейтронов в ядре: $A = Z + N$.' , see : [ 'нуклид' ] } ,
'изотопы' : { d : 'Атомы одного элемента с разным числом нейтронов (одинаковый Z, разный A).' , see : [ 'нуклид' ] } ,
'электронное облако' : { d : 'Область вокруг ядра, где электрон бывает чаще всего.' , see : [ 'орбиталь' ] } ,
'орбиталь' : { d : 'Форма электронного облака: s — сфера, p — гантель.' , see : [ 'электронное облако' ] } ,
'электроотрицательность' : { d : 'Способность атома притягивать к себе общие электроны.' , see : [ 'ковалентная связь' ] } ,
'ковалентная связь' : { d : 'Связь за счёт общих электронных пар (между неметаллами).' , see : [ 'электроотрицательность' , 'ионная связь' ] } ,
'ионная связь' : { d : 'Связь за счёт полной передачи электронов от металла к неметаллу; образуются ионы.' , see : [ 'ковалентная связь' ] } ,
'металлическая связь' : { d : 'Связь ион-остовов металла «электронным газом» из общих электронов.' , see : [ ] } ,
'кристаллическая решётка' : { d : 'Упорядоченное расположение частиц в кристалле: ионная, атомная, молекулярная, металлическая.' , see : [ ] } ,
'степень окисления' : { d : 'Условный заряд атома в соединении (H +1, O − 2, сумма = 0).' , see : [ 'окисление' , 'восстановление' ] } ,
'окисление' : { d : 'Процесс отдачи электронов (степень окисления повышается).' , see : [ 'восстановление' , 'степень окисления' ] } ,
'восстановление' : { d : 'Процесс приёма электронов (степень окисления понижается).' , see : [ 'окисление' ] } ,
'окислитель' : { d : 'Частица, принимающая электроны (сама восстанавливается).' , see : [ 'восстановитель' ] } ,
'восстановитель' : { d : 'Частица, отдающая электроны (сама окисляется).' , see : [ 'окислитель' ] } ,
'окислительно-восстановительная реакция' : { d : 'Реакция с изменением степеней окисления (переход электронов).' , see : [ 'степень окисления' ] } ,
'смесь' : { d : 'Несколько веществ вместе: однородная (раствор) или неоднородная.' , see : [ 'раствор' ] } ,
'раствор' : { d : 'Однородная смесь растворителя и растворённого вещества.' , see : [ 'растворимость' , 'массовая доля' ] } ,
'растворимость' : { d : 'Масса вещества, растворяющаяся в 100 г воды при данной температуре.' , see : [ 'раствор' ] } ,
'насыщенный раствор' : { d : 'Раствор, в котором вещество больше не растворяется при данной температуре.' , see : [ 'раствор' ] } ,
'массовая доля' : { d : 'Отношение массы растворённого вещества к массе раствора: $w = m_{в-ва}/m_{р -ра}$.' , see : [ 'раствор' ] } ,
'молярная концентрация' : { d : 'Химическое количество вещества в 1 л раствора: $c = n/V$ (моль/л).' , see : [ 'раствор' ] }
} ;
var TERMS = Object . keys ( G ) . sort ( function ( a , b ) { return b . length - a . length ; } ) ; // длинные раньше
function injectCSS ( ) {
if ( D . getElementById ( 'chem8-gloss-css' ) ) return ;
var s = D . createElement ( 'style' ) ; s . id = 'chem8-gloss-css' ;
s . textContent =
'.gloss{border-bottom:1.5px dotted var(--pri,#d97706);cursor:help;text-decoration:none}'
+ '.gl-fab{position:fixed;left:16px;bottom:16px;z-index:55;display:inline-flex;align-items:center;gap:7px;padding:9px 14px;border:none;border-radius:99px;background:var(--pri,#d97706);color:#fff;font-weight:700;font-size:.84rem;cursor:pointer;box-shadow:0 6px 18px rgba(0,0,0,.18);font-family:inherit}'
+ '.gl-fab:hover{filter:brightness(1.08)}.gl-fab svg{width:16px;height:16px;stroke:#fff;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}'
+ '.gl-modal{position:fixed;inset:0;z-index:80;background:rgba(0,0,0,.45);display:none;align-items:flex-start;justify-content:center;padding:40px 16px;overflow:auto}'
+ '.gl-modal.show{display:flex}'
+ '.gl-box{background:var(--card,#fff);color:var(--text,#1c1917);border-radius:16px;max-width:600px;width:100%;padding:20px;box-shadow:0 20px 60px rgba(0,0,0,.3)}'
+ '.gl-h{display:flex;align-items:center;gap:10px;margin-bottom:12px}.gl-h h3{font-family:Outfit,sans-serif;font-size:1.15rem;font-weight:800;flex:1}'
+ '.gl-close{border:none;background:transparent;font-size:1.4rem;cursor:pointer;color:var(--muted,#888);line-height:1}'
+ '.gl-search{width:100%;padding:10px 13px;border:1.5px solid var(--border,#ddd);border-radius:10px;background:var(--card,#fff);color:var(--text,#1c1917);font-family:inherit;font-size:.95rem;margin-bottom:12px}'
+ '.gl-list{max-height:60vh;overflow:auto}'
+ '.gl-item{padding:10px 12px;border-bottom:1px solid var(--border,#eee)}.gl-item:last-child{border-bottom:0}'
+ '.gl-term{font-weight:800;color:var(--pri-d,#b45309);text-transform:capitalize}'
+ '.gl-def{font-size:.9rem;margin-top:3px;line-height:1.5}'
+ '.gl-see{font-size:.8rem;color:var(--muted,#888);margin-top:4px}'
+ '.gl-pop{position:absolute;z-index:90;max-width:280px;background:var(--card,#fff);color:var(--text,#1c1917);border:1.5px solid var(--pri,#d97706);border-radius:10px;padding:10px 13px;font-size:.86rem;line-height:1.5;box-shadow:0 8px 24px rgba(0,0,0,.2);display:none}'
+ '.gl-pop.show{display:block}.gl-pop b{color:var(--pri-d,#b45309);text-transform:capitalize}' ;
D . head . appendChild ( s ) ;
}
function esc ( s ) { return s . replace ( /[.*+?^${}()|[\]\\]/g , '\\$&' ) ; }
/* авто-подсветка терминов в .card-body (первое вхождение каждого, в текстовых узлах) */
function decorate ( root ) {
if ( ! root ) return ;
var bodies = root . matches && root . matches ( '.card-body' ) ? [ root ] : root . querySelectorAll ? root . querySelectorAll ( '.card-body' ) : [ ] ;
Array . prototype . forEach . call ( bodies , function ( body ) {
if ( body . _glossed ) return ; body . _glossed = 1 ;
var used = { } ;
TERMS . forEach ( function ( term ) {
if ( used [ term ] ) return ;
var walker = D . createTreeWalker ( body , W . NodeFilter . SHOW _TEXT , null ) ;
var node , re = new RegExp ( '(^|[^а -яёА-ЯЁ-])(' + esc ( term ) + ')(?![а -яёА-ЯЁ])' , 'i' ) ;
while ( ( node = walker . nextNode ( ) ) ) {
if ( node . parentNode && ( node . parentNode . classList && ( node . parentNode . classList . contains ( 'gloss' ) || node . parentNode . closest ( '.gloss,abbr,a,.ph-formula,.main-f,code' ) ) ) ) continue ;
var m = node . nodeValue . match ( re ) ;
if ( m ) {
var idx = m . index + m [ 1 ] . length ;
var before = node . nodeValue . slice ( 0 , idx ) , word = node . nodeValue . slice ( idx , idx + term . length ) , after = node . nodeValue . slice ( idx + term . length ) ;
var ab = D . createElement ( 'abbr' ) ; ab . className = 'gloss' ; ab . setAttribute ( 'data-term' , term . toLowerCase ( ) ) ; ab . textContent = word ;
var frag = D . createDocumentFragment ( ) ;
frag . appendChild ( D . createTextNode ( before ) ) ; frag . appendChild ( ab ) ; frag . appendChild ( D . createTextNode ( after ) ) ;
node . parentNode . replaceChild ( frag , node ) ;
used [ term ] = 1 ; break ;
}
}
} ) ;
} ) ;
}
/* popover при наведении/клике на .gloss */
var pop ;
function showPop ( ab ) {
var term = ab . getAttribute ( 'data-term' ) ; var g = G [ term ] ; if ( ! g ) return ;
if ( ! pop ) { pop = D . createElement ( 'div' ) ; pop . className = 'gl-pop' ; D . body . appendChild ( pop ) ; }
pop . innerHTML = '<b>' + term + '</b><br>' + g . d + ( g . see && g . see . length ? '<div class="gl-see">См.: ' + g . see . join ( ', ' ) + '</div>' : '' ) ;
var r = ab . getBoundingClientRect ( ) ;
pop . style . left = Math . min ( r . left , W . innerWidth - 300 ) + 'px' ;
pop . style . top = ( r . bottom + W . scrollY + 6 ) + 'px' ;
pop . classList . add ( 'show' ) ;
renderMath ( pop ) ;
}
function hidePop ( ) { if ( pop ) pop . classList . remove ( 'show' ) ; }
function renderMath ( el ) { if ( typeof W . renderMathInElement === 'function' ) { try { W . renderMathInElement ( el , { delimiters : [ { left : '$' , right : '$' , display : false } ] , throwOnError : false } ) ; } catch ( e ) { } } }
/* модалка */
var modal ;
function buildModal ( ) {
modal = D . createElement ( 'div' ) ; modal . className = 'gl-modal' ;
modal . innerHTML = '<div class="gl-box"><div class="gl-h"><h3>Глоссарий — Химия 8</h3><button class="gl-close" aria-label="Закрыть">×</button></div>'
+ '<input class="gl-search" placeholder="Поиск термина...">'
+ '<div class="gl-list"></div></div>' ;
D . body . appendChild ( modal ) ;
var list = modal . querySelector ( '.gl-list' ) , search = modal . querySelector ( '.gl-search' ) ;
function render ( q ) {
q = ( q || '' ) . toLowerCase ( ) . trim ( ) ;
var keys = Object . keys ( G ) . sort ( ) ;
list . innerHTML = keys . filter ( function ( t ) { return ! q || t . indexOf ( q ) >= 0 || G [ t ] . d . toLowerCase ( ) . indexOf ( q ) >= 0 ; } )
. map ( function ( t ) { return '<div class="gl-item"><div class="gl-term">' + t + '</div><div class="gl-def">' + G [ t ] . d + '</div>' + ( G [ t ] . see && G [ t ] . see . length ? '<div class="gl-see">См.: ' + G [ t ] . see . join ( ', ' ) + '</div>' : '' ) + '</div>' ; } ) . join ( '' ) || '<div class="gl-item">Ничего не найдено.</div>' ;
renderMath ( list ) ;
}
search . addEventListener ( 'input' , function ( ) { render ( search . value ) ; } ) ;
modal . querySelector ( '.gl-close' ) . addEventListener ( 'click' , close ) ;
modal . addEventListener ( 'click' , function ( e ) { if ( e . target === modal ) close ( ) ; } ) ;
render ( '' ) ;
}
function open ( ) { if ( ! modal ) buildModal ( ) ; modal . classList . add ( 'show' ) ; var s = modal . querySelector ( '.gl-search' ) ; if ( s ) setTimeout ( function ( ) { s . focus ( ) ; } , 50 ) ; }
function close ( ) { if ( modal ) modal . classList . remove ( 'show' ) ; }
function init ( ) {
injectCSS ( ) ;
var fab = D . createElement ( 'button' ) ; fab . className = 'gl-fab' ;
fab . innerHTML = '<svg viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg> Глоссарий' ;
fab . addEventListener ( 'click' , open ) ;
D . body . appendChild ( fab ) ;
D . addEventListener ( 'keydown' , function ( e ) { if ( e . key === 'Escape' ) close ( ) ; } ) ;
// авто-подсветка терминов: при наведении/клике — popover
D . body . addEventListener ( 'mouseover' , function ( e ) { if ( e . target . classList && e . target . classList . contains ( 'gloss' ) ) showPop ( e . target ) ; } ) ;
D . body . addEventListener ( 'mouseout' , function ( e ) { if ( e . target . classList && e . target . classList . contains ( 'gloss' ) ) hidePop ( ) ; } ) ;
D . body . addEventListener ( 'click' , function ( e ) { if ( e . target . classList && e . target . classList . contains ( 'gloss' ) ) { e . preventDefault ( ) ; showPop ( e . target ) ; } } ) ;
// первичная декорация + наблюдение за лениво строящимися §
decorate ( D . body ) ;
try {
var obs = new W . MutationObserver ( function ( muts ) {
muts . forEach ( function ( m ) { Array . prototype . forEach . call ( m . addedNodes , function ( n ) { if ( n . nodeType === 1 ) decorate ( n ) ; } ) ; } ) ;
} ) ;
obs . observe ( D . body , { childList : true , subtree : true } ) ;
} catch ( e ) { }
}
W . Chem8Glossary = { open : open , decorate : decorate , terms : G } ;
if ( D . readyState === 'loading' ) D . addEventListener ( 'DOMContentLoaded' , init ) ; else init ( ) ;
} ) ( window ) ;