/* ==========================================================================
   sections-rsvp.css — Nửa cuối thiệp
   --------------------------------------------------------------------------
   PHẠM VI:
     · #rsvp         (.rsvp)          — S10, form xác nhận + 2 nhánh hoa góc dưới
     · #rsvp-success (.rsvp-success)  — popup cảm ơn (con TRỰC TIẾP của body)
     · #thanks       (.thanks)        — S11, lời cảm ơn + khối .gift
     · #footer       (.footer)        — brand olive + 3 icon mạng xã hội
     · các class trong <template> thuộc các khối trên: .rsvp__option*,
       .gift__account…, .footer__social…

   KHÔNG thuộc file này (dù bản phân chia ban đầu có ghi): #gallery và
   #lightbox — agent sections-event.css ĐÃ viết đầy đủ hai khối đó (lưới
   masonry 2/3/4 cột + lớp phủ xem ảnh) với selector đặc hiệu hơn
   (`.gallery .gallery__grid`, `#lightbox .lightbox__img`). Viết trùng ở đây
   chỉ tạo hai nguồn sự thật nên đã bỏ hẳn. Xem báo cáo bàn giao.

   LUẬT ĐÃ TUÂN THỦ
   1. Không một literal màu / font-family nào — tất cả qua biến tokens.css.
   2. Mọi rule đều nằm dưới gốc section của mình (.rsvp / .rsvp-success /
      .thanks / .gift / .footer). Không rule nào là selector phần tử trần có
      thể "rò" ra section của agent khác.
   3. Mobile-first: rule gốc ngắm 390px, rồi @media 768px và 1024px chỉ để
      hoạ tiết tràn rộng ra --w-stage, còn cột chữ vẫn --w-content.
   4. KHÔNG đặt transform cố định lên phần tử mang .reveal (animations.css
      có `.reveal.is-visible { transform: none }` sẽ xoá ngay). Mọi nghiêng /
      phóng đều đặt ở phần tử CON: .gift__plate-img.
   5. Mọi chuyển động do file này thêm vào đều bị tắt trong
      @media (prefers-reduced-motion: reduce) ở mục 6 cuối file.
   6. Ảnh luôn aspect-ratio + object-fit: cover, không bao giờ khoá chiều cao
      cứng gây méo ảnh.

   TOẠ ĐỘ REFERENCE (LadiPage, cột 420px) đã quy sang % để scale theo khung:
     · SECTION11 cao 625.7 — form 382px ở top 90.4; nhánh hoa trái
       121.6×79.1 tại top 521.5/left 0 → 29% chiều rộng, mép dưới;
       nhánh hoa phải 77.5×88 tại top 521.5/left 283 → 19% chiều rộng,
       left 67.4%.
     · SECTION12 cao 377.7 — ảnh BOX17 420×324 (brightness 86% = lớp scrim),
       "Thank you" script trắng ở top 138.5, lời cảm ơn ở top 205;
       footer GROUP65 ở top 332: brand trái, 3 icon phải (left 293/326/365).
     · POPUP mừng cưới: thẻ giấy 295×291 (top 14.6/left 23), 25/10 Lora
       28.67px ở góc phải, nhánh tú cầu 87×196 (top 25/left 0), bó hoa
       125×191 (top 170/left 241) → tương ứng 24% và 30% chiều rộng.
     · POPUP thank-you: "Thank you!" script 23.36px sage, lời cảm ơn Lora
       15px/1.5 sage, QR 126×127.
   ========================================================================== */

/* ==========================================================================
   1. S10 — RSVP (#rsvp)
   Reference: chữ dẫn Lora 16px/1.5 sage giữa khung, form 382px, nút gửi nền
   sage chữ trắng 19px in hoa tracking 1px, hai nhánh hoa ở hai góc dưới.
   Ô nhập theo yêu cầu thiết kế của ta: nền trong suốt, CHỈ một nét sage 1px
   ở đáy (reference bọc viên thuốc 20px — ta cố ý làm nhẹ hơn cho thoáng).
   ========================================================================== */

/* Chừa chỗ dưới cùng cho 2 nhánh hoa (ở reference chúng nằm dưới form). */
.rsvp__stage {
  padding-block-end: var(--sp-9);
}

.rsvp__intro {
  color: var(--sage);
  margin-block-end: var(--sp-6);
}

.rsvp__form {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  inline-size: 100%;
}

/* Mỗi field CHỪA SẴN chỗ cho dòng lỗi (padding-block-end) và dòng lỗi được
   đặt absolute vào đúng khoảng đó → lỗi hiện ra KHÔNG làm nhảy layout.
   Bắt buộc làm cách này vì .rsvp__error mang [hidden] (display:none) nên
   min-height trên chính nó sẽ không chừa được chỗ. */
.rsvp__field {
  position: relative;
  padding-block-end: var(--sp-5);
}

.rsvp__label {
  display: block;
  color: var(--sage);
  margin-block-end: var(--sp-2);
}

/* --- Ô nhập chữ & vùng nhắn ------------------------------------------- */
.rsvp__input {
  inline-size: 100%;
  min-block-size: 2.75rem; /* 44px vùng chạm */
  padding: var(--sp-2) var(--sp-1);
  background-color: transparent;
  border: 0;
  border-block-end: 1px solid var(--sage);
  border-radius: 0;
  color: var(--sage);
  font-family: var(--ff-body);
  font-size: var(--fs-base);
  line-height: var(--lh-normal);
  transition: border-color var(--dur-fast) var(--ease-out);
}

/* TUYỆT ĐỐI KHÔNG chồng opacity lên màu token ở đây. Bản trước là
   `color: var(--sage); opacity: 0.5` → Chrome trộn ra rgb(169,174,151), chỉ
   2.20:1 trên nền ô ngà, tức chưa tới một nửa ngưỡng AA 4.5:1; và vì độ mờ
   nằm ngoài token nên NHÌN VÀO CODE KHÔNG THẤY ĐƯỢC tương phản thật là bao
   nhiêu. Nay dùng --sage-mute (4.8:1) ở alpha đầy đủ: gợi ý nhập vẫn nhạt hơn
   chữ khách gõ (--sage) nhưng đọc được ngoài trời. */
.rsvp__input::placeholder {
  color: var(--sage-mute);
  opacity: 1;
}

/* Nét đáy dày lên khi focus. Bù 1px vào padding để KHÔNG nhảy layout.
   Vòng focus của animations.css (outline 2px + box-shadow) vẫn giữ nguyên —
   tuyệt đối không tắt chỉ báo focus. */
.rsvp__input:focus {
  border-block-end-width: 2px;
  padding-block-end: calc(var(--sp-2) - 1px);
}

.rsvp__textarea {
  min-block-size: 4.5rem;
  line-height: var(--lh-relaxed);
}

/* --- Nhóm lựa chọn dạng "viên thuốc phân đoạn" ------------------------ */
/* FLEX WRAP, KHÔNG PHẢI GRID 2 CỘT CỨNG.
   Bản cũ là `grid-template-columns: repeat(2, minmax(0, 1fr))` — hai cột ở MỌI
   bề rộng. Đo thật trong Chrome: nhãn dài nhất "Tôi chắc chắn sẽ đến" cần
   135,7px ở cỡ chữ 14px, trong khi mỗi ô chỉ chừa được
     390px → 119px · 360px → 104px · 320px → 84px · desktop (cột 420px) → 134px
   nên CẢ BỐN nhãn đều rớt hai dòng, kể cả trên desktop. Hai cột cứng không bao
   giờ đủ chỗ: muốn vừa ở 320px thì cỡ chữ phải tụt xuống ~9px.

   Cách chữa: để chính bề rộng nội dung quyết định xếp ngang hay xếp dọc.
   `flex: 1 1 0` + `min-inline-size: max-content` ⇒ kích thước giả định của mỗi
   ô ĐÚNG BẰNG bề rộng chữ không ngắt dòng, nên flexbox tự xuống hàng khi hai ô
   không còn đủ chỗ cạnh nhau; xuống hàng rồi thì flex-grow kéo mỗi ô rộng hết
   dòng. Kết quả: ≥ ~410px hai ô nằm cạnh nhau như thiết kế gốc, hẹp hơn thì
   xếp dọc — và KHÔNG BAO GIỜ rớt dòng chữ ở cả hai trạng thái.
   Đệm ngang hạ --sp-3 → --sp-2 và khe dấu-chọn/chữ --sp-2 → --sp-1: hai giá
   trị này chỉ quyết định NGƯỠNG xuống hàng (khi nằm cạnh nhau ô đã được kéo
   giãn nên nhìn vẫn thoáng như cũ), nhờ đó desktop dư 10px thay vì thiếu 2px.
   Tự co giãn theo độ dài nhãn nên sửa chữ trong data.js không phải sửa CSS —
   trần an toàn ở khổ hẹp nhất (280px) là ~190px chữ, gấp rưỡi nhãn hiện tại. */
.rsvp__options {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}

.rsvp__option {
  position: relative;
  flex: 1 1 0;
  /* Không bao giờ hẹp hơn chính dòng chữ của nó → flexbox xuống hàng thay vì
     bóp chữ. Cặp đôi với white-space: nowrap ở .rsvp__option-text. */
  min-inline-size: max-content;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-1);
  min-block-size: 2.75rem;
  padding: var(--sp-2);
  border: 1px solid var(--sage);
  border-radius: var(--r-pill);
  background-color: transparent;
  color: var(--sage);
  text-align: center;
  cursor: pointer;
  transition:
    background-color var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out);
}

/* Nền mờ khi trỏ vào: nhìn thấy được vì radio phủ trên nó đang trong suốt. */
.rsvp__option:hover {
  background-color: rgba(var(--sage-rgb), 0.08);
}

/* Radio thật CHÍNH LÀ mặt nền của viên thuốc: appearance:none + phủ kín
   padding-box của nhãn. Ba cái lợi:
     1. Trạng thái tô nền chỉ phụ thuộc :checked của CHÍNH nó → không bao giờ
        lệch. (Đã thử nghiệm: Chromium BỎ SÓT invalidation của :has() khi một
        radio bị bỏ chọn ngầm do người dùng chọn ô khác cùng group, nên
        `.rsvp__option:has(:checked)` có thể để lại nền sage cũ. Không dùng
        :has() cho phần tô nền vì lẽ đó.)
     2. Vòng focus mặc định của animations.css (input:focus-visible → outline
        2px + box-shadow) tự vẽ đúng hình viên thuốc quanh cả ô lựa chọn.
     3. Radio vẫn là radio thật: focus được, đi bằng mũi tên, đọc được bằng
        trình đọc màn hình (nhãn <label> bọc ngoài).                        */
.rsvp__radio {
  appearance: none;
  -webkit-appearance: none;
  position: absolute;
  inset: 0;
  z-index: 0;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: var(--r-pill);
  background-color: transparent;
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out);
}

.rsvp__radio:checked {
  background-color: var(--sage);
}

/* Dấu chọn + chữ nằm TRÊN mặt nền radio. */
.rsvp__option-mark,
.rsvp__option-text {
  position: relative;
  z-index: 1;
}

/* Vòng tròn dấu chọn — viền theo màu chữ hiện tại (sage khi chưa chọn). */
.rsvp__option-mark {
  flex: 0 0 auto;
  inline-size: 0.875rem;
  aspect-ratio: 1;
  border: 1px solid currentColor;
  border-radius: var(--r-pill);
  background-color: transparent;
  transition:
    background-color var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out);
}

.rsvp__option-text {
  line-height: var(--lh-snug);
  /* Nhãn lựa chọn là một mệnh đề ngắn, ngắt đôi ("Tôi chắc chắn sẽ / đến") đọc
     rất tệ trong một viên thuốc. Ô chứa đã được min-inline-size: max-content
     bảo đảm luôn đủ rộng cho dòng này, nên nowrap không gây tràn. */
  white-space: nowrap;
  transition: color var(--dur-fast) var(--ease-out);
}

/* Đã chọn: chữ + dấu chọn đổi sang màu ngà cho nổi trên nền sage.
   Chỉ dùng quan hệ anh-em (~) — chạy đúng ở MỌI trình duyệt, kể cả loại
   không hỗ trợ :has(). */
.rsvp__radio:checked ~ .rsvp__option-mark {
  border-color: var(--ivory);
  background-color: var(--ivory);
}

.rsvp__radio:checked ~ .rsvp__option-text {
  color: var(--ivory);
  font-weight: 600;
}

/* Vòng focus THẤY RÕ trên cả ô lựa chọn khi radio bên trong đang focus.
   Ba lớp bảo hiểm, cùng hình dạng nên trùng nhau không thấy khác biệt:
     a) vòng focus mặc định của animations.css vẽ trên chính <input> (input
        đã phủ kín ô) — luôn có, không cần :has();
     b) :has() — theo đúng yêu cầu thiết kế, vẽ trên nhãn;
     c) .is-focused — HOOK ĐỂ DÀNH, hiện KHÔNG có JS nào bật nó. Giữ lại vì
        nếu sau này cần chạy trên WebView cũ không hỗ trợ :has() thì chỉ việc
        cho rsvp.js gắn/gỡ class này theo focusin/focusout, không phải sửa CSS.
        Hôm nay lớp (a) đã đủ nên không có lỗi nào lộ ra. */
.rsvp__option:has(.rsvp__radio:focus-visible),
.rsvp__option.is-focused {
  outline: 2px solid var(--sage);
  outline-offset: 3px;
  box-shadow: 0 0 0 4px rgba(var(--sage-rgb), 0.22);
}

/* --- Dòng lỗi & dòng trạng thái --------------------------------------- */
/* Nằm đúng trong khoảng padding-block-end đã chừa của .rsvp__field. */
.rsvp__error {
  position: absolute;
  inset-block-end: 0;
  inset-inline: 0;
  color: var(--sage-deep);
  font-weight: 600;
  text-align: start;
}

/* --- Nút gửi ---------------------------------------------------------- */
/* Hình thức (nền sage, chữ ngà in hoa 19px, tracking 1px, bo viên thuốc) đã
   có ở .btn/.btn--solid của base.css. Ở đây chỉ thêm: tràn ngang + trạng
   thái đang gửi. */
.rsvp__submit {
  inline-size: 100%;
  margin-block-start: var(--sp-4);
}

/* Đang gửi: rsvp.js đặt submit.disabled = true + aria-busy="true" (đã kiểm
   trong assets/js/rsvp.js), nên bắt theo [aria-busy='true']; vẫn nhận thêm
   .is-busy để JS đổi cách làm sau này vẫn đúng.
   [disabled] đã được base.css làm mờ 0.55; ở đây thêm con quay. */
.rsvp__submit.is-busy,
.rsvp__submit[aria-busy='true'] {
  cursor: progress;
  pointer-events: none;
}

.rsvp__submit.is-busy::after,
.rsvp__submit[aria-busy='true']::after {
  content: '';
  inline-size: 1rem;
  block-size: 1rem;
  flex: 0 0 auto;
  border: 2px solid rgba(var(--ivory-rgb), 0.45);
  border-block-start-color: var(--ivory);
  border-radius: var(--r-pill);
  animation: rsvp-busy-spin var(--dur-slow) linear infinite;
}

@keyframes rsvp-busy-spin {
  to {
    transform: rotate(1turn);
  }
}

/* Dòng trạng thái luôn hiện diện (không [hidden]) nên chừa chỗ bằng
   min-block-size là đủ. rsvp.js thêm .is-error / .is-success. */
.rsvp__status {
  min-block-size: var(--sp-5);
  margin-block-start: var(--sp-3);
  color: var(--sage);
}

.rsvp__status.is-error {
  color: var(--sage-deep);
  font-weight: 600;
}

/* --- <select> (hiện KHÔNG có trong DOM) -------------------------------
   Reference vẽ mũi tam giác của ô chọn bằng data-URI SVG polygon "0,0 32,0 16,24" tô
   rgb(82,97,62). Ta KHÔNG inline data-URI đó vì nó nhúng literal màu (vi
   phạm luật 3 của REFERENCE.md) — dựng lại đúng hình tam giác ấy bằng
   clip-path + background-color: var(--sage). Rule để sẵn cho tương lai. */
.rsvp__field select {
  appearance: none;
  -webkit-appearance: none;
  background-image: none;
  padding-inline-end: var(--sp-6);
}

.rsvp__field:has(select)::after {
  content: '';
  position: absolute;
  inset-inline-end: var(--sp-3);
  inset-block-start: 2.5rem;
  inline-size: 0.5rem;
  aspect-ratio: 32 / 24; /* đúng tỉ lệ polygon của reference */
  background-color: var(--sage);
  clip-path: polygon(0 0, 100% 0, 50% 100%);
  pointer-events: none;
}

/* --- Hai nhánh hoa góc dưới ------------------------------------------- */
/* .decor không có offset mặc định (cạm bẫy C) → bắt buộc đặt inset +
   inline-size ở đây. Kích thước theo % của .stage để tự co giãn. */
.rsvp__sprig-left {
  inline-size: 29%; /* reference 121.6/420 */
  inset-block-end: 0;
  inset-inline-start: 0;
}

.rsvp__sprig-right {
  inline-size: 19%; /* reference 77.5/420 */
  inset-block-end: var(--sp-2);
  inset-inline-start: 67.4%; /* reference left 283/420 */
}

/* ==========================================================================
   2. POPUP CẢM ƠN SAU KHI GỬI (#rsvp-success) — con trực tiếp của <body>
   rsvp.js CHỈ cần gỡ [hidden] + gắn .no-scroll lên <body>. Mọi hình thức ở
   đây. z-index --z-toast để nằm trên cả lightbox.
   ========================================================================== */
.rsvp-success {
  position: fixed;
  inset: 0;
  z-index: var(--z-toast);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-5);
}

/* Lớp phủ mềm (bấm để đóng). */
.rsvp-success__backdrop {
  position: absolute;
  inset: 0;
  background-color: rgba(var(--sage-rgb), 0.5);
}

/* .card của base.css đã cho nền --cream + bo góc + đổ bóng; ở đây nới rộng
   padding để chừa chỗ cho nút X và làm bóng sâu hơn cho popup. */
.rsvp-success__panel {
  position: relative;
  z-index: 1;
  inline-size: min(100%, 22rem);
  padding: var(--sp-7) var(--sp-5) var(--sp-6);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg);
  text-align: center;
}

.rsvp-success__close {
  position: absolute;
  inset-block-start: var(--sp-1);
  inset-inline-end: var(--sp-1);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 2.75rem; /* 44px vùng chạm tối thiểu */
  block-size: 2.75rem;
  padding: 0;
  border-radius: var(--r-pill);
  color: var(--sage);
  transition:
    color var(--dur-fast) var(--ease-out),
    background-color var(--dur-fast) var(--ease-out);
}

.rsvp-success__close:hover {
  background-color: rgba(var(--sage-rgb), 0.1);
  color: var(--sage-deep);
}

/* Icon X của sprite vẽ bằng 2 nét stroke="currentColor" → chỉ cần color. */
.rsvp-success__close svg {
  inline-size: 1.25rem;
  block-size: auto;
}

/* "Thank you!" — reference 23.36px. .t-script-lg là 32–40px (dùng cho
   "All of me loves") nên ở popup ta hạ cỡ về đúng reference; vẫn giữ
   nguyên font script của utility. */
.rsvp-success__script {
  color: var(--sage);
  font-size: var(--fs-xl);
  margin-block-end: var(--sp-2);
}

/* Reference: Lora 15px/1.5 → đúng --fs-sm + --lh-normal. */
.rsvp-success__body {
  color: var(--sage);
  font-size: var(--fs-sm);
  line-height: var(--lh-normal);
}

/* ==========================================================================
   3. S11 — CẢM ƠN + HỘP MỪNG CƯỚI (#thanks)
   Reference SECTION12: ảnh 420×324 (brightness 86%) làm nền cho "Thank you"
   + lời cảm ơn, rồi khối mừng cưới trên thẻ giấy trắng.

   KỸ THUẬT: .thanks__media và .thanks__inner là hai phần tử ANH EM trong
   markup (không được tái cấu trúc), nên dùng CSS Grid xếp CHỒNG chúng vào
   cùng một ô (hàng 1), còn .gift xuống hàng 2. Nhờ vậy ảnh cao đúng bằng
   khối chữ, không cần khoá chiều cao cứng, và vẫn tràn hết chiều ngang.
   ========================================================================== */
.thanks {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  /* Bỏ padding-block của .section để ảnh chạm sát mép trên/dưới khối chữ. */
  padding-block: 0;
}

/* TỈ LỆ KHUNG ẢNH LÀ BẮT BUỘC, KHÔNG PHẢI TRANG TRÍ.
   Bản trước để khối này TRÀN HẾT bề rộng trang mà chỉ khoá min-block-size
   (16rem / 20rem ≥1024). Ở 1440px khung thành 1440×320 = tỉ lệ 4.5, còn ảnh
   nguồn là 750×1000 (0.75) → object-fit: cover chỉ giữ lại ~22% chiều cao ảnh:
   khi cô dâu chú rể thay ảnh thật vào thì mặt bị cắt mất, chỉ còn tóc với
   trời, và cả dải đọc ra như một vệt xanh chưa làm xong.
   Nay: khung bám ĐÚNG tỉ lệ 420/324 của reference (#BOX17) và bị chặn ở
   --w-stage như .calendar__panel / .countdown__painting đã làm — dưới 520px
   không đổi gì so với trước, từ 520px trở lên thành một tấm ảnh 520px canh
   giữa thay vì dải tràn viền rỗng ruột.
   min-block-size giữ lại làm sàn cho khung nhìn cực hẹp. */
.thanks__media {
  grid-area: 1 / 1;
  position: relative;
  z-index: var(--z-decor);
  inline-size: 100%;
  max-inline-size: var(--w-stage);
  margin-inline: auto;
  aspect-ratio: 420 / 324; /* reference #BOX17: 420×324 */
  min-block-size: 16rem;
  overflow: hidden;
}

.thanks__photo {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  /* Reference dùng background-position: center top, nhưng với ảnh chân dung
     "top" cắt phần dưới → đầu người nằm gần mép trên là thứ bị cắt đầu tiên.
     25% giữ được vùng mặt khi khung rộng hơn ảnh. */
  object-position: 50% 25%;
}

/* Lớp tối cho chữ trắng đọc được. Reference chỉ dùng filter: brightness(86%)
   vì ảnh cưới thật đã tối; ảnh placeholder của ta sáng màu kem nên phải phủ
   đậm hơn: ở đáy 0.8 cho chữ 13px đạt ~5:1, thừa ngưỡng AA. */
/* SỬA SAU KHI ĐO PIXEL THẬT (không phải tính nhẩm):
   Bản trước pha từ --sage-rgb ở alpha 0.45 → 0.80. Giải mã ảnh chụp thật ở
   390px cho ra nền dưới chữ có luminance 0.195 → chữ trắng 13px của
   .thanks__body chỉ đạt 4.29:1, TRƯỢT ngưỡng AA 4.5:1 (còn "Thank you" cỡ lớn
   thì 3.43:1, đạt ngưỡng chữ lớn 3:1 nhưng sát).
   --sage-deep-rgb (mới thêm vào tokens.css) đậm hơn hẳn nên cùng một mức tối
   chỉ cần alpha thấp hơn ~0.16 → chữ đạt chuẩn MÀ ảnh phía dưới còn thấy được
   nhiều hơn bản cũ. Cao nguyên đậm nằm ở 18%–88% vì .thanks__inner căn giữa
   (padding-block --sp-9) nên chữ luôn nằm trong dải đó; hai đầu nhạt hơn để
   mép ảnh không bị bít thành một khối phẳng. */
/* SỬA LẦN 2 — LÀM THÀNH DẢI CHỨ KHÔNG CÒN LÀ MÀN NƯỚC PHẲNG.
   Bản trước phủ 0.74–0.78 gần như đều từ 18% đến 88%, tức chỉ còn ~22% dải
   sáng-tối của ảnh sống sót → cả khối đọc ra một hình chữ nhật xanh phẳng, và
   ảnh cưới thật cũng sẽ phẳng như vậy. Nay đi theo đúng cách .quote__scrim
   làm: NHẠT ở trên và ở đáy (ảnh hiện rõ), chỉ ĐẬM đúng dải giữa nơi
   .thanks__inner (align-self: center) đặt chữ.
   KHÔNG hạ cao nguyên xuống 0.5 như đề xuất của reviewer: đo lại bằng công
   thức tương phản WCAG với trường hợp xấu nhất (ảnh trắng tinh) thì
   sage-deep α0.50 → 2.9:1, α0.62 → 3.4:1, α0.66 → 3.8:1, α0.74 → 4.63:1.
   Chữ .thanks__body chỉ 13px nên ngưỡng là 4.5:1, không phải 3:1 → 0.74 là
   SÀN, không phải lựa chọn thẩm mỹ. Phần "ảnh phải thấy được" lấy lại ở hai
   đầu dải (0.20–0.30) chứ không lấy bằng cách bỏ tương phản của chữ. */
.thanks__scrim {
  position: absolute;
  inset: 0;
  background-image: linear-gradient(
    to bottom,
    rgba(var(--sage-deep-rgb), 0.2) 0%,
    rgba(var(--sage-deep-rgb), 0.26) 10%,
    rgba(var(--sage-deep-rgb), 0.74) 26%,
    rgba(var(--sage-deep-rgb), 0.74) 76%,
    rgba(var(--sage-deep-rgb), 0.3) 92%,
    rgba(var(--sage-deep-rgb), 0.22) 100%
  );
}

.thanks__inner {
  grid-area: 1 / 1;
  align-self: center;
  padding-block: var(--sp-9);
  color: var(--on-dark);
}

/* Bóng chữ: tầng tương phản thứ hai, bù lại phần scrim đã được làm nhạt ở hai
   đầu dải. Pha từ --sage-deep-rgb (đậm hơn --sage-rgb) và bán kính nhỏ hơn để
   bóng bám sát nét chữ thay vì loang thành một quầng mờ.
   "Thank you" — reference #HEADLINE96 là 29.55px, còn .t-script-lg là 32–40px
   (cỡ của #HEADLINE34 "All of me loves"); hạ về --fs-script-md ≈ 29px cho đúng
   reference và cho script không chen sát đoạn 4 dòng bên dưới. */
.thanks__script {
  color: var(--on-dark);
  font-size: var(--fs-script-md);
  text-shadow: 0 1px 3px rgba(var(--sage-deep-rgb), 0.7);
}

.thanks__body {
  color: var(--on-dark);
  text-shadow: 0 1px 3px rgba(var(--sage-deep-rgb), 0.7);
  margin-block-start: var(--sp-4);
  /* Câu đầu do data.js tự ngắt dài 284px, cột chữ ở khổ 320px chỉ 272px ⇒ nó
     ngắt tiếp và bỏ lại "mình!" trơ một dòng. `pretty` dồn thành
     "…tình cảm cho / chúng mình!" — cụm "chúng mình!" còn nguyên. */
  text-wrap: pretty;
}

/* --- Khối hộp mừng cưới (.gift) — hàng 2 của lưới ---------------------- */
.gift {
  grid-area: 2 / 1;
  padding-block: var(--sp-8);
}

.gift__spray-left {
  inline-size: 24%; /* reference 87.2/420 ≈ 21%, nới nhẹ cho cân khung */
  inset-block-start: 5%;
  inset-inline-start: 0;
}

/* Chừa 1% mép phải: lớp này mang .drift (nghiêng tối đa 1.5deg) nên hộp bao
   của nó phình ra ~1px — để sát 0 là bị .section cắt mất một vệt lá. */
.gift__bouquet-right {
  inline-size: 30%; /* reference 124.9/420 */
  inset-block-end: 3%;
  inset-inline-end: 1%;
}

/* Thẻ giấy. VẤN ĐỀ ĐÃ SỬA: card-white-square-tilt-left.png là ảnh một tấm
   giấy trắng ĐÃ XOAY (611×600, gần vuông, mép vát + bóng mềm). Đổ nó vào khung
   342×488 (dọc) bằng object-fit: cover thì chỉ còn 69% bề ngang, nên hai góc
   trên lộ ra hai vết vát chéo và hai bên bị cắt thẳng đứng — đọc ra như lỗi
   render chứ không ra ý đồ thiết kế.
   Cách sửa: KHÔNG dùng ảnh làm hình dáng thẻ nữa. Hình dáng thẻ do chính
   .gift__plate lo (nền --paper + bo --r-lg + shadow), còn ảnh chỉ còn vai trò
   VÂN GIẤY: scale lên rồi clip, để phần mép vát của ảnh rơi hẳn ra ngoài khung.
   Ưu điểm so với object-fit: contain mà reviewer đề xuất: chữ luôn nằm trên
   nền trắng bất kể nội dung cao bao nhiêu, không phải khoá aspect-ratio của
   thẻ theo tỉ lệ ảnh. */
.gift__plate {
  position: relative;
  overflow: hidden; /* clip phần ảnh đã scale ra ngoài */
  background-color: var(--paper);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-md);
}

.gift__plate-img {
  position: absolute;
  inset: 0;
  z-index: var(--z-decor);
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  object-position: center;
  /* Đẩy mép vát + bóng của ảnh ra ngoài vùng clip, chỉ giữ lại vùng giấy
     phẳng ở giữa. 1.25 đủ để góc vát của ảnh 611×600 ra khỏi khung dọc. */
  transform: scale(1.25);
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;
}

.gift__card {
  position: relative;
  z-index: var(--z-content);
  padding: var(--sp-6) var(--sp-5);
}

.gift__title {
  color: var(--sage);
}

/* "25 / 10" — reference: Lora 28.67px sage (KHÔNG phải font display).
   Markup gắn .t-name (Hastegi 50–62px) nên ở đây hạ về đúng reference.
   --fs-script-md là mốc gần nhất (≈29px ở khung 420px). */
.gift__date {
  color: var(--sage);
  font-family: var(--ff-body);
  font-size: var(--fs-script-md);
  font-weight: 400;
  line-height: var(--lh-normal);
  letter-spacing: var(--ls-wide);
  text-transform: none;
  margin-block: var(--sp-2) var(--sp-3);
}

.gift__slash {
  color: var(--sage-soft);
  padding-inline: var(--sp-1);
}

.gift__note {
  color: var(--sage);
  margin-block-end: var(--sp-5);
}

/* --- Một tài khoản ---------------------------------------------------- */
.gift__accounts {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}

/* Lưới 2 cột: cột 1 là thông tin (canh trái theo reference), cột 2 là nút
   copy. Gán grid-row TƯỜNG MINH cho từng phần tử để thứ tự không phụ thuộc
   thuật toán tự động xếp ô. Dòng "Đã copy" đặt absolute vào khoảng
   padding-block-end đã chừa → hiện ra không làm nhảy layout. */
.gift__account {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  column-gap: var(--sp-3);
  /* Mốc đo cho .gift__holder — xem khối đó. Đặt ở đây vì đây chính là phần tử
     có bề rộng quyết định chỗ còn lại cho tên chủ tài khoản. An toàn: bề rộng
     của nó do flex cha quyết định chứ không do nội dung, nên size containment
     không đổi gì; và nó vốn đã là position: relative nên vẫn là containing
     block của .gift__copied như cũ. */
  container-type: inline-size;
  padding-block: var(--sp-4) var(--sp-5); /* sp-5 = chỗ chừa cho "Đã copy" */
  border-block-start: 1px solid rgba(var(--sage-rgb), 0.22);
  text-align: start;
}

.gift__account:first-child {
  border-block-start: 0;
  padding-block-start: 0;
}

/* "Chú rể" / "Cô dâu" — reference #HEADLINE99: UVNHoaTay 30.41px sage, canh
   trái. Markup nay gắn .t-hand (đúng chữ ký tay như .story__role) nhưng
   .t-hand dùng --fs-script-xl (36–46px, cỡ của "Cô dâu" 35.57px ở khối chuyện
   tình) → ở đây hạ về --fs-script-md ≈ 29px cho khớp 30.41px.
   Màu là --sage đầy: --sage-soft ở 14px trên thẻ giấy trắng chỉ 3.61:1, trượt
   AA, và reference cũng để nguyên rgb(82,97,62). */
.gift__role {
  grid-row: 1;
  grid-column: 1 / -1;
  color: var(--sage);
  font-size: var(--fs-script-md);
  margin-block-end: var(--sp-1);
}

.gift__bank {
  grid-row: 2;
  grid-column: 1;
  color: var(--sage);

  /* Markup gắn .t-venue (21.6px, Lora 600, in hoa, giãn chữ 1px) — cỡ đó là
     của TÊN ĐỊA ĐIỂM, đặt vào đây thì tên ngân hàng to hơn cả số tài khoản
     (17.5px) và gần bằng tên chủ tài khoản (26.5px). Sai thứ bậc: trong ba
     dòng này tên ngân hàng là thông tin ÍT quan trọng nhất — cả hai tài khoản
     đều cùng một ngân hàng, thứ khách phải đọc kỹ là TÊN CHỦ và SỐ.
     Reference cũng để tên ngân hàng và số tài khoản BẰNG NHAU (cùng 21px),
     chứ không cho tên ngân hàng trội hơn.

     VÌ SAO LÀ clamp CHỨ KHÔNG PHẢI MỘT SỐ CỨNG: .gift__number buộc phải co
     theo bề rộng màn (dãy 14–16 chữ số phải nằm trọn một dòng), nên để tên
     ngân hàng ở px cứng thì ở màn hẹp nó lại vượt lên trên số tài khoản —
     đúng cái thứ bậc vừa sửa. Ba mốc dưới đây bám sát và luôn thấp hơn
     .gift__number một bậc nhỏ:
         khung nhìn 320px → 12.0px  (số: 13.0px)
         khung nhìn 430px → 13.8px  (số: 15.1px)
         khung nhìn ≥500px → 16.0px (số: 17.5px)
     Vẫn giữ in hoa + giãn chữ của .t-venue nên nó đọc ra là một cái NHÃN,
     không phải chữ nhỏ bị lép. */
  font-size: clamp(0.75rem, 3.2vw, 1rem);
}

/* TÊN CHỦ TÀI KHOẢN là dòng khách PHẢI đối chiếu trước khi chuyển tiền, nên nó
   là dòng nổi nhất của khối — reference #HEADLINE100 đặt Lora 27px in hoa,
   lớn hơn tên ngân hàng + số tài khoản (21px). Bản trước để nó ở .t-body-sm
   (13px), tức dòng NHỎ NHẤT, đảo ngược thứ bậc của reference.
   --fs-script-md ≈ 27–32px là mốc gần nhất; cùng cách .gift__date đã làm cho
   một chuỗi Lora khác của reference. */
.gift__holder {
  grid-row: 3;
  grid-column: 1;
  color: var(--sage);
  line-height: var(--lh-tight);
  margin-block: var(--sp-1);

  /* TÊN CHỦ TÀI KHOẢN PHẢI NẰM TRỌN MỘT DÒNG.
     Bản trước để --fs-script-md (trần 32px) và cả hai tên đều rớt 2 dòng —
     "Nguyễn / Quang Danh" và "Nguyễn Thị / Bích Anh". Tên người bị bẻ đôi ngay
     trên khối chuyển tiền là chỗ tệ nhất để xuống dòng: đây đúng là dòng khách
     phải đối chiếu trước khi bấm chuyển.

     ĐO THẬT (Chrome, Lora đã nạp, đo advance width chứ không phải bề rộng nét):
       "Nguyễn Quang Danh"    9.635em
       "Nguyễn Thị Bích Anh"  9.745em  ← dài nhất, lấy làm mốc
     Chia cho 10.13 thay vì 9.745 = chừa biên an toàn 4% cho sai khác khi
     render và cho trường hợp Lora chưa kịp nạp (Georgia dự phòng rộng hơn).

     BỀ RỘNG CÒN LẠI CHO TÊN = bề rộng .gift__account − 56px
     (44px nút copy + 12px column-gap). Số 56 là hằng số DUY NHẤT trong công
     thức, và cả hai giá trị tạo ra nó đều nằm ngay trong file này.
     Đo được: 268px ở điện thoại/tablet, nhưng chỉ 252px từ 1024px trở lên vì
     @media (min-width: 1024px) nới .gift__card sang padding --sp-6 (32px/bên).
     Vì vậy KHÔNG dùng được một con số px cứng.

     Trần 1.6875rem = 27px — đúng cỡ #HEADLINE100 của reference. Nói cách khác
     đây không phải "thu nhỏ chữ để chống rớt dòng" mà là trả về đúng cỡ thiết
     kế gốc; 32px trước đó vốn đã vượt reference.

     ĐỔI TÊN CHỦ TÀI KHOẢN DÀI HƠN thì phải đo lại số em rồi sửa 10.13.
     Cố ý KHÔNG dùng white-space: nowrap: gặp tên dài bất thường thì rớt dòng
     (vẫn đọc được) còn hơn tràn ra ngoài thẻ rồi bị cắt mất chữ. */

  /* Dự phòng cho trình duyệt chưa hỗ trợ container query (trần hạ xuống 24px
     vì công thức theo vw không biết tới padding rộng hơn ở mốc 1024px). */
  font-size: clamp(1rem, calc(9.87vw - 15px), 1.5rem);
  /* Chính xác: đo thẳng theo bề rộng .gift__account nên đúng ở mọi khổ màn. */
  font-size: min(1.6875rem, calc((100cqw - 56px) / 10.13));
}

.gift__number {
  grid-row: 4;
  grid-column: 1;
  color: var(--sage);
  /* Số tài khoản KHÔNG được bẻ đôi: một dãy 16 chữ số rớt xuống hai dòng là
     mời khách đọc nhầm hoặc chép thiếu — mà đây là dòng dính tới tiền.
     Đo thật ở 360px: số 16 chữ số ở --fs-lg rớt 2 dòng. Nên cỡ chữ co theo bề
     rộng màn hình để dãy dài vẫn nằm trọn một dòng ở màn hẹp; từ ~530px trở
     lên vẫn đúng --fs-lg như thiết kế.
     Vẫn GIỮ overflow-wrap: anywhere làm lối thoát cuối — thà xuống dòng (vẫn
     đọc được) còn hơn nowrap rồi bị cắt mất chữ số. */
  font-size: clamp(0.8125rem, 3.5vw, var(--fs-lg));
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
  overflow-wrap: anywhere;
}

/* Nút copy: .btn.btn--ghost của base.css, thu về đúng ô vuông 44px. */
.gift__copy {
  grid-row: 2 / 5;
  grid-column: 2;
  align-self: center;
  inline-size: 2.75rem;
  block-size: 2.75rem;
  min-block-size: 2.75rem;
  padding: 0;
}

/* main.js gỡ [hidden] của phần tử này sau khi copy rồi ẩn lại. */
/* Màu --sage: đây là dòng XÁC NHẬN đã copy, phải đọc được (--sage-soft chỉ
   3.6:1 trên thẻ giấy trắng, trượt AA cho chữ 14px). */
.gift__copied {
  position: absolute;
  inset-block-end: 0;
  inset-inline-start: 0;
  color: var(--sage);
  animation: gift-copied-in var(--dur-base) var(--ease-out) both;
}

@keyframes gift-copied-in {
  from {
    opacity: 0;
    transform: translateY(0.25rem);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* Ô QR — ĐANG DÙNG THẬT (data.js: qr-chu-re.png / qr-co-dau.png).
   160px vuông, viền sage mảnh, nền giấy.

   `object-fit: contain` + `aspect-ratio: 1` nên ảnh nguồn PHẢI VUÔNG, nếu
   không mã QR bị thu nhỏ lại cho vừa cạnh ngắn. Hai ảnh hiện tại là 640×640,
   đã cắt sát mã kèm vùng lặng từ ảnh chụp màn hình gốc (screenshot dọc
   1290×2503 — bỏ nguyên vào thì QR chỉ còn ~82px, quét không nổi).
   Thay ảnh QR mới thì nhớ cắt vuông trước. */
.gift__qr {
  grid-row: 5;
  grid-column: 1 / -1;
  justify-self: center;
  inline-size: 10rem;
  max-inline-size: 100%;
  aspect-ratio: 1;
  object-fit: contain;
  margin-block-start: var(--sp-4);
  padding: var(--sp-1);
  border: 1px solid var(--sage);
  border-radius: var(--r-sm);
  background-color: var(--paper);
}

/* ==========================================================================
   4. FOOTER (#footer) — nằm NGOÀI <main>, không phải .section
   Reference GROUP65: brand canh trái + 3 icon canh phải trên cùng một hàng.
   Ở mobile xếp dọc & canh giữa cho khỏi chật, từ 768px mới thành 1 hàng.
   ========================================================================== */
.footer {
  position: relative;
  padding-block: var(--sp-6) var(--sp-8);
  overflow-x: hidden;
  overflow-x: clip;
}

.footer__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  justify-items: center;
  row-gap: var(--sp-4);
}

/* .rule có margin-block var(--sp-5) — trong lưới đã có row-gap nên bỏ đi. */
.footer__rule {
  grid-column: 1 / -1;
  margin-block: 0;
}

/* Reference: Lora 14.04px in hoa, tracking 1px, màu --olive.
   Markup gắn .t-label (Rift, tracking 2px) → ở đây đổi về Lora/1px cho
   đúng reference. Cỡ chữ của .t-label (--fs-xs = 14px) đã đúng. */
.footer__brand {
  color: var(--olive);
  font-family: var(--ff-body);
  letter-spacing: var(--ls-wide);
  text-align: center;
}

.footer__socials {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
}

/* Vùng chạm 44px cho từng icon (icon vẽ 22px, phần còn lại là padding). */
.footer__social-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 2.75rem;
  block-size: 2.75rem;
  border-radius: var(--r-pill);
  color: var(--olive);
}

.footer__social-link:hover {
  background-color: rgba(var(--sage-rgb), 0.1);
  color: var(--sage-deep);
}

/* Các <symbol> facebook/tiktok/zalo không khai báo fill → phải tô ở đây,
   nếu không sẽ ra màu đen mặc định của SVG. */
.footer__social-icon {
  inline-size: 1.375rem; /* 22px */
  block-size: auto;
  fill: currentColor;
}

/* ==========================================================================
   5. BREAKPOINT — chỉ để hoạ tiết tràn rộng ra --w-stage, cột chữ giữ
   --w-content. Không rule nào ở đây có thể sinh cuộn ngang: .stage rộng tối
   đa 520px < 768px, và .section/.footer đều overflow-x: clip.
   ========================================================================== */
@media (min-width: 768px) {
  /* Hoạ tiết RSVP tràn ra ngoài cột chữ (từ đây .stage rộng 520px trong khi
     khung nhìn ≥ 768px nên inset âm vẫn nằm gọn trong trang). */
  /* Giữ ĐÚNG mép trái 0: bông bên trái của PNG này bị cắt sẵn trong pixel
     (CATALOG ghi rõ "phải nằm sát left:0"), nên không đẩy ra ngoài stage. */
  .rsvp__sprig-left {
    inline-size: 26%;
    inset-inline-start: 0;
  }

  .rsvp__sprig-right {
    inline-size: 17%;
    inset-inline-start: 71%;
  }

  .gift__spray-left {
    inline-size: 22%;
    inset-inline-start: -3%;
  }

  .gift__bouquet-right {
    inline-size: 27%;
    inset-inline-end: -3%;
  }

  /* Hàng footer như reference: brand trái, icon phải — NHƯNG CHỈ KHI THẬT SỰ
     CÓ ICON. Bố cục 2 cột này chỉ có nghĩa khi cột phải có thứ để cân với
     brand ở cột trái. data.js đang để cả ba socials.url = '' ⇒ render.js xoá
     sạch <li>, <ul> còn lại rỗng và rộng 0px. Đo thật ở 1440px: lưới thành
     `348px 0px`, brand canh trái trong ô 348px nên nằm LỆCH TRÁI 35px so với
     tâm — lệch hẳn khỏi nét kẻ <hr> ngay phía trên nó (nét kẻ vẫn canh giữa)
     và lệch khỏi trục giữa mà cả tấm thiệp đang theo.
     :has() chỉ khớp khi còn ít nhất một .footer__social ⇒ điền link thật vào
     data.js là hàng 2 cột của reference trở lại, để rỗng thì giữ nguyên bố
     cục canh giữa của mobile. Trình duyệt không hiểu :has() (Firefox < 121)
     bỏ nguyên khối rule ⇒ rơi về canh giữa, tức vẫn đúng cho dữ liệu hiện
     tại — hỏng theo hướng an toàn. */
  .footer__inner:has(.footer__social) {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    justify-items: start;
    column-gap: var(--sp-5);
  }

  .footer__inner:has(.footer__social) .footer__rule {
    justify-self: center;
  }

  .footer__inner:has(.footer__social) .footer__brand {
    grid-column: 1;
    text-align: start;
  }

  .footer__inner:has(.footer__social) .footer__socials {
    grid-column: 2;
    justify-self: end;
    gap: var(--sp-2);
  }
}

/* <ul> rỗng (mọi socials.url = '') vẫn là một ô lưới và vẫn ăn row-gap
   var(--sp-4) ⇒ chừa 16px trống dưới brand mà không ai thấy vì sao. Giấu hẳn.
   KHÔNG dùng :empty được: render.js để lại các node xuống dòng giữa các <li>
   đã xoá, nên <ul> vẫn "có nội dung" dưới mắt :empty. */
.footer__socials:not(:has(.footer__social)) {
  display: none;
}

@media (min-width: 1024px) {
  /* KHÔNG khoá min-block-size ở đây nữa: tỉ lệ 420/324 + chặn --w-stage ở
     rule gốc đã cho khung 520×401 — cao hơn 20rem cũ, và không bao giờ dẹt.
     Khoá thêm chiều cao chỉ tạo lại đúng cái bug letterbox vừa sửa. */
  .thanks__inner {
    padding-block: var(--sp-10);
  }

  /* Hai ô lựa chọn RSVP thoáng hơn. */
  .rsvp__options {
    gap: var(--sp-3);
  }

  .gift__card {
    padding: var(--sp-7) var(--sp-6);
  }
}

/* ==========================================================================
   6. TÔN TRỌNG prefers-reduced-motion
   Tắt mọi chuyển động do FILE NÀY thêm vào. Nguyên tắc: không phần tử nào
   bị bỏ lại ở opacity 0 — con quay đứng yên, dòng "Đã copy" hiện ngay.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) and (max-width: 0px) { /* guard tắt theo yêu cầu: thiệp LUÔN chạy animation. Xoá ' and (max-width: 0px)' để bật lại. */
  .rsvp__input,
  .rsvp__option,
  .rsvp__radio,
  .rsvp__option-mark,
  .rsvp__option-text,
  .rsvp-success__close,
  .footer__social-link {
    transition: none;
  }

  .rsvp__submit.is-busy::after,
  .rsvp__submit[aria-busy='true']::after {
    animation: none;
  }

  .gift__copied {
    animation: none;
    opacity: 1;
    transform: none;
  }
}
