feat(geom9): полировка — анимации появления, bump-эффекты, hover, плавные переходы
This commit is contained in:
@@ -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}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -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<muts.length && !need;i++){
|
||||
var m = muts[i];
|
||||
for(var j=0;j<m.addedNodes.length;j++){
|
||||
var n = m.addedNodes[j];
|
||||
if(n.nodeType===1){
|
||||
if(n.classList && n.classList.contains('score-display')) { need = true; break; }
|
||||
if(n.querySelector && n.querySelector('.score-display b')) { need = true; break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
if(need) rescanScores();
|
||||
});
|
||||
rootObs.observe(document.body, {childList:true, subtree:true});
|
||||
}catch(e){}
|
||||
|
||||
/* psel-card 'done' marker — based on STATE.progress >= 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 = '<svg viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg>';
|
||||
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);
|
||||
})();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
@@ -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}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -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<muts.length && !need;i++){
|
||||
var m = muts[i];
|
||||
for(var j=0;j<m.addedNodes.length;j++){
|
||||
var n = m.addedNodes[j];
|
||||
if(n.nodeType===1){
|
||||
if(n.classList && n.classList.contains('score-display')) { need = true; break; }
|
||||
if(n.querySelector && n.querySelector('.score-display b')) { need = true; break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
if(need) rescanScores();
|
||||
});
|
||||
rootObs.observe(document.body, {childList:true, subtree:true});
|
||||
}catch(e){}
|
||||
|
||||
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 = '<svg viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg>';
|
||||
c.appendChild(s);
|
||||
}
|
||||
c.classList.toggle('done', pct >= 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);
|
||||
})();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
@@ -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}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -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<muts.length && !need;i++){
|
||||
var m = muts[i];
|
||||
for(var j=0;j<m.addedNodes.length;j++){
|
||||
var n = m.addedNodes[j];
|
||||
if(n.nodeType===1){
|
||||
if(n.classList && n.classList.contains('score-display')) { need = true; break; }
|
||||
if(n.querySelector && n.querySelector('.score-display b')) { need = true; break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
if(need) rescanScores();
|
||||
});
|
||||
rootObs.observe(document.body, {childList:true, subtree:true});
|
||||
}catch(e){}
|
||||
|
||||
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 = '<svg viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg>';
|
||||
c.appendChild(s);
|
||||
}
|
||||
c.classList.toggle('done', pct >= 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);
|
||||
})();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
@@ -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}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -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<muts.length && !need;i++){
|
||||
var m = muts[i];
|
||||
for(var j=0;j<m.addedNodes.length;j++){
|
||||
var n = m.addedNodes[j];
|
||||
if(n.nodeType===1){
|
||||
if(n.classList && n.classList.contains('score-display')) { need = true; break; }
|
||||
if(n.querySelector && n.querySelector('.score-display b')) { need = true; break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
if(need) rescanScores();
|
||||
});
|
||||
rootObs.observe(document.body, {childList:true, subtree:true});
|
||||
}catch(e){}
|
||||
|
||||
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 = '<svg viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg>';
|
||||
c.appendChild(s);
|
||||
}
|
||||
c.classList.toggle('done', pct >= 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);
|
||||
})();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
@@ -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}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -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 <b> 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);
|
||||
})();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user