/* ==========================================================================
   Home page sections - ported from the static site (tomcamau/css/index.css).

   Loads after tokens / typography / buttons / layout: text roles and buttons
   come from those, this file only adds what is specific to a section.
   ========================================================================== */

/* ==========================================================================
   Hero - Figma node 7129:87.

   Photo, a left-to-right dark gradient over it, and the content anchored to
   the bottom-left from 1024px up: the dates/venue chip, the organiser logos,
   the two-line headline, the buttons. The "produced by" badge is not part of
   that stack - it floats on its own near the bottom-right corner - which is
   why it is a sibling of `.hero__content`, not a child of it.

   Authored mobile-first: the base rules are the centred single column that
   fits a phone, and the one `@media (min-width: 1024px)` block below turns
   that into the bottom-left, gradient-lit design. `.hero__content` and
   `.hero__credit` also switch from flow to `position: absolute` at that
   width, but that switch lives with the rest of this file's z-index fix
   further down - see the comment there for why.
   ========================================================================== */
   
   
.hero {
  position: relative;
  isolation: isolate;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-xl);
  min-height: min(100svh, 1080px);
  padding: calc(var(--header-h) + var(--space-xl)) var(--gutter) var(--space-xl);
  overflow: hidden;
  text-align: center;
  color: var(--c-on-dark);
}

.hero__picture {
  display: contents;
}

.hero__bg {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Dark at the left, where the text sits, fading out toward the photo's right
   half - replaces the flat tint the previous design used. */
.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--c-hero-overlay);
}

.hero__content {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-xl);
  max-width: 100%;
}

/* --- Chip ngày tổ chức | địa điểm ---------------------------------------- */
.hero__meta {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3xs);
  max-width: 100%;
  padding: var(--space-2xs) var(--space-md);
  background: var(--c-glass-soft);
  border: 1px solid var(--c-glass-line);
  border-radius: var(--radius-md);
  font-weight: var(--fw-medium);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  letter-spacing: var(--ls-contact);
  text-align: center;
}

.hero__meta-item {
  position: relative;
}

/* The pin sits with the venue text as one unit; the icon-to-text gap matches
   the same pairing everywhere else on the site (footer contact list). */
.hero__meta-item--venue {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
}

.hero__meta-pin {
  display: none;
  flex: none;
  width: 24px;
  height: 24px;

  @media (min-width: 1024px) {
    display: block;
  }
}

/* Below 1024px the chip stacks and a vertical rule between two centred lines
   would be meaningless, so it is off by default and only drawn once the chip
   goes back to a single row. */
.hero__meta-item + .hero__meta-item::before {
  content: none;
}

/* --- Hàng logo đơn vị bảo trợ / chỉ đạo / chủ trì -------------------------
   Each organisation is one pill: a small logo and its two lines of text side
   by side, backdrop-blurred glass over the photo. */
/* The marquee wrappers and the looping copy only exist for the phone layout.
   The marquee is a real box (not `display: contents`) because it, not the
   list, is the direct child of `.hero__content` and so is what the hero
   entrance below fades and lifts - a contents box takes no opacity/transform. */
.hero__orgs-marquee {
  display: flex;
  max-width: 100%;
}

.hero__orgs-track {
  display: contents;
}

.hero__orgs {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-sm);
  max-width: 100%;
  list-style: none;
}

/* Must come after `.hero__orgs` - same specificity, so order decides. */
.hero__orgs--clone {
  display: none;
}

/* Sized to its content, never stretched and never shrunk - the badge and the
   two lines of text next to it are always meant to read on one line each, so
   the pill has to stay exactly as wide as they need. Flexbox's own automatic
   minimum size would likely get there anyway (a nowrap child's min-content is
   its full width), but that is a subtle, cross-browser-inconsistent rule to
   lean on when `width: max-content` says the same thing outright. */
.hero-org {
  display: flex;
  flex: none;
  width: max-content;
  align-items: center;
  gap: var(--space-xs);
  padding: var(--space-xs) var(--space-lg) var(--space-xs) var(--space-xs);
  background: var(--c-org-badge-bg);
  border: 1px solid var(--c-glass-line);
  border-radius: var(--radius-pill);
  backdrop-filter: blur(2.45px);
  -webkit-backdrop-filter: blur(2.45px);
}

.hero-org__badge {
  flex: none;
  width: 54px;
  height: 54px;
}

.hero-org__badge img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.hero-org__logo--round {
  border-radius: var(--radius-pill);
}

.hero-org__text {
  display: flex;
  flex: none;
  flex-direction: column;
  gap: 1px;
  white-space: nowrap;
}

.hero-org__role {
  font-family: var(--font-body);
  font-weight: var(--fw-regular);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  letter-spacing: var(--ls-contact);
  color: var(--c-on-dark-strong);
}

.hero-org__name {
  font-family: var(--font-body);
  font-weight: var(--fw-semibold);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  letter-spacing: var(--ls-contact);
  color: var(--c-on-dark);
}

/* --- Headline --------------------------------------------------------------
   One field, two sizes: the event name and its edition/year, split by
   section-hero.php wherever the content itself has a dash. Same italic bold
   face as the brand lockup in the header and footer (Figma: Arimo/Arial Bold
   Italic) - this is the site's other display voice, not the body font.

   No text-transform: capitalize, even though the design has it. Vietnamese
   content here is typed with its correct casing already ("Lần thứ II năm
   2026"), and forcing every word's initial letter upper-case breaks that -
   the same reason the org names and footer/hero unit names never got it
   either. */
.hero__title {
  display: flex;
  flex-direction: column;
  font-family: var(--font-wordmark);
  font-weight: var(--fw-bold);
  line-height: var(--lh-heading);
  color: inherit;
}

.hero__title-line {
  font-size: var(--fs-hero-lg);
}

.hero__title-line--sub {
  font-size: var(--fs-hero);
}

/* Above the headline (Figma 6675:420 draws the two as one block): the content
   stack's 32px gap is pulled to 8px under it. */
.hero__subtitle {
  margin-bottom: calc(var(--space-lg) * -1); /* 32px gap - 24px = 8px */
  font-style: italic;
  color: inherit;
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-md);
}

/* --- Huy hiệu "Đơn vị thực hiện" -------------------------------------------
   Opaque white, not glass over the photo like the org pills - it needs to
   read as a credential, not as part of the same translucent system. Sits in
   the content stack between the headline and the buttons; the stack's 32px
   gap is pulled to 16px under it so it reads as part of the CTA group. */
.hero__credit {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
  padding: var(--space-2xs) var(--space-lg) var(--space-2xs) var(--space-xs);
  background: var(--c-hero-credit-bg);
  border: 1px solid var(--c-hero-credit-border);
  border-radius: var(--radius-pill);
}

/* Only pulled in when buttons follow; as the last item it would hang out of
   the content box and sit closer to the hero's bottom edge. */
.hero__credit:has(+ .hero__actions) {
  margin-bottom: calc(var(--space-md) * -1); /* 32px gap - 16px = 16px */
}

.hero__credit-badge {
  flex: none;
  width: 54px;
  height: 54px;
}

.hero__credit-badge img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.hero__credit-text {
  display: flex;
  flex-direction: column;
  gap: 1px;
  white-space: nowrap;
}

.hero__credit-label {
  font-family: var(--font-body);
  font-weight: var(--fw-regular);
  font-size: var(--fs-lg);
  line-height: var(--lh-body);
  letter-spacing: var(--ls-contact);
  color: var(--c-ink-pure);
}

.hero__credit-name {
  font-family: var(--font-body);
  font-weight: var(--fw-semibold);
  font-size: var(--fs-xl);
  line-height: var(--lh-body);
  letter-spacing: var(--ls-contact);
  color: var(--c-ink-pure);
}

/* --- Desktop: bottom-left, one row each -----------------------------------
   `.hero__content`'s and `.hero__credit`'s own switch from flow to
   `position: absolute` is not here - see the z-index fix below for why. */
@media (min-width: 1024px) {
  .hero {
    text-align: left;
  }

  .hero__content {
    align-items: flex-start;
    text-align: left;
  }

  .hero__meta {
    flex-direction: row;
    align-items: center;
    gap: var(--space-lg);
    padding: var(--space-2xs) var(--space-md);
    border-radius: var(--radius-pill);
    text-align: left;
  }

  .hero__meta-item--venue {
    justify-content: flex-start;
  }

  /* Now that the chip is one row, the rule between its two halves is
     meaningful again - centred in the gap, not stuck to either side. */
  .hero__meta-item + .hero__meta-item::before {
    content: "";
    position: absolute;
    inset: 0 auto 0 calc(var(--space-sm) * -1);
    width: 1px;
    background: var(--c-glass-line);
  }

  .hero__orgs {
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: flex-start;
  }

  .hero__actions {
    justify-content: flex-start;
  }
}

@keyframes hero-orgs-marquee {
  to {
    transform: translateX(-50%);
  }
}

/* --- Phone: Figma node 5939:602 -------------------------------------------
   A different composition, not a scaled-down desktop one: the date and the
   venue are two separate pills pinned under the header, the organisers are a
   single row you swipe along the bottom, and the "produced by" badge is one
   line under them. The headline and the two buttons are not drawn at all
   (the drawer carries the same two buttons) - the headline stays in the page
   for screen readers and search, the rest is simply not shown. */
@media (max-width: 767px) {
  .hero {
    justify-content: space-between;
    gap: var(--space-md);
    padding-bottom: var(--space-md);
  }

  .hero::before {
    background: var(--c-hero-overlay-sm);
  }

  /* Fills the space between the two ends so the meta pills sit at the top and
     the organiser row at the bottom. */
  .hero__content {
    flex: 1 1 auto;
    align-self: stretch;
    justify-content: space-between;
    gap: var(--space-md);
  }

  .hero__title {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .hero__subtitle,
  .hero__actions {
    display: none;
  }

  /* The wrapper stops being a chip; each line becomes its own glass pill. */
  .hero__meta {
    align-self: stretch;
    gap: var(--space-2xs);
    padding: 0;
    background: none;
    border: 0;
    border-radius: 0;
    font-size: var(--fs-sm);
  }

  .hero__meta-item {
    padding: var(--space-3xs) 8px;
    background: var(--c-glass-soft);
    border: 1px solid var(--c-glass-line);
    border-radius: var(--radius-pill);
    backdrop-filter: blur(2px);
    -webkit-backdrop-filter: blur(2px);
  }

  .hero__meta-item--venue {
    display: flex;
    align-self: stretch;
  }

  /* One row that loops endlessly, edge to edge. The track holds two identical
     lists, each ending in a gap-sized padding, so shifting by exactly half the
     track lands the second copy where the first began - no visible seam. */
  .hero__orgs-marquee {
    display: block;
    align-self: stretch;
    margin-top: auto; /* organisers + credit sink to the bottom together */
    margin-inline: calc(var(--gutter) * -1);
  }

  .hero__orgs-track {
    display: flex;
    width: max-content;
    animation: hero-orgs-marquee var(--hero-orgs-marquee-duration, 16s) linear infinite;
  }

  .hero__orgs,
  .hero__orgs--clone {
    flex-direction: row;
    flex-wrap: nowrap;
    flex: none;
    justify-content: flex-start;
    gap: 8px;
    max-width: none;
    padding-inline-end: 8px;
  }

  .hero__orgs--clone {
    display: flex;
  }

  /* Without motion the loop is pointless: one swipeable row, as before. */
  @media (prefers-reduced-motion: reduce) {
    .hero__orgs-marquee {
      overflow-x: auto;
      padding-inline: var(--gutter);
      scrollbar-width: none;
    }

    .hero__orgs-marquee::-webkit-scrollbar {
      display: none;
    }

    .hero__orgs-track {
      animation: none;
    }

    .hero__orgs--clone {
      display: none;
    }
  }

  .hero-org {
    gap: 8px;
    padding: 8px var(--space-lg) 8px 8px;
  }

  .hero-org__badge {
    width: 32px;
    height: 32px;
  }

  .hero-org__text {
    gap: 0;
    text-align: left;
  }

  .hero-org__role,
  .hero-org__name {
    font-size: var(--fs-label);
  }

  /* "ĐƠN VỊ THỰC HIỆN:VINAFIS" as one line. The colon is punctuation joining
     the two fields, so it is drawn here rather than typed into either. */
  .hero__credit {
    gap: var(--space-3xs);
    margin-bottom: 0;
    padding: 2px 8px 2px 2px;
    backdrop-filter: blur(2.45px);
    -webkit-backdrop-filter: blur(2.45px);
  }

  .hero__credit-badge {
    width: 24px;
    height: 24px;
  }

  .hero__credit-text {
    flex-direction: row;
    gap: 0;
  }

  .hero__credit-label,
  .hero__credit-name {
    font-weight: var(--fw-medium);
    font-size: var(--fs-label);
    text-transform: uppercase;
  }

  .hero__credit-label::after {
    content: ":";
  }
}

/* ==========================================================================
   Phone: what every section below the hero shares (Figma node 5907:441) -
   32px above and below, 24px at the sides, 24px headings.
   ========================================================================== */
@media (max-width: 767px) {
  .about,
  .events,
  .zones,
  .news {
    padding-block: var(--space-xl);
  }

  :is(.about, .events, .zones, .news) > .container {
    padding-inline: var(--space-lg);
  }

  :is(.about, .events, .zones, .news) .section-head__title {
    font-size: var(--fs-h3);
  }
}

/* ==========================================================================
   Về chúng tôi
   ========================================================================== */
.about__grid {
  display: flex;
  flex-direction: column;
  gap: var(--space-xl);
}

.about__intro {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  max-width: 861px;
}

.about__text {
  font-size: var(--fs-lead);
  line-height: var(--lh-relaxed);
  text-align: justify;
  color: var(--c-ink);
}

.about__text strong {
  font-weight: var(--fw-semibold);
}

.stats {
  display: grid;
  grid-template-columns: max-content;
  gap: var(--space-lg) var(--space-xl);
}

/* 2 cột dạng max-content như design: cột trái hẹp, cột phải rộng theo số */
@media (min-width: 460px) {
  .stats {
    grid-template-columns: repeat(2, max-content);
  }
}

.stat__value {
  font-family: var(--font-num);
  font-weight: var(--fw-bold);
  font-size: var(--fs-stat);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-stat);
  color: var(--c-text);
  white-space: nowrap;
  font-variation-settings: "wdth" 100;
}

.stat__value.is-counting {
  display: grid;
}

/* Invisible but still read aloud: the finished number holds the width. */
.stat__ghost {
  grid-area: 1 / 1;
  opacity: 0;
}

.stat__live {
  grid-area: 1 / 1;
}

.stat__label {
  margin-top: var(--space-3xs);
  font-weight: var(--fw-medium);
  font-size: var(--fs-sm);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-stat);
  color: var(--c-ink-strong);
}

@media (min-width: 1024px) {
  .about__grid {
    flex-direction: row;
    align-items: flex-start;
    justify-content: space-between;
    gap: clamp(48px, 8.33vw, 160px);
  }

  /* A fixed share of the row, not "whatever the stats leave over", so the
     column can never move while the numbers count. */
  .about__intro {
    flex: 0 0 44%;
    max-width: 861px;
  }

  .stats {
    flex: 0 0 auto;
    gap: var(--space-5xl) clamp(48px, 5.1vw, 98px);
  }
}

/* Phone: 14px body, a 2x2 grid of figures, and 10px labels (Figma 5907:627).
   The columns may shrink below their content so a narrow phone wraps the
   label instead of pushing the page sideways. */
@media (max-width: 767px) {
  .about__grid {
    gap: var(--space-md);
  }

  .about__intro {
    gap: var(--space-sm);
  }

  .about__text {
    font-size: var(--fs-sm);
  }

  .stats {
    grid-template-columns: repeat(2, minmax(0, max-content));
    gap: var(--space-xl) var(--space-4xl); /* design: 64 - 60 keeps "Khách tham quan chuyên ngành" on one line at 393 */
  }

  .stat__value {
    font-size: var(--fs-stat-sm);
  }

  .stat__label {
    font-size: var(--fs-caption);
  }
}

/* ==========================================================================
   Nhà tài trợ
   ========================================================================== */
.sponsors {
  padding-block: 28px;
}

/* The rail owns its own clipping and fade edges rather than the section
   owning them, because the rail sits inside .container now: on a very wide
   screen the ticker has to stop at the same width as everything else, and the
   fade has to sit at the rail's own edge, not the viewport's. */
.sponsors__rail {
  position: relative;
  display: flex;
  overflow: hidden;
}

.sponsors__track {
  display: flex;
  align-items: center;
  flex: none;
  gap: 28px;
  padding-right: 28px;
  animation: sponsors-scroll 45s linear infinite;
}

.sponsors__track img {
  width: 185px;
  height: 104px;
  padding-inline: var(--space-md);
  object-fit: contain;
}

/* Mờ dần 2 mép như design */
.sponsors__rail::before,
.sponsors__rail::after {
  content: "";
  position: absolute;
  inset-block: 0;
  width: clamp(48px, 12vw, 260px);
  z-index: 2;
  pointer-events: none;
}

.sponsors__rail::before {
  left: 0;
  background: linear-gradient(90deg, var(--c-bg), transparent);
}

.sponsors__rail::after {
  right: 0;
  background: linear-gradient(270deg, var(--c-bg), transparent);
}

@keyframes sponsors-scroll {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-100%);
  }
}

@media (prefers-reduced-motion: reduce) {
  .sponsors__track {
    animation: none;
  }

  .sponsors__rail {
    overflow-x: auto;
  }
}

/* ==========================================================================
   Các sự kiện chính
   ========================================================================== */

/* The entrance further down parks each event 48px off to the right until it
   is revealed. Unclipped, that widens the page on load (a phone then pans
   sideways and stretches every fixed element) until the section scrolls into
   view and the offset is gone. `clip`, not `hidden`: it does not turn the
   section into a scroll container or clip vertically. */
.events {
  overflow-x: clip;
}

.events__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xl);
}

/* Cột trái: tiêu đề + đoạn dẫn + poster */
.events__intro {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xl);
}

.events__photo {
  width: 100%;
  height: auto;
  aspect-ratio: 835 / 464;
  border-radius: var(--radius-md);
  object-fit: cover;
  background: var(--c-media-placeholder);
}

.events__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
}

.event {
  display: flex;
  align-items: flex-start;
  gap: var(--space-md);
}

.event + .event {
  border-top: 1px solid var(--c-border-pill);
  padding-top: var(--space-lg);
}

.event__num {
  width: clamp(72px, 7.3vw, 140px);
  height: auto;
  flex: none;
}

.event__text {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  min-width: 0;
}

.event__title {
  font-family: var(--font-display);
  font-weight: var(--fw-semibold);
  font-size: var(--fs-h3);
  line-height: var(--lh-normal);
  text-transform: capitalize;
  color: var(--c-ink);
}

.event__desc {
  font-size: var(--fs-body);
  line-height: var(--lh-relaxed);
  color: var(--c-ink);
}

/* 12px below the description comes from the .event__text gap; just keep the
   button its natural width instead of stretching across the column. */
.event__cta {
  align-self: flex-start;
}

.event__list {
  padding-left: var(--space-md);
  list-style: disc;
  font-size: var(--fs-body);
  line-height: var(--lh-relaxed);
  color: var(--c-ink);
}

.event__text {
  flex: 1 1 0;
}

/* Desktop (Figma 6826:557): the intro and poster run full width, and the
   events sit under them in two columns filled top to bottom. */
@media (min-width: 1024px) {
  /* Full-width banner now, not the 835px column it used to be. */
  .events__photo {
    aspect-ratio: 1830 / 727;
  }

  .events__list {
    display: grid;
    grid-template-rows: repeat(var(--rows, 3), auto);
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    column-gap: var(--space-4xl);
  }

  .event {
    gap: var(--space-lg);
  }

  /* The rule belongs between two events of the same column, not on top of
     the first one of the second column. */
  .event + .event--col-start {
    border-top: 0;
    padding-top: 0;
  }
}

/* Each section sets its own lead width (the shared default is 982px). */
.events .section-head__lead {
  max-width: 750px;
}

.zones .section-head__lead {
  max-width: 1440px;
}

/* Phone (Figma 5907:652): the 01-04 numerals are not drawn, so each event is
   just a title and a line, ruled off from the next. */
@media (max-width: 767px) {
  .events__body {
    gap: calc(var(--space-lg) * 2);
  }

  .events__intro {
    gap: var(--space-lg);
  }

  .events .section-head {
    gap: var(--space-sm);
  }

  .events .section-head__lead,
  .event__desc {
    font-size: var(--fs-label);
  }

  .events__photo {
    aspect-ratio: 361 / 210;
  }

  .events__list {
    gap: calc(var(--space-2xl) / 2);
  }

  .event + .event {
    padding-top: calc(var(--space-2xl) / 2);
  }

  .events .event__num {
    display: none; /* outranks .event__num--text, which is declared further down */
  }
}

/* ==========================================================================
   06 lĩnh vực triển lãm chuyên đề (carousel Swiper qua <vnf-carousel>)
   ========================================================================== */
.zones__inner {
  display: flex;
  flex-direction: column;
  gap: var(--space-xl);
}

.zones__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
}

/* SVG nút đã chứa sẵn 10px vùng đổ bóng mỗi bên → margin âm để mép nút thẳng lề */
.zones__nav {
  display: flex;
  align-items: center;
  margin: -10px;
  margin-left: auto; /* khi tiêu đề xuống dòng (mobile) nút vẫn nằm sát phải */
}

.carousel-nav {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  transition: opacity var(--dur) var(--ease), transform var(--dur-fast) var(--ease);
}

.carousel-nav[hidden] {
  display: none;
}

.carousel-nav img {
  width: 100%;
  height: 100%;
}

.carousel-nav:hover {
  transform: translateY(-1px);
}

.carousel-nav.is-disabled {
  opacity: 0.35;
  pointer-events: none;
}

/* Chừa chỗ cho đổ bóng card khỏi bị Swiper cắt mất */
.zones__carousel .swiper {
  margin: -8px;
  padding: 8px;
}

.zones__carousel .swiper-slide {
  width: min(500px, 82vw);
  height: auto;
}

.zone-card {
  display: flex !important;
  flex-direction: column;
  gap: 20px;
  height: 100%;
  padding: 26px var(--space-md) var(--space-md);
  border-radius: var(--radius-lg);
  background: var(--c-surface);
  box-shadow: var(--shadow-card);
}

.zone-card__title {
  font-weight: var(--fw-bold);
  font-size: calc(var(--fs-zone) * 0.85); /* a touch under the shared zone size so the longest title fits two lines at 4 per view */
  line-height: var(--lh-tight);
  text-align: center;
  text-transform: uppercase;
  color: var(--c-ink-navy);
}

.zone-card__photo {
  width: 100%;
  height: auto;
  margin-top: auto;
  aspect-ratio: 468 / 278;
  border-radius: var(--radius-md);
  object-fit: cover;
  background: var(--c-media-placeholder);
}

/* Phone (Figma 5907:700): 40px round arrows, cards 332px wide with the next
   one peeking 25px past the edge, 18px titles over a squarer photo. */
@media (max-width: 767px) {
  .zones__inner {
    gap: var(--space-lg);
  }

  /* The arrow artwork carries 9px of shadow room on each side at this size
     (64px canvas drawn at 58px, so the disc is 40px) - the negative margins
     put the disc, not the canvas, on the grid line. */
  .zones__nav {
    margin: -9px -9px -9px auto;
  }

  .zones__nav .carousel-nav {
    width: 58px;
    height: 58px;
  }

  .zones__nav .carousel-nav + .carousel-nav {
    margin-left: -8px; /* 10px between the discs, not 10 + both shadow rooms */
  }

  .zones__carousel .swiper-slide {
    width: min(500px, calc(100vw - 61px));
  }

  .zone-card {
    gap: 20px;
  }

  .zone-card__title {
    font-size: var(--fs-h4);
  }

  .zone-card__photo {
    aspect-ratio: 300 / 223;
  }
}

/* Không có JS/Swiper: vẫn xem được dạng danh sách cuộn ngang */
vnf-carousel:not([data-ready]) .swiper-wrapper {
  display: flex;
  gap: var(--space-sm);
  overflow-x: auto;
}

/* ==========================================================================
   Tin tức và sự kiện
   ========================================================================== */
.news__inner {
  display: flex;
  flex-direction: column;
  gap: var(--space-xl);
  align-items: center;
}

.news__inner > .section-head {
  align-self: stretch;
}

.news__grid {
  display: flex;
  flex-direction: column;
  gap: var(--space-xl);
  width: 100%;
}

.news__side {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
}

@media (min-width: 1024px) {
  /* Two independent columns.

     Deliberately different from the static site, which used align-items:
     stretch so both columns ended up the same height - the feature photo grew
     past its aspect ratio and the two side cards split whatever height was
     left. Here each column is only as tall as its own content. */
  .news__grid {
    flex-direction: row;
    align-items: flex-start;
  }

  .news__feature {
    flex: 0 0 calc(832/1824*100%);
    min-width: 0;
  }

  /* Keep the design aspect ratio instead of filling leftover row height. */
  .news__feature .news-card__photo {
    flex: 0 0 auto;
  }

  .news__feature .news-card__body {
    flex: none;
  }

  .news__side {
    flex: 1 0;
    min-width: 0;
  }

  /* Each side card is as tall as its own content, not an equal share. */
  .news__side .news-card {
    flex: 0 0 auto;
  }

  .news__side .news-card__body {
    justify-content: center;
  }

  .news__side .news-card__main {
    flex: 1 1 auto;
  }
}

/* Phone (Figma 5907:733). Scoped under .news because cards.css loads before
   this file at equal specificity otherwise. The side cards' photo is wider
   than the shared card ratio; the source line keeps the paper's name in its
   own column so a long one wraps under itself, not under the label. */
@media (max-width: 767px) {
  .news__inner {
    gap: var(--space-lg);
  }

  .news__grid,
  .news__side {
    gap: 22px;
  }

  .news .news__side .news-card__photo {
    aspect-ratio: 337 / 177;
  }

  .news .news-card__source {
    flex-wrap: nowrap;
    align-items: flex-start;
    font-size: var(--fs-caption);
  }

  .news .news-card__source span {
    flex: 1 1 0;
    min-width: 0;
  }

  .news .news-card__title {
    font-size: var(--fs-xl);
  }

  .news .news-card__desc {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    overflow: hidden;
    font-size: var(--fs-label);
  }

  .news .news-card__main {
    gap: 20px;
  }

  .news .btn--detail {
    min-height: 32px;
    font-size: var(--fs-caption);
  }

  .news .btn--detail img {
    width: 10px;
    height: auto;
  }

  .news .news__inner > .btn--pill-icon {
    gap: 9px;
    min-height: 36px;
    padding: 6px 6px 6px 12px;
    border-width: 0;
    font-size: var(--fs-label);
  }

  .news .news__inner > .btn--pill-icon img {
    width: 24px;
    height: 24px;
  }
}

/* --------------------------------------------------------------------------
   Zones fallback: if Swiper never loads, vnf-carousel tags itself
   [data-fallback] and the slides stay a plain scrollable row.
   -------------------------------------------------------------------------- */
vnf-carousel[data-fallback] .swiper-wrapper {
  display: flex;
  gap: 12px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
}

vnf-carousel[data-fallback] .swiper-slide {
  scroll-snap-align: start;
}

/* Fallback numeral when an event goes past the four artwork SVGs. */
.event__num--text {
  display: inline-block;
  width: 6rem;
  font-family: var(--font-num);
  font-size: var(--fs-numeral);
  font-weight: var(--fw-bold);
  line-height: 1; /* a display numeral wants no leading at all */
  color: var(--c-numeral);
}

/* --------------------------------------------------------------------------
   Hero layering, and - from 1024px - positioning.

   The static site stacks the photo and its tint on negative z-indexes, which
   relies on .hero being the stacking context. Inside the theme's extra
   .site / .site-main wrappers that stopped holding and the photo disappeared
   behind the page background, so the same three layers are expressed with
   non-negative indexes instead. Visually identical, just not fragile.

   `.hero__content` also switches to `position: absolute` here rather than
   back where the rest of its rules live: it starts the file at
   `position: relative` for this same z-index fix, and a later plain rule
   beats an earlier one under a media query at equal specificity regardless
   of which viewport the media query matches - so the desktop `absolute` has
   to be declared after this rule, not before it, or this `relative` would
   keep winning past 1024px too.
   -------------------------------------------------------------------------- */
.hero__bg {
  z-index: 0;
}

.hero::before {
  z-index: 1;
}

.hero__content {
  position: relative;
  z-index: 2;
}

@media (min-width: 1024px) {
  /*
   * Anchored to the same left/right edge .container's content sits on, not
   * to a flat --gutter - .hero has no container of its own to inherit that
   * from (the photo has to bleed full-width), so the formula is repeated
   * here: max(gutter, half of whatever the viewport has left over past
   * --content-max). A flat --gutter matched .container only up to the width
   * where content-max caps it (~1920px); past that .container stays centred
   * while a flat gutter keeps hugging the viewport edge, so on an ultrawide
   * screen the hero content and badge would sit far outside where every
   * other section's content starts and ends.
   */
  .hero__content {
    position: absolute;
    left: max(var(--gutter), calc((100% - var(--content-max)) / 2));
    bottom: calc(var(--gutter) + 28px); /* 76px at 1920, where the gutter tops out at 48 */
  }
}


/* --------------------------------------------------------------------------
   Hero entrance. The photo and its tint fade in first, then the content and
   the badge fade in and rise. Hidden only under `.has-reveal` (set by an
   inline script in <head>), so with JS off or reduced motion asked for, the
   hero is simply there. `main.js` adds `.is-revealed` once it is on screen.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  .has-reveal .hero[data-reveal] .hero__bg,
  .has-reveal .hero[data-reveal]::before {
    opacity: 0;
    transition: opacity 1.6s var(--ease-out);
  }

  /* Children of the content stack enter one after another. */
  .has-reveal .hero[data-reveal] .hero__content > * {
    --reveal-delay: calc(1s + var(--i, 0) * 0.1s);
    opacity: 0;
    transform: translateY(var(--reveal-dist));
    transition:
      opacity var(--reveal-dur) var(--ease) var(--reveal-delay),
      transform var(--reveal-dur) var(--ease) var(--reveal-delay);
  }

  .has-reveal .hero[data-reveal] .hero__content > :nth-child(2) { --i: 1; }
  .has-reveal .hero[data-reveal] .hero__content > :nth-child(3) { --i: 2; }
  .has-reveal .hero[data-reveal] .hero__content > :nth-child(4) { --i: 3; }
  .has-reveal .hero[data-reveal] .hero__content > :nth-child(5) { --i: 4; }
  .has-reveal .hero[data-reveal] .hero__content > :nth-child(6) { --i: 5; }

  .has-reveal .hero[data-reveal].is-revealed .hero__bg,
  .has-reveal .hero[data-reveal].is-revealed::before {
    opacity: 1;
  }

  .has-reveal .hero[data-reveal].is-revealed .hero__content > * {
    opacity: 1;
    transform: none;
  }
}

/* --------------------------------------------------------------------------
   About entrance: the intro fades in and slides in from the left. The stats
   count up in main.js instead. Same gating as the hero entrance above.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  .has-reveal .about[data-reveal] .about__intro {
    opacity: 0;
    transform: translateX(calc(var(--reveal-dist) * -2));
    transition:
      opacity var(--reveal-dur) var(--ease-out),
      transform var(--reveal-dur) var(--ease-out);
  }

  .has-reveal .about[data-reveal].is-revealed .about__intro {
    opacity: 1;
    transform: none;
  }
}

/* --------------------------------------------------------------------------
   Events entrance: the children of the intro slide in from the left one
   after another, the events list does the same from the right. `--i` on each
   `.event` is set in section-events.php, so any number of events staggers.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  .has-reveal .events[data-reveal] .events__intro > *,
  .has-reveal .events[data-reveal] .event {
    --reveal-delay: calc(var(--i, 0) * 0.25s);
    opacity: 0;
    transform: translateX(calc(var(--reveal-dist) * -2));
    transition:
      opacity var(--reveal-dur) var(--ease-out) var(--reveal-delay),
      transform var(--reveal-dur) var(--ease-out) var(--reveal-delay);
  }

  .has-reveal .events[data-reveal] .event {
    transform: translateX(calc(var(--reveal-dist) * 2));
  }

  .has-reveal .events[data-reveal] .events__intro > :nth-child(2) { --i: 1; }

  .has-reveal .events[data-reveal].is-revealed .events__intro > *,
  .has-reveal .events[data-reveal].is-revealed .event {
    opacity: 1;
    transform: none;
  }
}

/* --------------------------------------------------------------------------
   Zones entrance: the whole block (heading, arrows, carousel) fades in and
   rises as one.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  .has-reveal .zones[data-reveal] .zones__inner {
    opacity: 0;
    transform: translateY(var(--reveal-dist));
    transition:
      opacity var(--reveal-dur) var(--ease-out),
      transform var(--reveal-dur) var(--ease-out);
  }

  .has-reveal .zones[data-reveal].is-revealed .zones__inner {
    opacity: 1;
    transform: none;
  }
}

/* Sponsors entrance: the logo rail fades in and rises as one. */
@media (prefers-reduced-motion: no-preference) {
  .has-reveal .sponsors[data-reveal] > .container {
    opacity: 0;
    transform: translateY(var(--reveal-dist));
    transition:
      opacity var(--reveal-dur) var(--ease-out),
      transform var(--reveal-dur) var(--ease-out);
  }

  .has-reveal .sponsors[data-reveal].is-revealed > .container {
    opacity: 1;
    transform: none;
  }
}

/* --------------------------------------------------------------------------
   News entrance: the heading rises first, then the cards and the button one
   after another.

   These use a keyframe animation with `backwards` fill instead of the
   transition the other sections use: `.news-card` and `.btn` already carry
   their own hover transition, and a reveal transition would replace it (and
   leave its delay behind, making hover lag). With `backwards` the animation
   holds the hidden state during its delay and then hands back to the normal
   styles, so hover is untouched.
   -------------------------------------------------------------------------- */
@keyframes reveal-up {
  from {
    opacity: 0;
    transform: translateY(var(--reveal-dist));
  }
}

@media (prefers-reduced-motion: no-preference) {
  .has-reveal .news[data-reveal]:not(.is-revealed) .news__inner > .section-head,
  .has-reveal .news[data-reveal]:not(.is-revealed) .news-card,
  .has-reveal .news[data-reveal]:not(.is-revealed) .news__inner > .btn {
    opacity: 0;
  }

  .has-reveal .news[data-reveal].is-revealed .news__inner > .section-head,
  .has-reveal .news[data-reveal].is-revealed .news-card,
  .has-reveal .news[data-reveal].is-revealed .news__inner > .btn {
    animation: reveal-up var(--reveal-dur) var(--ease) calc(var(--i, 0) * 0.2s) backwards;
  }

  .news__feature { --i: 1; }
  .news__side > :nth-child(1) { --i: 2; }
  .news__side > :nth-child(2) { --i: 3; }
  .news__side > :nth-child(n + 3) { --i: 4; }
  .news__inner > .btn { --i: 4; }
}
