/* Tư Trà — Price flip animation (scoped to your button markup) */
.tt-price-anim .tu-tra-live-price.tt-price-flip{
display:inline-block;
position:relative; /* anchor overlay */
vertical-align:baseline;
}
/* overlay wrapper injected only during the flip */
.tt-price-anim .tu-tra-live-price .ttpf-wrap{
position:absolute;
inset:0; /* left:0; top:0; right:0; bottom:0 */
overflow:hidden;
pointer-events:none;
}
/* two lines that slide */
.tt-price-anim .tu-tra-live-price .ttpf-line{
position:absolute;
left:0; right:0;
will-change:transform, opacity;
transform:translateY(0);
opacity:1;
transition:transform 360ms cubic-bezier(.22,.61,.36,1), opacity 360ms linear;
text-align:inherit;
white-space:nowrap;
}
/* enter positions */
.tt-price-anim .tu-tra-live-price .ttpf-line.new-up{ transform:translateY(100%); } /* price ↑ enters from bottom */
.tt-price-anim .tu-tra-live-price .ttpf-line.new-dn{ transform:translateY(-100%); } /* price ↓ enters from top */
/* phase classes on wrapper */
.tt-price-anim .tu-tra-live-price .ttpf-anim-up .old{ transform:translateY(-100%); opacity:0; }
.tt-price-anim .tu-tra-live-price .ttpf-anim-up .new{ transform:translateY(0%); opacity:1; }
.tt-price-anim .tu-tra-live-price .ttpf-anim-dn .old{ transform:translateY(100%); opacity:0; }
.tt-price-anim .tu-tra-live-price .ttpf-anim-dn .new{ transform:translateY(0%); opacity:1; }
(function(){
// Host (.tt-price-anim) → we bind/rebind ONLY inside these
var HOST_SEL = '.tt-price-anim';
var PRICE_SEL = '.tu-tra-live-price, #tu-tra-live-price, [data-tu-tra-live-price], [data-dynamic-tag="tu_tra_live_price"], [data-brx-bind*="tu_tra_live_price"], [data-brx-content*="tu_tra_live_price"]';
function parsePrice(text){
if(!text) return null;
var t = String(text).replace(/\u00A0/g,' ').trim();
if(!t || /không có/i.test(t)) return null;
var digits = t.replace(/[^0-9]/g,'');
if(!digits) return null;
var n = parseInt(digits,10);
return Number.isFinite(n) ? n : null;
}
function flipOnce(el, oldText, newText){
if(oldText === newText) return;
if(el.__ttpfBusy) { el.__ttpfPrev = newText; return; }
el.__ttpfBusy = true;
var wrap = document.createElement('span');
wrap.className = 'ttpf-wrap';
var lineOld = document.createElement('span');
var lineNew = document.createElement('span');
lineOld.className = 'ttpf-line old';
lineNew.className = 'ttpf-line new';
var oldN = parsePrice(oldText);
var newN = parsePrice(newText);
var up = true;
if(oldN !== null && newN !== null) up = newN > oldN;
lineNew.classList.add(up ? 'new-up' : 'new-dn');
lineOld.textContent = oldText;
lineNew.textContent = newText;
// lock width/height to avoid jiggle during flip
var rect = el.getBoundingClientRect();
if(rect.width) el.style.minWidth = rect.width + 'px';
if(rect.height) el.style.minHeight = rect.height + 'px';
wrap.appendChild(lineOld);
wrap.appendChild(lineNew);
el.appendChild(wrap);
// set final text underneath (so DOM is correct during/after anim)
el.textContent = newText;
// kick animation next frame
requestAnimationFrame(function(){
wrap.classList.add(up ? 'ttpf-anim-up' : 'ttpf-anim-dn');
});
// cleanup
var cleaned = false;
function done(){
if(cleaned) return; cleaned = true;
if(wrap.parentNode) wrap.parentNode.removeChild(wrap);
el.style.minWidth = el.style.minHeight = '';
el.__ttpfPrev = newText;
el.__ttpfBusy = false;
}
wrap.addEventListener('transitionend', done, { once:true });
setTimeout(done, 650); // safety
}
function bindPrice(el){
if(!el) return;
if(el.__ttpfBound) return;
el.__ttpfBound = true;
el.__ttpfPrev = (el.textContent || '').trim();
el.__ttpfBusy = false;
el.classList.add('tt-price-flip');
// Observe this price span for text changes; coalesce via RAF
var mo = new MutationObserver(function(){
if(!el.isConnected){ try{mo.disconnect();}catch(e){} return; }
if(el.__ttpfRaf) cancelAnimationFrame(el.__ttpfRaf);
el.__ttpfRaf = requestAnimationFrame(function(){
var curr = (el.textContent || '').trim();
var prev = el.__ttpfPrev || '';
if(curr !== prev) flipOnce(el, prev, curr);
});
});
try{
mo.observe(el, { characterData:true, childList:true, subtree:true });
el.__ttpfMO = mo;
}catch(e){ /* ignore */ }
}
function bindHost(host){
if(!host || !host.isConnected) return;
// (re)bind price element inside this host
var priceEl = host.querySelector(PRICE_SEL);
if(priceEl) bindPrice(priceEl);
// host-level observer: if the price span is replaced, bind again
if(host.__ttpfHostMO) return;
var hostMO = new MutationObserver(function(muts){
// only react if a price element appears or is replaced inside host
if(!host.isConnected){ try{hostMO.disconnect();}catch(e){} return; }
var el = host.querySelector(PRICE_SEL);
if(el && !el.__ttpfBound) bindPrice(el);
});
try{
hostMO.observe(host, { childList:true, subtree:true });
host.__ttpfHostMO = hostMO;
}catch(e){ /* ignore */ }
}
function initAll(){
document.querySelectorAll(HOST_SEL).forEach(bindHost);
}
// Initial + late load safety
if(document.readyState === 'loading'){
document.addEventListener('DOMContentLoaded', initAll, { once:true });
} else {
initAll();
}
window.addEventListener('load', initAll, { once:true });
// Optional: small, filtered page observer to catch hosts added later by Bricks
var pageMO = new MutationObserver(function(muts){
for (var i=0;i<muts.length;i++){
var m = muts[i];
for (var j=0;j<m.addedNodes.length;j++){
var n = m.addedNodes[j];
if(!(n instanceof Element)) continue;
if(n.matches && n.matches(HOST_SEL)) bindHost(n);
var sub = n.querySelectorAll ? n.querySelectorAll(HOST_SEL) : [];
if(sub.length) sub.forEach(bindHost);
}
}
});
try{
pageMO.observe(document.body, { childList:true, subtree:true });
}catch(e){ /* ignore */ }
})();