/**
 * Tư-Trà Hero Header Suppress
 *
 * Keeps the header at full size while a "fullsize hero" section is
 * visible above the header. .scrolling only activates once the next
 * section's top crosses the bottom of the header — i.e., the moment
 * the next section actually starts sliding under the sticky header.
 *
 * Usage:
 *   In Bricks Builder, add the CSS class `tt-fullsize-hero` to the
 *   hero section. The script automatically pairs it with the next
 *   sibling section to detect when the hero region ends.
 *
 * Architecture:
 *   - Threshold: next-sibling.rect.top vs header.rect.bottom
 *     > 0  : hero region dominant — suppress .scrolling
 *     <= 0 : next section overlapping header — allow .scrolling
 *
 *   - Synchronous scroll listener (no rAF throttle): runs in the same
 *     event tick as Bricks's handler, so .scrolling is stripped before
 *     the browser paints. No frame-ordering race, no hesitation on iOS
 *     momentum scroll.
 *
 *   - MutationObserver on body + header class attribute: backup for
 *     when Bricks toggles .scrolling outside of scroll events (rAF,
 *     setTimeout, transitionend). Fires as a microtask, before paint —
 *     the briefly-added class is gone before the user sees it.
 *
 *   - Auto-no-op on pages without a hero (early return).
 *   - No CSS changes required.
 *
 * Version: 1.2.0
 * Author: Charlie + Claude
 *
 * History
 * v1.0.0: IntersectionObserver on hero. Broke for sticky heroes —
 *         IO reports the hero as intersecting for its entire stuck
 *         duration because the bounding rect stays at top:0.
 * v1.1.0: Switched to next-sibling rect check. Fixed sticky case.
 *         Worked on desktop, but rAF throttle exposed a frame-ordering
 *         race against Bricks's own rAF on iOS momentum scroll —
 *         visible hesitation when scrolling back to top.
 * v1.2.0: Synchronous scroll handler eliminates the rAF race.
 *         MutationObserver covers Bricks's non-scroll additions of
 *         .scrolling. Threshold raised from y=0 to header.bottom so
 *         shrink begins at the correct visual moment (next section
 *         meeting the header, not crossing the viewport top).
 */
(function () {
  'use strict';

  var HERO_SELECTOR   = '.tt-fullsize-hero';
  var SCROLLING_CLASS = 'scrolling';
  var HEADER_SELECTOR = '#brx-header, header.brxe-header';

  function init() {
    var heroes = document.querySelectorAll(HERO_SELECTOR);
    if (heroes.length === 0) return;

    // Pair each hero with its next sibling. Next sibling acts as the
    // visual signal for "hero region has ended" — works for both
    // sticky and non-sticky heroes.
    var pairs = [];
    for (var i = 0; i < heroes.length; i++) {
      pairs.push({ hero: heroes[i], next: heroes[i].nextElementSibling });
    }

    var headerEls = document.querySelectorAll(HEADER_SELECTOR);
    var heroActive = false;

    function isHeroActive() {
      // Read header bottom each call. The header may itself shrink
      // (changing its height) when .scrolling is on, but we only ever
      // need this measurement at the threshold moment, where the
      // header is still full-size.
      var headerBottom = 0;
      if (headerEls.length > 0) {
        headerBottom = headerEls[0].getBoundingClientRect().bottom;
      }
      for (var j = 0; j < pairs.length; j++) {
        var p = pairs[j];
        var endRef = p.next
          ? p.next.getBoundingClientRect().top
          : p.hero.getBoundingClientRect().bottom;
        if (endRef > headerBottom) return true;
      }
      return false;
    }

    function clearScrolling() {
      if (document.body.classList.contains(SCROLLING_CLASS)) {
        document.body.classList.remove(SCROLLING_CLASS);
      }
      for (var k = 0; k < headerEls.length; k++) {
        headerEls[k].classList.remove(SCROLLING_CLASS);
      }
    }

    function update() {
      heroActive = isHeroActive();
      if (heroActive) clearScrolling();
    }

    // Synchronous scroll listener: no rAF. Runs in the same tick as
    // Bricks's handler; ordering doesn't matter because the
    // MutationObserver below catches any add we miss.
    window.addEventListener('scroll', update, { passive: true });
    window.addEventListener('resize', update);

    // Backup: if anything (Bricks's rAF, a transitionend handler, etc.)
    // adds .scrolling while heroActive, strip it immediately as a
    // microtask, before the next paint.
    var classObserver = new MutationObserver(function () {
      if (heroActive) clearScrolling();
    });
    classObserver.observe(document.body, {
      attributes: true,
      attributeFilter: ['class']
    });
    for (var m = 0; m < headerEls.length; m++) {
      classObserver.observe(headerEls[m], {
        attributes: true,
        attributeFilter: ['class']
      });
    }

    update(); // Handle reload-mid-page
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init);
  } else {
    init();
  }
})();

Group Order

Một lựa chọn nhẹ nhàng hơn cho người yêu trà sữa.
Trà được ủ lạnh nhiều giờ để giữ vị trà rõ ràng, mượt và tươi.
Một dòng trà xanh dịu và tĩnh, dành cho những khoảnh khắc cần lắng lại.

No group order token found in URL.

Tổng hợp đơn nhóm

No group order token in URL.