diff --git a/frontend/textbooks/geometry_9_ch1.html b/frontend/textbooks/geometry_9_ch1.html
index 0ad32f5..9c4ffc9 100644
--- a/frontend/textbooks/geometry_9_ch1.html
+++ b/frontend/textbooks/geometry_9_ch1.html
@@ -184,6 +184,38 @@ a{color:inherit;text-decoration:none}
.search-empty{padding:20px;text-align:center;color:var(--muted);font-size:.88rem}
.search-foot{padding:8px 14px;border-top:1px solid var(--border);font-size:.74rem;color:var(--muted);display:flex;gap:14px}
.search-foot kbd{padding:2px 6px;background:var(--card);border:1px solid var(--border);border-radius:4px;font-family:'JetBrains Mono',monospace;font-size:.72rem}
+
+/* === GEOM9 POLISH (visual + micro-interactions) === */
+@keyframes wgFadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
+.sec.active .wg{animation:wgFadeIn .35s cubic-bezier(.16,1,.3,1) backwards}
+.sec.active .wg:nth-of-type(1){animation-delay:.02s}
+.sec.active .wg:nth-of-type(2){animation-delay:.08s}
+.sec.active .wg:nth-of-type(3){animation-delay:.14s}
+.sec.active .wg:nth-of-type(4){animation-delay:.20s}
+.sec.active .wg:nth-of-type(5){animation-delay:.26s}
+.sec.active .wg:nth-of-type(6){animation-delay:.32s}
+.wg svg{transition:filter .25s ease}
+.wg:hover svg{filter:drop-shadow(0 4px 16px rgba(0,0,0,.10))}
+input[type=range]:active{box-shadow:0 0 0 4px var(--pri-soft);border-radius:8px}
+.wg input[type=range]{cursor:ew-resize}
+.score-display b{transition:transform .22s cubic-bezier(.16,1,.3,1),color .22s;display:inline-block;transform-origin:center}
+.score-display b.bump{transform:scale(1.28);color:var(--pri)}
+.katex{transition:color .2s}
+.wg-help .katex:hover,.card-body .katex:hover{color:var(--pri2,var(--pri));cursor:help}
+.hp-fill,.psel-prog-fill,.xp-fill,[id$="-overall-fill"]{transition:width .6s cubic-bezier(.16,1,.3,1)!important}
+.boss-card,.btn.primary,.btn-primary{position:relative;overflow:hidden}
+.btn.primary::after,.btn-primary::after{content:'';position:absolute;inset:0;background:radial-gradient(circle at center,rgba(255,255,255,.42) 0%,transparent 60%);opacity:0;transition:opacity .25s;pointer-events:none}
+.btn.primary:hover::after,.btn-primary:hover::after{opacity:1}
+.psel-card{position:relative}
+.psel-card .psel-done{position:absolute;top:6px;right:6px;width:18px;height:18px;border-radius:50%;background:#10b981;display:none;align-items:center;justify-content:center;box-shadow:0 2px 6px rgba(16,185,129,.45);z-index:2}
+.psel-card .psel-done svg{width:11px;height:11px;stroke:#fff;fill:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:round}
+.psel-card.done .psel-done{display:flex}
+.boss-card{transition:border-color .35s,box-shadow .6s,background .3s,transform .2s}
+.boss-card.glow{box-shadow:0 0 24px rgba(16,185,129,.6),0 0 0 2px rgba(16,185,129,.45)!important}
+@keyframes bossPulse{0%{box-shadow:0 0 0 0 rgba(16,185,129,.55)}70%{box-shadow:0 0 0 14px rgba(16,185,129,0)}100%{box-shadow:0 0 0 0 rgba(16,185,129,0)}}
+.boss-card.pulse{animation:bossPulse .8s ease-out}
+.psel-card{transition:transform .2s,box-shadow .2s,border-color .2s,background .25s}
+.sec{transition:opacity .25s}
@@ -2679,6 +2711,132 @@ function init(){
}
}
document.addEventListener('DOMContentLoaded', init);
+
+/* === GEOM9 POLISH JS === */
+(function(){
+ function bumpScore(el){
+ if(!el) return;
+ el.classList.remove('bump');
+ void el.offsetWidth;
+ el.classList.add('bump');
+ setTimeout(function(){ try{ el.classList.remove('bump'); }catch(e){} }, 270);
+ }
+ window.__geom9BumpScore = bumpScore;
+
+ /* Observe .score-display b text changes — auto bump */
+ function observeScores(root){
+ root = root || document;
+ var nodes = root.querySelectorAll('.score-display b');
+ nodes.forEach(function(b){
+ if(b.__scoreObs) return;
+ b.__scoreObs = true;
+ var last = b.textContent;
+ try{
+ var mo = new MutationObserver(function(){
+ var nv = b.textContent;
+ if(nv !== last){ last = nv; bumpScore(b); }
+ });
+ mo.observe(b, {childList:true, characterData:true, subtree:true});
+ }catch(e){}
+ });
+ }
+ function rescanScores(){ try{ observeScores(document); }catch(e){} }
+ if(document.readyState === 'loading') document.addEventListener('DOMContentLoaded', rescanScores);
+ else rescanScores();
+
+ /* Re-scan on DOM additions (lazy-built sections) */
+ try{
+ var rootObs = new MutationObserver(function(muts){
+ var need = false;
+ for(var i=0;i= 50 */
+ function refreshDoneMarks(){
+ try{
+ if(typeof STATE === 'undefined' || !STATE.progress) return;
+ document.querySelectorAll('.psel-card').forEach(function(c){
+ var id = c.dataset.id || c.dataset.progCard;
+ if(!id) return;
+ var pct = +STATE.progress[id] || 0;
+ if(!c.querySelector('.psel-done')){
+ var s = document.createElement('span');
+ s.className = 'psel-done';
+ s.setAttribute('title','Прочитано');
+ s.innerHTML = '';
+ c.appendChild(s);
+ }
+ c.classList.toggle('done', pct >= 50);
+ });
+ }catch(e){}
+ }
+ /* Wrap refreshProgressUI if present */
+ try{
+ if(typeof window.refreshProgressUI === 'function'){
+ var _origRP = window.refreshProgressUI;
+ window.refreshProgressUI = function(){ var r = _origRP.apply(this, arguments); setTimeout(refreshDoneMarks, 0); return r; };
+ }
+ }catch(e){}
+ setTimeout(refreshDoneMarks, 600);
+ setTimeout(refreshDoneMarks, 1800);
+ window.addEventListener('focus', function(){ setTimeout(refreshDoneMarks, 200); });
+
+ /* Smooth-scroll on psel-card click */
+ document.addEventListener('click', function(e){
+ var card = e.target.closest && e.target.closest('.psel-card');
+ if(!card) return;
+ var id = card.dataset.id;
+ if(!id) return;
+ setTimeout(function(){
+ var sec = document.getElementById('sec-' + id);
+ if(sec) try{ sec.scrollIntoView({behavior:'smooth', block:'start'}); }catch(e){}
+ }, 60);
+ });
+
+ /* Boss-card glow when defeated */
+ function observeBossGlow(){
+ var cards = document.querySelectorAll('.boss-card');
+ cards.forEach(function(card){
+ if(card.__bossObs) return;
+ card.__bossObs = true;
+ try{
+ var mo = new MutationObserver(function(){
+ if(card.__glowed) return;
+ var go = card.querySelector('button[id*="-go"]');
+ var fb = card.querySelector('.feedback.ok, .boss-fb.ok');
+ var defeatedByBtn = go && go.disabled;
+ var defeatedByFb = fb && fb.textContent && /Побед|повержен/i.test(fb.textContent);
+ if(defeatedByBtn || defeatedByFb){
+ card.__glowed = true;
+ card.classList.add('glow','pulse');
+ setTimeout(function(){ try{ card.classList.remove('pulse'); }catch(e){} }, 900);
+ setTimeout(function(){ try{ card.classList.remove('glow'); }catch(e){} }, 1400);
+ }
+ });
+ mo.observe(card, {childList:true, subtree:true, attributes:true, attributeFilter:['class','style','disabled']});
+ /* Initial: if already defeated (loaded from state) — skip glow (no animation on page load) */
+ var goNow = card.querySelector('button[id*="-go"]');
+ if(goNow && goNow.disabled) card.__glowed = true;
+ }catch(e){}
+ });
+ }
+ var bossRescan = function(){ try{ observeBossGlow(); }catch(e){} };
+ setTimeout(bossRescan, 800);
+ var bossPoll = setInterval(bossRescan, 2500);
+ setTimeout(function(){ try{ clearInterval(bossPoll); }catch(e){} }, 90000);
+})();
diff --git a/frontend/textbooks/geometry_9_ch2.html b/frontend/textbooks/geometry_9_ch2.html
index 3ceea07..7788d5e 100644
--- a/frontend/textbooks/geometry_9_ch2.html
+++ b/frontend/textbooks/geometry_9_ch2.html
@@ -165,6 +165,38 @@ a{color:inherit;text-decoration:none}
.search-empty{padding:20px;text-align:center;color:var(--muted);font-size:.88rem}
.search-foot{padding:8px 14px;border-top:1px solid var(--border);font-size:.74rem;color:var(--muted);display:flex;gap:14px}
.search-foot kbd{padding:2px 6px;background:var(--card);border:1px solid var(--border);border-radius:4px;font-family:'JetBrains Mono',monospace;font-size:.72rem}
+
+/* === GEOM9 POLISH (visual + micro-interactions) === */
+@keyframes wgFadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
+.sec.active .wg{animation:wgFadeIn .35s cubic-bezier(.16,1,.3,1) backwards}
+.sec.active .wg:nth-of-type(1){animation-delay:.02s}
+.sec.active .wg:nth-of-type(2){animation-delay:.08s}
+.sec.active .wg:nth-of-type(3){animation-delay:.14s}
+.sec.active .wg:nth-of-type(4){animation-delay:.20s}
+.sec.active .wg:nth-of-type(5){animation-delay:.26s}
+.sec.active .wg:nth-of-type(6){animation-delay:.32s}
+.wg svg{transition:filter .25s ease}
+.wg:hover svg{filter:drop-shadow(0 4px 16px rgba(0,0,0,.10))}
+input[type=range]:active{box-shadow:0 0 0 4px var(--pri-soft);border-radius:8px}
+.wg input[type=range]{cursor:ew-resize}
+.score-display b{transition:transform .22s cubic-bezier(.16,1,.3,1),color .22s;display:inline-block;transform-origin:center}
+.score-display b.bump{transform:scale(1.28);color:var(--pri)}
+.katex{transition:color .2s}
+.wg-help .katex:hover,.card-body .katex:hover{color:var(--pri2,var(--pri));cursor:help}
+.hp-fill,.psel-prog-fill,.xp-fill,[id$="-overall-fill"]{transition:width .6s cubic-bezier(.16,1,.3,1)!important}
+.boss-card,.btn.primary,.btn-primary{position:relative;overflow:hidden}
+.btn.primary::after,.btn-primary::after{content:'';position:absolute;inset:0;background:radial-gradient(circle at center,rgba(255,255,255,.42) 0%,transparent 60%);opacity:0;transition:opacity .25s;pointer-events:none}
+.btn.primary:hover::after,.btn-primary:hover::after{opacity:1}
+.psel-card{position:relative}
+.psel-card .psel-done{position:absolute;top:6px;right:6px;width:18px;height:18px;border-radius:50%;background:#10b981;display:none;align-items:center;justify-content:center;box-shadow:0 2px 6px rgba(16,185,129,.45);z-index:2}
+.psel-card .psel-done svg{width:11px;height:11px;stroke:#fff;fill:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:round}
+.psel-card.done .psel-done{display:flex}
+.boss-card{transition:border-color .35s,box-shadow .6s,background .3s,transform .2s}
+.boss-card.glow{box-shadow:0 0 24px rgba(16,185,129,.6),0 0 0 2px rgba(16,185,129,.45)!important}
+@keyframes bossPulse{0%{box-shadow:0 0 0 0 rgba(16,185,129,.55)}70%{box-shadow:0 0 0 14px rgba(16,185,129,0)}100%{box-shadow:0 0 0 0 rgba(16,185,129,0)}}
+.boss-card.pulse{animation:bossPulse .8s ease-out}
+.psel-card{transition:transform .2s,box-shadow .2s,border-color .2s,background .25s}
+.sec{transition:opacity .25s}
@@ -1615,6 +1647,125 @@ function init(){
}
}
document.addEventListener('DOMContentLoaded', init);
+
+/* === GEOM9 POLISH JS === */
+(function(){
+ function bumpScore(el){
+ if(!el) return;
+ el.classList.remove('bump');
+ void el.offsetWidth;
+ el.classList.add('bump');
+ setTimeout(function(){ try{ el.classList.remove('bump'); }catch(e){} }, 270);
+ }
+ window.__geom9BumpScore = bumpScore;
+
+ function observeScores(root){
+ root = root || document;
+ var nodes = root.querySelectorAll('.score-display b');
+ nodes.forEach(function(b){
+ if(b.__scoreObs) return;
+ b.__scoreObs = true;
+ var last = b.textContent;
+ try{
+ var mo = new MutationObserver(function(){
+ var nv = b.textContent;
+ if(nv !== last){ last = nv; bumpScore(b); }
+ });
+ mo.observe(b, {childList:true, characterData:true, subtree:true});
+ }catch(e){}
+ });
+ }
+ function rescanScores(){ try{ observeScores(document); }catch(e){} }
+ if(document.readyState === 'loading') document.addEventListener('DOMContentLoaded', rescanScores);
+ else rescanScores();
+
+ try{
+ var rootObs = new MutationObserver(function(muts){
+ var need = false;
+ for(var i=0;i= 50);
+ });
+ }catch(e){}
+ }
+ try{
+ if(typeof window.refreshProgressUI === 'function'){
+ var _origRP = window.refreshProgressUI;
+ window.refreshProgressUI = function(){ var r = _origRP.apply(this, arguments); setTimeout(refreshDoneMarks, 0); return r; };
+ }
+ }catch(e){}
+ setTimeout(refreshDoneMarks, 600);
+ setTimeout(refreshDoneMarks, 1800);
+ window.addEventListener('focus', function(){ setTimeout(refreshDoneMarks, 200); });
+
+ document.addEventListener('click', function(e){
+ var card = e.target.closest && e.target.closest('.psel-card');
+ if(!card) return;
+ var id = card.dataset.id;
+ if(!id) return;
+ setTimeout(function(){
+ var sec = document.getElementById('sec-' + id);
+ if(sec) try{ sec.scrollIntoView({behavior:'smooth', block:'start'}); }catch(e){}
+ }, 60);
+ });
+
+ function observeBossGlow(){
+ var cards = document.querySelectorAll('.boss-card');
+ cards.forEach(function(card){
+ if(card.__bossObs) return;
+ card.__bossObs = true;
+ try{
+ var mo = new MutationObserver(function(){
+ if(card.__glowed) return;
+ var go = card.querySelector('button[id*="-go"]');
+ var fb = card.querySelector('.feedback.ok, .boss-fb.ok');
+ var defeatedByBtn = go && go.disabled;
+ var defeatedByFb = fb && fb.textContent && /Побед|повержен/i.test(fb.textContent);
+ if(defeatedByBtn || defeatedByFb){
+ card.__glowed = true;
+ card.classList.add('glow','pulse');
+ setTimeout(function(){ try{ card.classList.remove('pulse'); }catch(e){} }, 900);
+ setTimeout(function(){ try{ card.classList.remove('glow'); }catch(e){} }, 1400);
+ }
+ });
+ mo.observe(card, {childList:true, subtree:true, attributes:true, attributeFilter:['class','style','disabled']});
+ var goNow = card.querySelector('button[id*="-go"]');
+ if(goNow && goNow.disabled) card.__glowed = true;
+ }catch(e){}
+ });
+ }
+ var bossRescan = function(){ try{ observeBossGlow(); }catch(e){} };
+ setTimeout(bossRescan, 800);
+ var bossPoll = setInterval(bossRescan, 2500);
+ setTimeout(function(){ try{ clearInterval(bossPoll); }catch(e){} }, 90000);
+})();
diff --git a/frontend/textbooks/geometry_9_ch3.html b/frontend/textbooks/geometry_9_ch3.html
index 54b2eb5..9087060 100644
--- a/frontend/textbooks/geometry_9_ch3.html
+++ b/frontend/textbooks/geometry_9_ch3.html
@@ -165,6 +165,38 @@ a{color:inherit;text-decoration:none}
.search-empty{padding:20px;text-align:center;color:var(--muted);font-size:.88rem}
.search-foot{padding:8px 14px;border-top:1px solid var(--border);font-size:.74rem;color:var(--muted);display:flex;gap:14px}
.search-foot kbd{padding:2px 6px;background:var(--card);border:1px solid var(--border);border-radius:4px;font-family:'JetBrains Mono',monospace;font-size:.72rem}
+
+/* === GEOM9 POLISH (visual + micro-interactions) === */
+@keyframes wgFadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
+.sec.active .wg{animation:wgFadeIn .35s cubic-bezier(.16,1,.3,1) backwards}
+.sec.active .wg:nth-of-type(1){animation-delay:.02s}
+.sec.active .wg:nth-of-type(2){animation-delay:.08s}
+.sec.active .wg:nth-of-type(3){animation-delay:.14s}
+.sec.active .wg:nth-of-type(4){animation-delay:.20s}
+.sec.active .wg:nth-of-type(5){animation-delay:.26s}
+.sec.active .wg:nth-of-type(6){animation-delay:.32s}
+.wg svg{transition:filter .25s ease}
+.wg:hover svg{filter:drop-shadow(0 4px 16px rgba(0,0,0,.10))}
+input[type=range]:active{box-shadow:0 0 0 4px var(--pri-soft);border-radius:8px}
+.wg input[type=range]{cursor:ew-resize}
+.score-display b{transition:transform .22s cubic-bezier(.16,1,.3,1),color .22s;display:inline-block;transform-origin:center}
+.score-display b.bump{transform:scale(1.28);color:var(--pri)}
+.katex{transition:color .2s}
+.wg-help .katex:hover,.card-body .katex:hover{color:var(--pri2,var(--pri));cursor:help}
+.hp-fill,.psel-prog-fill,.xp-fill,[id$="-overall-fill"]{transition:width .6s cubic-bezier(.16,1,.3,1)!important}
+.boss-card,.btn.primary,.btn-primary{position:relative;overflow:hidden}
+.btn.primary::after,.btn-primary::after{content:'';position:absolute;inset:0;background:radial-gradient(circle at center,rgba(255,255,255,.42) 0%,transparent 60%);opacity:0;transition:opacity .25s;pointer-events:none}
+.btn.primary:hover::after,.btn-primary:hover::after{opacity:1}
+.psel-card{position:relative}
+.psel-card .psel-done{position:absolute;top:6px;right:6px;width:18px;height:18px;border-radius:50%;background:#10b981;display:none;align-items:center;justify-content:center;box-shadow:0 2px 6px rgba(16,185,129,.45);z-index:2}
+.psel-card .psel-done svg{width:11px;height:11px;stroke:#fff;fill:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:round}
+.psel-card.done .psel-done{display:flex}
+.boss-card{transition:border-color .35s,box-shadow .6s,background .3s,transform .2s}
+.boss-card.glow{box-shadow:0 0 24px rgba(16,185,129,.6),0 0 0 2px rgba(16,185,129,.45)!important}
+@keyframes bossPulse{0%{box-shadow:0 0 0 0 rgba(16,185,129,.55)}70%{box-shadow:0 0 0 14px rgba(16,185,129,0)}100%{box-shadow:0 0 0 0 rgba(16,185,129,0)}}
+.boss-card.pulse{animation:bossPulse .8s ease-out}
+.psel-card{transition:transform .2s,box-shadow .2s,border-color .2s,background .25s}
+.sec{transition:opacity .25s}
@@ -1612,6 +1644,125 @@ function init(){
}
}
document.addEventListener('DOMContentLoaded', init);
+
+/* === GEOM9 POLISH JS === */
+(function(){
+ function bumpScore(el){
+ if(!el) return;
+ el.classList.remove('bump');
+ void el.offsetWidth;
+ el.classList.add('bump');
+ setTimeout(function(){ try{ el.classList.remove('bump'); }catch(e){} }, 270);
+ }
+ window.__geom9BumpScore = bumpScore;
+
+ function observeScores(root){
+ root = root || document;
+ var nodes = root.querySelectorAll('.score-display b');
+ nodes.forEach(function(b){
+ if(b.__scoreObs) return;
+ b.__scoreObs = true;
+ var last = b.textContent;
+ try{
+ var mo = new MutationObserver(function(){
+ var nv = b.textContent;
+ if(nv !== last){ last = nv; bumpScore(b); }
+ });
+ mo.observe(b, {childList:true, characterData:true, subtree:true});
+ }catch(e){}
+ });
+ }
+ function rescanScores(){ try{ observeScores(document); }catch(e){} }
+ if(document.readyState === 'loading') document.addEventListener('DOMContentLoaded', rescanScores);
+ else rescanScores();
+
+ try{
+ var rootObs = new MutationObserver(function(muts){
+ var need = false;
+ for(var i=0;i= 50);
+ });
+ }catch(e){}
+ }
+ try{
+ if(typeof window.refreshProgressUI === 'function'){
+ var _origRP = window.refreshProgressUI;
+ window.refreshProgressUI = function(){ var r = _origRP.apply(this, arguments); setTimeout(refreshDoneMarks, 0); return r; };
+ }
+ }catch(e){}
+ setTimeout(refreshDoneMarks, 600);
+ setTimeout(refreshDoneMarks, 1800);
+ window.addEventListener('focus', function(){ setTimeout(refreshDoneMarks, 200); });
+
+ document.addEventListener('click', function(e){
+ var card = e.target.closest && e.target.closest('.psel-card');
+ if(!card) return;
+ var id = card.dataset.id;
+ if(!id) return;
+ setTimeout(function(){
+ var sec = document.getElementById('sec-' + id);
+ if(sec) try{ sec.scrollIntoView({behavior:'smooth', block:'start'}); }catch(e){}
+ }, 60);
+ });
+
+ function observeBossGlow(){
+ var cards = document.querySelectorAll('.boss-card');
+ cards.forEach(function(card){
+ if(card.__bossObs) return;
+ card.__bossObs = true;
+ try{
+ var mo = new MutationObserver(function(){
+ if(card.__glowed) return;
+ var go = card.querySelector('button[id*="-go"]');
+ var fb = card.querySelector('.feedback.ok, .boss-fb.ok');
+ var defeatedByBtn = go && go.disabled;
+ var defeatedByFb = fb && fb.textContent && /Побед|повержен/i.test(fb.textContent);
+ if(defeatedByBtn || defeatedByFb){
+ card.__glowed = true;
+ card.classList.add('glow','pulse');
+ setTimeout(function(){ try{ card.classList.remove('pulse'); }catch(e){} }, 900);
+ setTimeout(function(){ try{ card.classList.remove('glow'); }catch(e){} }, 1400);
+ }
+ });
+ mo.observe(card, {childList:true, subtree:true, attributes:true, attributeFilter:['class','style','disabled']});
+ var goNow = card.querySelector('button[id*="-go"]');
+ if(goNow && goNow.disabled) card.__glowed = true;
+ }catch(e){}
+ });
+ }
+ var bossRescan = function(){ try{ observeBossGlow(); }catch(e){} };
+ setTimeout(bossRescan, 800);
+ var bossPoll = setInterval(bossRescan, 2500);
+ setTimeout(function(){ try{ clearInterval(bossPoll); }catch(e){} }, 90000);
+})();
diff --git a/frontend/textbooks/geometry_9_ch4.html b/frontend/textbooks/geometry_9_ch4.html
index b809491..616a4bc 100644
--- a/frontend/textbooks/geometry_9_ch4.html
+++ b/frontend/textbooks/geometry_9_ch4.html
@@ -180,6 +180,38 @@ a{color:inherit;text-decoration:none}
.drop-box:hover{border-color:var(--pri);background:var(--pri-soft)}
.drop-box h5{font-family:'Unbounded',sans-serif;font-size:.78rem;color:var(--pri2);margin-bottom:8px;text-transform:uppercase;letter-spacing:.05em}
.drop-box.over{border-color:var(--pri);background:var(--pri-soft);border-style:solid;transform:scale(1.015)}
+
+/* === GEOM9 POLISH (visual + micro-interactions) === */
+@keyframes wgFadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
+.sec.active .wg{animation:wgFadeIn .35s cubic-bezier(.16,1,.3,1) backwards}
+.sec.active .wg:nth-of-type(1){animation-delay:.02s}
+.sec.active .wg:nth-of-type(2){animation-delay:.08s}
+.sec.active .wg:nth-of-type(3){animation-delay:.14s}
+.sec.active .wg:nth-of-type(4){animation-delay:.20s}
+.sec.active .wg:nth-of-type(5){animation-delay:.26s}
+.sec.active .wg:nth-of-type(6){animation-delay:.32s}
+.wg svg{transition:filter .25s ease}
+.wg:hover svg{filter:drop-shadow(0 4px 16px rgba(0,0,0,.10))}
+input[type=range]:active{box-shadow:0 0 0 4px var(--pri-soft);border-radius:8px}
+.wg input[type=range]{cursor:ew-resize}
+.score-display b{transition:transform .22s cubic-bezier(.16,1,.3,1),color .22s;display:inline-block;transform-origin:center}
+.score-display b.bump{transform:scale(1.28);color:var(--pri)}
+.katex{transition:color .2s}
+.wg-help .katex:hover,.card-body .katex:hover{color:var(--pri2,var(--pri));cursor:help}
+.hp-fill,.psel-prog-fill,.xp-fill,[id$="-overall-fill"]{transition:width .6s cubic-bezier(.16,1,.3,1)!important}
+.boss-card,.btn.primary,.btn-primary{position:relative;overflow:hidden}
+.btn.primary::after,.btn-primary::after{content:'';position:absolute;inset:0;background:radial-gradient(circle at center,rgba(255,255,255,.42) 0%,transparent 60%);opacity:0;transition:opacity .25s;pointer-events:none}
+.btn.primary:hover::after,.btn-primary:hover::after{opacity:1}
+.psel-card{position:relative}
+.psel-card .psel-done{position:absolute;top:6px;right:6px;width:18px;height:18px;border-radius:50%;background:#10b981;display:none;align-items:center;justify-content:center;box-shadow:0 2px 6px rgba(16,185,129,.45);z-index:2}
+.psel-card .psel-done svg{width:11px;height:11px;stroke:#fff;fill:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:round}
+.psel-card.done .psel-done{display:flex}
+.boss-card{transition:border-color .35s,box-shadow .6s,background .3s,transform .2s}
+.boss-card.glow{box-shadow:0 0 24px rgba(16,185,129,.6),0 0 0 2px rgba(16,185,129,.45)!important}
+@keyframes bossPulse{0%{box-shadow:0 0 0 0 rgba(16,185,129,.55)}70%{box-shadow:0 0 0 14px rgba(16,185,129,0)}100%{box-shadow:0 0 0 0 rgba(16,185,129,0)}}
+.boss-card.pulse{animation:bossPulse .8s ease-out}
+.psel-card{transition:transform .2s,box-shadow .2s,border-color .2s,background .25s}
+.sec{transition:opacity .25s}
@@ -1925,6 +1957,125 @@ function init(){
}
}
document.addEventListener('DOMContentLoaded', init);
+
+/* === GEOM9 POLISH JS === */
+(function(){
+ function bumpScore(el){
+ if(!el) return;
+ el.classList.remove('bump');
+ void el.offsetWidth;
+ el.classList.add('bump');
+ setTimeout(function(){ try{ el.classList.remove('bump'); }catch(e){} }, 270);
+ }
+ window.__geom9BumpScore = bumpScore;
+
+ function observeScores(root){
+ root = root || document;
+ var nodes = root.querySelectorAll('.score-display b');
+ nodes.forEach(function(b){
+ if(b.__scoreObs) return;
+ b.__scoreObs = true;
+ var last = b.textContent;
+ try{
+ var mo = new MutationObserver(function(){
+ var nv = b.textContent;
+ if(nv !== last){ last = nv; bumpScore(b); }
+ });
+ mo.observe(b, {childList:true, characterData:true, subtree:true});
+ }catch(e){}
+ });
+ }
+ function rescanScores(){ try{ observeScores(document); }catch(e){} }
+ if(document.readyState === 'loading') document.addEventListener('DOMContentLoaded', rescanScores);
+ else rescanScores();
+
+ try{
+ var rootObs = new MutationObserver(function(muts){
+ var need = false;
+ for(var i=0;i= 50);
+ });
+ }catch(e){}
+ }
+ try{
+ if(typeof window.refreshProgressUI === 'function'){
+ var _origRP = window.refreshProgressUI;
+ window.refreshProgressUI = function(){ var r = _origRP.apply(this, arguments); setTimeout(refreshDoneMarks, 0); return r; };
+ }
+ }catch(e){}
+ setTimeout(refreshDoneMarks, 600);
+ setTimeout(refreshDoneMarks, 1800);
+ window.addEventListener('focus', function(){ setTimeout(refreshDoneMarks, 200); });
+
+ document.addEventListener('click', function(e){
+ var card = e.target.closest && e.target.closest('.psel-card');
+ if(!card) return;
+ var id = card.dataset.id;
+ if(!id) return;
+ setTimeout(function(){
+ var sec = document.getElementById('sec-' + id);
+ if(sec) try{ sec.scrollIntoView({behavior:'smooth', block:'start'}); }catch(e){}
+ }, 60);
+ });
+
+ function observeBossGlow(){
+ var cards = document.querySelectorAll('.boss-card');
+ cards.forEach(function(card){
+ if(card.__bossObs) return;
+ card.__bossObs = true;
+ try{
+ var mo = new MutationObserver(function(){
+ if(card.__glowed) return;
+ var go = card.querySelector('button[id*="-go"]');
+ var fb = card.querySelector('.feedback.ok, .boss-fb.ok');
+ var defeatedByBtn = go && go.disabled;
+ var defeatedByFb = fb && fb.textContent && /Побед|повержен/i.test(fb.textContent);
+ if(defeatedByBtn || defeatedByFb){
+ card.__glowed = true;
+ card.classList.add('glow','pulse');
+ setTimeout(function(){ try{ card.classList.remove('pulse'); }catch(e){} }, 900);
+ setTimeout(function(){ try{ card.classList.remove('glow'); }catch(e){} }, 1400);
+ }
+ });
+ mo.observe(card, {childList:true, subtree:true, attributes:true, attributeFilter:['class','style','disabled']});
+ var goNow = card.querySelector('button[id*="-go"]');
+ if(goNow && goNow.disabled) card.__glowed = true;
+ }catch(e){}
+ });
+ }
+ var bossRescan = function(){ try{ observeBossGlow(); }catch(e){} };
+ setTimeout(bossRescan, 800);
+ var bossPoll = setInterval(bossRescan, 2500);
+ setTimeout(function(){ try{ clearInterval(bossPoll); }catch(e){} }, 90000);
+})();
diff --git a/frontend/textbooks/geometry_9_hub.html b/frontend/textbooks/geometry_9_hub.html
index 9a87adf..80579ba 100644
--- a/frontend/textbooks/geometry_9_hub.html
+++ b/frontend/textbooks/geometry_9_hub.html
@@ -199,6 +199,34 @@ html.dark .final-cta-sub{color:#fcd34d}
.final-cta-btn{padding:10px 18px;border-radius:10px;background:linear-gradient(135deg,var(--pri),#fb7185);color:#fff;text-decoration:none;font-weight:800;font-size:.9rem;display:inline-flex;align-items:center;gap:7px;transition:filter .15s}
.final-cta-btn:hover{filter:brightness(1.1)}
.final-cta-btn svg{width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
+
+/* === GEOM9 POLISH (hub) === */
+@keyframes chCardIn{from{opacity:0;transform:translateY(10px) scale(.985)}to{opacity:1;transform:none}}
+.ch-card{animation:chCardIn .4s cubic-bezier(.16,1,.3,1) backwards}
+.ch-card:nth-child(1){animation-delay:.04s}
+.ch-card:nth-child(2){animation-delay:.10s}
+.ch-card:nth-child(3){animation-delay:.16s}
+.ch-card:nth-child(4){animation-delay:.22s}
+@keyframes bossCardIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
+.final-wrap.open #fin-bosses-container .boss-card{animation:bossCardIn .35s cubic-bezier(.16,1,.3,1) backwards}
+.final-wrap.open #fin-bosses-container .boss-card:nth-child(1){animation-delay:.06s}
+.final-wrap.open #fin-bosses-container .boss-card:nth-child(2){animation-delay:.12s}
+.final-wrap.open #fin-bosses-container .boss-card:nth-child(3){animation-delay:.18s}
+.final-wrap.open #fin-bosses-container .boss-card:nth-child(4){animation-delay:.24s}
+.final-wrap.open #fin-bosses-container .boss-card:nth-child(5){animation-delay:.30s}
+.final-wrap.open #fin-bosses-container .boss-card:nth-child(6){animation-delay:.36s}
+.final-wrap.open #fin-bosses-container .boss-card:nth-child(7){animation-delay:.42s}
+.po-fill,.ch-prog-fill,.boss-overall-bar .fill{transition:width .6s cubic-bezier(.16,1,.3,1)!important}
+.boss-btn,.ch-action,.final-cta-btn{position:relative;overflow:hidden}
+.boss-btn::after,.ch-action::after,.final-cta-btn::after{content:'';position:absolute;inset:0;background:radial-gradient(circle at center,rgba(255,255,255,.45) 0%,transparent 60%);opacity:0;transition:opacity .25s;pointer-events:none}
+.boss-btn:hover::after,.ch-action:hover::after,.final-cta-btn:hover::after{opacity:1}
+.cheat-list li .katex{transition:color .2s}
+.cheat-list li .katex:hover{color:var(--pri-d);cursor:help}
+.boss-card.glow-once{box-shadow:0 0 24px rgba(16,185,129,.6),0 0 0 2px rgba(16,185,129,.45)!important}
+@keyframes bossPulseH{0%{box-shadow:0 0 0 0 rgba(16,185,129,.55)}70%{box-shadow:0 0 0 14px rgba(16,185,129,0)}100%{box-shadow:0 0 0 0 rgba(16,185,129,0)}}
+.boss-card.pulse-once{animation:bossPulseH .85s ease-out}
+.fin-boss-bar-bump b{transition:transform .2s,color .2s;display:inline-block}
+.fin-boss-bar-bump b.bump{transform:scale(1.18);color:#10b981}
@@ -836,6 +864,84 @@ if (document.readyState === 'loading') {
loadProgress();
}
window.addEventListener('focus', loadProgress);
+
+/* === GEOM9 HUB POLISH JS === */
+(function(){
+ /* Smooth scroll into view when final opens */
+ try{
+ var finalHead = document.getElementById('final-head');
+ var finalWrap = document.getElementById('course-final');
+ if(finalHead && finalWrap){
+ finalHead.addEventListener('click', function(){
+ setTimeout(function(){
+ if(finalWrap.classList.contains('open')){
+ try{ finalWrap.scrollIntoView({behavior:'smooth', block:'start'}); }catch(e){}
+ }
+ }, 80);
+ });
+ }
+ }catch(e){}
+
+ /* Boss-card glow on defeat (one-shot, only on user action, not page-load state) */
+ function observeFinBosses(){
+ var cards = document.querySelectorAll('#fin-bosses-container .boss-card');
+ cards.forEach(function(card){
+ if(card.__finObs) return;
+ card.__finObs = true;
+ try{
+ var go = card.querySelector('button[id*="-go"]');
+ /* If already defeated at observation start — suppress glow */
+ if(go && go.disabled) card.__glowed = true;
+ var mo = new MutationObserver(function(){
+ if(card.__glowed) return;
+ var g = card.querySelector('button[id*="-go"]');
+ if(g && g.disabled){
+ card.__glowed = true;
+ card.classList.add('glow-once','pulse-once');
+ setTimeout(function(){ try{ card.classList.remove('pulse-once'); }catch(e){} }, 900);
+ setTimeout(function(){ try{ card.classList.remove('glow-once'); }catch(e){} }, 1500);
+ }
+ });
+ mo.observe(card, {childList:true, subtree:true, attributes:true, attributeFilter:['class','disabled']});
+ }catch(e){}
+ });
+ }
+
+ /* Bump for boss overall counter */
+ var lastLab = '';
+ function bumpLab(){
+ try{
+ var lab = document.getElementById('fin-boss-lab');
+ if(!lab) return;
+ var nv = lab.textContent;
+ if(nv === lastLab){ return; }
+ lastLab = nv;
+ if(!lab.parentElement.classList.contains('fin-boss-bar-bump')){
+ lab.parentElement.classList.add('fin-boss-bar-bump');
+ }
+ /* wrap digits in a if not already — simplest: bump the whole label briefly */
+ lab.style.transition = 'transform .25s ease, color .25s';
+ lab.style.transform = 'scale(1.04)';
+ lab.style.color = '#10b981';
+ setTimeout(function(){
+ try{ lab.style.transform=''; lab.style.color=''; }catch(e){}
+ }, 320);
+ }catch(e){}
+ }
+ try{
+ var labEl = document.getElementById('fin-boss-lab');
+ if(labEl){
+ lastLab = labEl.textContent;
+ var labObs = new MutationObserver(bumpLab);
+ labObs.observe(labEl, {childList:true, characterData:true, subtree:true});
+ }
+ }catch(e){}
+
+ /* Rescan periodically (final-body uses lazy render) */
+ setTimeout(observeFinBosses, 600);
+ var pollT = setInterval(observeFinBosses, 1500);
+ setTimeout(function(){ try{ clearInterval(pollT); }catch(e){} }, 90000);
+})();