/* ==========================================================================
   Evixos — Hiệu ứng chuyển động
   --------------------------------------------------------------------------
   Ngôn ngữ chuyển động "Ánh sáng ngọc": ánh sáng ngọc bích lan trên nền trắng,
   phản hồi theo con trỏ, theo thao tác và theo trạng thái của AI.

   NGUYÊN TẮC 60FPS
   Trình duyệt dựng mỗi khung hình qua: style → layout → paint → composite.
   Chỉ `transform` và `opacity` (cùng các thuộc tính riêng `translate`,
   `scale`, `rotate`) được xử lý ở bước composite trên GPU. Mọi chuyển động
   liên tục trong file này chỉ dùng nhóm thuộc tính đó. Một số hiệu ứng vẽ lại
   (gradient xoay bằng biến `--angle`) chỉ áp dụng trên phần tử nhỏ hoặc chỉ
   chạy khi cần (ví dụ viền sáng ô soạn thảo chỉ quay khi đang gõ).

   QUY ƯỚC FILL-MODE
   Hiệu ứng xuất hiện dùng `backwards` chứ không dùng `forwards`: phần tử hiển
   thị bình thường theo mặc định, animation chỉ đặt trạng thái ẩn *trước khi*
   chạy. Nhờ vậy (1) nếu animation bị tắt, nội dung vẫn hiện đủ; (2) animation
   kết thúc không "khoá" transform — hiệu ứng nghiêng 3D bằng JavaScript vẫn
   hoạt động sau đó.

   Mục lục
     1  Biến CSS có kiểu (@property)       12  Tải lên: vòng tiến trình, tia sáng
     2  Nền aurora & quầng sáng con trỏ   13  AI đang suy nghĩ
     3  Màn hình khởi động                 14  Câu trả lời xuất hiện
     4  Logo                               15  Chỉ báo căn cứ dữ liệu
     5  Chuyển cảnh đăng nhập → ứng dụng   16  Hộp thoại & gói cước
     6  Thẻ đăng nhập nghiêng 3D           17  Thanh toán VietQR
     7  Tiêu đề chữ lộ dần                 18  Thông báo & thanh đo
     8  Đèn soi theo con trỏ               20  Trang giới thiệu & chuyển cảnh
     9  Gợn sóng khi bấm & ánh quét nút    21  Ảnh đính kèm câu hỏi
    10  Ô soạn câu hỏi                     22  Chatbot hỗ trợ & thanh toán VietQR
    11  Thả tài liệu toàn màn hình         25  Hiệu ứng trên nền kính
                                           19  Tôn trọng lựa chọn người dùng
                                              (đặt cuối file để luôn thắng)
   ========================================================================== */

/* ==========================================================================
   1. Biến CSS có kiểu
   --------------------------------------------------------------------------
   Đăng ký `--angle` là một góc thật để trình duyệt nội suy được nó — nhờ đó
   gradient hình nón xoay mượt. Không có @property, biến chỉ nhảy cóc từ giá
   trị đầu sang giá trị cuối.
   ========================================================================== */

@property --angle {
  syntax: "<angle>";
  initial-value: 0deg;
  inherits: false;
}

@keyframes spin-angle { to { --angle: 360deg; } }
@keyframes fade-in { from { opacity: 0; } }
@keyframes fade-out { to { opacity: 0; } }

@keyframes rise-in {
  from { opacity: 0; transform: translate3d(0, 18px, 0); }
}

@keyframes slide-in-left {
  from { opacity: 0; transform: translate3d(-14px, 0, 0); }
}

/* Các viền gradient dạng vòng bên dưới đều dùng cùng một kỹ thuật: phủ gradient
   lên toàn bộ phần tử giả, rồi dùng mask "exclude" khoét rỗng vùng nội dung —
   chỉ còn lại dải viền mảnh bằng đúng độ dày `padding`. */

/* ==========================================================================
   2. Nền
   --------------------------------------------------------------------------
   Nền đứng yên; màu của nó (rất nhạt, lấy từ logo) đặt ở mục "Kính" cuối
   styles.css. Bản rất cũ có quầng màu trôi, dải sáng xoay và lưới chấm — đẹp
   trong ảnh chụp nhưng tranh sự chú ý với số liệu, nên các phần tử đó vẫn ẩn;
   chúng còn trong HTML để JavaScript cũ không lỗi.
   ========================================================================== */

.aurora__blob,
.aurora__sheen,
.aurora__grid,
.cursor-aura { display: none; }

/* ==========================================================================
   3. Màn hình khởi động
   ========================================================================== */

.boot__mark { animation: boot-breathe 1.6s ease-in-out infinite; }

@keyframes boot-breathe {
  0%, 100% { transform: scale(1); box-shadow: 0 6px 22px rgba(15, 155, 108, 0.3); }
  50% { transform: scale(1.08); box-shadow: 0 10px 40px rgba(15, 155, 108, 0.5); }
}

.boot.is-leaving { animation: boot-out 0.55s var(--ease-out) forwards; }

@keyframes boot-out {
  to { opacity: 0; transform: scale(1.06); visibility: hidden; }
}

/* Lưới an toàn: JavaScript lỗi thì màn khởi động vẫn tự biến mất, không che trang mãi. */
.boot:not(.is-leaving) { animation: boot-failsafe 0.4s ease-out 9s forwards; }
@keyframes boot-failsafe { to { opacity: 0; visibility: hidden; pointer-events: none; } }

/* --- Màn mở đầu đầy đủ (lần đầu mở trang trong tab) -----------------------------
   0,05s quầng ngọc nở · 0,1s logo nảy lên, vạch ngọc dưới logo kẻ ra · 0,45s tên hiện
   từng chữ (mờ nhoè → rõ) · 1,0s dòng giới thiệu trượt lên · 1,55s "có nguồn" được gạch
   chân · 2,4s cả màn nhấc lên, nền tan, trang hiện dần phía sau (waitForIntro). */

html[data-intro="full"] .boot__glow { animation: intro-glow 1.6s var(--ease-out) 0.05s backwards; }
html[data-intro="full"] .boot__mark {
  animation:
    intro-mark 0.8s var(--ease-spring) 0.1s backwards,
    intro-bar 0.55s var(--ease-out) 0.6s backwards;
}
html[data-intro="full"] .boot__word > span { animation: intro-letter 0.65s var(--ease-out) calc(0.45s + var(--i, 0) * 32ms) backwards; }
html[data-intro="full"] .boot__tagline { animation: fade-slide-in 0.75s var(--ease-out) 1s backwards; }
html[data-intro="full"] .boot__proof {
  animation:
    intro-proof-line 0.6s var(--ease-in-out) 1.55s backwards,
    intro-proof-ink 0.5s ease-out 1.55s backwards;
}
html[data-intro="full"] .boot__skip { animation: fade-in 0.8s ease-out 0.9s backwards; }

@keyframes intro-glow { from { opacity: 0; transform: scale(0.55); } }
@keyframes intro-mark { from { opacity: 0; transform: scale(0.55) translate3d(0, 10px, 0); } }
@keyframes intro-bar { from { box-shadow: inset 0 0 0 var(--jade); } }
@keyframes intro-letter { from { opacity: 0; filter: blur(8px); transform: translate3d(0, 0.35em, 0); } }
@keyframes intro-proof-line { from { background-size: 0 2px; } }
@keyframes intro-proof-ink { from { color: var(--text-secondary); background-color: transparent; } }

/* Rời đi: chữ nhấc lên và mờ nhoè trước, nền trắng tan sau một nhịp */
html[data-intro="full"] .boot.is-leaving { animation: intro-out 0.65s ease-out 0.15s forwards; }
html[data-intro="full"] .boot.is-leaving :is(.boot__inner, .boot__skip) { animation: intro-lift 0.5s var(--ease-in-out) forwards; }

@keyframes intro-out { to { opacity: 0; visibility: hidden; } }
@keyframes intro-lift { to { opacity: 0; filter: blur(6px); transform: translate3d(0, -16px, 0); } }

/* ==========================================================================
   4. Logo — khối mực đặc, không hiệu ứng
   ========================================================================== */

.brand-mark::before,
.brand-mark::after { content: none; }

/* ==========================================================================
   5. Chuyển cảnh đăng nhập → ứng dụng
   ========================================================================== */

.auth-screen.is-leaving .auth-main { animation: auth-leave 0.5s var(--ease-in-out) forwards; }
.auth-screen.is-leaving .auth-visual { animation: auth-leave-right 0.5s var(--ease-in-out) forwards; }

@keyframes auth-leave {
  to { opacity: 0; transform: scale(0.96) translate3d(0, -14px, 0); filter: blur(10px); }
}

@keyframes auth-leave-right {
  to { opacity: 0; transform: translate3d(40px, 0, 0); filter: blur(10px); }
}

@media (min-width: 861px) {
  .app-shell.is-entering .rail { animation: rail-in 0.75s var(--ease-out) backwards; }
}

.app-shell.is-entering .topbar { animation: topbar-in 0.6s var(--ease-out) 0.1s backwards; }
.app-shell.is-entering .composer { animation: rise-in 0.8s var(--ease-spring) 0.3s backwards; }

@keyframes rail-in { from { opacity: 0; transform: translate3d(-32px, 0, 0); } }
@keyframes topbar-in { from { opacity: 0; transform: translate3d(0, -16px, 0); } }

/* ==========================================================================
   6. Thẻ câu trả lời mẫu ở trang đăng nhập nghiêng 3D theo con trỏ
   ========================================================================== */

[data-tilt] {
  transition: transform 0.7s var(--ease-out);
  will-change: transform;
}

[data-tilt].is-tilting { transition: transform 0.12s linear; }

/* Vệt phản chiếu ánh sáng di chuyển trên bề mặt kính khi nghiêng */
.tilt-glare {
  position: absolute;
  inset: 0;
  z-index: 5;
  border-radius: inherit;
  pointer-events: none;
  background: radial-gradient(circle at var(--gx, 50%) var(--gy, 0%), rgba(255, 255, 255, 0.85), rgba(255, 255, 255, 0) 48%);
  mix-blend-mode: soft-light;
  opacity: 0;
  transition: opacity 0.5s var(--ease-out);
}

[data-tilt].is-tilting .tilt-glare { opacity: 1; }

@keyframes dot-ping {
  0% { box-shadow: 0 0 0 0 rgba(15, 155, 108, 0.45); }
  70%, 100% { box-shadow: 0 0 0 9px rgba(15, 155, 108, 0); }
}

/* ==========================================================================
   7. Tiêu đề chữ lộ dần từng từ
   --------------------------------------------------------------------------
   JavaScript tách tiêu đề thành từng `.word` để mỗi từ hiện ra lần lượt.
   Chữ giữ một màu mực: bản trước tô gradient theo bề ngang khiến riêng cụm
   chữ cuối dòng bị xanh, trông như cố tình nhấn một từ. Ánh ngọc giờ đi qua
   bằng tia sáng lướt (`::after`) bên dưới.
   ========================================================================== */

.headline .word {
  display: inline-block;
  padding: 0.08em 0.02em 0.14em;
  margin: -0.08em -0.02em -0.14em;       /* bù padding: chừa chỗ cho dấu tiếng Việt mà không đổi layout */
  color: var(--ink);
}

.headline.is-revealed .word {
  animation: word-in 1s var(--ease-out) calc(var(--i, 0) * 75ms + 150ms) backwards;
}

@keyframes word-in {
  from { opacity: 0; transform: translate3d(0, 0.55em, 0) rotate(3deg); filter: blur(12px); }
}

/* Tia sáng lướt qua chữ định kỳ. Trên nền trắng, dải trắng vô hình ở vùng
   trống và chỉ hiện ra khi đi qua chữ — trông như ánh sáng loé trên mặt kim loại. */
.headline::after {
  content: "";
  position: absolute;
  top: -12%;
  bottom: -12%;
  left: 0;
  width: 26%;
  background: linear-gradient(100deg, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.92), rgba(255, 255, 255, 0));
  /* Vô hình khi đứng yên: trên nền có quầng ngọc, dải trắng nằm chờ ngoài mép chữ sẽ lộ thành vệt sáng. */
  opacity: 0;
  transform: translate3d(-140%, 0, 0) skewX(-18deg);
  pointer-events: none;
  animation: headline-glint 7s var(--ease-in-out) 2.4s infinite;
}

@keyframes headline-glint {
  0% { opacity: 0; transform: translate3d(-140%, 0, 0) skewX(-18deg); }
  3%, 19% { opacity: 1; }
  22%, 100% { opacity: 0; transform: translate3d(470%, 0, 0) skewX(-18deg); }
}

/* Hiệu ứng xuất hiện lần lượt cho nhóm phần tử */
.stagger > * { animation: rise-in 0.7s var(--ease-out) calc(var(--i, 0) * 70ms + 250ms) backwards; }

/* ==========================================================================
   8. Đèn soi theo con trỏ
   --------------------------------------------------------------------------
   `--mx`/`--my` được JavaScript cập nhật theo vị trí chuột, gộp theo từng
   khung hình bằng requestAnimationFrame.
   ========================================================================== */

.spotlight::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  background: radial-gradient(380px circle at var(--mx, 50%) var(--my, -40%), rgba(15, 155, 108, 0.1), transparent 44%);
  opacity: 0;
  transition: opacity 0.4s var(--ease-out);
  pointer-events: none;
}

.spotlight:hover::before { opacity: 1; }

/* Viền phát sáng chỉ ở phần gần con trỏ */
.spotlight-edge::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  padding: 1px;
  border-radius: inherit;
  background: radial-gradient(240px circle at var(--mx, 50%) var(--my, -40%), rgba(15, 155, 108, 0.7), transparent 46%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
  opacity: 0;
  transition: opacity 0.4s var(--ease-out);
  pointer-events: none;
}

.spotlight-edge:hover::after { opacity: 1; }

/* ==========================================================================
   9. Gợn sóng khi bấm & ánh quét trên nút chính
   ========================================================================== */

.ripple {
  position: absolute;
  z-index: 0;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.16;
  transform: scale(0);
  pointer-events: none;
  animation: ripple 700ms var(--ease-out) forwards;
}

.btn--primary .ripple,
.composer__send .ripple { background: #ffffff; opacity: 0.4; }

@keyframes ripple { to { transform: scale(1); opacity: 0; } }

.btn--primary::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 42%;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, 0.5), transparent);
  transform: translate3d(-170%, 0, 0) skewX(-20deg);
  pointer-events: none;
}

.btn--primary:hover::after { animation: button-shine 0.95s var(--ease-out); }

@keyframes button-shine { to { transform: translate3d(340%, 0, 0) skewX(-20deg); } }

/* Nút loang màu (phỏng theo "Interactive Hover Button" của 21st.dev), chỉ dùng cho các nút
   dẫn tới đăng ký / mua gói. Bản gốc phóng to chấm bằng left/top/width/height — mỗi khung
   hình phải tính lại bố cục; ở đây chấm chỉ phóng bằng `transform: scale`, chạy trên GPU.
   Trên máy cảm ứng không có rê chuột: hiệu ứng chỉ bật khi có chuột, hoặc khi dùng phím Tab. */
.btn-sweep::after { content: none; }

.btn-sweep__dot { transition: transform 0.55s var(--ease-out), background-color 0.3s var(--ease-out); }

.btn-sweep__label,
.btn-sweep__reveal { transition: transform 0.4s var(--ease-out), opacity 0.3s var(--ease-out); }

.btn-sweep__reveal { transform: translate3d(2.6rem, 0, 0); }

.btn-sweep:not(:disabled):focus-visible .btn-sweep__dot { transform: scale(90); background-color: var(--sweep-fill); }
.btn-sweep:not(:disabled):focus-visible .btn-sweep__label { transform: translate3d(2.6rem, 0, 0); opacity: 0; }
.btn-sweep:not(:disabled):focus-visible .btn-sweep__reveal { transform: none; opacity: 1; }

@media (hover: hover) {
  .btn-sweep:not(:disabled):hover .btn-sweep__dot { transform: scale(90); background-color: var(--sweep-fill); }
  .btn-sweep:not(:disabled):hover .btn-sweep__label { transform: translate3d(2.6rem, 0, 0); opacity: 0; }
  .btn-sweep:not(:disabled):hover .btn-sweep__reveal { transform: none; opacity: 1; }
}

/* Nút "Nâng cấp" có viền gradient xoay nhẹ để thu hút mà không làm phiền */
.btn--glow { border-color: transparent; }

.btn--glow::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  padding: 1.5px;
  border-radius: inherit;
  background: conic-gradient(from var(--angle), var(--jade), var(--teal), var(--indigo), #7891ff, var(--jade));
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
  animation: spin-angle 4s linear infinite;
}

.magnetic { transition: translate 0.4s var(--ease-spring); }

/* ==========================================================================
   10. Ô soạn câu hỏi — vòng sáng xoay khi đang gõ
   ========================================================================== */

.composer::before {
  content: "";
  position: absolute;
  inset: -1.5px;
  z-index: 3;
  padding: 1.5px;
  border-radius: calc(var(--r-xl) + 1.5px);
  background: conic-gradient(
    from var(--angle),
    rgba(15, 155, 108, 0.95),
    rgba(14, 165, 165, 0.45),
    rgba(45, 78, 204, 0.9),
    rgba(52, 211, 156, 0.55),
    rgba(15, 155, 108, 0.95)
  );
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.45s var(--ease-out);
  /* Chỉ quay khi ô đang được focus — không tốn khung hình khi không dùng. */
  animation: spin-angle 4s linear infinite paused;
}

.composer:focus-within::before { opacity: 1; animation-play-state: running; }

/* Nút gửi "bật" lên khi có nội dung */
.composer__send:not(:disabled) { animation: send-ready 0.55s var(--ease-spring); }

@keyframes send-ready {
  0% { scale: 0.7; }
  60% { scale: 1.12; }
  100% { scale: 1; }
}

.composer__send.is-busy svg { opacity: 0; }

/* Đang trả lời: nút gửi thành nút Dừng — ô vuông trắng, nhịp đập nhẹ để thấy máy còn đang làm việc. */
.composer__send.is-busy::after {
  content: "";
  position: absolute;
  width: 12px;
  height: 12px;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.96);
  animation: stop-pulse 1.4s ease-in-out infinite;
}

@keyframes stop-pulse {
  0%, 100% { scale: 1; opacity: 1; }
  50% { scale: 0.82; opacity: 0.78; }
}

.composer__attach.is-bump { animation: attach-bump 0.6s var(--ease-spring); }

@keyframes attach-bump {
  30% { scale: 1.25; rotate: -12deg; }
  60% { scale: 0.95; rotate: 6deg; }
}

@keyframes spin { to { transform: rotate(360deg); } }

/* Viên chỉ báo agent trượt theo lựa chọn, nảy nhẹ khi dừng */
.agent-indicator { transition: transform 0.55s var(--ease-spring), height 0.3s var(--ease-out), opacity 0.3s var(--ease-out); }
.agent-indicator.no-transition { transition: none; }

.segmented__thumb { transition: transform 0.5s var(--ease-spring), width 0.35s var(--ease-out); }

/* ==========================================================================
   11. Thả tài liệu toàn màn hình
   ========================================================================== */

.drop-overlay { animation: fade-in 0.22s ease-out backwards; }
.drop-overlay.is-leaving { animation: fade-out 0.2s ease-in forwards; }

.drop-panel { animation: drop-panel-in 0.6s var(--ease-spring) backwards; }

@keyframes drop-panel-in {
  from { opacity: 0; transform: translate3d(0, 24px, 0) scale(0.9); }
}

/* Vòng sáng chạy quanh viền bảng */
.drop-panel::before {
  content: "";
  position: absolute;
  inset: 0;
  padding: 2.5px;
  border-radius: inherit;
  background: conic-gradient(
    from var(--angle),
    transparent 0% 20%,
    var(--jade) 36%,
    var(--teal) 48%,
    var(--indigo) 60%,
    transparent 76% 100%
  );
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
  animation: spin-angle 2.2s linear infinite;
}

.drop-panel::after {
  content: "";
  position: absolute;
  inset: 16px;
  border: 2px dashed rgba(15, 155, 108, 0.28);
  border-radius: 22px;
  pointer-events: none;
}

/* Sóng radar lan ra từ biểu tượng */
.drop-ring {
  position: absolute;
  inset: 0;
  border: 2px solid rgba(15, 155, 108, 0.45);
  border-radius: 50%;
  animation: radar 2.4s var(--ease-out) infinite;
}

.drop-ring:nth-child(2) { animation-delay: 0.8s; }
.drop-ring:nth-child(3) { animation-delay: 1.6s; }

@keyframes radar {
  from { transform: scale(0.55); opacity: 0.9; }
  to { transform: scale(1.75); opacity: 0; }
}

.drop-core { animation: core-bob 1.8s ease-in-out infinite; }
.drop-core svg { animation: arrow-dive 1.8s var(--ease-in-out) infinite; }

@keyframes core-bob {
  0%, 100% { transform: translate3d(0, 0, 0) rotate(0deg); }
  50% { transform: translate3d(0, -9px, 0) rotate(-3deg); }
}

@keyframes arrow-dive {
  0%, 100% { transform: translate3d(0, -3px, 0); }
  50% { transform: translate3d(0, 5px, 0); }
}

/* Biểu tượng tệp quay quanh — vòng ngoài quay, từng biểu tượng quay ngược để luôn đứng thẳng */
.drop-orbit {
  position: absolute;
  inset: -20px;
  animation: orbit-spin 10s linear infinite;
}

.drop-orbit span {
  position: absolute;
  width: 36px;
  height: 36px;
  display: grid;
  place-items: center;
  border-radius: 11px;
  background: #ffffff;
  box-shadow: var(--shadow-raised), 0 0 0 1px rgba(15, 155, 108, 0.12);
  font-size: 1rem;
  animation: orbit-counter 10s linear infinite;
}

.drop-orbit span:nth-child(1) { top: -6px; left: calc(50% - 18px); }
.drop-orbit span:nth-child(2) { bottom: 8%; left: -4px; }
.drop-orbit span:nth-child(3) { bottom: 8%; right: -4px; }

@keyframes orbit-spin { to { transform: rotate(360deg); } }
@keyframes orbit-counter { to { transform: rotate(-360deg); } }

.drop-formats .chip { animation: rise-in 0.5s var(--ease-out) calc(var(--i, 0) * 50ms + 200ms) backwards; }

/* Vùng tải trong thanh bên sáng lên khi đang kéo tệp trên cửa sổ */
.dropzone::before {
  content: "";
  position: absolute;
  inset: -2px;
  z-index: -1;
  padding: 2px;
  border-radius: calc(var(--r-lg) + 2px);
  background: conic-gradient(from var(--angle), transparent 0% 30%, var(--jade) 50%, var(--indigo) 65%, transparent 80% 100%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
  opacity: 0;
  transition: opacity 0.3s var(--ease-out);
  animation: spin-angle 2.4s linear infinite paused;
}

.dropzone[data-active="true"]::before,
.dropzone.is-uploading::before { opacity: 1; animation-play-state: running; }

.dropzone[data-active="true"] .dropzone__icon { animation: core-bob 1.2s ease-in-out infinite; }

/* ==========================================================================
   12. Tải lên
   ========================================================================== */

.doc-item--enter { animation: slide-in-left 0.5s var(--ease-out) calc(var(--i, 0) * 40ms) backwards; }

.doc-item--uploading { animation: slide-in-left 0.45s var(--ease-spring) backwards; }

.doc-item--uploading.is-processing .progress-ring svg { animation: spin 1.1s linear infinite; }
.doc-item--uploading.is-processing .progress-ring__bar { stroke-dashoffset: 58 !important; }

.doc-item--uploading.is-done .progress-ring { animation: done-pop 0.6s var(--ease-spring); }

@keyframes done-pop {
  40% { scale: 1.35; }
  100% { scale: 1; }
}

.doc-item--uploading.is-leaving { animation: upload-leave 0.45s var(--ease-in-out) forwards; }

@keyframes upload-leave {
  to { opacity: 0; transform: translate3d(24px, 0, 0) scale(0.96); }
}

.doc-item--uploading.is-failed { animation: shake 0.45s ease-in-out; }

.doc-item--new { animation: doc-new 2s var(--ease-out); }

@keyframes doc-new {
  0% { background-color: var(--jade-wash); box-shadow: 0 0 0 0 rgba(15, 155, 108, 0.4); }
  35% { box-shadow: 0 0 0 7px rgba(15, 155, 108, 0); }
  100% { background-color: transparent; box-shadow: 0 0 0 0 rgba(15, 155, 108, 0); }
}

@keyframes shake {
  0%, 100% { transform: translate3d(0, 0, 0); }
  20%, 60% { transform: translate3d(-6px, 0, 0); }
  40%, 80% { transform: translate3d(6px, 0, 0); }
}

.anim-shake { animation: shake 0.42s ease-in-out; }

/* Tia sáng toả ra (phần tử do JavaScript tạo và điều khiển bằng Web Animations API) */
.sparkle {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 460;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  pointer-events: none;
  will-change: transform, opacity;
}

.confetti-canvas {
  position: fixed;
  inset: 0;
  z-index: 600;
  width: 100vw;
  height: 100vh;
  pointer-events: none;
}

/* ==========================================================================
   13. AI đang suy nghĩ — quả cầu gradient biến hình
   ========================================================================== */

.thinking-card { animation: rise-in 0.5s var(--ease-out) backwards; }

.ai-orb { position: relative; width: 32px; height: 32px; flex-shrink: 0; isolation: isolate; }

.ai-orb::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 42% 58% 60% 40% / 45% 45% 55% 55%;
  background: conic-gradient(from var(--angle), var(--jade), var(--teal), var(--indigo), #7891ff, var(--jade-bright), var(--jade));
  animation: spin-angle 2.4s linear infinite, orb-morph 3.2s ease-in-out infinite;
}

.ai-orb::after {
  content: "";
  position: absolute;
  inset: -9px;
  z-index: -1;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(15, 155, 108, 0.38), transparent 68%);
  animation: orb-glow 1.8s ease-in-out infinite;
}

.ai-orb--done::before { animation-play-state: paused; }

@keyframes orb-morph {
  0%, 100% { border-radius: 42% 58% 60% 40% / 45% 45% 55% 55%; transform: rotate(0deg) scale(1); }
  33% { border-radius: 58% 42% 38% 62% / 55% 40% 60% 45%; transform: rotate(120deg) scale(1.08); }
  66% { border-radius: 48% 52% 64% 36% / 38% 62% 38% 62%; transform: rotate(240deg) scale(0.94); }
}

@keyframes orb-glow {
  0%, 100% { transform: scale(0.85); opacity: 0.6; }
  50% { transform: scale(1.15); opacity: 1; }
}

/* Khung xương chờ tải, ánh quét bằng transform */
.skeleton {
  position: relative;
  overflow: hidden;
  height: 12px;
  margin-bottom: 10px;
  border-radius: var(--r-sm);
  background: var(--paper-sunken);
}

.skeleton:last-child { margin-bottom: 0; }

.skeleton::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.85), transparent);
  transform: translate3d(-100%, 0, 0);
  animation: shimmer 1.4s var(--ease-in-out) infinite;
}

.skeleton--block { height: 88px; margin: 0; }

@keyframes shimmer { to { transform: translate3d(100%, 0, 0); } }

.spinner {
  width: 15px;
  height: 15px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
  flex-shrink: 0;
}

/* ==========================================================================
   14. Câu trả lời xuất hiện
   ========================================================================== */

.bubble-user { transform-origin: 100% 100%; animation: bubble-pop 0.55s var(--ease-spring) backwards; }

@keyframes bubble-pop {
  from { opacity: 0; transform: translate3d(0, 14px, 0) scale(0.88); }
}

.answer.is-entering { animation: card-in 0.6s var(--ease-out) backwards; }

@keyframes card-in {
  from { opacity: 0; transform: translate3d(0, 16px, 0) scale(0.985); }
}

.reveal-block { animation: block-in 0.65s var(--ease-out) calc(var(--i, 0) * 60ms + 160ms) backwards; }

@keyframes block-in {
  from { opacity: 0; transform: translate3d(0, 10px, 0); filter: blur(5px); }
}

.source { animation: slide-in-left 0.55s var(--ease-out) calc(var(--i, 0) * 90ms + 500ms) backwards; }

.answer__body .cite-ref { animation: cite-pop 0.5s var(--ease-spring) 0.6s backwards; }

@keyframes cite-pop { from { opacity: 0; transform: scale(0.3); } }

.source.is-highlighted { animation: source-flash 1.4s var(--ease-out); }

@keyframes source-flash {
  0% { background-color: var(--indigo-wash); box-shadow: inset 3px 0 0 var(--indigo); }
  100% { background-color: transparent; box-shadow: inset 3px 0 0 transparent; }
}

/* Câu trả lời đang hiện dần: con trỏ ở cuối nhấp nháy; dòng "Đang viết…" thở nhẹ */
.answer--streaming .answer__body > :last-child:not(ul, ol)::after,
.answer--streaming .answer__body > :is(ul, ol):last-child > li:last-child::after { animation: caret-blink 1.05s steps(1) infinite; }
.answer__writing { animation: reduced-pulse 1.6s ease-in-out infinite; }
/* Chữ đang chảy được vẽ lại liên tục: số trích dẫn đứng yên chứ không "nảy" lại ở mỗi lần vẽ. Câu trả lời
   chính thức thay vào chỗ bản xem trước cũng vậy — số trích dẫn đã nằm sẵn trên màn hình. */
.answer--streaming .answer__body .cite-ref,
.answer--settled .answer__body .cite-ref { animation: none; }

/* ==========================================================================
   15. Chỉ báo căn cứ dữ liệu
   ========================================================================== */

/* Chỉ lần đầu câu trả lời hiện ra; mở lại từ lịch sử thì đứng yên. Thanh đo chạy bằng JS. */
.answer.is-entering .trust { animation: rise-in 0.6s var(--ease-out) 0.35s backwards; }
.answer.is-entering .trust-check { animation: fade-slide-in 0.5s var(--ease-out) calc(var(--i, 0) * 70ms + 1.1s) backwards; }

/* Trang hướng dẫn: tiêu đề hiện ra một nhịp, mục lục theo sau. Phần còn lại đứng yên để đọc. */
.guide.is-entering .guide__intro { animation: fade-slide-in 0.7s var(--ease-out) 0.1s backwards; }
.guide.is-entering .guide-toc { animation: fade-in 0.6s ease-out 0.35s backwards; }

/* Bấm một ý trong danh sách "Cần kiểm chứng lại": ý đó trong câu trả lời sáng lên một nhịp. */
.answer__body .claim-flag.is-flash { animation: claim-flash 1.5s var(--ease-out); }

@keyframes claim-flash {
  0%, 30% { outline: 2px solid var(--amber); outline-offset: 3px; }
  100% { outline: 2px solid transparent; outline-offset: 3px; }
}

/* ==========================================================================
   16. Hộp thoại & gói cước
   ========================================================================== */

.overlay { animation: fade-in 0.25s ease-out backwards; }
.overlay.is-closing { animation: fade-out 0.22s ease-in forwards; }

.modal { animation: modal-in 0.55s var(--ease-spring) backwards; }
.overlay.is-closing .modal { animation: modal-out 0.22s ease-in forwards; }

@keyframes modal-in {
  from { opacity: 0; transform: translate3d(0, 28px, 0) scale(0.93); }
}

@keyframes modal-out {
  to { opacity: 0; transform: translate3d(0, 12px, 0) scale(0.96); }
}

.stagger-item { animation: rise-in 0.65s var(--ease-out) calc(var(--i, 0) * 80ms + 140ms) backwards; }

/* Gói nổi bật: viền gradient xoay liên tục */
.plan--featured::after {
  content: "";
  position: absolute;
  inset: -1.5px;
  z-index: 1;
  padding: 1.5px;
  border-radius: calc(var(--r-lg) + 1.5px);
  background: conic-gradient(from var(--angle), var(--jade), var(--teal), var(--indigo), #7891ff, var(--jade-bright), var(--jade));
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
  animation: spin-angle 6s linear infinite;
  pointer-events: none;
}

.plan__amount.is-counting { color: var(--jade-dim); transition: color 0.4s; }

.plan__save:not(:empty) { animation: rise-in 0.45s var(--ease-spring) backwards; }

/* ==========================================================================
   17. Thanh toán VietQR
   ========================================================================== */

.qr-frame { animation: qr-in 0.7s var(--ease-spring) backwards; }

@keyframes qr-in {
  from { opacity: 0; transform: scale(0.8) rotate(-4deg); }
}

.qr-corner { animation: corner-breathe 1.8s ease-in-out infinite; }
.qr-corner--tl { transform-origin: 0 0; }
.qr-corner--tr { transform-origin: 100% 0; }
.qr-corner--bl { transform-origin: 0 100%; }
.qr-corner--br { transform-origin: 100% 100%; }

@keyframes corner-breathe {
  0%, 100% { transform: scale(1); opacity: 1; }
  50% { transform: scale(0.82); opacity: 0.55; }
}

/* Vạch quét laser chạy dọc mã */
.qr-frame[data-scanning="true"]::after {
  content: "";
  position: absolute;
  left: 18px;
  right: 18px;
  top: 16px;
  height: 3px;
  border-radius: 3px;
  background: linear-gradient(90deg, transparent, var(--jade), transparent);
  box-shadow: 0 0 14px 2px rgba(15, 155, 108, 0.5);
  animation: qr-scan 2.6s var(--ease-in-out) infinite;
  will-change: transform;
}

/* Ảnh khung chuẩn VietQR: không chạy vạch quét đè lên logo và thông tin tài khoản */
.qr-frame--card[data-scanning="true"]::after { content: none; }

@keyframes qr-scan {
  0%, 100% { transform: translate3d(0, 0, 0); opacity: 0; }
  10%, 90% { opacity: 1; }
  50% { transform: translate3d(0, 228px, 0); }
}

.qr-frame.is-paid { animation: paid-pop 0.7s var(--ease-spring); }
.qr-frame.is-paid .qr-corner { animation: none; border-color: var(--jade-bright); }

@keyframes paid-pop {
  30% { transform: scale(1.06); box-shadow: 0 0 0 10px rgba(15, 155, 108, 0.18); }
  100% { transform: scale(1); }
}

.awaiting-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--amber);
  animation: dot-ping-amber 1.6s ease-out infinite;
}

@keyframes dot-ping-amber {
  0% { box-shadow: 0 0 0 0 rgba(183, 121, 31, 0.5); }
  80%, 100% { box-shadow: 0 0 0 8px rgba(183, 121, 31, 0); }
}

.success-mark { animation: success-pop 0.7s var(--ease-spring) backwards; }
.success-mark .success-mark__check { stroke-dasharray: 30; animation: check-draw 0.5s var(--ease-out) 0.3s backwards; }
.success-mark .success-mark__ring { stroke-dasharray: 64; animation: check-draw-ring 0.6s var(--ease-out) backwards; }

@keyframes success-pop { from { opacity: 0; transform: scale(0.4); } }
@keyframes check-draw { from { stroke-dashoffset: 30; } }
@keyframes check-draw-ring { from { stroke-dashoffset: 64; } }

/* ==========================================================================
   18. Thông báo nổi & thanh đo
   ========================================================================== */

.toast { animation: toast-in 0.5s var(--ease-spring) backwards; }
.toast.is-closing { animation: toast-out 0.26s ease-in forwards; }

@keyframes toast-in {
  from { opacity: 0; transform: translate3d(36px, 0, 0) scale(0.94); }
}

@keyframes toast-out {
  to { opacity: 0; transform: translate3d(36px, 0, 0) scale(0.96); }
}

.meter__fill::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.7), transparent);
  transform: translate3d(-100%, 0, 0);
  animation: shimmer 3s var(--ease-in-out) 1s infinite;
}

.chip--plan.is-upgraded { animation: plan-upgraded 1.2s var(--ease-spring); }
/* Gói vừa đổi (thanh toán xong): ô "Gói hiện tại" ở cột trái cũng nảy lên một nhịp như nhãn gói. */
.plan-card.is-upgraded { animation: plan-upgraded 1.2s var(--ease-spring); }

@keyframes plan-upgraded {
  30% { scale: 1.2; box-shadow: 0 0 0 6px rgba(15, 155, 108, 0.2); }
  100% { scale: 1; box-shadow: 0 0 0 0 rgba(15, 155, 108, 0); }
}

/* ==========================================================================
   20. Trang giới thiệu & chuyển cảnh giữa ba phần
   --------------------------------------------------------------------------
   Một khoảnh khắc được dàn dựng duy nhất khi trang mở: tiêu đề lộ dần, rồi
   tấm sứ minh hoạ nâng lên. Sau đó trang đứng yên — chỉ bản minh hoạ tiếp tục
   chạy, và nó tự dừng khi cuộn khỏi màn hình (xem `startDemo` trong index.html).
   ========================================================================== */

.landing.is-entering .site-header { animation: fade-in 0.6s ease-out backwards; }

.landing.is-entering :is(.hero__lede, .hero__actions, .hero__note) {
  animation: rise-in 0.8s var(--ease-out) backwards;
}

.landing.is-entering .hero__lede { animation-delay: 0.5s; }
.landing.is-entering .hero__actions { animation-delay: 0.62s; }
.landing.is-entering .hero__note { animation-delay: 0.74s; }

.landing.is-entering .lens-demo__slab { animation: slab-rise 1.1s var(--ease-out) 0.35s backwards; }

@keyframes slab-rise {
  from { opacity: 0; transform: translate3d(0, 44px, 0) rotateX(9deg) scale(0.96); }
}

/* Quầng ngọc sau tấm minh hoạ "thở" rất chậm — chỉ transform và opacity */
.lens-demo__aura { animation: aura-breathe 9s ease-in-out infinite alternate; will-change: transform, opacity; }

@keyframes aura-breathe {
  from { transform: scale(0.94); opacity: 0.72; }
  to { transform: scale(1.06); opacity: 1; }
}

.ask__caret { animation: caret-blink 1.05s steps(1) infinite; }
.ask__caret[hidden] { display: none; }

@keyframes caret-blink { 50% { opacity: 0; } }

.sheet__row::before { transition: opacity 0.55s var(--ease-out); }
.sheet__row { transition: color 0.4s var(--ease-out); }
.sheet__cite { transition: opacity 0.3s var(--ease-out), transform 0.5s var(--ease-spring); }
.lens { will-change: transform, opacity; }

/* Tia sáng quét qua gói nổi bật một lần khi bảng giá cuộn vào màn hình */
.price-col--featured::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 45%;
  background: linear-gradient(100deg, rgba(255, 255, 255, 0), rgba(207, 233, 220, 0.9), rgba(255, 255, 255, 0));
  opacity: 0;                      /* vô hình khi đứng yên — nghiêng skew làm mép dải sáng lấn vào cột */
  transform: translate3d(-160%, 0, 0) skewX(-16deg);
  pointer-events: none;
}

.pricing.is-visible .price-col--featured::before { animation: jade-sweep 1.5s var(--ease-in-out) 0.3s; }

@keyframes jade-sweep {
  0% { opacity: 1; transform: translate3d(-160%, 0, 0) skewX(-16deg); }
  100% { opacity: 1; transform: translate3d(330%, 0, 0) skewX(-16deg); }
}

.price-table[aria-busy="true"] .skeleton { width: 70%; margin: var(--sp-3) auto; }

.site-actions .btn { transition: transform var(--dur-fast) var(--ease-out), box-shadow var(--dur-base) var(--ease-out), background var(--dur-fast); }

/* ==========================================================================
   21. Ảnh đính kèm câu hỏi
   ========================================================================== */

.attach-tray:not([hidden]) { animation: fade-in 0.25s ease-out; }

.attach-item { animation: attach-in 0.5s var(--ease-spring) backwards; }
.attach-item.is-leaving { animation: attach-out 0.22s ease-in forwards; }

@keyframes attach-in { from { opacity: 0; transform: scale(0.55) rotate(-6deg); } }
@keyframes attach-out { to { opacity: 0; transform: scale(0.6); } }

.attach-item[data-state="ready"] .attach-item__thumb { animation: thumb-clear 0.45s var(--ease-out); }
@keyframes thumb-clear { from { opacity: 0.45; } }

.attach-item[data-state="error"] { animation: shake 0.42s ease-in-out; }

.bubble-media__item { animation: bubble-pop 0.55s var(--ease-spring) backwards; transition: transform var(--dur-base) var(--ease-out); }
.bubble-media__item:hover { transform: translateY(-2px); }

.lightbox img { animation: modal-in 0.5s var(--ease-spring) backwards; }

/* ==========================================================================
   22. Chatbot hỗ trợ & thanh toán VietQR
   ========================================================================== */

/* Nút hỗ trợ xuất hiện một lần sau khi trang đã ổn định — không tranh chú ý với hiệu ứng mở đầu. */
.support-launcher { animation: launcher-in 0.7s var(--ease-spring) 1.4s backwards; }

@keyframes launcher-in {
  from { opacity: 0; transform: translate3d(0, 20px, 0) scale(0.6); }
}

.support-panel:not([hidden]) { animation: support-open 0.42s var(--ease-spring) backwards; }
.support-panel.is-closing { animation: support-close 0.2s ease-in forwards; }

@keyframes support-open {
  from { opacity: 0; transform: translate3d(0, 18px, 0) scale(0.92); }
}

@keyframes support-close {
  to { opacity: 0; transform: translate3d(0, 12px, 0) scale(0.95); }
}

.support-nudge:not([hidden]) { animation: nudge-in 0.55s var(--ease-spring) backwards; transform-origin: 100% 100%; }

@keyframes nudge-in {
  from { opacity: 0; transform: translate3d(0, 10px, 0) scale(0.85); }
}

.support-msg.is-new { animation: rise-in 0.4s var(--ease-out) backwards; }
.support-msg--user.is-new { animation: bubble-pop 0.45s var(--ease-spring) backwards; transform-origin: 100% 100%; }
.support-ticket.is-new { animation: rise-in 0.45s var(--ease-out) backwards; }
.support-actions .support-action { animation: rise-in 0.4s var(--ease-out) 0.15s backwards; }

.support-typing span { animation: typing-dot 1.1s ease-in-out infinite; }
.support-typing span:nth-child(2) { animation-delay: 0.15s; }
.support-typing span:nth-child(3) { animation-delay: 0.3s; }

@keyframes typing-dot {
  0%, 60%, 100% { transform: translate3d(0, 0, 0); opacity: 0.45; }
  30% { transform: translate3d(0, -4px, 0); opacity: 1; }
}

.support-online { animation: dot-ping 2.2s ease-out infinite; }

.btn.is-copied { animation: done-pop 0.45s var(--ease-spring); }

/* ==========================================================================
   23. Xuất hiện lần lượt, mờ nhoè → rõ nét
   --------------------------------------------------------------------------
   Phỏng theo mẫu trang đăng nhập của 21st.dev: từng khối trượt lên và rõ dần
   theo nhịp 80 ms; tấm minh hoạ bên phải trượt vào từ mép phải; các thẻ nổi
   lên sau cùng. Dùng cho ba khoảnh khắc: mở trang đăng nhập, mở trang giới
   thiệu, mở cuộc trò chuyện mới. Mỗi màn chỉ một lần — không lặp lại khi cuộn.

   Khác bản gốc: bản gốc đặt sẵn `opacity: 0` rồi animation đưa về 1 — tắt hiệu
   ứng là nội dung biến mất. Ở đây trạng thái ẩn nằm trong `from` với fill
   `backwards` (xem quy ước đầu file), nên tắt hiệu ứng thì mọi thứ vẫn hiện đủ.
   ========================================================================== */

@keyframes fade-slide-in {
  from { opacity: 0; filter: blur(6px); transform: translate3d(0, 14px, 0); }
}

@keyframes slide-right-in {
  from { opacity: 0; filter: blur(10px); transform: translate3d(56px, 0, 0); }
}

@keyframes card-lift-in {
  from { opacity: 0; filter: blur(6px); transform: translate3d(0, 20px, 0) scale(0.96); }
}

/* Trang đăng nhập / đăng ký */
.auth-screen.is-entering .enter-seq { animation: fade-slide-in 0.7s var(--ease-out) calc(var(--d, 0) * 80ms + 80ms) backwards; }
.auth-screen.is-entering .auth-back { animation: fade-in 0.6s ease-out 0.35s backwards; }
.auth-screen.is-entering .auth-visual__panel { animation: slide-right-in 1s var(--ease-out) 0.3s backwards; }
.auth-screen.is-entering .auth-visual__quote { animation: fade-slide-in 0.8s var(--ease-out) 0.7s backwards; }
.auth-screen.is-entering .reel { animation: card-lift-in 0.8s var(--ease-out) 0.9s backwards; }
.auth-screen.is-entering .auth-guide { animation: fade-in 0.6s ease-out 0.45s backwards; }
.auth-screen.is-entering .auth-proof { animation: card-lift-in 0.7s var(--ease-out) calc(1.15s + var(--i, 0) * 0.2s) backwards; }

/* Chuyển tab Đăng nhập ↔ Đăng ký: các ô của biểu mẫu mới chạy lại nhịp, nhanh hơn */
.auth-screen form.is-switching > .enter-seq { animation: fade-slide-in 0.5s var(--ease-out) calc((var(--d, 4) - 4) * 55ms) backwards; }

/* Trang giới thiệu: mô tả, nút và ghi chú dưới tiêu đề */
.landing.is-entering :is(.hero__lede, .hero__actions, .hero__note) { animation-name: fade-slide-in; }

/* Workspace: màn hình chào của mỗi cuộc trò chuyện mới */
.welcome.is-entering > :not(.headline, .radar),
.welcome.is-entering .radar__head { animation: fade-slide-in 0.7s var(--ease-out) calc(var(--d, 3) * 90ms + 60ms) backwards; }

/* Ô Radar nổi lên lần lượt mỗi khi số liệu được đếm lại (tải thêm hoặc xoá tài liệu) */
.radar-card { animation: card-lift-in 0.6s var(--ease-out) calc(var(--i, 0) * 80ms + 100ms) backwards; }
.welcome.is-entering .radar-card { animation-delay: calc(var(--i, 0) * 80ms + 380ms); }
.welcome.is-entering .welcome__guide { animation: fade-slide-in 0.7s var(--ease-out) 0.3s backwards; }

/* ==========================================================================
   24. Hiện dần khi cuộn tới & chuyển động nền rất chậm
   --------------------------------------------------------------------------
   Mỗi khối hiện ra MỘT lần khi cuộn tới (bindReveals trong index.html), và
   chuyển động nói đúng nội dung của khối: vạch của ba bước tự kẻ, dòng số liệu
   được đánh dấu rồi dòng sai hiện cảnh báo, câu hỏi mẫu nảy lên trước câu trả
   lời, dấu ✓ trong bảng so sánh bật lên theo từng hàng, số Radar đếm lên.
   Quãng trượt ngắn (≤ 18px), dưới một giây, không lặp lại.

   Trạng thái ẩn chỉ áp dụng khi <html> có lớp `has-reveal` (JavaScript đã chạy
   và trình duyệt có IntersectionObserver) VÀ đang bật hiệu ứng — thiếu một trong
   hai điều kiện thì nội dung hiện sẵn, không bao giờ bị kẹt ở trạng thái ẩn.
   ========================================================================== */

@keyframes reveal-up {
  from { opacity: 0; filter: blur(5px); transform: translate3d(0, 18px, 0); }
}

@keyframes reveal-pop {
  from { opacity: 0; transform: translate3d(0, 8px, 0) scale(0.92); }
}

@keyframes rule-draw {
  from { background-size: 0 2px; }
}

@keyframes num-settle {
  from { background-color: transparent; box-shadow: inset 0 0 0 transparent; color: inherit; }
}

@keyframes flag-ping {
  0% { box-shadow: inset 0 -1.5px 0 var(--amber), 0 0 0 0 rgba(154, 91, 0, 0.35); }
  100% { box-shadow: inset 0 -1.5px 0 var(--amber), 0 0 0 7px rgba(154, 91, 0, 0); }
}

@keyframes check-pop {
  from { opacity: 0; transform: scale(0.3); }
}

html.has-reveal[data-motion="full"] [data-reveal]:not(.is-revealed) .rv,
html.has-reveal[data-motion="full"] [data-reveal].rv:not(.is-revealed) { opacity: 0; }

html.has-reveal[data-motion="full"] [data-reveal].is-revealed .rv,
html.has-reveal[data-motion="full"] [data-reveal].rv.is-revealed {
  animation: reveal-up 0.75s var(--ease-out) calc(var(--i, 0) * 90ms) backwards;
}

/* Ba bước kiểm tra: vạch mực kẻ từ trái sang cùng lúc nội dung hiện lên */
html.has-reveal[data-motion="full"] .steps.is-revealed .step {
  animation:
    reveal-up 0.75s var(--ease-out) calc(var(--i, 0) * 140ms) backwards,
    rule-draw 0.9s var(--ease-in-out) calc(var(--i, 0) * 140ms) backwards;
}

/* …trong bước 2, dòng đúng hiện trước, dòng sai hiện sau rồi "báo động" một nhịp */
html.has-reveal[data-motion="full"] .steps.is-revealed .verify__line { animation: reveal-up 0.6s var(--ease-out) backwards; }
html.has-reveal[data-motion="full"] .steps.is-revealed .verify__line--ok { animation-delay: 0.6s; }
html.has-reveal[data-motion="full"] .steps.is-revealed .verify__line--ok .num-ok { animation: num-settle 0.6s var(--ease-out) 1s backwards; }
html.has-reveal[data-motion="full"] .steps.is-revealed .verify__line--flag { animation-delay: 1.05s; }
html.has-reveal[data-motion="full"] .steps.is-revealed .verify__line--flag .num-flag { animation: flag-ping 0.9s ease-out 1.55s; }
html.has-reveal[data-motion="full"] .steps.is-revealed .verify__why { animation: fade-in 0.5s ease-out 1.6s backwards; }

/* Ba việc mỗi ngày: câu hỏi nảy lên, câu trả lời hiện sau, con số được đánh dấu sau cùng */
html.has-reveal[data-motion="full"] .uses.is-revealed .exchange__q { animation: reveal-pop 0.5s var(--ease-spring) calc(var(--i, 0) * 90ms + 0.35s) backwards; transform-origin: 100% 100%; }
html.has-reveal[data-motion="full"] .uses.is-revealed .exchange__a { animation: fade-in 0.6s ease-out calc(var(--i, 0) * 90ms + 0.65s) backwards; }
html.has-reveal[data-motion="full"] .uses.is-revealed .exchange .num-ok { animation: num-settle 0.6s var(--ease-out) calc(var(--i, 0) * 90ms + 1.05s) backwards; }
html.has-reveal[data-motion="full"] .uses.is-revealed .exchange__src { animation: fade-in 0.5s ease-out calc(var(--i, 0) * 90ms + 1.2s) backwards; }

/* Bảng so sánh: hàng hiện lần lượt, dấu ✓ bật lên theo từng hàng */
html.has-reveal[data-motion="full"] .is-revealed .compare-table tbody tr.rv { animation-name: fade-in; animation-duration: 0.5s; }
html.has-reveal[data-motion="full"] .is-revealed .compare-table .is-yes::before { animation: check-pop 0.45s var(--ease-spring) calc(var(--i, 0) * 90ms + 0.25s) backwards; }

/* Câu hỏi thường gặp: nội dung trượt ra khi mở */
.faq__item[open] > p { animation: fade-slide-in 0.45s var(--ease-out) backwards; }

/* Thanh tiến độ đọc dưới thanh điều hướng (trang giới thiệu, trang hướng dẫn). Dựa trên
   vị trí cuộn, không cần JavaScript; trình duyệt chưa hỗ trợ thì không có thanh. */
@supports (animation-timeline: scroll()) {
  html[data-motion="full"] :is(body.is-landing .landing, body.is-guide .guide) > .site-header::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 2px;
    background: linear-gradient(90deg, var(--jade), var(--jade-bright));
    transform-origin: 0 50%;
    animation: read-progress linear both;
    animation-timeline: scroll(root block);
  }

  @keyframes read-progress {
    from { transform: scaleX(0); }
    to { transform: scaleX(1); }
  }
}

/* Quầng ngọc trôi rất chậm sau tấm mực (trang đăng nhập, lời mời cuối trang giới thiệu) */
.auth-visual__panel::before,
.closing__panel::before {
  content: "";
  position: absolute;
  z-index: -1;
  top: -30%;
  right: -25%;
  width: 75%;
  aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(47, 184, 132, 0.26), rgba(47, 184, 132, 0));
  animation: glow-drift 18s ease-in-out infinite alternate;
  pointer-events: none;
}

@keyframes glow-drift {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to { transform: translate3d(-38%, 46%, 0) scale(1.2); }
}

/* Menu tài khoản: mờ dần và phóng nhẹ từ góc trên bên phải (chỗ nút ảnh đại diện), như
   `data-[state=open]:zoom-in-95 slide-in-from-top-2` của mẫu Radix — đóng thì ngược lại, nhanh hơn. */
.account-menu:not([hidden]) { animation: menu-in 0.18s var(--ease-out); }
.account-menu.is-closing { animation: menu-out 0.14s ease-in forwards; pointer-events: none; }

@keyframes menu-in { from { opacity: 0; transform: translate3d(0, -6px, 0) scale(0.95); } }
@keyframes menu-out { to { opacity: 0; transform: translate3d(0, -4px, 0) scale(0.97); } }

/* Cài đặt: nhóm vừa chọn hiện ra nhẹ; chữ "Đã lưu" nảy một nhịp; ô xem trước đổi cỡ chữ mượt */
.settings__panel:not([hidden]) { animation: fade-slide-in 0.35s var(--ease-out); }
.settings__saved[data-state="saved"] { animation: reveal-pop 0.4s var(--ease-spring); }
.settings__saved[data-state="saving"] { animation: reduced-pulse 1.2s ease-in-out infinite; }
.set-preview__body, .set-sample__answer { transition: font-size var(--dur-base) var(--ease-out); }

/* Hồ sơ: ảnh đại diện nảy nhẹ khi mở, dấu ✓ của quy tắc mật khẩu bật lên khi đạt */
.overlay:not([hidden]) .profile .avatar--xl { animation: reveal-pop 0.55s var(--ease-spring) 0.12s backwards; }
.password-rules li.is-met::before { animation: check-pop 0.35s var(--ease-spring); }

/* Phim minh hoạ: quả cầu "đang đối chiếu" xoay, con trỏ nhấp nháy, nút gửi nhún khi gửi */
.reel-think__orb { animation: spin 1.2s linear infinite; }
.reel-composer__caret { animation: caret-blink 1.05s steps(1) infinite; }
.reel-scene--ask.is-typing .reel-composer__caret { animation: none; }
.reel-scene--ask.s1 .reel-composer__send { animation: reel-press 0.4s var(--ease-spring); }
.reel.is-paused .reel-scene * { animation-play-state: paused; }

@keyframes reel-press { 40% { transform: scale(0.82); } }

/* ==========================================================================
   25. Hiệu ứng trên nền kính
   --------------------------------------------------------------------------
   Thanh đầu trang giờ là một viên kính nổi (styles.css §17). Hai hiệu ứng cũ
   gắn vào khung bao ngoài của thanh nên phải chuyển sang chính viên kính.
   ========================================================================== */

/* Phần tử đang đổi độ mờ không cho con của nó nhìn xuyên ra nền: lúc trang mở,
   viên kính tự mờ dần vào chứ không phải khung bao ngoài. */
.landing.is-entering .site-header { animation: none; }
.landing.is-entering .site-header__inner { animation: fade-in 0.6s ease-out backwards; }

/* Thanh tiến độ đọc nằm ở mép dưới viên kính, trong đoạn thẳng giữa hai đầu bo tròn. */
@supports (animation-timeline: scroll()) {
  html[data-motion="full"] :is(body.is-landing .landing, body.is-guide .guide) > .site-header::after { content: none; }

  html[data-motion="full"] :is(body.is-landing .landing, body.is-guide .guide) > .site-header > .site-header__inner::after {
    content: "";
    position: absolute;
    left: 28px;
    right: 28px;
    bottom: 0;
    height: 2px;
    border-radius: 2px;
    background: linear-gradient(90deg, var(--jade), var(--jade-bright));
    transform-origin: 0 50%;
    animation: read-progress linear both;
    animation-timeline: scroll(root block);
  }
}

/* ==========================================================================
   19. Chế độ giảm chuyển động
   --------------------------------------------------------------------------
   Chuyển động có thể gây chóng mặt hoặc buồn nôn với một số người, nên chế
   độ này được tôn trọng: bỏ mọi thứ trôi, xoay, nảy, bay — nhưng GIỮ thông
   tin trạng thái (đang tải vẫn nhấp nháy độ mờ chậm).

   Chế độ được điều khiển bằng thuộc tính `html[data-motion]` thay vì trực tiếp
   bằng `@media (prefers-reduced-motion)`. Lý do: trên Windows, tắt "Hiệu ứng
   hoạt ảnh" để máy chạy nhanh hơn cũng khiến MỌI trình duyệt báo "giảm chuyển
   động" — người dùng không hề biết và nghĩ trang web không có hiệu ứng.
   Script nhỏ trong <head> của index.html đặt thuộc tính này theo cài đặt hệ
   điều hành, hoặc theo lựa chọn người dùng đã lưu qua nút "Hiệu ứng chuyển động".
   ========================================================================== */

@keyframes reduced-pulse {
  0%, 100% { opacity: 0.45; }
  50% { opacity: 1; }
}

html[data-motion="reduced"] *,
html[data-motion="reduced"] *::before,
html[data-motion="reduced"] *::after {
  animation-duration: 0.01ms !important;
  animation-delay: 0ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0.01ms !important;
  scroll-behavior: auto !important;
}

html[data-motion="reduced"] :is(
  .aurora__blob,
  .aurora__sheen,
  .drop-ring,
  .drop-orbit,
  .drop-orbit span,
  .drop-core,
  .drop-core svg,
  .skeleton,
  .qr-corner,
  .lens-demo__aura,
  .ask__caret
),
html[data-motion="reduced"] .headline::after,
html[data-motion="reduced"] .brand-mark::after,
html[data-motion="reduced"] .skeleton::after,
html[data-motion="reduced"] .meter__fill::after { animation: none !important; }

html[data-motion="reduced"] .answer--streaming .answer__body *::after { animation: none !important; }
html[data-motion="reduced"] :is(.cursor-aura, .tilt-glare, .bloom, .ask__caret),
html[data-motion="reduced"] .price-col--featured::before,
html[data-motion="reduced"] .qr-frame[data-scanning="true"]::after { display: none !important; }

/* Trạng thái đang xử lý vẫn phải nhận biết được — thay chuyển động bằng nhấp nháy chậm. */
html[data-motion="reduced"] :is(.spinner, .awaiting-dot, .doc-item--uploading.is-processing .progress-ring svg, .support-typing span),
html[data-motion="reduced"] .ai-orb::before,
html[data-motion="reduced"] .composer__send.is-busy::after {
  animation: reduced-pulse 1.6s ease-in-out infinite !important;
}

/* Nút bật/tắt hiệu ứng — nhấp nháy lấp lánh khi hiệu ứng đang bật */
.motion-toggle[aria-pressed="true"] svg { animation: sparkle-twinkle 2.4s ease-in-out infinite; }

@keyframes sparkle-twinkle {
  0%, 100% { transform: scale(1) rotate(0deg); }
  50% { transform: scale(1.15) rotate(12deg); }
}

/* Tương phản cao: bỏ lớp kính mờ vì nó làm giảm tỷ lệ tương phản chữ/nền. */
@media (prefers-contrast: more) {
  .rail,
  .topbar,
  .composer,
  .auth-card,
  .toast,
  .drop-overlay,
  .orbit-chip {
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
    background: #ffffff !important;
  }

  .aurora { display: none; }
}
