/* ==========================================================================
   sections-event.css — 4 section "sự kiện" của thiệp
   --------------------------------------------------------------------------
   Phạm vi DUY NHẤT của file này (mọi rule đều scope dưới id/class gốc):
     · #calendar  (.calendar)   — S6, ref SECTION7 (420×370.7)
         nền wash sage + "October" script + lưới 7 cột mon…sun + khoanh ngày 25
     · #timeline  (.timeline)   — S7, ref SECTION8 (420×515.7)
         "Timeline" + 2 mốc giờ có icon phía trên + khối .dresscode
     · #countdown (.countdown)  — S8, ref SECTION9 (420×509.7)
         panel tranh Monet lệch trục + chú thích script + 4 nhóm số Rift
     · #gallery   (.gallery)    — S9, ref SECTION10 (420×715)
         lưới ảnh kiểu masonry 2/3/4 cột + #lightbox (lớp phủ xem ảnh)

   HỢP ĐỒNG VỚI FILE KHÁC
   - Không literal màu / font-family: mọi giá trị lấy từ tokens.css.
   - Không định nghĩa lại utility của base.css (.t-*, .btn, .rule, .decor…);
     chỉ ghi đè thuộc tính lẻ trên class RIÊNG của section này.
   - CẠM BẪY (A) của DOM contract: KHÔNG đặt transform cố định lên phần tử
     đang mang .reveal (.calendar__panel, .countdown__painting, .gallery__item,
     .dresscode__swatch…) vì animations.css có `.reveal.is-visible {
     transform: none }` sẽ xoá ngay. Mọi transform cố định / hover đều đặt
     trên phần tử CON (.gallery__thumb).
   - Hoạ tiết (.decor) luôn ở --z-decor, sau nội dung (--z-content), và đã
     pointer-events: none từ base.css.
   - Mọi chuyển động của file này bị tắt trong @media (prefers-reduced-motion)
     ở cuối file.

   TOẠ ĐỘ REFERENCE → PHẦN TRĂM
   Reference là layout absolute ở cột 420px. Ở đây quy về % của khối cha
   (hoặc margin % — margin theo % luôn tính theo CHIỀU NGANG của khối cha,
   nên dùng được để đặt offset dọc mà vẫn co giãn theo bề rộng).
   ========================================================================== */


/* ==========================================================================
   S6 — #calendar (ref SECTION7)
   --------------------------------------------------------------------------
   Reference: BOX11 420×367 nền wash sage; GROUP49 369×293.5 ở top 36 left 25.5;
   "October" (BeyondPerfection 40.51px trắng) ở top 0; hàng nhãn thứ 14px ở
   top 81, các cột cách nhau 52.83px (= 369/7); các hàng ngày cách nhau 37px
   bắt đầu từ top 121.5; IMAGE34 (tim) 40.2×34 ở top 222.5 left 270 — tức là
   ngay ô ngày 25.
   Lưới do calendar.js dựng: mỗi hàng là một khối [role="row"], ô nhãn thứ
   .calendar__weekday, ô ngày .calendar__day (+ --empty / --highlight).
   ========================================================================== */

/* Panel tràn hết cột nội dung 420px như reference: triệt tiêu padding của
   .content bằng margin âm. Không sinh cuộn ngang vì bề rộng kết quả đúng
   bằng bề rộng của .content (≤ 420px, hoặc = viewport khi nhỏ hơn).        */
.calendar .calendar__panel {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  margin-inline: calc(var(--pad-x) * -1);
  padding: var(--sp-6) var(--sp-5);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-md);
}

/* Ảnh wash là PNG ĐẶC (không alpha) → phủ kín panel bằng object-fit thay vì
   background-image, để giữ đúng tỉ lệ ảnh ở mọi bề rộng.                   */
.calendar .calendar__wash {
  position: absolute;
  inset: 0;
  z-index: 0;
  inline-size: 100%;
  block-size: 100%;
  max-inline-size: none;
  object-fit: cover;
  object-position: center top;
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;
}

/* Lớp phủ tối rất nhẹ: wash có vệt trắng loang, chữ trắng dễ mất nét ở đó.
   Nằm GIỮA wash (z 0) và nội dung (--z-content).                          */
.calendar .calendar__panel::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: var(--z-decor);
  pointer-events: none;
  background-image: linear-gradient(
    to bottom,
    rgba(var(--sage-rgb), 0.12) 0%,
    rgba(var(--sage-rgb), 0.3) 45%,
    rgba(var(--sage-rgb), 0.44) 100%
  );
}

.calendar .calendar__body {
  position: relative;
  z-index: var(--z-content);
}

/* "October" — script trắng, ref 40.51px (đã là --fs-script-xl). */
.calendar .calendar__month {
  color: var(--on-dark);
  margin-block-end: var(--sp-5);
  text-shadow: 0 1px 3px rgba(var(--sage-rgb), 0.45);
}

/* Lưới: 7 cột đều nhau (ref 52.83px × 7 = 369). Khai báo cột ở CẢ hai cấp —
   ở .calendar__grid (nếu calendar.js dựng ô phẳng) và ở từng [role="row"]
   (đúng cấu trúc ARIA mà stub của calendar.js cam kết).                    */
.calendar .calendar__grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  row-gap: var(--sp-1);
  column-gap: 0;
}

.calendar .calendar__grid > [role='row'],
.calendar .calendar__row {
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  align-items: center;
}

/* Nhãn thứ — ref Lora 14px trắng in hoa. Không dựa vào .t-caption vì ô do JS
   sinh ra; đặt trực tiếp bằng token.                                       */
.calendar .calendar__weekday {
  font-family: var(--ff-body);
  font-size: var(--fs-xs);
  font-weight: 400;
  line-height: var(--lh-snug);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  text-align: center;
  color: var(--on-dark);
  padding-block-end: var(--sp-2);
  text-shadow: 0 1px 2px rgba(var(--sage-rgb), 0.5);
}

/* Ô ngày — ref Lora 13px trắng, các hàng cách 37px. */
.calendar .calendar__day {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-block-size: 2.25rem; /* ~36px, khớp bước hàng 37px của reference */
  font-family: var(--ff-body);
  font-size: var(--fs-2xs);
  font-weight: 400;
  line-height: 1;
  text-align: center;
  color: var(--on-dark);
  text-shadow: 0 1px 2px rgba(var(--sage-rgb), 0.5);
}

.calendar .calendar__day--empty {
  color: transparent;
}

/* Ngày cưới: vòng tròn mảnh màu trắng quanh chữ số.
   aspect-ratio: 1 + inline-size theo em → LUÔN là hình tròn, không bị bóp
   thành ellipse khi ô rộng ra ở desktop.                                   */
.calendar .calendar__day--highlight {
  font-weight: 600;
}

.calendar .calendar__day--highlight::before {
  content: '';
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  margin-block-start: -0.95em;
  margin-inline-start: -0.95em;
  inline-size: 1.9em;
  aspect-ratio: 1;
  border: 1px solid var(--on-dark);
  border-radius: 50%;
  pointer-events: none;
}

/* Tim trắng. calendar.js chuyển thẻ này VÀO ô .calendar__day--highlight.
   Rule đầu là vị trí dự phòng (khi JS chưa/không chạy, ảnh vẫn là con cuối
   của .calendar__panel) — góc dưới phải, không đè lên lưới.                */
.calendar .calendar__heart {
  position: absolute;
  z-index: var(--z-content);
  inset-block-end: var(--sp-4);
  inset-inline-end: var(--sp-5);
  inline-size: 2.25rem;
  block-size: auto;
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;
}

/* Khi đã nằm trong ô ngày 25: tim nhỏ, treo ở góc trên phải của vòng tròn
   (lệch ra ngoài một chút để không cắt qua nét vòng tròn).                 */
.calendar .calendar__day--highlight .calendar__heart {
  inset-block-start: -0.3em;
  inset-block-end: auto;
  inset-inline-end: -0.05em;
  inset-inline-start: auto;
  inline-size: 1.5em;
}

/* Nhịp nháy (@keyframes blink-soft, animations.css) — chỉ chỉnh PHA ở đây.
   Thiệp có HAI ngày cưới ⇒ calendar.js nhân bản trái tim và đánh dấu bản sao
   bằng [data-heart-clone]. Cho bản sao lệch nửa nhịp để hai tim không sáng tối
   răm rắp cùng lúc — nháy đồng bộ trên hai ô sát nhau đọc ra như đèn báo, lệch
   pha thì giống hai ngọn nến.
   Số ÂM là bắt buộc: delay dương sẽ giữ trái tim thứ hai đứng im ở khung hình
   đầu (opacity 0.3) trong 0.45s đầu tiên, tức nó mờ câm một nhịp rồi mới chạy. */
.calendar .calendar__heart[data-heart-clone] {
  --blink-delay: -0.45s;
}

@media (min-width: 768px) {
  .calendar .calendar__panel {
    padding: var(--sp-7) var(--sp-6);
  }
}


/* ==========================================================================
   S7 — #timeline + .dresscode (ref SECTION8)
   --------------------------------------------------------------------------
   Reference: GROUP67 378×151.7 ở top 131.5 chứa GROUP38 (176 wide, left 0) và
   GROUP39 (176 wide, left 202) → 2 cột ~176px, khe 26px. Icon ở top 0 của mỗi
   nhóm, chữ ở top 95.7 / 99.7 (Lora 20px/1.3 in hoa sage, canh giữa).
   LINE9: nét sage 2px rộng 88px ở top 26.8 left 149.6 → thanh nối NGANG nằm
   trong khe giữa hai icon.
   Dresscode: BOX12/BOX13 hình tròn 56.4px (border-radius 1000px).
   ========================================================================== */

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

/* HAI NHÓM MỐC GIỜ — một cho tiệc nhà gái 13.9, một cho tiệc nhà trai 20.9.
   Xếp dọc chứ không hai cột: mỗi nhóm BÊN TRONG đã là lưới 2 cột (bó hoa /
   tháp champagne), lồng hai lưới 2 cột cạnh nhau trong cột chữ 420px thì mỗi
   icon chỉ còn ~90px và cặp giờ + nhãn rớt dòng. */
.timeline .timeline__groups {
  display: grid;
  row-gap: var(--sp-7);
}

/* Nhãn "NHÀ GÁI · 13.09.2026" — bắt buộc phải có: hai nhóm giờ trông giống
   hệt nhau, không có nhãn thì khách rất dễ đọc nhầm giờ của bên kia. */
.timeline .timeline__group-head {
  color: var(--sage-soft);
  margin-block-end: var(--sp-4);
}

.timeline .timeline__group-dot {
  margin-inline: var(--sp-1);
}

/* Hai mốc cạnh nhau. Lưu ý: MỖI NHÓM phải có ĐÚNG 2 mốc; thanh nối ::before
   được canh cho đúng bố cục 2 cột. Thêm mốc thứ 3 thì phải xem lại rule này. */
.timeline .timeline__list {
  position: relative;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-5);
  align-items: start;
}

/* Thanh nối ngang giữa 2 icon (ref LINE9). Rộng 3.5rem — luôn nhỏ hơn khe
   giữa hai icon (≥ 88px ở 360px) nên không bao giờ đè lên icon.            */
.timeline .timeline__list::before {
  content: '';
  position: absolute;
  z-index: var(--z-decor);
  inset-block-start: 2.75rem; /* = 1/2 chiều cao icon (5.5rem) */
  inset-inline-start: 50%;
  margin-inline-start: -1.75rem;
  inline-size: 3.5rem;
  block-size: 2px;
  background-color: var(--sage);
  opacity: 0.55;
  pointer-events: none;
}

.timeline .timeline__item {
  position: relative;
  z-index: var(--z-content);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-1);
  text-align: center;
}

/* Icon: cố định CHIỀU CAO để chữ của hai mốc thẳng hàng, bề rộng tự co theo
   tỉ lệ gốc (bó hoa 234×244, tháp champagne 186×218 — tỉ lệ khác nhau).
   drop-shadow rất nhẹ để icon trắng không tan vào nền giấy ngà.            */
.timeline .timeline__icon {
  inline-size: auto;
  block-size: 5.5rem;
  max-inline-size: 100%;
  object-fit: contain;
  margin-block-end: var(--sp-3);
  filter: drop-shadow(0 1px 2px rgba(var(--sage-rgb), 0.22));
}

/* Ref: Lora 20px / line-height 1.3, in hoa, sage.
   .t-heading trên markup chỉ cấp 18.33px, nên ghi đè bằng --fs-md-lg để đúng
   20px của #HEADLINE81/82 — cặp giờ + nhãn phải nặng hơn chữ quanh nó. */
.timeline .timeline__time,
.timeline .timeline__label {
  color: var(--sage);
  font-size: var(--fs-md-lg);
  line-height: var(--lh-tight);
}

.timeline .timeline__time {
  font-weight: 600;
}

/* --- Dresscode --------------------------------------------------------- */
.timeline .dresscode {
  margin-block-start: var(--sp-7);
}

.timeline .dresscode__rule {
  margin-block: var(--sp-6);
}

.timeline .dresscode__title {
  color: var(--sage);
}

.timeline .dresscode__note {
  color: var(--sage);
  max-inline-size: 22rem;
  margin-block-start: var(--sp-3);
  margin-inline: auto;
}

/* Hàng màu: auto-fit để 5 ô luôn nằm gọn trong cột nội dung, kể cả ở 360px
   (5 track × ~52px + gap). Không bao giờ tràn ngang.                       */
.timeline .dresscode__swatches {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(3.25rem, 1fr));
  /* Khe NGANG co mạnh ở màn hẹp. Lý do đo được: ở đúng 360px lưới xếp vừa 5 ô,
     mỗi ô được 56.6px, trong khi nhãn dài nhất ("Olive nhạt") cần 57.1px —
     hụt 0.4px là đủ để nó rớt xuống hai dòng và cả hàng nhãn thành so le.
     Khe hẹp lại trả mỗi ô ~59px, đủ dư. Khe dọc giữ nguyên --sp-4. */
  gap: var(--sp-4) clamp(0.125rem, 1.2vw, var(--sp-2));
  margin-block-start: var(--sp-6);
  justify-items: center;
}

.timeline .dresscode__swatch {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2);
  text-align: center;
}

/* Ref: hình tròn 56.39px. --swatch do render.js gán từ data.js (data-css) —
   đây là con đường DUY NHẤT để màu của data.js vào CSS. Fallback --sage khi
   render.js chưa chạy. Viền sage mảnh là BẮT BUỘC: 3/5 màu trong data.js là
   kem / trắng ngà / vàng nhạt, không có viền thì mất hút trên nền giấy.    */
.timeline .dresscode__chip {
  display: block;
  inline-size: 2.75rem;
  aspect-ratio: 1;
  border: 1px solid rgba(var(--sage-rgb), 0.65);
  border-radius: var(--r-pill);
  background-color: var(--swatch, var(--sage));
  box-shadow: var(--shadow-sm);
}

/* Nhỏ hơn .t-caption một bậc (14px → 13px): "Trắng ngà" / "Vàng nhạt" là
   tên dài nhất trong data.js, ở 13px chúng vừa đúng một dòng trong track
   ~68px của desktop và tự ngắt 2 dòng ở 360px mà không chạm nhau.          */
/* Năm ô màu chia đều một hàng nên ở 360px mỗi ô chỉ ~56px, mà "Olive nhạt" ở
   --fs-2xs (13px) cần ~62px → rớt xuống hai dòng, hàng nhãn thành so le.
   Cỡ chữ co nhẹ ở màn hẹp; từ ~420px trở lên vẫn đúng --fs-2xs như thiết kế. */
.timeline .dresscode__swatch-name {
  color: var(--sage);
  font-size: clamp(0.7375rem, 3.2vw, var(--fs-2xs));
  line-height: var(--lh-tight);
  hyphens: auto;
}

@media (min-width: 768px) {
  .timeline .timeline__list {
    gap: var(--sp-6);
  }

  .timeline .timeline__icon {
    block-size: 6rem;
  }

  .timeline .timeline__list::before {
    inset-block-start: 3rem;
  }
}


/* ==========================================================================
   S8 — #countdown (ref SECTION9)
   --------------------------------------------------------------------------
   Reference (khối trên): GROUP70 420×331.9 gồm BOX15 420×236 nền sage đặc và
   BOX16 211.29×236 phủ ảnh tranh Monet ở góc trên-trái; HEADLINE84 (chú thích
   script 12.59px trắng, canh giữa) rộng 177 ở top 53.95 left 233.17 → nằm ở
   NỬA PHẢI trên nền sage; IMAGE38 (bó hoa 129.5×198.4) ở top 133.5 left 136.
   Khối dưới: GROUP69 ở top 343.9 — "Countdown" (script 28.87px sage) rồi hàng
   số: 4 nhóm rộng 25% và 3 dấu ":" ở left 70.4 / 163.8 / 257.3 của hàng 370.7
   → đúng các mốc 25% / 50% / 75%.
   ========================================================================== */

/* Panel tranh: 2 cột — tranh 50.3% (211/420), chú thích chiếm phần còn lại
   trên nền sage. Tràn hết cột nội dung 420px giống reference.              */
.countdown .countdown__painting {
  position: relative;
  isolation: isolate;
  display: grid;
  grid-template-columns: 50.3% 1fr;
  overflow: hidden;
  margin-inline: calc(var(--pad-x) * -1);
  border-radius: var(--r-md);
  background-color: var(--sage);
  box-shadow: var(--shadow-md);
}

/* Tỉ lệ 211:236 của reference quyết định chiều cao hàng → không đặt chiều cao
   cứng, ảnh không bao giờ bị bóp méo.                                      */
.countdown .countdown__painting-img {
  grid-area: 1 / 1;
  inline-size: 100%;
  block-size: 100%;
  min-block-size: 100%;
  aspect-ratio: 211 / 236;
  object-fit: cover;
  object-position: center;
}

/* Lớp phủ để mép phải của ảnh tan vào nền sage của panel.
   TINH LẠI khi đổi tranh Monet → ẢNH THẬT của cô dâu chú rể.
   Bản cũ (0.14 → 0.22 ở 62% → 0.6 ở 100%) hợp với một bức tranh phong cảnh
   olive-xám: phủ đều cả tấm cũng không ai để ý. Với ảnh người thì để nguyên là
   hỏng — trong gallery.photos[1] mặt cô dâu nằm ở x ≈ 62–90% của ô ảnh, đúng
   dải mà gradient cũ đã đậm 0.38–0.55 ⇒ mặt bị ám xanh rêu.
   Bản mới giữ ảnh gần như nguyên bản tới 78% rồi mới dốc: chỉ ~1/5 bề ngang
   ngoài cùng (phần nền/tóc, không phải mặt) bị hoà, đủ để không thấy đường
   cắt thẳng giữa ảnh và nền panel. 0.08 ở dải trái là một lớp mỏng thống nhất
   tông ảnh với màu thiệp, không đủ để đổi màu da.                           */
.countdown .countdown__scrim {
  grid-area: 1 / 1;
  z-index: var(--z-decor);
  pointer-events: none;
  background-image: linear-gradient(
    to right,
    rgba(var(--sage-rgb), 0.08) 0%,
    rgba(var(--sage-rgb), 0.1) 78%,
    rgba(var(--sage-rgb), 0.55) 100%
  );
}

/* Chú thích script trắng ở nửa phải, CANH GIỮA theo chiều dọc của panel.
   CỐ Ý LỆCH REFERENCE: reference đặt chú thích gần đỉnh (top 53.95 trong khung
   cao 236) vì bên trái nó là bức tranh Monet nằm ngang, mắt đọc từ trên xuống.
   Ta thay bằng ẢNH DỌC của cô dâu chú rể, chiếm trọn chiều cao panel, nên chữ
   canh đỉnh để hở một mảng sage rỗng phía dưới — đo thật: ở khổ 420px chữ cao
   104px trong ô 236px, hở trên 18px mà hở dưới 114px, lệch tâm 48px; khổ 390px
   lệch 27px. Mảng rỗng đó càng lộ khi màn rộng vì ô chữ cao theo tỉ lệ ảnh.
   `align-self: center` cho hai khoảng hở bằng nhau ở MỌI khổ màn — không cần
   padding bù, và cũng KHÔNG được thêm padding-block-start trở lại: nó phá đúng
   phép cân này (padding tính vào hộp <p> nên đẩy chữ xuống khỏi tâm).
   Canh giữa NGANG thì đã đúng sẵn nhờ .center + padding-inline đối xứng.
   padding-inline để hẹp: --fs-script-sm của ta (13.6→15.2px) lớn hơn
   reference (12.59px) nên phải nhường bề rộng cho chữ, nếu không mỗi dòng
   của data.js lại bị ngắt thêm một lần nữa.                                */
.countdown .countdown__caption {
  grid-area: 1 / 2;
  z-index: var(--z-content);
  align-self: center;
  padding-inline: var(--sp-2);
  color: var(--on-dark);
  /* Cột này chỉ rộng 159px ở khổ 320px, mà hai vế do data.js tự ngắt lại dài
     169px và 181px ⇒ mỗi vế bị ngắt tiếp và BỎ LẠI MỘT CHỮ TRƠ một dòng:
     "Every moment with / you" rồi "feels painted in gentle / light.".
     `pretty` cấm dòng cuối chỉ có một chữ, nên nó dồn lại thành
     "Every moment / with you" và "feels painted in / gentle light." — vẫn hai
     vế của câu, không còn chữ mồ côi. Không phải đụng vào chữ trong data.js. */
  text-wrap: pretty;
}

/* Bó hoa. Reference đặt nó ở ĐÚNG đường ghép giữa tranh và nền sage
   (left 136, top 133.5 — tức là ĐÈ LÊN panel). Ta không làm vậy được: luật
   của dự án buộc .decor nằm dưới nội dung (--z-decor < --z-content) mà panel
   lại là khối màu đặc → phần hoa nằm trong vùng panel sẽ bị che.
   Cách xử lý: dịch bó hoa xuống mép dưới-trái panel. Bông hoa (nằm ở nửa
   TRÊN của ảnh) lộ hẳn ra dưới panel, chỉ còn một dải nhỏ ở đỉnh giắt sau
   panel — đọc ra đúng cảm giác "hoa cài sau tấm tranh" của reference; đuôi
   amaranthus rủ xuống cạnh chữ "Countdown".
   margin-block-start theo % → margin % LUÔN tính theo BỀ RỘNG khối cha, nên
   offset dọc này co giãn đồng bộ với bề rộng (và do đó chiều cao) của panel. */
.countdown .countdown__bouquet {
  inset-block-start: 0;
  inset-inline-start: -5%;
  margin-block-start: 50%;
  inline-size: 25%;
  block-size: auto;
}

.countdown .countdown__title {
  color: var(--sage);
  margin-block-start: var(--sp-7);
}

/* Hàng số: 4 nhóm flex 1 + 3 dấu ":" → các dấu rơi đúng 25/50/75% như ref. */
.countdown .countdown__clock {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  gap: 0;
  margin-block-start: var(--sp-5);
}

.countdown .countdown__unit {
  flex: 1 1 0;
  min-inline-size: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2);
}

/* CHỐNG NHẢY LAYOUT MỖI GIÂY (rủi ro thật, xử lý ngay ở CSS):
   - tabular-nums + 'tnum': mọi chữ số cùng bề rộng
   - min-inline-size: 3ch: đủ cho "120" ngày lẫn "07" giây → hàng số không
     bao giờ co giãn khi giây đổi
   (.t-digit của base.css cũng đặt tabular-nums; nhắc lại ở đây để ô số vẫn
   đúng nếu markup lỡ thiếu utility.)                                       */
.countdown .countdown__value {
  display: block;
  min-inline-size: 3ch;
  color: var(--sage);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
  text-align: center;
}

/* Nhãn đơn vị nhạt hơn số một chút. 0.85 là ngưỡng thấp nhất còn giữ được
   tương phản AA (sage 85% trên nền ngà ≈ 4.7:1 cho chữ 14px) — đừng giảm nữa. */
.countdown .countdown__unit-label {
  color: var(--sage);
  opacity: 0.85;
}

/* Dấu ":" là ký tự nối (aria-hidden) → nhạt hơn chữ số để mắt đọc vào số
   trước. Không xuống dưới 0.7: dưới mức đó nét ":" mảnh gần như biến mất
   trên nền giấy ngà.                                                       */
.countdown .countdown__sep {
  flex: 0 0 auto;
  inline-size: 0.75em;
  color: var(--sage);
  text-align: center;
  opacity: 0.7;
}

.countdown .countdown__passed {
  color: var(--sage);
  margin-block-start: var(--sp-5);
}

@media (min-width: 768px) {
  /* .stage giãn tới --w-stage (520) còn .content vẫn 420 → panel chỉ chiếm
     420/520 = 80.8% bề rộng .stage và lệch vào 9.6%, nên chiều cao panel
     (236px) chỉ còn 45.4% bề rộng .stage. Quy lại các mốc % theo hệ 520px
     để bó hoa vẫn nằm đúng mép dưới-trái panel.                           */
  .countdown .countdown__bouquet {
    margin-block-start: 40%;
    inset-inline-start: 1%;
    inline-size: 20%;
  }
}


/* ==========================================================================
   S9 — #gallery + #lightbox (ref SECTION10)
   --------------------------------------------------------------------------
   Reference dùng widget carousel của LadiPage (ảnh lớn + dải thumbnail 80px).
   Theo yêu cầu thiết kế lại: lưới masonry bằng CSS grid (grid-auto-rows +
   row span), KHÔNG dùng JS masonry. 2 cột (mobile) → 3 cột (≥768) → 4 cột
   (≥1024, có ô cao 2 hàng).

   GHI CHÚ VỀ CHỒNG PHẠM VI — ĐÃ GIẢI QUYẾT, GIỮ LẠI ĐỂ KHỎI TÁI DIỄN
   Bản phân chia file ban đầu giao #gallery + #lightbox cho CẢ file này và
   sections-rsvp.css, nên đã có lúc hai file cùng định nghĩa. Ở bước tích hợp
   đã kiểm tra lại: sections-rsvp.css KHÔNG còn khối nào của #gallery/#lightbox
   (chỉ còn comment giải thích), và không có class nào bị định nghĩa ở hai file
   section khác nhau. File này là chủ duy nhất của #gallery và #lightbox.
   Các selector vẫn giữ gốc ID (#gallery / #lightbox, độ ưu tiên 1,1,0) — không
   còn cần để thắng cascade nhưng đổi hết sang class-only là một thay đổi rủi
   ro không mang lại gì, nên để nguyên. Nếu THÊM rule mới thì phải dùng CÙNG
   gốc ID, nếu không rule mới (0,1,0) sẽ yếu hơn rule cũ và không có tác dụng —
   đây chính là cái bẫy đã làm khối prefers-reduced-motion ở cuối file từng vô
   hiệu (xem comment ở đó).
   ========================================================================== */

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

#gallery .gallery__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-auto-flow: row dense;
  grid-auto-rows: auto;
  gap: var(--sp-2);
}

/* Mobile: 2 cột ô đều nhau, tỉ lệ dọc 3:4. `grid-column: auto` ghi đè tường
   minh rule `.gallery__item:first-child { grid-column: 1 / -1 }` của
   sections-rsvp.css (nếu không, ảnh đầu vẫn tràn 2 cột).                   */
#gallery .gallery__item {
  position: relative;
  grid-column: auto;
  grid-row: auto;
  aspect-ratio: 3 / 4;
}

/* Ô ảnh là <button> → phải bấm được, có vòng focus thấy rõ (vòng focus dùng
   chung của animations.css tự áp cho button:focus-visible; outline vẽ NGOÀI
   border-box nên không bị overflow: hidden của chính nó cắt).              */
#gallery .gallery__open {
  display: block;
  position: relative;
  inline-size: 100%;
  block-size: 100%;
  padding: 0;
  overflow: hidden;
  border-radius: var(--r-md);
  background-color: rgba(var(--sage-rgb), 0.08);
  cursor: zoom-in;
}

/* Viền trong 1px cho ảnh sáng (ref: .ladi-gallery có border 1px trắng). */
#gallery .gallery__open::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: var(--z-decor);
  border-radius: inherit;
  box-shadow: inset 0 0 0 1px rgba(var(--sage-rgb), 0.16);
  pointer-events: none;
}

/* Ảnh lấp trọn ô lưới; `aspect-ratio: auto` ghi đè aspect-ratio mà
   sections-rsvp.css đặt trên .gallery__thumb (ô lưới mới là nơi quyết định
   tỉ lệ, nếu không hai nguồn tỉ lệ sẽ đánh nhau ở breakpoint ≥768).        */
#gallery .gallery__thumb {
  inline-size: 100%;
  block-size: 100%;
  aspect-ratio: auto;
  object-fit: cover;
  object-position: center;
  transition:
    transform var(--dur-base) var(--ease-out),
    filter var(--dur-base) var(--ease-out);
}

/* Phóng nhẹ + sáng lên. Đặt trên .gallery__thumb (con) chứ KHÔNG trên
   .gallery__item vì item mang .reveal.                                     */
#gallery .gallery__open:hover .gallery__thumb,
#gallery .gallery__open:focus-visible .gallery__thumb {
  transform: scale(1.06);
  filter: brightness(1.08) saturate(1.04);
}

@media (min-width: 768px) {
  /* Lưới rộng ra hết cột nội dung 420px (triệt tiêu padding của .content).

     CHIỀU CAO HÀNG PHẢI SUY RA TỪ BỀ RỘNG CỘT, KHÔNG ĐƯỢC ĐẶT SỐ CỨNG.
     Bản trước dùng `grid-auto-rows: 6rem`: ô đơn thành 134.67×96 = tỉ lệ 1.40
     trong khi ảnh nguồn là 750×1000 (0.75), nên object-fit: cover NÉM ĐI 47%
     chiều cao mỗi ảnh, cắt từ chính giữa → 7 trong 8 ảnh chân dung biến thành
     dải ngang mất đầu. Bản mobile đặt aspect-ratio 3/4 nên đúng, vì vậy lỗi
     chỉ hiện khi mở bằng laptop.
     Lưới ở đây rộng ĐÚNG --w-content (372px content-box + 2×--pad-x âm), nên
     bề rộng cột = (--w-content − 2 khe) / 3 và chiều cao hàng dọc 3:4 tính
     thẳng ra được. Ô đơn khớp tỉ lệ ảnh (không cắt gì); ô cao 2 hàng thành
     ~3/8, tức cắt theo BỀ NGANG của ảnh dọc — nhẹ hơn hẳn cắt mất mặt. */
  #gallery .gallery__grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    grid-auto-rows: calc((var(--w-content) - 2 * var(--sp-2)) / 3 * 4 / 3);
    margin-inline: calc(var(--pad-x) * -1);
  }

  /* 3 cột × 8 ảnh: đúng 1 ô cao 2 hàng thì lưới lấp KÍN (9 = 3×3), không
     để lỗ. Chiều cao ô do grid-auto-rows quyết định → bỏ aspect-ratio.     */
  #gallery .gallery__item {
    aspect-ratio: auto;
    grid-column: auto;
    grid-row: auto / span 1;
  }

  #gallery .gallery__item:nth-child(1) {
    grid-row: auto / span 2;
  }
}

@media (min-width: 1024px) {
  /* Lưới bleed ra tới --w-stage (520px) trong khi cột chữ vẫn 420px.
     -24px (padding .content) - 50px ((520-420)/2) = -74px mỗi bên.         */
  /* Cùng cách tính như ở 768px, nhưng lưới nay rộng --w-stage (520px) và có 4
     cột / 3 khe → hàng cao (520 − 3 khe)/4 × 4/3 = 165.3px, ô đơn đúng 3/4.
     Trước đây `grid-auto-rows: 6.5rem` cho ô 124×104 = tỉ lệ 1.19, cắt mất
     37% chiều cao của các ảnh 2, 4, 5, 7. */
  #gallery .gallery__grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    grid-auto-rows: calc((var(--w-stage) - 3 * var(--sp-2)) / 4 * 4 / 3);
    margin-inline: calc(
      (var(--pad-x) + (var(--w-stage) - var(--w-content)) / 2) * -1
    );
  }

  /* 4 cột × 3 hàng = 12 ô cho 8 ảnh → cần ĐÚNG 4 ảnh cao 2 hàng. Đặt vị trí
     tường minh để lưới lấp kín, không phụ thuộc thuật toán dense:
       cột1: 1(2 hàng) + 5      cột2: 2 + 6(2 hàng)
       cột3: 3(2 hàng) + 7      cột4: 4 + 8(2 hàng)
     LƯU Ý: bố cục này khớp với gallery.photos = 8 ảnh trong data.js. Đổi số
     lượng ảnh thì ảnh thứ 9 trở đi tự chảy tiếp (rule cuối), còn nếu ÍT hơn
     8 ảnh sẽ hở vài ô — lúc đó chỉnh lại khối này.                          */
  #gallery .gallery__item:nth-child(1) { grid-column: 1; grid-row: 1 / span 2; }
  #gallery .gallery__item:nth-child(2) { grid-column: 2; grid-row: 1; }
  #gallery .gallery__item:nth-child(3) { grid-column: 3; grid-row: 1 / span 2; }
  #gallery .gallery__item:nth-child(4) { grid-column: 4; grid-row: 1; }
  #gallery .gallery__item:nth-child(5) { grid-column: 1; grid-row: 3; }
  #gallery .gallery__item:nth-child(6) { grid-column: 2; grid-row: 2 / span 2; }
  #gallery .gallery__item:nth-child(7) { grid-column: 3; grid-row: 3; }
  #gallery .gallery__item:nth-child(8) { grid-column: 4; grid-row: 2 / span 2; }

  #gallery .gallery__item:nth-child(n + 9) {
    grid-column: auto;
    grid-row: auto / span 1;
  }
}


/* ==========================================================================
   #lightbox — lớp phủ xem ảnh (gallery.js chỉ việc bỏ/thêm [hidden])
   --------------------------------------------------------------------------
   Là con TRỰC TIẾP của <body>, KHÔNG trong .section (xem ghi chú DOM
   contract: .section có isolation: isolate nên position: fixed bên trong bị
   giới hạn stacking context).
   [hidden] đã được base.css cho display: none !important → JS chỉ toggle
   thuộc tính này để hiện/ẩn.
   NGOÀI RA gallery.js còn thêm class .is-open ở frame kế tiếp (sau khi đã bỏ
   [hidden]) đúng để chạy một hiệu ứng vào. Trước bước tích hợp KHÔNG file nào
   hiện thực .is-open nên hiệu ứng đó là no-op; nay đã bổ sung ở dưới. Ràng
   buộc từ gallery.js: .is-open CHỈ được mang thuộc tính trang trí
   (opacity / transform), TUYỆT ĐỐI không mang position / inset / display —
   khi đóng, class bị gỡ và [hidden] trả lại trong cùng một tick, không có
   animation ra, nên lớp phủ không bao giờ bị treo.
   ========================================================================== */

#lightbox.lightbox {
  position: fixed;
  inset: 0;
  z-index: var(--z-lightbox);
  display: flex;
  align-items: center;
  justify-content: center;
  /* padding-inline nhỏ ở mobile để ảnh đạt gần 92vw; hai nút chuyển ảnh nằm
     đè lên mép ảnh (đúng kiểu lightbox, chúng có nền sage mờ nên vẫn rõ). */
  padding: var(--sp-8) var(--sp-3);
  overscroll-behavior: contain;
}

/* Nền: --sage-deep ở độ mờ cao. Nay tokens.css đã có --sage-deep-rgb nên viết
   thẳng rgba() được — không cần opacity trên cả lớp nền nữa (opacity tạo
   stacking context, rgba thì không). */
#lightbox .lightbox__backdrop {
  position: absolute;
  inset: 0;
  background-color: rgba(var(--sage-deep-rgb), 0.95);
  cursor: pointer;
}

/* Hiệu ứng vào cho khối ảnh: gallery.js bỏ [hidden] rồi thêm .is-open ở frame
   sau, nên transition có mốc đầu để chạy. Chỉ opacity + scale (xem ghi chú ở
   đầu khối). Trạng thái ĐÓNG phải là trạng thái "chưa vào" — nếu vì lý do gì
   .is-open không bao giờ được thêm, ảnh vẫn hiện đủ (opacity 1 ở dưới), chỉ là
   không có hiệu ứng: không bao giờ để khách nhìn vào một lớp phủ trống. */
#lightbox .lightbox__stage {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-3);
  inline-size: 100%;
  max-inline-size: 92vw;

  opacity: 1;
  transform: scale(0.97);
  transition:
    opacity var(--dur-base) var(--ease-out),
    transform var(--dur-base) var(--ease-out);
}

#lightbox.lightbox.is-open .lightbox__stage {
  transform: none;
}

/* Ảnh: contain trong 92vw × 88vh. Trần chiều cao còn trừ thêm phần padding
   dọc + dòng đếm ảnh (calc(100vh - 9rem)) để khối ảnh + dòng đếm KHÔNG bao
   giờ cao hơn khung nhìn (nếu tràn thì .lightbox căn giữa sẽ cắt cả hai
   đầu và dòng đếm biến mất).                                               */
#lightbox .lightbox__img {
  inline-size: auto;
  block-size: auto;
  max-inline-size: min(100%, 92vw);
  max-block-size: min(88vh, calc(100vh - 9rem));
  object-fit: contain;
  border-radius: var(--r-md);
  box-shadow: var(--shadow-lg);
  background-color: rgba(var(--ivory-rgb), 0.06);
}

#lightbox .lightbox__counter {
  color: var(--on-dark);
  opacity: 0.85;
  text-align: center;
}

/* 3 nút điều khiển: tối thiểu 44×44px (vùng chạm mobile). */
#lightbox .lightbox__nav,
#lightbox .lightbox__close {
  position: absolute;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 2.75rem;
  block-size: 2.75rem;
  padding: 0;
  border-radius: var(--r-pill);
  background-color: rgba(var(--sage-rgb), 0.55);
  color: var(--on-dark);
  cursor: pointer;
  transition:
    background-color var(--dur-fast) var(--ease-out),
    opacity var(--dur-fast) var(--ease-out);
}

#lightbox .lightbox__nav:hover,
#lightbox .lightbox__close:hover {
  background-color: var(--sage);
}

/* Icon chevron / close trong sprite dùng stroke="currentColor" fill="none". */
#lightbox .lightbox__nav svg,
#lightbox .lightbox__close svg {
  inline-size: 1.375rem;
  block-size: 1.375rem;
  fill: none;
  stroke: currentColor;
  pointer-events: none;
}

/* Canh dọc bằng margin âm chứ KHÔNG bằng translate(-50%), để dành transform
   cho hiệu ứng hover (và để không phải viết lại translate trong mọi state). */
#lightbox .lightbox__nav {
  inset-block-start: 50%;
  margin-block-start: -1.375rem;
}

#lightbox .lightbox__nav--prev {
  inset-inline-start: var(--sp-2);
}

#lightbox .lightbox__nav--next {
  inset-inline-end: var(--sp-2);
}

#lightbox .lightbox__close {
  inset-block-start: var(--sp-3);
  inset-inline-end: var(--sp-3);
}

@media (min-width: 768px) {
  /* Màn rộng: chừa lề ngang để 2 nút chuyển ảnh nằm NGOÀI mép ảnh.
     padding dọc giữ nguyên var(--sp-8) để công thức max-block-size ở trên
     vẫn đúng.                                                              */
  #lightbox.lightbox {
    padding: var(--sp-8) var(--sp-10);
  }

  #lightbox .lightbox__nav,
  #lightbox .lightbox__close {
    inline-size: 3.25rem;
    block-size: 3.25rem;
  }

  #lightbox .lightbox__nav {
    margin-block-start: -1.625rem;
  }

  #lightbox .lightbox__nav--prev {
    inset-inline-start: var(--sp-5);
  }

  #lightbox .lightbox__nav--next {
    inset-inline-end: var(--sp-5);
  }

  #lightbox .lightbox__close {
    inset-block-start: var(--sp-5);
    inset-inline-end: var(--sp-5);
  }
}


/* ==========================================================================
   TÔN TRỌNG prefers-reduced-motion
   Tắt mọi chuyển động do FILE NÀY sinh ra. Nguyên tắc: không phần tử nào bị
   bỏ lại ở trạng thái ẩn — chỉ bỏ transition / transform, nội dung vẫn đủ.
   (Hoạ tiết .drift / .pulse-soft đã được animations.css tắt sẵn.)
   ========================================================================== */
/* Selector ở đây PHẢI cùng gốc #gallery / #lightbox như rule gốc. Nếu viết
   `.gallery …` (0,2,0) thì rule gốc `#gallery …` (1,1,0) sẽ THẮNG và chuyển
   động không hề bị tắt — @media không cộng thêm độ ưu tiên nào.            */
@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. */
  #gallery .gallery__thumb,
  #lightbox .lightbox__nav,
  #lightbox .lightbox__close {
    transition: none;
  }

  /* Hiệu ứng vào của lightbox: bỏ cả transition VÀ trạng thái scale ban đầu,
     nếu không ảnh sẽ nằm mãi ở 97% khi .is-open chưa kịp thêm. */
  #lightbox .lightbox__stage {
    transition: none;
    transform: none;
  }

  #gallery .gallery__open:hover .gallery__thumb,
  #gallery .gallery__open:focus-visible .gallery__thumb {
    transform: none;
    filter: none;
  }
}
