/* Hiệu ứng trái tim rơi (chỉ thiệp thôi nôi 1): thưa, nhẹ, chỉ dùng transform/opacity.
   Nằm dưới nút nhạc, hộp thoại và bìa; không chặn thao tác chạm. */
.tn-hearts {
  position: fixed; top: 0; bottom: 0; left: 50%; z-index: 40;
  width: min(100%, 900px); translate: -50% 0;
  overflow: hidden; pointer-events: none; opacity: 0; transition: opacity 1.2s ease;
}
/* Hiện ngay khi thiệp tải xong (kể cả ở bìa phong bì); ẩn khi đang tải hoặc lỗi. */
body:not(.is-loading):not(.has-error) .tn-hearts { opacity: 1; }
body.has-error .tn-hearts { display: none; }
/* Khi bìa còn hiện (z-index 100) đưa lên trên bìa; mở thiệp xong hạ xuống dưới nút nhạc / hộp thoại. */
body.has-cover .tn-hearts, body.is-opening .tn-hearts { z-index: 101; }
.tn-heart {
  position: absolute; top: 0; display: block; opacity: 0; will-change: transform, opacity;
  animation: tn-heart-fall linear infinite;
}
.tn-heart svg { display: block; width: 100%; height: 100%; }
@keyframes tn-heart-fall {
  0%   { transform: translate3d(0, -12vh, 0) rotate(var(--rot)) scale(var(--sc)); opacity: 0; }
  10%  { opacity: var(--op); }
  30%  { transform: translate3d(var(--s1), 30vh, 0) rotate(calc(var(--rot) + 40deg)) scale(var(--sc)); }
  60%  { transform: translate3d(var(--s2), 65vh, 0) rotate(calc(var(--rot) - 30deg)) scale(var(--sc)); }
  90%  { opacity: var(--op); }
  100% { transform: translate3d(var(--s3), 112vh, 0) rotate(calc(var(--rot) + 60deg)) scale(var(--sc)); opacity: 0; }
}
