Le Bruni

Ô long Sữa Gạo rang

Vị nồng ấm của gạo rang kết hợp với vị trà ô long đặc sản đậm đà
Thức uống này được phục vụ với hai kích cỡ
Order on GrabFood 45,000₫
/* 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 */ }
})();