/* ============================================================
   Sân khấu dùng chung cho 3 option app khách
   - khung LED thật (prototype/public/player.html?mini=1) chiếm trọn màn
   - lớp tim TikTok port từ src/core.html (option 02 · Thác lời chúc):
     bảng màu "gold" + glow, tim bay lên, chạm màn thì nổ 1 tim to
   Option tự lo phần chrome (nút, thẻ, nhãn) — file này chỉ lo sân khấu.
   ============================================================ */

.jp-stage{position:fixed;inset:0;overflow:hidden;background:#000;container-type:size;touch-action:manipulation}
.jp-stage iframe{position:absolute;inset:0;width:100%;height:100%;border:0;display:block}
.jp-veil{position:absolute;inset:0;display:grid;place-items:center;background:#000;z-index:2;
  font:300 12px 'Be Vietnam Pro',system-ui,sans-serif;letter-spacing:.14em;text-transform:uppercase;color:rgba(244,239,230,.45);
  transition:opacity .6s}
.jp-stage.on .jp-veil{opacity:0;pointer-events:none}

/* lớp hứng chạm: trong suốt, đè lên iframe để bắt được chạm ở MỌI CHỖ trên ảnh
   (event bên trong iframe không bubble ra ngoài, nên không có lớp này thì chạm lên ảnh mất luôn) */
.jp-touch{position:absolute;inset:0;z-index:2;background:transparent;touch-action:manipulation;
  user-select:none;-webkit-user-select:none;-webkit-touch-callout:none}  /* giữ lâu trên iOS không bật menu copy */

/* lớp tim + hiệu ứng chạm, luôn nằm trên khung LED nhưng không chặn chạm */
.jp-layer{position:absolute;inset:0;overflow:hidden;pointer-events:none;z-index:8}  /* trên cả nút, kẻo tim thả ra từ nút bị che */

/* ---- TIM BAY — một logic đường đi cho cả quả to lẫn quả nhỏ ----
   Khung ngoài lo bay lên (--rise / --dur / --delay), khung trong lo lắc ngang (--sway),
   chữ nằm trong khung lắc nên bám theo quả tim. Quả to chỉ khác ở --in (nở lâu) + --delay (đứng chờ). */
.jp-heart{position:absolute;left:var(--ox);bottom:var(--oy);transform:translateX(-50%);
  animation:jp-rise var(--dur) cubic-bezier(.36,.02,.52,1) var(--delay,0s) forwards;
  will-change:transform,opacity}
.jp-heart .sway{display:flex;align-items:center;gap:1.2cqw;
  animation:jp-sway calc(var(--dur)*.72) ease-in-out infinite,
            jp-fadein var(--in,.4s) ease-out both}
.jp-heart .ic{display:block;flex:none;animation:jp-grow var(--in,.4s) cubic-bezier(.2,1.5,.35,1) both}
.jp-heart svg{display:block;width:var(--sz);height:var(--sz);fill:var(--c);
  filter:drop-shadow(0 0 1.4cqw var(--c)) drop-shadow(0 .4cqw 1cqw rgba(0,0,0,.35));
  animation:jp-tilt calc(var(--dur)*.72) ease-in-out infinite}
.jp-heart .who{font-family:'Be Vietnam Pro',system-ui,sans-serif;font-weight:500;letter-spacing:.02em;white-space:nowrap;
  font-size:clamp(11.5px,3.1cqw,16px);   /* chữ không co theo quả tim, luôn đủ to để đọc */
  color:#fff;text-shadow:0 1px 3px rgba(0,0,0,.95),0 0 10px rgba(0,0,0,.75);
  animation:jp-whoout var(--dur) linear var(--delay,0s) forwards}

@keyframes jp-rise{
  0%{transform:translate(-50%,0);opacity:1}
  70%{opacity:1}
  100%{transform:translate(-50%,calc(var(--rise)*-1));opacity:0}}
@keyframes jp-sway{0%{transform:translateX(0)}25%{transform:translateX(calc(var(--sway)*-1))}
  75%{transform:translateX(var(--sway))}100%{transform:translateX(0)}}
@keyframes jp-tilt{0%{transform:rotate(0)}25%{transform:rotate(-13deg)}75%{transform:rotate(13deg)}100%{transform:rotate(0)}}
@keyframes jp-fadein{from{opacity:0}to{opacity:1}}
@keyframes jp-grow{0%{transform:scale(.2)}62%{transform:scale(1.12)}100%{transform:scale(1)}}
@keyframes jp-whoout{0%{opacity:.92}68%{opacity:.92}86%{opacity:0}100%{opacity:0}}

/* ---- quả to: tên nằm dưới, nở theo nhịp bơm bóng rồi mới bay (cùng jp-rise) ---- */
.jp-heart.big .sway{flex-direction:column;gap:1.6cqw}
.jp-heart.big .ic{transform-origin:50% 62%;animation:jp-blow var(--in) cubic-bezier(.34,.1,.3,1) both}
.jp-heart.big svg{filter:drop-shadow(0 0 3.4cqw var(--c)) drop-shadow(0 .6cqw 1.6cqw rgba(0,0,0,.45))}
.jp-heart.big .who{font-size:clamp(12px,3cqw,18px);letter-spacing:.2em;text-transform:uppercase;color:#f3dea3;
  text-shadow:0 1px 4px rgba(0,0,0,.95),0 0 12px rgba(0,0,0,.7)}
/* bơm: to dần theo từng nhịp, mỗi nhịp hụt lại một chút như bóng bay */
@keyframes jp-blow{
  0%{transform:scale(.12)}
  14%{transform:scale(.46)} 22%{transform:scale(.38)}
  38%{transform:scale(.74)} 46%{transform:scale(.66)}
  62%{transform:scale(.97)} 70%{transform:scale(.89)}
  86%{transform:scale(1.03)}
  94%{transform:scale(1)}
  100%{transform:scale(1)}}   /* khung cuối đứng yên ở scale(1) — trước đây còn đang nhún nên nối sang lúc bay bị giật */

/* ============ QUẢ TO VỠ RA — «nổ dây» ============
   Cùng một quả tim to của cụm spam, nhưng phần lớn số lần nó sẽ VỠ thay vì bay lên:
   phình căng → nổ thành mấy quả vừa → mỗi quả vừa lại nổ tiếp thành tim nhỏ.
   Port từ demo app-heart-burst.html (kiểu 05 · Nổ dây), giữ nguyên bảng màu và
   đường bay của lớp tim — chỉ khác đoạn giữa.
   Mỗi lớp lồng nhau lo đúng MỘT việc, vì hai animation cùng đụng transform là giành nhau. */

/* ---- quả to lúc chờ vỡ: trôi lên xuống · nghiêng · ép dẹt · glow đập ---- */
.jp-bh{position:absolute;left:var(--x);bottom:var(--y);transform:translate(-50%,50%);
  display:flex;flex-direction:column;align-items:center;gap:1.6cqw}
.jp-bh .bob,.jp-bh .rock,.jp-bh .trm,.jp-bh .sq{display:block}
.jp-bh .sq{transform-origin:50% 72%}
.jp-bh .ic{display:block;position:relative;transform-origin:50% 62%;
  animation:jp-blow var(--in) cubic-bezier(.34,.1,.3,1) both}   /* dùng chung nhịp bơm bóng của quả to */
/* Quầng ĐỨNG YÊN ở mức đáy của nhịp đập. Nhịp đập nằm ở bản sao .gl (khai ngay bên dưới).
   ĐỪNG animate `filter` ở đây: drop-shadow đổi bán kính mỗi khung là GPU phải làm nhoè lại
   cả vùng quả tim mỗi khung. Đo 24/09 trên LED 1920×1440: mưa tim 9,7 ms GPU/khung (rớt
   51-57 fps ngay trên M3 Pro), bỏ nhịp filter còn 3,9 ms. Filter tĩnh thì raster một lần.
   Có test chặn: prototype/test/app-stage.test.js. */
.jp-bh svg{display:block;width:var(--sz);height:var(--sz);fill:var(--c);
  filter:drop-shadow(0 0 3.2cqw var(--c)) drop-shadow(0 .6cqw 1.6cqw rgba(0,0,0,.45))}
/* Bản sao quả tim mang quầng ĐỈNH vẽ sẵn (tĩnh). Nhịp đập = opacity của bản sao này, chạy
   trên compositor, không làm nhoè lại. Quầng đỉnh lấy theo nhịp căng (7cqw + lõi trắng
   3cqw), nhịp thở cũng mở tới 1: bản cũ quầng đỉnh chỉ 6cqw nhưng vòng .echo cũng dính nhịp
   filter nên sát mép tim sáng thêm — so ảnh đóng băng nhịp thì mở tới 1 mới bằng.
   Bản sao PHỦ LÊN quả chính (position:absolute nên vẽ sau thẻ svg thường, trước .echo), đừng
   đẩy xuống dưới bằng z-index:-1: bản cũ là MỘT chuỗi drop-shadow nên bóng tối nằm dưới
   cùng; bản sao nằm dưới là bóng tối của quả chính đè lên quầng sáng, quầng đỉnh xỉn và hẹp
   hẳn đi (thấy rõ khi so ảnh chụp đóng băng nhịp). Thân tim cùng màu nên phủ lên không lộ. */
.jp-bh .gl{position:absolute;inset:0;pointer-events:none;opacity:0;
  animation:jpb-glowop 2.1s ease-in-out infinite}
.jp-bh .gl svg{filter:drop-shadow(0 0 7cqw var(--c)) drop-shadow(0 0 3cqw #fff6dd)}
@keyframes jpb-glowop{0%,100%{opacity:0}50%{opacity:1}}
.jp-bh .bob{animation:jpb-bob 3.6s ease-in-out infinite}
.jp-bh .rock{animation:jpb-rock 2.9s ease-in-out infinite}
.jp-bh .sq{animation:jpb-squash 1.9s ease-in-out infinite}
/* vòng tim rỗng phát sáng toả ra từ quả đặc — món "lạ" của kiểu nổ dây */
.jp-bh .echo{position:absolute;inset:0;display:grid;place-items:center;
  animation:jpb-echo 1.9s ease-out infinite}
.jp-bh .echo svg{fill:none;stroke:#fff6dd;stroke-width:1.8;stroke-linejoin:round}
.jp-bh .who{font-family:'Be Vietnam Pro',system-ui,sans-serif;font-weight:500;white-space:nowrap;
  font-size:clamp(12px,3cqw,18px);letter-spacing:.2em;text-transform:uppercase;color:#f3dea3;
  text-shadow:0 1px 4px rgba(0,0,0,.95),0 0 12px rgba(0,0,0,.7)}
/* căng trước khi vỡ: hai nhịp đập, phình quá cỡ, căng quá thì run lên */
.jp-bh.tense .ic{animation:jpb-tense var(--hold) ease-in-out both}
.jp-bh.tense .trm{animation:jpb-trem .085s linear infinite}
.jp-bh.tense .gl{animation:jpb-glowup var(--hold) ease-in both}   /* quầng dâng dần tới đỉnh rồi vỡ */
@keyframes jpb-tense{
  0%{transform:scale(1)} 16%{transform:scale(1.07)} 30%{transform:scale(1.005)}
  48%{transform:scale(1.11)} 62%{transform:scale(1.02)}
  84%{transform:scale(1.17)} 100%{transform:scale(1.22)}}
@keyframes jpb-glowup{from{opacity:0}to{opacity:1}}
/* vỡ */
.jp-bh.gone .ic{animation:jpb-pop .3s cubic-bezier(.2,.7,.3,1) forwards}
.jp-bh.gone .who{animation:jpb-out .3s linear forwards}
@keyframes jpb-pop{0%{transform:scale(1.22);opacity:1}34%{transform:scale(1.46);opacity:.8}
  100%{transform:scale(1.95);opacity:0}}
@keyframes jpb-out{to{opacity:0}}
@keyframes jpb-bob{0%,100%{transform:translateY(0)}50%{transform:translateY(-1.5cqw)}}
@keyframes jpb-rock{0%,100%{transform:rotate(-3.6deg)}50%{transform:rotate(3.6deg)}}
@keyframes jpb-squash{0%,100%{transform:scale(1,1)}40%{transform:scale(1.05,.95)}
  72%{transform:scale(.97,1.035)}}

/* ---- vòng nổ hình quả tim, chỉ còn nét viền ---- */
.jp-hring{position:absolute;left:var(--x);bottom:var(--y);transform:translate(-50%,50%);
  animation:jpb-hring .72s cubic-bezier(.15,.7,.3,1) forwards}
.jp-hring svg{display:block;width:var(--sz);height:var(--sz);fill:none;stroke:#fff6dd;stroke-width:2;
  stroke-linejoin:round;filter:drop-shadow(0 0 2.6cqw var(--c))}
@keyframes jpb-hring{0%{transform:translate(-50%,50%) scale(.45);opacity:1}
  100%{transform:translate(-50%,50%) scale(2.7);opacity:0}}

/* ---- tim vỡ ra: .up (bay lên) > .fly (đường vỡ) > .sw (lắc) > .rl (lăn)
        > .jl (rung jelly) > .jt (dạt ngang / run) > .ic (nở) > svg (nghiêng + glow) ---- */
.jp-sh{position:absolute;left:var(--x);bottom:var(--y);transform:translate(-50%,50%);will-change:transform;
  --dx:0cqw;--dy:0cqw;--spin:0deg;--fling:.8s;--dur:7s;--rdelay:0s;--rise:70cqh;--sway:3cqw;
  --in:.32s;--sz:4cqw;--c:#f3dea3;--jel:1.5s;--gl:2s;--drift:1.4cqw;--ech:1.7s}
.jp-sh span{display:block}
.jp-sh .up{animation:jpb-rise var(--dur) cubic-bezier(.36,.02,.52,1) var(--rdelay) forwards}
.jp-sh .fly{animation:jpb-radial var(--fling) cubic-bezier(.1,.74,.26,1) both}
.jp-sh .sw{animation:jp-sway calc(var(--dur)*.72) ease-in-out infinite}
.jp-sh .rl{animation:jpb-roll var(--fling) cubic-bezier(.12,.72,.3,1) both}
.jp-sh .jl{animation:jpb-jelly var(--jel) ease-in-out infinite}
.jp-sh .jt{animation:jpb-drift calc(var(--dur)*.5) ease-in-out infinite alternate}
.jp-sh .ic{position:relative;animation:jp-grow var(--in) cubic-bezier(.2,1.5,.35,1) both}
/* Luôn phát sáng, mỗi quả một nhịp — cùng cách làm với quả to (.jp-bh): quầng ĐÁY tĩnh
   trên quả chính, quầng ĐỈNH vẽ sẵn trên bản sao .gl, nhịp đập là opacity của bản sao.
   Bản cũ animate thẳng `filter` trên từng mảnh, mà một cú nổ dây ra tới ~70 mảnh. */
.jp-sh svg{display:block;width:var(--sz);height:var(--sz);fill:var(--c);
  filter:drop-shadow(0 0 1.3cqw var(--c)) drop-shadow(0 .4cqw 1cqw rgba(0,0,0,.35));
  animation:jp-tilt calc(var(--dur)*.72) ease-in-out infinite}
.jp-sh .gl{position:absolute;inset:0;pointer-events:none;opacity:0;
  animation:jpb-glowop2 var(--gl) ease-in-out infinite}
.jp-sh .gl svg{filter:drop-shadow(0 0 2.9cqw var(--c)) drop-shadow(0 0 .9cqw #fff6dd)}
/* quá nửa số quả là tim VIỀN phát sáng — nhìn lạ, không thành một đám đặc kịt */
.jp-sh.out svg{fill:none;stroke:var(--c);stroke-width:1.6;stroke-linejoin:round;
  filter:drop-shadow(0 0 2cqw var(--c)) drop-shadow(0 0 .6cqw #fff6dd)}
.jp-sh.out .gl svg{filter:drop-shadow(0 0 4.6cqw var(--c)) drop-shadow(0 0 1.8cqw #fff6dd)}
/* Vòng viền toả ra (.echo) vẫn nghiêng cùng quả tim, quầng đáy tĩnh, không có bản sao
   đập theo — nó vốn đã nở rồi tan. */
@keyframes jpb-glowop2{0%,100%{opacity:0}50%{opacity:1}}
.jp-sh .echo{position:absolute;inset:0;display:none;place-items:center;
  animation:jpb-echo var(--ech) ease-out infinite}
.jp-sh.eco .echo{display:grid}
.jp-sh .echo svg{fill:none;stroke:#fff6dd;stroke-width:2.4;stroke-linejoin:round}
/* sắp nổ thì run lên, rung nhanh gấp ba */
.jp-sh.shiver .jt{animation:jpb-trem .07s linear infinite}
.jp-sh.shiver .jl{animation:jpb-jelly calc(var(--jel)*.34) ease-in-out infinite}
/* nổ tiếp: hụt vào một nhịp rồi mới bung */
.jp-sh.boom .ic{animation:jpb-chain .26s cubic-bezier(.2,.7,.3,1) forwards}
.jp-sh.boom .echo{animation:jpb-out .16s linear forwards}
@keyframes jpb-rise{0%{transform:translateY(0);opacity:1}70%{opacity:1}
  100%{transform:translateY(calc(var(--rise)*-1));opacity:0}}
@keyframes jpb-radial{0%{transform:translate(0,0) scale(.35)}
  100%{transform:translate(var(--dx),var(--dy)) scale(1)}}
@keyframes jpb-roll{from{transform:rotate(0)}to{transform:rotate(var(--spin))}}
@keyframes jpb-jelly{0%,100%{transform:scale(1,1)}28%{transform:scale(1.11,.9)}
  52%{transform:scale(.91,1.09)}76%{transform:scale(1.04,.97)}}
@keyframes jpb-drift{from{transform:translateX(calc(var(--drift)*-1))}to{transform:translateX(var(--drift))}}
@keyframes jpb-trem{0%{transform:translate(0,0)}20%{transform:translate(.55cqw,-.4cqw)}
  45%{transform:translate(-.5cqw,.45cqw)}70%{transform:translate(.4cqw,.35cqw)}
  100%{transform:translate(-.3cqw,-.2cqw)}}
@keyframes jpb-chain{0%{transform:scale(1)}20%{transform:scale(.8)}
  46%{transform:scale(1.46);opacity:.9}100%{transform:scale(1.95);opacity:0}}
@keyframes jpb-echo{0%{transform:scale(.96);opacity:.95}78%{opacity:0}100%{transform:scale(2.5);opacity:0}}

/* ---- tim to nổ ngay chỗ chạm ---- */
.jp-pop{position:absolute;left:var(--x);top:var(--y);width:16cqw;height:16cqw;transform:translate(-50%,-50%);
  animation:jp-pop .9s ease-out forwards}
.jp-pop svg{width:100%;height:100%;fill:#ff2d55;filter:drop-shadow(0 0 3cqw rgba(255,45,85,.6))}
@keyframes jp-pop{
  0%{transform:translate(-50%,-50%) scale(.2);opacity:0}
  30%{opacity:1;transform:translate(-50%,-50%) scale(1.1) rotate(-8deg)}
  100%{transform:translate(-50%,-90%) scale(1.3) rotate(6deg);opacity:0}}

/* ---- vòng sóng khi giữ để gọi hiệu ứng ---- */
.jp-ring{position:absolute;left:var(--x);top:var(--y);width:24cqw;height:24cqw;transform:translate(-50%,-50%);
  border-radius:50%;border:2px solid rgba(243,222,163,.85);animation:jp-ring .5s ease-out forwards}
@keyframes jp-ring{from{transform:translate(-50%,-50%) scale(.2);opacity:1}to{transform:translate(-50%,-50%) scale(1.5);opacity:0}}
.jp-hold{position:absolute;left:var(--x);top:var(--y);width:17cqw;height:17cqw;transform:translate(-50%,-50%);
  border-radius:50%;border:2px solid rgba(243,222,163,.9);animation:jp-hold .48s linear forwards}
@keyframes jp-hold{from{transform:translate(-50%,-50%) scale(.28);opacity:.2}to{transform:translate(-50%,-50%) scale(1);opacity:.95}}


/* ---- gợi ý lần đầu ---- */
.jp-coach{position:absolute;inset:0;z-index:9;display:grid;place-items:center;text-align:center;
  background:radial-gradient(60% 40% at 50% 52%,rgba(0,0,0,.62),rgba(0,0,0,.28) 70%,transparent);
  transition:opacity .45s}
.jp-coach.off{opacity:0;pointer-events:none}
.jp-coach .in{display:flex;flex-direction:column;align-items:center;gap:14px;padding:0 40px}
.jp-coach .tap{width:84px;height:84px;border-radius:50%;border:1px solid rgba(243,222,163,.5);
  display:grid;place-items:center;position:relative}
.jp-coach .tap::after{content:'';position:absolute;inset:-1px;border-radius:50%;border:1px solid rgba(243,222,163,.6);
  animation:jp-pulse 1.9s ease-out infinite}
@keyframes jp-pulse{from{transform:scale(1);opacity:.9}to{transform:scale(1.7);opacity:0}}
.jp-coach .tap svg{width:34px;height:34px;fill:#ff5c8a;filter:drop-shadow(0 0 10px rgba(255,92,138,.7))}
.jp-coach p{margin:0;font:400 15px/1.6 'Be Vietnam Pro',system-ui,sans-serif;color:#f4efe6}
.jp-coach small{font:300 12.5px/1.6 'Be Vietnam Pro',system-ui,sans-serif;color:rgba(244,239,230,.55)}

/* ---- nút phóng toàn màn hình, dùng chung cho cả 3 option ---- */
.jp-fs{flex:none;width:34px;height:34px;border-radius:50%;cursor:pointer;display:grid;place-items:center;
  border:1px solid rgba(255,255,255,.16);background:rgba(11,10,13,.5);backdrop-filter:blur(10px);
  color:rgba(244,239,230,.85);font:400 14px 'Be Vietnam Pro',system-ui,sans-serif;line-height:1}

/* ---- nhãn hiệu: z-index 12 nên nằm TRÊN lớp tim (8), tim bay rợp vẫn đọc được ----
   Bản của engine bị app tắt đi trong dungNhanHieu(). */
.jp-brand{position:absolute;top:4%;left:50%;transform:translateX(-50%);z-index:12;pointer-events:none;
  /* top do JS đặt lại theo khung ảnh 9:16 của engine, 4% đây chỉ là mức lùi */
  display:flex;align-items:center;gap:3.2cqw;padding:1.8cqw 4.4cqw;white-space:nowrap;text-align:center;
  background:radial-gradient(58% 150% at 50% 50%,rgba(8,6,11,.66),rgba(8,6,11,.2) 66%,transparent 78%)}
.jp-brand .ln{width:7cqw;height:1px;flex:none;background:linear-gradient(to right,transparent,rgba(243,222,163,.8))}
.jp-brand .ln:last-child{background:linear-gradient(to left,transparent,rgba(243,222,163,.8))}
.jp-brand .tx{display:block}
.jp-brand b{display:block;font:600 clamp(12px,1.95cqw,17px) 'Be Vietnam Pro',system-ui,sans-serif;
  letter-spacing:.42em;padding-left:.42em;text-transform:uppercase;color:#fdf3d9;
  background:linear-gradient(180deg,#fffaea,#f3dea3 48%,#cfa95f);
  -webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;
  filter:drop-shadow(0 1px 2px rgba(0,0,0,.95)) drop-shadow(0 2px 12px rgba(0,0,0,.85))}
.jp-brand i{display:block;margin-top:.75em;font:500 clamp(9px,1.3cqw,12px) 'Be Vietnam Pro',system-ui,sans-serif;
  font-style:normal;letter-spacing:.34em;padding-left:.34em;color:rgba(249,236,206,.94);
  text-shadow:0 1px 3px rgba(0,0,0,.98),0 0 14px rgba(0,0,0,.8)}

/* ---- hạ sáng ĐỈNH MÀN cho nhãn hiệu đọc được ----
   Ảnh đài phun Trevi trắng loá đúng ở dải trên, chữ FANOS trắng nằm đó là chìm.
   Cùng thủ pháp với .mw-scrim ở đáy màn, chỉ nhạt hơn và thấp hơn: đây là để ĐỌC
   được nhãn, không phải để hạ sáng ảnh — nên đậm nhất cũng chỉ ở vài dòng trên
   cùng rồi tan hết trước khi chạm tới mặt cô dâu chú rể.
   Khác hẳn tấm nền sau chữ đã bỏ trước đây: tấm đó là một mảng bám quanh chữ, ai
   cũng nhìn ra; dải này trải hết bề ngang và tan dần nên đọc ra như ánh sáng của
   chính tấm ảnh.
   z-index 3 = trên ảnh, dưới lớp tim (8) và dưới nhãn (12). Thẻ lời chúc khách
   khác cũng z-index 3 nhưng do JS thêm vào sau nên vẫn nằm trên, không bị ám. */
.jp-topdim{position:absolute;left:0;right:0;top:0;z-index:3;pointer-events:none;
  height:var(--jp-topdim-h,24cqh);
  background:linear-gradient(to bottom,
    rgba(6,5,8,var(--jp-topdim,.45)),
    rgba(6,5,8,calc(var(--jp-topdim,.45) * .4)) 44%,
    transparent)}

/* ---- nhãn hiệu bản ẢNH: FANOS đính hạt kim cương + dòng chữ thường ----
   Hai dòng, hai chất liệu khác hẳn nhau:
     dòng 1 = ảnh chữ đính hạt (fanos-diamond.webp, cắt từ fanos_diamond.PNG)
     dòng 2 = chữ thường, serif của thiệp, giãn chữ cho ra vẻ nhãn hiệu.
   KHÔNG tấm nền, KHÔNG quầng sáng, KHÔNG nhịp nhún: nhãn đứng thẳng trên ảnh,
   tách khỏi nền bằng bóng đổ ôm sát nét chữ thôi. Cái động duy nhất còn lại là
   nhịp LOÉ mời chạm — sao loé trên nét chữ + lằn sáng quét qua mặt đá + một nhịp
   sáng gắt, cùng ngôn ngữ với viên đá trên váy (.bl trong app-bling.css).
   Sao do app-stage.js dựng (loeNhanHieu), rắc đúng vào nét chữ.
   --jpb-w: bề ngang khối chữ so với khung (22cqw ≈ 86px ở màn 390). */
.jp-brand.jpb{display:block;text-align:center;white-space:normal;gap:0;padding:0;background:none;
  --jpb-br:1;--jpb-tint:contrast(.92) sepia(.2) saturate(1.3);
  --jpb-peak:1.2;--jpb-loe-dur:7s}
.jpb-mark{display:block;width:clamp(60px,var(--jpb-w,22cqw),168px);margin:0 auto;
  /* chặn trên 168px: sân khấu phủ nguyên màn nên cqw đo theo bề ngang MÁY, mở trên
     máy tính hay máy tính bảng mà để nguyên cqw là khối chữ dài cả gang tay.
     168 chứ không phải 128 vì kiểu lg-left/lg-right gộp cả logo vào cùng một hàng. */
  padding:0;border:0;background:none;
  appearance:none;-webkit-appearance:none;cursor:default;pointer-events:auto;line-height:0;
  transition:transform .1s}
.jpb-mark:active{transform:scale(.96)}
/* Hai lớp bóng (một sát, một toả) giữ cho chữ không chìm vào nền đài phun sáng loá.
   Bóng ĐEN TUYỀN của bản trước là một nửa lý do khối chữ nhìn ra một mảng tối: chữ
   đã hạ sáng, lại viền đen kịt quanh mép. Nay đổi sang nâu ấm và nhạt bớt, hợp với
   bảng vàng của thiệp, mép chữ vẫn tách khỏi cột đá trắng. */
/* Trong khối chữ có tối đa hai ảnh: LOGO (khối 4 ô vuông đính đá) và CHỮ FANOS.
   Xếp chồng hay xếp hàng do class lg-* quyết định — so ba cách ở app-fanos-logo.html.
   Cả hai ảnh cùng ăn một animation jpb-loe nên thở đúng một nhịp, không so le. */
.jpb-in{display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:var(--jpb-gap,.5em);position:relative;isolation:isolate;
  filter:drop-shadow(0 1px 1.5px rgba(26,18,8,.6)) drop-shadow(0 2px 9px rgba(26,18,8,.45))}
/* Khung riêng cho ảnh CHỮ: lớp sao loé bám khung này chứ không bám cả khối, không
   thì toạ độ BRAND_PTS (đo trên ảnh chữ) lệch đi ngay khi thêm logo vào. */
.jpb-wm{position:relative;display:block;flex:0 1 auto;min-width:0;width:100%}
/* Selector phải ĐỦ NẶNG: .jpb-in img ở trên đã đặt width:100% (0,1,1), một class
   trơn (0,1,0) thua — logo nở bằng cả khối chữ, đè luôn chỗ của chữ. */
.jpb-in img.jpb-logo{flex:none;width:var(--jpb-logo-w,30%)}
/* ĐÃ CHỐT: lg-right — chữ trước, logo sau, đúng thứ tự trong fanos_diamond.PNG.
   lg-top là bố cục viết sẵn ở trên (cột dọc), chỉ cần cỡ logo.
   lg-left / lg-right xếp một hàng: khối chữ co lại nhường chỗ cho logo, nên
   --jpb-w của hai kiểu đó phải nới ra thì chữ mới giữ nguyên cỡ. */
.jp-brand.jpb.lg-left .jpb-in,
.jp-brand.jpb.lg-right .jpb-in{flex-direction:row;align-items:center;gap:var(--jpb-gap,.42em)}
.jp-brand.jpb.lg-right .jpb-in{flex-direction:row-reverse}
.jp-brand.jpb.lg-left .jpb-wm,
.jp-brand.jpb.lg-right .jpb-wm{flex:1 1 auto}
/* Xếp một hàng thì logo ăn mất chỗ của chữ, nên nới khối lên 30.5cqw (114px ở màn
   375): 114 = chữ 86 + khe 6.6 + logo 21.7.
   Ba con số dưới đây khoá nhau để LOGO CAO ĐÚNG BẰNG CHỮ, như trong ảnh gốc:
     khung ảnh chữ 960x258 -> cao = .2688 bề ngang
     khung ảnh logo 320x339 -> cao = bề ngang / .944
     hai ảnh chừa lề cùng tỉ lệ (nét chiếm .943 chiều cao khung) nên khung cao bằng
     nhau là nét cao bằng nhau.
   Giải ra: logo 19% và khe 5.8% của khối thì chữ còn 75.2%, logo cao .2015 khối,
   chữ cao .2021 khối — lệch 0.3%, mắt không thấy.
   Khe phải tính bằng PHẦN TRĂM chứ không phải em: em bám cỡ chữ gốc của trang nên
   đổi bề ngang màn là tỉ lệ ba phần lệch đi, chiều cao hai ảnh hết bằng nhau. */
.jp-brand.jpb.lg-left,.jp-brand.jpb.lg-right{--jpb-logo-w:19%;--jpb-gap:5.8%;--jpb-w:30.5cqw}
/* Hạ chói cho khối chữ. Ảnh gốc là hạt kim cương TRẮNG chụp trên nền đen, đưa
   nguyên lên là sáng hơn mọi thứ khác trên màn, hút hết mắt khỏi cô dâu chú rể.
     --jpb-br   : mức sáng lúc nghỉ, đã chốt 1 (đúng ảnh gốc). Từng thử hạ .5 rồi
                  .86: hạ brightness là nhân thẳng giá trị điểm ảnh, hạt kim cương
                  trắng tụt xuống xám, cả khối thành mảng tối — không phải chói ít
                  đi mà là CHẾT chữ. Phần hạ chói để --jpb-tint gánh.
     --jpb-tint : contrast .92 bóp phần loá gắt nhất của mấy điểm bắt sáng (đó mới
                  là chỗ chói), sepia .2 + saturate 1.3 kéo chữ từ trắng lạnh sang
                  champagne — về đúng bảng vàng của dòng Eternal Wishes và của cả
                  app, nên nhìn hoà chứ không phải một vệt trắng dán lên ảnh.
                  Đây mới là thứ trị chói, nên giữ brightness ở 1 vẫn không gắt.
   Filter phải nằm TRONG keyframes: đặt filter tĩnh ở đây thì animation jpb-loe
   ghi đè lên chính thuộc tính đó, con số tĩnh không có tác dụng. */
.jpb-in img{display:block;width:100%;height:auto;-webkit-user-drag:none;user-select:none;
  animation:jpb-loe var(--jpb-loe-dur,7s) ease-in-out infinite}
.jpb-stars{position:absolute;inset:0;display:block;pointer-events:none}
/* ---- sao loé mời chạm trên khối chữ ----
   Cỡ tính theo % BỀ NGANG KHỐI CHỮ, không theo cqw — khối chữ chỉ còn 22cqw nên
   sao đo theo màn là to hơn cả chữ cái. Dùng % thì thu nhỏ khối chữ là sao nhỏ theo.
   Bản đầu dày và giật: 0,4-0,9s một ngôi, sống 0,9-1,4s nên 2-3 ngôi chồng nhau
   trên một khối chữ 82px, lại còn XOAY 38° trong lúc mờ dần nên bốn cái tia quét
   ngang mặt chữ. Nay mỗi lúc MỘT ngôi, thưa gấp ba, sống lâu gấp rưỡi, bỏ hẳn phần
   xoay. Nhịp và cỡ do app đặt (brand.bling, xem app-stage.js).
   Bốn kiểu SAO so ở app-fanos-bling.html, chọn bằng class b1..b4.
   ĐÃ CHỐT: b1 «sao mảnh» — lõi + tia ngang dọc + tia chéo, mảnh và ngắn. */
.jpb-st{position:absolute;left:var(--x);top:var(--y);width:var(--sz,20%);aspect-ratio:1;
  transform:translate(-50%,-50%);pointer-events:none;mix-blend-mode:screen;
  animation:jpb-sao var(--life,1900ms) cubic-bezier(.4,0,.25,1) forwards}
.jpb-st i{position:absolute;display:block}
.jpb-st .c{inset:0;border-radius:50%;
  background:radial-gradient(circle,rgba(255,255,253,1) 0%,rgba(255,253,244,.95) 12%,
    rgba(255,246,214,.45) 26%,rgba(255,230,158,.14) 42%,rgba(255,214,120,0) 66%)}
/* tia mảnh hơn bản đầu (1.4px -> 1px) và ngắn hơn (-56% -> -42%): ở cỡ 82px thì
   cây thánh giá cũ dài hơn cả chữ cái, nhìn ra đồ hoạ chứ không ra hạt đá bắt sáng */
.jpb-st .f{inset:-42%;
  background:linear-gradient(to right,transparent,rgba(255,252,240,.85) 50%,transparent) center/100% 1px no-repeat,
    linear-gradient(to bottom,transparent,rgba(255,252,240,.85) 50%,transparent) center/1px 100% no-repeat}
.jpb-st .f2{inset:-26%;transform:rotate(45deg);opacity:.5;
  background:linear-gradient(to right,transparent,rgba(255,248,222,.7) 50%,transparent) center/100% 1px no-repeat,
    linear-gradient(to bottom,transparent,rgba(255,248,222,.7) 50%,transparent) center/1px 100% no-repeat}
.jpb-st .r{inset:-10%;border-radius:50%;border:1px solid rgba(255,248,222,.75);opacity:0}
/* b2 · chỉ lõi (không dùng, giữ để so lại) — bỏ hết tia, còn một đốm tròn nở ra rồi lịm. Mượt nhất. */
.jp-brand.jpb.b2 .jpb-st .f,.jp-brand.jpb.b2 .jpb-st .f2{display:none}
.jp-brand.jpb.b2 .jpb-st .c{background:radial-gradient(circle,rgba(255,255,253,1) 0%,
  rgba(255,252,238,.9) 16%,rgba(255,244,206,.4) 34%,rgba(255,226,150,.1) 54%,transparent 72%)}
/* b3 · tia dài mảnh (không dùng, giữ để so lại) — chỉ một cây ngang dọc, dài mà rất nhạt, kiểu ánh sao chụp phơi */
.jp-brand.jpb.b3 .jpb-st .f2{display:none}
.jp-brand.jpb.b3 .jpb-st .f{inset:-90%;opacity:.62;
  background:linear-gradient(to right,transparent,rgba(255,252,240,.8) 50%,transparent) center/100% 1px no-repeat,
    linear-gradient(to bottom,transparent,rgba(255,252,240,.8) 50%,transparent) center/1px 100% no-repeat}
/* b4 · lõi + vòng lan (không dùng, giữ để so lại) — không tia, thay bằng một vòng mảnh toả ra rồi tan */
.jp-brand.jpb.b4 .jpb-st .f,.jp-brand.jpb.b4 .jpb-st .f2{display:none}
.jp-brand.jpb.b4 .jpb-st .r{animation:jpb-vong var(--life,1900ms) cubic-bezier(.2,.7,.3,1) forwards}
/* vừa chạm: cả khối loé một nhát rồi nghỉ mời chạm mấy giây (app-stage.js hẹn giờ) */
.jpb-mark.hit .jpb-in{animation:jpb-cham .62s cubic-bezier(.3,0,.35,1)}
/* Nở ra rồi lịm đi, KHÔNG xoay: bản đầu xoay 38° trong lúc mờ dần nên bốn cái tia
   quét ngang mặt chữ, đó là chỗ nhìn ra giật. Đỉnh giữ lâu (30-62%) cho mắt kịp
   bắt rồi tắt chậm. */
@keyframes jpb-sao{0%{opacity:0;transform:translate(-50%,-50%) scale(.35)}
  30%{opacity:1;transform:translate(-50%,-50%) scale(1)}
  62%{opacity:.88;transform:translate(-50%,-50%) scale(1.03)}
  100%{opacity:0;transform:translate(-50%,-50%) scale(.62)}}
@keyframes jpb-vong{0%{opacity:0;transform:scale(.3)}20%{opacity:.7}100%{opacity:0;transform:scale(2.3)}}
/* ---- nhịp sáng của khối chữ: LÊN từ từ · GIỮ · XUỐNG từ từ · NGHỈ ----
   Bốn chặng đều nhau trên vòng 7 giây:
       0 -> 21,43%   (1,5s)  sáng dần lên
     21,43 -> 50%    (2s)    GIỮ ở mức sáng
     50 -> 71,43%    (1,5s)  giảm dần về bình thường
     71,43 -> 100%   (2s)    nghỉ, rồi mới lên lại
   Muốn đổi tổng vòng thì sửa --jpb-loe-dur; bốn chặng co giãn theo tỉ lệ, tức là
   7s cho ra đúng 1,5 / 2 / 1,5 / 2 giây như trên.
   Biên độ đã chốt: đáy 1 (--jpb-br) · đỉnh 1,2 (--jpb-br * --jpb-peak). Đỉnh phải
   khiêm tốn vì nó GIỮ 2 giây chứ không phải nháy một cái — bản đầu nháy 0,3 giây
   nên đỉnh 1,6 vẫn không thấy chói, kéo dài ra thì mắt đọc khác hẳn.
   Ease-in-out ăn vào TỪNG chặng, nên hai chặng lên/xuống mềm hai đầu, còn hai
   chặng giữ/nghỉ thì hai mốc bằng nhau nên đứng yên thật. */
@keyframes jpb-loe{
  0%{filter:var(--jpb-tint) brightness(var(--jpb-br,1))}
  21.43%{filter:var(--jpb-tint) brightness(calc(var(--jpb-br,1) * var(--jpb-peak,1.2)))}
  50%{filter:var(--jpb-tint) brightness(calc(var(--jpb-br,1) * var(--jpb-peak,1.2)))}
  71.43%{filter:var(--jpb-tint) brightness(var(--jpb-br,1))}
  100%{filter:var(--jpb-tint) brightness(var(--jpb-br,1))}}
@keyframes jpb-cham{0%,100%{transform:scale(1);
    filter:var(--jpb-tint) brightness(var(--jpb-br,1))
      drop-shadow(0 1px 1.5px rgba(26,18,8,.6)) drop-shadow(0 2px 9px rgba(26,18,8,.45))}
  22%{transform:scale(1.07);
    /* 1,6 chứ không phải 1,75: đỉnh nhịp thở nay chỉ 1,2, cú chạm cần nhỉnh hơn
       đỉnh đó cho thấy rõ là "ăn", nhưng vọt quá thì lại thành nháy chói. */
    filter:var(--jpb-tint) brightness(calc(var(--jpb-br,1) * 1.6))
      drop-shadow(0 0 10px rgba(255,240,200,.85))}}
/* Dòng chữ thường. Selector phải ĐỦ NẶNG: .jpb-sub là thẻ <i> nằm trong .jp-brand
   nên dính luôn rule .jp-brand i của nhãn chữ cũ (0,1,1 > 0,1,0) — để một class
   trơn là cả font lẫn cỡ chữ bị bản cũ đè, chỉnh ở đây không ăn.
   Cỡ chữ ĐẶT TO HƠN dòng ký tên ở đáy màn (.mywish .mw-who b = 2.1cqw) một bậc rõ. */
.jp-brand.jpb .jpb-sub{display:inline-block;position:relative;isolation:isolate;
  margin-top:1.4cqw;font-style:normal;font-family:'Cormorant Garamond',serif;line-height:1.15;
  /* Cormorant Garamond là serif nét rất mảnh; ở cỡ 18px trên nền đài phun sáng thì
     nét đứng gần như biến mất. --sub-wt để dò độ đậm, mặc định 600. */
  font-weight:var(--sub-wt,600);
  /* --sub-k là hệ số cỡ chữ, tách riêng để dò bằng thanh kéo ở app-fanos-options.html
     rồi chốt lại một con số. 1.5 = bản đã tăng 50% so với cỡ đầu tiên. */
  font-size:calc(clamp(11px,3.05cqw,17px) * var(--sub-k,1.6));
  letter-spacing:.26em;padding-left:.26em;color:rgba(250,240,216,.96);
  /* Quầng sáng ôm NÉT CHỮ, lúc nào cũng bật — khác hẳn quầng elip sau lưng
     (--sb-glow) vốn lâu lâu mới nở một nhát.
     NĂM lớp sáng xếp từ sát ra xa (.10 / .26 / .60 / 1.2 / 2.2 em) rồi mới tới
     bóng tối. Thứ tự đó bắt buộc: text-shadow vẽ lớp đầu NẰM TRÊN, nên bóng tối
     phải xuống cuối, không thì nó phủ mất phần sáng.
     Lớp .10em gần như trùng nét chữ — đó là thứ làm chính NÉT sáng lên, chứ mấy
     lớp rộng chỉ toả ra xung quanh. Bản đầu thiếu lớp này nên nhìn ra sương mù
     quanh chữ chứ chữ thì vẫn xỉn.
     Bóng tối rút còn MỘT lớp sát (0 1px 2px). Bản đầu có thêm một vầng tối rộng
     14px, nó nằm đúng chỗ quầng ấm nên hai cái triệt nhau, tăng --sub-glow mấy
     cũng không sáng thêm được bao nhiêu. Phần tách chữ khỏi nền đã có dải tối
     đỉnh màn (.jp-topdim) lo.
     Bán kính tính bằng em để đổi cỡ chữ là quầng co theo, khỏi chỉnh lại.
     --sub-glow: hệ số mạnh yếu, đã chốt 1.2. Ở mức này lớp trong cùng đã chạm trần
     alpha (.95 * 1.2 > 1) nên đẩy cao hơn nữa chỉ làm mấy lớp ngoài dày thêm, nét
     không sáng hơn được — muốn hơn phải đổi công thức chứ không phải vặn số. */
  text-shadow:
    0 0 .10em rgba(255,253,246,calc(.95 * var(--sub-glow,1.2))),
    0 0 .26em rgba(255,246,224,calc(.90 * var(--sub-glow,1.2))),
    0 0 .60em rgba(255,236,190,calc(.62 * var(--sub-glow,1.2))),
    0 0 1.2em rgba(243,222,163,calc(.42 * var(--sub-glow,1.2))),
    0 0 2.2em rgba(214,184,120,calc(.28 * var(--sub-glow,1.2))),
    0 1px 2px rgba(0,0,0,.85)}
/* ---- nền dòng chữ thường: quầng ÔM CHỮ, lâu lâu sáng lên ----
   Chữ Eternal Wishes mảnh mà nền là đài phun trắng loá nên chìm. Đặt một tấm sau
   chữ, bo 50% trên hộp bè ngang nên ra ELIP cong theo dáng dòng chữ. Vì .jpb-sub
   là inline-block nên hộp ôm đúng bề ngang chữ, elip dài ngắn theo chữ; hai mép
   nở thêm tính bằng em nên đổi cỡ chữ là elip nở theo, khỏi chỉnh lại.
   Hai lớp chồng nhau:
     ::before = tấm nền, đứng yên        (cỡ --sb-x / --sb-y)
     ::after  = lớp sáng, lâu lâu dâng lên rồi tắt (cỡ --sb-gx / --sb-gy)
   Cả bốn kiểu đều là QUẦNG VÀNG, khác nhau ở cách gánh phần đọc được:
   s1 chỉ có quầng · s2 quầng hai tầng · s3 quầng + gạch hai bên · s4 quầng lót tối.
   Chọn bằng class s1..s4 trên .jp-brand.jpb, so ở app-fanos-options.html. */
.jp-brand.jpb .jpb-sub::before,
.jp-brand.jpb .jpb-sub::after{content:'';position:absolute;z-index:-1;pointer-events:none;
  box-sizing:border-box;left:50%;top:50%;border-radius:50%}
.jp-brand.jpb .jpb-sub::before{transform:translate(-50%,-50%);
  width:calc(100% + var(--sb-x,3em));height:calc(100% + var(--sb-y,2em));
  background:var(--sb-bg,none)}
.jp-brand.jpb .jpb-sub::after{transform:translate(-50%,-50%) scale(.82);opacity:0;
  width:calc(100% + var(--sb-gx,var(--sb-x,3em)));height:calc(100% + var(--sb-gy,var(--sb-y,2em)));
  background:var(--sb-glow,none);
  animation:jpb-sub-glow var(--sb-every,4s) ease-in-out infinite}
/* nhịp sáng: gần hết vòng mới nở lên một nhát rồi tắt — "lâu lâu", không đập đều */
@keyframes jpb-sub-glow{0%,58%,100%{opacity:0;transform:translate(-50%,-50%) scale(.82)}
  74%{opacity:1;transform:translate(-50%,-50%) scale(1)}
  89%{opacity:.2;transform:translate(-50%,-50%) scale(1.09)}}
/* Bốn kiểu dưới đây nay KHÔNG kiểu nào bật mặc định: nét chữ đã có quầng sáng
   riêng luôn bật, thêm quầng ở nền nữa là hai lớp sáng chồng nhau, chữ nhoè đi
   chứ không rõ thêm. Giữ lại để bật bằng ?sub=1..4 khi cần so lại.
   01 · quầng vàng — KHÔNG hạ sáng nền, chỉ hắt một quầng ấm sau chữ */
.jp-brand.jpb.s1 .jpb-sub{--sb-x:3em;--sb-y:2em;
  --sb-bg:radial-gradient(closest-side,rgba(243,222,163,.24),rgba(214,184,120,.08) 54%,transparent);
  --sb-glow:radial-gradient(closest-side,rgba(255,246,222,.6),rgba(243,222,163,.2) 52%,transparent)}
/* 02 · quầng hai tầng — tầng ngoài rộng và mờ, tầng trong bó sát chữ và gắt hơn.
   Lúc sáng chỉ tầng trong nở, nên chữ nảy lên chứ không phải cả mảng cùng sáng. */
.jp-brand.jpb.s2 .jpb-sub{--sb-x:4.6em;--sb-y:3em;--sb-gx:1.4em;--sb-gy:.9em;
  --sb-bg:radial-gradient(closest-side,rgba(243,222,163,.2),rgba(214,184,120,.07) 50%,transparent),
    radial-gradient(closest-side,rgba(255,240,206,.26),transparent 34%);
  --sb-glow:radial-gradient(closest-side,rgba(255,250,232,.85),rgba(255,236,190,.34) 56%,transparent)}
/* 03 · quầng + gạch hai bên — hai vạch vàng mảnh kẹp hai đầu dòng chữ, đúng lối
   nhãn hiệu cũ (.jp-brand .ln). Vạch vẽ bằng lớp nền phụ của ::before, nằm gọn
   trong phần mép nở ra hai bên nên không đè lên chữ. */
.jp-brand.jpb.s3 .jpb-sub{--sb-x:5.6em;--sb-y:2.2em;--sb-gx:3.4em;--sb-gy:1.8em;
  --sb-bg:linear-gradient(to right,transparent,rgba(243,222,163,.85)) no-repeat left center/2em 1px,
    linear-gradient(to left,transparent,rgba(243,222,163,.85)) no-repeat right center/2em 1px,
    radial-gradient(closest-side,rgba(243,222,163,.2),rgba(214,184,120,.06) 52%,transparent);
  --sb-glow:radial-gradient(closest-side,rgba(255,246,222,.55),rgba(243,222,163,.16) 54%,transparent)}
/* 04 · quầng lót tối — vẫn là quầng vàng, nhưng lót dưới một lớp tối rất mỏng.
   Đây là kiểu DUY NHẤT thật sự nâng tương phản: quầng vàng thuần nằm trên nền
   đài phun trắng thì chữ trắng vẫn chìm, phải có cái gì tối mới tách ra được. */
.jp-brand.jpb.s4 .jpb-sub{--sb-x:3.4em;--sb-y:2.2em;
  --sb-bg:radial-gradient(closest-side,rgba(243,222,163,.26),rgba(214,184,120,.08) 52%,transparent),
    radial-gradient(closest-side,rgba(10,8,14,.52),rgba(10,8,14,.22) 60%,transparent);
  --sb-glow:radial-gradient(closest-side,rgba(255,248,226,.6),rgba(243,222,163,.2) 52%,transparent)}
@media (prefers-reduced-motion:reduce){
  .jpb-in img{animation:none}
  .jp-brand.jpb .jpb-sub::after{animation:none;opacity:.45}
  .jpb-stars{display:none}
}
/* ---- nút thả tim (bản thiết kế lại: kính tối + vành vàng + tim vàng phát sáng) ---- */
.jp-heartbtn{position:relative;width:78px;height:78px;border-radius:50%;border:0;cursor:pointer;flex:none;
  display:grid;place-items:center;isolation:isolate;
  background:radial-gradient(circle at 50% 40%,rgba(243,222,163,.4),rgba(18,15,22,.62) 66%),rgba(18,15,22,.5);
  backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
  box-shadow:0 0 0 1px rgba(243,222,163,.7),0 16px 36px -14px rgba(0,0,0,.95),
    0 0 40px -6px rgba(243,222,163,.65),inset 0 1px 0 rgba(255,255,255,.22);
  transition:transform .09s,box-shadow .3s}
.jp-heartbtn::before{content:'';position:absolute;inset:7px;border-radius:50%;border:1px solid rgba(243,222,163,.22)}
.jp-heartbtn::after{content:'';position:absolute;inset:-2px;border-radius:50%;border:1px solid rgba(243,222,163,.8);opacity:0}
.jp-heartbtn:active{transform:scale(.93)}
.jp-heartbtn.pulse{box-shadow:0 0 0 1px rgba(243,222,163,.8),0 16px 36px -14px rgba(0,0,0,.95),
  0 0 46px -6px rgba(243,222,163,.8),inset 0 1px 0 rgba(255,255,255,.2)}
.jp-heartbtn.pulse::after{animation:jp-btnring .6s ease-out}
@keyframes jp-btnring{from{transform:scale(1);opacity:.85}to{transform:scale(1.5);opacity:0}}
/* ---- Nhịp mời bấm (đã chốt: mức 02 «đập rồi rùng mình», so ở app-heartbtn-options.html) ----
   Cứ 2 giây diễn MỘT lần rồi đứng im: tim đập hai nhát (0–0,5s), rùng mình lắc
   9° tắt dần (0,54–0,94s), còn lại 1,06s là nghỉ hẳn. Vành trong toả ra theo nhát
   đập đầu. Vòng chạm tay vẫn là ::after (class .pulse), hai thứ không đụng nhau.
   Nhanh chậm chỉnh bằng --sp: 1 = 2s một lần diễn, 1.4 = nhanh hơn 40%,
   0.8 = thưa ra thành 2,5s. App khách còn nhận --sp qua ?sp=… để dò ngoài máy. */
.jp-heartbtn svg{width:34px;height:34px;filter:drop-shadow(0 0 9px rgba(243,222,163,.6));
  animation:jp-btnidle calc(2s / var(--sp,1)) ease-in-out infinite}
.jp-heartbtn::before{animation:jp-btninvite calc(2s / var(--sp,1)) ease-out infinite}
@keyframes jp-btnidle{
  /* hai nhát đập */
  0%{transform:scale(1) rotate(0deg);filter:drop-shadow(0 0 8px rgba(243,222,163,.55))}
  6%{transform:scale(1.22) rotate(0deg);filter:drop-shadow(0 0 16px rgba(243,222,163,.95))}
  13%{transform:scale(1.04) rotate(0deg)}
  19%{transform:scale(1.14) rotate(0deg);filter:drop-shadow(0 0 13px rgba(243,222,163,.8))}
  25%{transform:scale(1) rotate(0deg);filter:drop-shadow(0 0 8px rgba(243,222,163,.55))}
  /* rùng mình rồi tắt dần */
  27%{transform:scale(1.02) rotate(9deg)}
  31%{transform:scale(1.02) rotate(-8deg)}
  35%{transform:scale(1.01) rotate(6deg)}
  39%{transform:scale(1) rotate(-4deg)}
  43%{transform:scale(1) rotate(2deg)}
  47%,100%{transform:scale(1) rotate(0deg);filter:drop-shadow(0 0 8px rgba(243,222,163,.55))}}
@keyframes jp-btninvite{
  0%{transform:scale(1);opacity:.6}
  22%{transform:scale(1.45);opacity:0}
  100%{transform:scale(1.45);opacity:0}}
@media (prefers-reduced-motion:reduce){
  .jp-heartbtn svg,.jp-heartbtn::before{animation:none}}
.jp-heartbtn .cap{position:absolute;bottom:-20px;left:0;right:0;text-align:center;
  font:400 9px 'Be Vietnam Pro',system-ui,sans-serif;letter-spacing:.22em;text-transform:uppercase;color:rgba(243,222,163,.92);
  text-shadow:0 2px 10px rgba(0,0,0,.95)}


/* ---- lời chúc phụ của khách khác: làn PHẢI, trôi lên, bị cô dâu che (mask dựng trong app-stage.js) ---- */
.jp-sub{position:absolute;inset:0;z-index:3;overflow:hidden;pointer-events:none;
  -webkit-mask-size:100% 100%;mask-size:100% 100%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}
/* Bề ngang co theo chữ, tối đa 30cqw: câu ngắn một dòng thì thẻ ôm sát chữ, không ôm khoảng trống.
   Câu dài vẫn nở đủ 30cqw rồi mới xuống dòng — mép phải làn (95-98%) giữ nguyên. */
.jp-chip{position:absolute;left:var(--lx);bottom:52%;width:fit-content;max-width:30cqw;pointer-events:auto;cursor:pointer;padding:2.2cqw 2.6cqw;border-radius:2.4cqw;
  /* kính TỐI, không phải kính sáng: nền ảnh giờ là đài phun Trevi trắng loá,
     thẻ sáng mờ trên nền sáng thì mất chữ. Nền tối + viền vàng đậm mới đọc được. */
  background:linear-gradient(160deg,rgba(9,7,12,.72),rgba(9,7,12,.52));
  border:1px solid rgba(243,222,163,.45);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);
  box-shadow:0 2cqw 5cqw -2cqw rgba(0,0,0,.9);
  animation:jp-chiprise var(--dur) linear forwards;will-change:transform}
.jp-chip b{display:block;font:500 2cqw 'Be Vietnam Pro',system-ui,sans-serif;font-weight:500;
  letter-spacing:.16em;text-transform:uppercase;color:#f3dea3;text-shadow:0 1px 4px rgba(0,0,0,.9)}
/* text-wrap:auto, KHÔNG pretty: pretty né dòng cuối cụt bằng cách rút ngắn mấy dòng trên,
   ba dòng cùng lửng nửa thẻ, nửa phải trống trơn (cùng bệnh lời chúc chính, xem PR #10).
   Căn đều hai bên: mọi dòng trừ dòng cuối chạm đúng hai mép chữ, như lời chúc chính.
   Bề ngang <p> do omChu() trong app-stage.js đặt lại theo dòng dài nhất; max-width:100%
   để lỡ khung co lại (xoay máy) thì chữ gói vào trong thẻ chứ không tràn ra. */
.jp-chip p{margin:1cqw 0 0;max-width:100%;font:400 3.2cqw/1.38 'Cormorant Garamond',serif;color:#f6f2ea;text-wrap:auto;text-align:justify;
  text-shadow:0 1px 5px rgba(0,0,0,.92)}
/* SONG NGỮ như thẻ phụ LED (.vom-card p.en trong led.html): bản Anh chữ không chân xám nhạt dưới
   lời chúc gốc, ≤ 3 dòng — thẻ chỉ rộng 30cqw, câu 200 chữ dịch ra đủ dài để thẻ cao gấp đôi.
   Muốn đọc trọn thì chạm thẻ: bản Anh lên thẻ chính (app-bling.js). Căn trái: chữ Anh nhỏ
   căn đều trong khung hẹp thì hở lỗ giữa chữ. Cỡ = .75 cỡ lời chúc gốc như LED (2cqh / 1,5cqh);
   thẻ bay app khách phóng to cả hai ở app-live.css (.jp-sub). */
.jp-chip p.en{margin-top:.9cqw;font:400 2.4cqw/1.35 'Be Vietnam Pro',system-ui,sans-serif;letter-spacing:.01em;
  color:rgba(236,229,218,.74);text-align:start;
  display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3;line-clamp:3;overflow:hidden}
/* Dải trôi nằm ở GÓC TRÊN BÊN PHẢI. Đo mặt nạ người trên khung 9:16 của ảnh này:
   dưới y 25% cô dâu và chú rể chiếm x 0-71%, chỉ còn khe hẹp 72-82% - không đủ chỗ
   cho một thẻ chữ. Trên y 38% thì bên phải trống hẳn, mà cột triện lại bắt đầu từ
   y 39% nên cũng không đụng. Vậy thẻ chỉ sống trong dải y 0-38%. */
/* Thẻ cao ~28cqh. Trồi lên từ mặt nước: lúc sinh mép dưới ở y 64% — khúc dưới
   còn nằm sau đuôi váy nên bị mặt nạ cắt, thẻ lộ dần ra như nổi từ bồn nước lên.
   Kết ở mép dưới y 28% (mép trên vừa chạm 0) nên không bị cắt đỉnh trước khi tan. */
@keyframes jp-chiprise{
  /* 0-7%: nhịp hiện ra — mờ, nhoè và nhỏ, nở dần cho ra vẻ nổi từ dưới nước lên.
     7-68%: trôi thẳng, rõ nét.
     68-100%: tan dần. Mốc tan vốn gióng theo dòng JOLIPOLI - FANOS (y 12-18%):
     bắt đầu nhạt khi mép dưới thẻ ở y 30%, mất hẳn khi mép dưới tới y 14%,
     tức là thẻ tan ĐÚNG lúc ngang chữ. Quãng tan chiếm 1/3 vòng đời (~6-7s)
     nên không còn hẫng như bản trước (chỉ 12%). App khách đã bỏ nhãn đó
     (brand:false) — mốc giữ nguyên, chỉ mất chỗ gióng. */
  0%{transform:translateY(16cqh) scale(.72);opacity:0;filter:blur(7px)}
  3%{opacity:.75}
  7%{transform:translateY(13cqh) scale(1.03);opacity:1;filter:blur(0)}
  11%{transform:translateY(12cqh) scale(1)}
  68%{opacity:1;filter:blur(0)}
  100%{transform:translateY(-34cqh) scale(.9);opacity:0;filter:blur(3px)}}

/* Bấm thẻ: loé một nhịp rồi tan. Chỉ đụng opacity/filter — transform do JS ghim
   inline trước khi đổi animation, nếu đụng vào đây thẻ sẽ nhảy về chỗ gốc. */
.jp-chip.fired{pointer-events:none;animation:jp-chipfire .5s cubic-bezier(.3,0,.4,1) forwards}
@keyframes jp-chipfire{
  0%{opacity:1;filter:brightness(1)}
  22%{opacity:1;filter:brightness(2.1) drop-shadow(0 0 2.2cqw rgba(243,222,163,.95))}
  100%{opacity:0;filter:brightness(1.3) blur(4px)}}

/* ---- kịch bản hiệu ứng: loé sáng khi gọi · nền tối đi cho đom đóm nổi · nhãn tên hiệu ứng ---- */
.jp-flash{position:absolute;inset:0;z-index:5;pointer-events:none;opacity:0}
.jp-flash.on{animation:jp-flash var(--fl,.6s) ease-out forwards}
/* --fo = độ mạnh nhịp loé chỉnh tay (fxTune.<triện>.flash); không đặt thì 1 như cũ */
@keyframes jp-flash{0%{opacity:0}10%{opacity:var(--fo,1)}100%{opacity:0}}

.jp-dim{position:absolute;inset:0;z-index:5;pointer-events:none;background:#06050a;opacity:0}
.jp-dim.on{animation:jp-dim var(--dm,2.4s) ease-in-out forwards}
@keyframes jp-dim{0%{opacity:0}4%{opacity:.76}12%{opacity:.72}70%{opacity:.7}100%{opacity:0}}

.jp-fxlabel{position:absolute;left:0;right:0;top:44%;z-index:9;pointer-events:none;text-align:center;
  font:500 11px 'Be Vietnam Pro',system-ui,sans-serif;letter-spacing:.42em;text-transform:uppercase;
  color:#f3dea3;text-shadow:0 2px 14px rgba(0,0,0,.95),0 0 26px rgba(243,222,163,.5);opacity:0}
.jp-fxlabel.on{animation:jp-fxlabel 1.6s ease-out forwards}
@keyframes jp-fxlabel{
  0%{opacity:0;letter-spacing:.9em;transform:translateY(6px)}
  18%{opacity:1;letter-spacing:.42em;transform:none}
  62%{opacity:1}
  100%{opacity:0;transform:translateY(-10px)}}

/* quả tim của nhịp mở màn: sáng hơn hẳn để thấy ngay là vừa gọi hiệu ứng */
.jp-heart.hot svg{filter:drop-shadow(0 0 3.2cqw var(--c)) drop-shadow(0 0 1.4cqw #fff)
  drop-shadow(0 .4cqw 1cqw rgba(0,0,0,.35))}
/* Quầng + bóng tối CHỈNH TAY của tim mưa (fxTune.hearts.glow -> --hg, .shadow -> --hs), chỉ quả nào
   có class .tg — không chỉnh thì không có class, giữ đúng hai dòng trên. --hg nhân bán kính quầng và
   thêm một lớp quầng sát mép cho tim nổi trên nền vòm sáng; --hs là độ đậm bóng tối lót dưới.
   Filter TĨNH, không animate (xem test «không animate filter»). Lite vẫn tắt được (.jp-stage.lite …). */
.jp-heart.tg svg{filter:drop-shadow(0 0 calc(.45cqw*var(--hg,1)) var(--c)) drop-shadow(0 0 calc(1.4cqw*var(--hg,1)) var(--c))
  drop-shadow(0 .4cqw 1cqw rgba(0,0,0,var(--hs,.35)))}
.jp-heart.tg.hot svg{filter:drop-shadow(0 0 calc(3.2cqw*var(--hg,1)) var(--c)) drop-shadow(0 0 calc(1.4cqw*var(--hg,1)) #fff)
  drop-shadow(0 .4cqw 1cqw rgba(0,0,0,var(--hs,.35)))}

/* ---- đom đóm của app: đốm to, quầng sáng ấm, trôi chậm, nhấp nháy lệch nhịp ----
   (hạt đom đóm của engine đã kịch trần amount=1 nên phải bù thêm lớp này cho nổi) */
.jp-fly{position:absolute;left:var(--x);top:var(--y);width:var(--sz);height:var(--sz);
  pointer-events:none;will-change:transform,opacity;
  animation:jp-flydrift var(--dur) cubic-bezier(.4,0,.5,1) var(--dl) both}
.jp-fly span{display:block;width:100%;height:100%;border-radius:50%;
  background:radial-gradient(circle,var(--f0,#fffbe8) 0%,var(--f1,#ffe9a8) 34%,var(--f2,rgba(255,190,90,.55)) 60%,rgba(255,170,60,0) 76%);
  box-shadow:0 0 calc(2.6cqw*var(--fg,1)) calc(.5cqw*var(--fg,1)) var(--f3,rgba(255,198,110,.85)),
    0 0 calc(6cqw*var(--fg,1)) calc(1.4cqw*var(--fg,1)) var(--f4,rgba(255,170,70,.38));
  animation:jp-flytw var(--tw) ease-in-out infinite alternate}
/* --f0..--f4 (màu) và --fg (hệ số quầng) = fxTune.fireflies.colors/glow; không đặt thì y như cũ.
   .op = fxTune.fireflies.soft: nhấp nháy bằng opacity, chạy trên compositor. jp-flytw animate
   `filter` nên mỗi đốm vẽ lại mỗi khung — tăng số đốm lên hàng trăm là GPU máy LED gánh đủ. */
.jp-fly.op span{animation-name:jp-flytwo}
@keyframes jp-flytwo{from{transform:scale(.72);opacity:.4}to{transform:scale(1.1);opacity:1}}
@keyframes jp-flydrift{
  0%{transform:translate(0,0) scale(.1);opacity:0}
  4%{transform:translate(calc(var(--dx)*.05),calc(var(--dy)*.05)) scale(1.18);opacity:1}
  9%{transform:translate(calc(var(--dx)*.09),calc(var(--dy)*.09)) scale(1)}
  84%{opacity:1}
  100%{transform:translate(var(--dx),var(--dy)) scale(.78);opacity:0}}
@keyframes jp-flytw{from{transform:scale(.72);filter:brightness(.55)}to{transform:scale(1.1);filter:brightness(1.5)}}

/* ---- BẢN LIVE (spec v2.1 mục 6.3, 7.5, 7.3, 5): tim to từ xa, thẻ tên, hàng tên bão, chế độ nhẹ ---- */
.jp-heart.big.remote .who{font-size:clamp(11px,2.6cqw,15px);letter-spacing:.14em}
.jp-namechip{position:absolute;left:var(--x);bottom:30%;transform:translateX(-50%);white-space:nowrap;pointer-events:none;
  padding:1cqw 2.2cqw;border-radius:999px;background:rgba(9,7,12,.62);border:1px solid rgba(243,222,163,.45);
  font:500 clamp(10px,2.6cqw,14px) 'Be Vietnam Pro',system-ui,sans-serif;letter-spacing:.14em;text-transform:uppercase;color:#f3dea3;
  animation:jp-namechip 3.2s ease-out forwards}
@keyframes jp-namechip{0%{opacity:0;transform:translate(-50%,3cqh)}12%{opacity:1;transform:translate(-50%,0)}80%{opacity:1}100%{opacity:0;transform:translate(-50%,-22cqh)}}
.jp-ticker{position:absolute;left:0;right:0;bottom:calc(var(--s4-btn,78px) + var(--s4-pad,14px) + 14px + env(safe-area-inset-bottom));z-index:9;
  display:flex;justify-content:flex-end;gap:2.4cqw;padding:0 3cqw;overflow:hidden;white-space:nowrap;pointer-events:none;
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 14%);mask-image:linear-gradient(90deg,transparent,#000 14%)}
.jp-ticker span{flex:none;font:500 clamp(10px,2.6cqw,14px) 'Be Vietnam Pro',system-ui,sans-serif;letter-spacing:.14em;text-transform:uppercase;color:#f3dea3;
  text-shadow:0 1px 4px rgba(0,0,0,.95);animation:jp-tkin .35s ease-out both}
@keyframes jp-tkin{from{opacity:0;transform:translateX(3cqw)}to{opacity:1;transform:none}}
.jp-stage.lite .jp-heart svg{filter:none}
.jp-stage.lite .jp-heart.big svg{filter:drop-shadow(0 .4cqw 1cqw rgba(0,0,0,.45))}
/* Trước 24/09 dòng dưới KHÔNG ăn với quả to: quầng của nó là animation trên `filter`, mà
   animation luôn thắng khai báo thường. Giờ quầng quả to là filter tĩnh + bản sao .gl nên
   bản nhẹ tắt được thật. */
.jp-stage.lite .jp-bh svg,.jp-stage.lite .jp-hring svg{filter:none}
.jp-stage.lite .jp-bh .echo,.jp-stage.lite .jp-bh .gl,.jp-stage.lite .jp-sh .gl{display:none}
