/* ============================================================
   Luxshine — About page (about.html), built from the designer's
   file "about us 2.ai" (user 2026-10-21: keep the design, images
   and copy, add strong motion). Tokens only.
   The artboard is 1080pt wide: 1pt = 1.78px on a 1920 screen.
   The frame is the file's 844pt column, 78% of the screen
   (1500px at 1920), and grows with the screen; type stays on the
   fixed DS steps (user 2026-10-21).
   ============================================================ */

.ab {
  --ab-ease: cubic-bezier(.16, 1, .3, 1);
  /* The office photo rises 62pt into the 380pt banner. */
  --ab-overlap: 5.74vw;
  overflow-x: clip;
  color: var(--color-black);
  background: var(--color-white);
}
.ab-wrap { width: min(78.125vw, 1500px); margin-inline: auto; }

/* ---------- Shared pieces ---------- */
/* Section titles: Bergola capitals on a gold bar that sits under the lower half of the letters
   and runs a little past both ends (the file's bar: 0.45em tall, 0.34em past each end). */
.ab-title { margin: 0; font-family: var(--font-heading); font-weight: 400; font-size: var(--sec-title-size); line-height: var(--sec-title-lh); text-transform: uppercase; color: inherit; }
.ab-title--center { text-align: center; }
.ab-title__t { position: relative; z-index: 0; display: inline-block; max-width: 100%; padding-inline: .34em; overflow: hidden; vertical-align: top; }
.ab-title__t::before { content: ""; position: absolute; z-index: -1; left: 0; right: 0; bottom: 0; height: .45em; background: var(--color-primary); transform-origin: 0 50%; }
.ab-title--center .ab-title__t::before { transform-origin: 50% 50%; }
.ab-title__in { display: block; text-wrap: balance; }

/* Running copy: 24px with the file's 1.5 leading, a blank line between paragraphs. */
.ab-copy { font-size: var(--h5-size); line-height: 1.5; }
/* Lines break where the file breaks them, short last lines included; narrow screens tidy them (below). */
.ab-copy p { margin: 0; }
.ab-copy p + p { margin-top: var(--space-8); }
.ab-copy .ab-title + p { margin-top: var(--space-6); }
.ab-lead { font-weight: 700; }
.ab-copy .ab-lead--tight + p { margin-top: var(--space-4); }
.ab-justify p { text-align: justify; hyphens: none; }

/* A photo in a fixed frame. Its curtain (the colour of what is behind it) lifts as it comes into view. */
.ab-shot { position: relative; overflow: hidden; margin: 0; background: var(--surface-beige); }
.ab-shot img { display: block; width: 100%; height: 100%; object-fit: cover; }
.ab-shot::after { content: ""; position: absolute; inset: 0; background: var(--ab-curtain, var(--color-white)); transform: scaleY(0); transform-origin: top; pointer-events: none; }

.ab-dark { --ab-curtain: var(--surface-dark-deep); color: var(--color-white); background: var(--surface-dark-deep) url("../assets/images/home/silk-dark.jpg") center / cover no-repeat; }
/* Light silk, faint as in the file: the photo under a white veil. */
.ab-silk { background: linear-gradient(color-mix(in srgb, var(--color-white) 60%, transparent), color-mix(in srgb, var(--color-white) 60%, transparent)), var(--color-white) url("../assets/images/home/silk-white.jpg") center / cover no-repeat; }

/* ---------- Banner: the team photo across the screen, 380pt of the 1080pt artboard ---------- */
.ab-banner { position: relative; height: max(35.19vw, 240px); overflow: hidden; background: var(--surface-beige); }
.ab-banner__media { position: absolute; inset: 0; }
.ab-banner__media img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* No wash over the photo (user 2026-10-22: the file's 58% grey wash made it look blurred), as on the product-group banners. */
/* The white header sits above the photo, never on it (user 2026-10-22: "background trắng luôn"), so the whole photo shows. */

/* ---------- About Luxshine: photo 461pt, gap 23pt, copy 360pt ---------- */
.ab-about { position: relative; z-index: 1; padding-bottom: var(--space-16); }
.ab-about__grid { display: grid; grid-template-columns: minmax(0, 461fr) minmax(0, 355fr); column-gap: var(--space-12); align-items: start; }
/* A thick white border lifts the photo off the banner it overlaps (comment cmvkh332vo71w). */
.ab-about__shot { margin-top: calc(-1 * var(--ab-overlap)); aspect-ratio: var(--ty-le-khoi_ngang); border: var(--space-3) solid var(--color-white); }
.ab-about__text { padding-top: var(--space-12); }
/* Side by side, the photo runs exactly as deep as the copy (comment cmvkh2i2dp61d): the copy sets the row, the photo fills it,
   cropped by cover. A fixed ratio ended 82px short of the copy at 1280 and 168px past it at 1920. */
@media (min-width: 1025px) {
  .ab-about__grid { align-items: stretch; }
  .ab-about__shot { aspect-ratio: auto; }
  .ab-about__shot img { position: absolute; inset: 0; }
}
.ab-about__text .ab-lead + p { margin-top: var(--space-6); }
/* About only (Wholesale shares these blocks): where the copy beside a photo outgrows it, the copy comes down a step of the DS
   scale; the photos keep their frames (comments cmvkhvlcwiwum, cmvkhvs4bckqx, cmvkhvzx0hau1). From 1800 they already match. */
@media (min-width: 1025px) and (max-width: 1799px) {
  .ab:not(.wh) :is(.ab-story__text, .ab-partners__text) { font-size: var(--h6-size); }
}
@media (min-width: 1025px) and (max-width: 1439px) {
  .ab:not(.wh) :is(.ab-story__text, .ab-partners__text) { font-size: var(--body-size); }
}
/* Come Meet Us: the invitation and the form stay in their column; the photos beside them stick under the header and travel
   with the words, stopping where the column ends (comment cmvkkq81fyxfd, as on Wholesale; it rolled back the form spread
   across the frame). About has no --header-h, so the fallback is its 72px header. */
@media (min-width: 1025px) {
  .ab:not(.wh) .ab-meet__media { position: sticky; top: calc(var(--header-h, calc(var(--space-16) + var(--space-2))) + var(--space-6)); }
}

/* ---------- Our Story: two rows of copy and photo on dark silk ---------- */
/* flow-root: the gold line's negative bottom margin ends the dark silk at the line's middle. */
.ab-story { position: relative; z-index: 2; display: flow-root; padding-top: var(--space-16); }
.ab-story .ab-title { margin-bottom: var(--space-8); }
.ab-story__row { display: grid; grid-template-columns: minmax(0, 435fr) minmax(0, 382fr); column-gap: var(--space-12); align-items: center; }
.ab-story__row + .ab-story__row { margin-top: var(--space-16); }
.ab-story__row--flip { grid-template-columns: minmax(0, 382fr) minmax(0, 435fr); }
.ab-story__row--flip .ab-story__shot { grid-column: 1; grid-row: 1; }
.ab-story__row--flip .ab-story__text { grid-column: 2; grid-row: 1; }
.ab-story__shot { aspect-ratio: var(--ty-le-cau_chuyen); }
/* The gold line across the seam between the dark and the light silk. js/about.js publishes its height
   (--ab-band-h) so the dark ends at its middle and the next section clears its lower half. */
.ab-band { position: relative; z-index: 1; margin: var(--space-24) var(--space-4) calc(var(--ab-band-h, var(--space-24)) / -2); padding: var(--space-6) var(--space-8); background: var(--primary-300); font-family: var(--font-heading); font-size: var(--h1-size); line-height: var(--h1-lh); text-transform: uppercase; text-align: center; color: var(--color-black); }
.ab-band__text { display: block; text-wrap: balance; }
/* Word and letter masks made by js/about.js (only when motion is welcome). */
:is(.ab-w, .ab-c) { display: inline-block; overflow: hidden; vertical-align: top; }
:is(.ab-w__in, .ab-c__in) { display: inline-block; }

/* ---------- People Behind Luxshine: eleven photos where the file puts them ---------- */
.ab-people { padding: calc(var(--ab-band-h, var(--space-24)) / 2 + var(--space-16)) 0 var(--space-16); }
.ab-people .ab-title { margin-bottom: var(--space-16); }
/* The cluster is 832 × 578pt and starts 12pt in from the column's left edge. */
.ab-crowd { position: relative; width: 98.5%; margin-left: auto; aspect-ratio: var(--ty-le-cum_nhan_su); }
.ab-crowd > figure { position: absolute; left: var(--x); top: var(--y); width: var(--w); height: var(--h); margin: 0; }
/* The small photos carry the file's thin light-gold edge; the portrait does not. */
.ab-crowd__tile { overflow: hidden; border: 2px solid var(--primary-300); background: var(--surface-beige); }
.ab-crowd img { display: block; width: 100%; height: 100%; object-fit: cover; }
.ab-crowd__ceo { z-index: 1; display: grid; grid-template-rows: minmax(0, 1fr) auto; overflow: hidden; background: var(--surface-beige); }
.ab-crowd__cap { padding: var(--space-2) var(--space-4); background: var(--primary-300); font-family: var(--font-heading); font-size: var(--h4-size); line-height: var(--h4-lh); text-transform: uppercase; text-align: center; text-wrap: balance; color: var(--color-black); }
.ab-people__text { margin-top: var(--space-8); }

/* ---------- Partners' Reputation First: copy 372pt, gap 27pt, photo 445pt ---------- */
.ab-partners { padding-block: var(--space-24); }
.ab-partners__grid { display: grid; grid-template-columns: minmax(0, 372fr) minmax(0, 445fr); column-gap: var(--space-12); align-items: center; }
.ab-partners__shot { aspect-ratio: var(--ty-le-uy_tin); }
/* At the section-title size of 56 (user 2026-10-23) the one-line title ran under the photo at 1920, so it wraps where
   it must and each line keeps its bar, as titles do on phones. */
.ab-partners .ab-title__t { display: inline; padding: 0; overflow: visible; }
.ab-partners .ab-title__t::before { content: none; }
.ab-partners .ab-title__in { display: inline; padding-inline: .34em; background: linear-gradient(var(--color-primary), var(--color-primary)) 0 100% / 100% .45em no-repeat; -webkit-box-decoration-break: clone; box-decoration-break: clone; }

/* ---------- Our Journey: five years on a gold line ---------- */
.ab-journey { padding: var(--space-16) 0 calc(var(--space-16) * 2); }
.ab-journey .ab-title { margin-bottom: var(--space-12); }
.ab-years { --ab-gap: var(--space-12); --ab-dot: var(--space-6); position: relative; display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); column-gap: var(--ab-gap); margin: 0; padding: 0; list-style: none; }
/* The line runs from the first dot's centre to the last one's. */
.ab-years::before { content: ""; position: absolute; top: calc(var(--space-3) - 1px); left: calc((100% - 4 * var(--ab-gap)) / 10); right: calc((100% - 4 * var(--ab-gap)) / 10); height: 2px; background: var(--color-primary); transform-origin: 0 50%; }
.ab-year { display: flex; flex-direction: column; align-items: center; min-width: 0; text-align: center; }
.ab-year__dot { position: relative; z-index: 1; flex: none; width: var(--ab-dot); height: var(--ab-dot); border: 2px solid var(--color-primary); border-radius: var(--radius-round); background: var(--primary-300); }
.ab-year__num { margin: var(--space-6) 0 var(--space-4); font-family: var(--font-heading); font-size: var(--h2-size); line-height: var(--h2-lh); font-variant-numeric: lining-nums; color: var(--color-black); }
.ab-year__card { display: flex; flex: 1; flex-direction: column; width: 100%; }
.ab-year__shot { aspect-ratio: var(--ty-le-hanh_trinh); }
.ab-year__body { flex: 1; padding: var(--space-3) var(--space-4) var(--space-4); background: var(--surface-beige); }
/* Text in the year cards balanced against the 48px year (comment cmvksgtlnmg6s, 2026-10-23: "cân đối lại font size
   content"): the name 16 → 20, the line under it 12 → 16. On wide screens each name holds a two-line slot, centred, so
   the five descriptions start on one line. */
.ab-year__title { margin: 0; font-family: var(--font-heading); font-weight: 400; font-size: var(--h6-size); line-height: var(--h6-lh); text-transform: uppercase; text-wrap: balance; color: var(--color-black); }
@media (min-width: 1025px) { .ab-year__title { display: flex; align-items: center; justify-content: center; min-height: calc(var(--h6-lh) * 2); } }
.ab-year__text { margin: var(--space-2) 0 0; font-size: var(--body-size); line-height: var(--body-lh); color: var(--color-black); }

/* ---------- Luxshine Today: map 578pt beside the reach, figures across the seam ---------- */
.ab-today { position: relative; z-index: 1; display: flow-root; padding-top: var(--space-16); }
/* The silk-and-terraces photo fills the section down to the middle of the figures band. */
.ab-today__bg { position: absolute; inset: 0; z-index: 0; overflow: hidden; pointer-events: none; }
.ab-today__bg img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: 50% 70%; }
/* Desktop, two columns (user 2026-10-22, the haze taken off): silk behind the map and the copy (.ab-silk), the plain terraces photo
   in the bottom-right corner below them, down to the band's middle. It reaches up into the free space under the country list
   (--ab-today-room, measured by js/about.js; 0 at 1280, ~110px at 1920) and fades out in an ellipse, not along its top edge only
   (user 2026-10-22: "cho mờ theo hình tròn"); the ellipse is clear at the box's top and left, so no text ever sits on the photo. */
@media (min-width: 1280px) {
  .ab-today__bg { inset: auto 0 0 auto; width: 55%; height: calc(var(--space-16) * 2 + var(--space-8) + var(--ab-stats-h, var(--space-24)) / 2 + var(--ab-today-room, 0px));
    -webkit-mask-image: radial-gradient(ellipse 74% 80% at 72% 80%, var(--color-black) 35%, transparent);
            mask-image: radial-gradient(ellipse 74% 80% at 72% 80%, var(--color-black) 35%, transparent); }
}
.ab-today > .ab-wrap { position: relative; z-index: 1; }
.ab-today .ab-title { margin-bottom: calc(var(--space-16) * 2); }
.ab-today__grid { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: var(--space-16); align-items: center; margin-bottom: calc(var(--space-16) * 2 + var(--space-8)); }
.ab-map { position: relative; margin: 0; }
.ab-map img { display: block; width: 100%; height: auto; }
.ab-map__labels { position: absolute; inset: 0; margin: 0; padding: 0; list-style: none; pointer-events: none; }
.ab-map__labels li { position: absolute; left: var(--x); top: var(--y); translate: -50% 0; font-family: var(--font-heading); font-size: var(--caption-size); line-height: 1.1; text-transform: uppercase; text-align: center; white-space: nowrap; color: var(--color-black); }
.ab-reach { min-width: 0; color: var(--color-black); }
.ab-reach__vn { margin: 0; font-family: var(--font-heading); font-size: var(--display-2-size); line-height: 1; text-transform: uppercase; color: var(--color-red); }
.ab-reach__serve { margin: var(--space-3) 0 0; font-size: var(--display-3-size); line-height: 1.25; }
.ab-reach__count { font-weight: 700; font-size: var(--h2-size); line-height: 1; white-space: nowrap; }
.ab-reach__list { display: grid; grid-template-rows: repeat(4, auto); grid-auto-flow: column; justify-content: start; gap: var(--space-3) var(--space-6); margin: var(--space-6) 0 0; padding: 0; list-style: none; font-size: var(--h5-size); line-height: var(--h5-lh); }
.ab-reach__list li { display: flex; align-items: center; gap: var(--space-2); white-space: nowrap; }
.ab-reach__list li::before { content: ""; flex: none; width: var(--space-3); height: var(--space-3); border-radius: var(--radius-round); background: var(--color-primary); }
/* Figures: black band, five figures split by gold rules. js/about.js publishes its height (--ab-stats-h). */
.ab-stats { position: relative; z-index: 1; display: flex; margin: 0 0 calc(var(--ab-stats-h, var(--space-24)) / -2); padding: var(--space-16) var(--space-12); list-style: none; background: var(--surface-dark); color: var(--color-white); }
.ab-stat { position: relative; display: flex; flex: 1 1 auto; flex-direction: column; align-items: center; padding-inline: var(--space-6); text-align: center; }
.ab-stat + .ab-stat::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 1px; background: var(--color-primary); transform-origin: 50% 50%; }
.ab-stat__num { font-family: var(--font-heading); font-weight: 400; font-size: var(--display-2-size); line-height: 1; font-variant-numeric: tabular-nums lining-nums; white-space: nowrap; }
.ab-stat__label { margin-top: var(--space-2); font-family: var(--font-heading); font-size: var(--h6-size); line-height: var(--h6-lh); text-transform: uppercase; text-wrap: balance; }
/* A counting figure is held at its final width (js/about.js) so the band's columns and gold rules do not shift. Neither brand font
   has even-width digits (a 1 is about 60% of a 0), so a wider value on the way overflows to the left, away from its suffix. */
[data-ab-count] { display: inline-flex; justify-content: flex-end; white-space: nowrap; }

/* ---------- Vision & Mission: two cards, photo over a gold panel ---------- */
.ab-vm { padding: calc(var(--ab-stats-h, var(--space-24)) / 2 + var(--space-12)) 0 calc(var(--space-16) * 2); }
.ab-vm .ab-title { margin-bottom: var(--space-16); }
.ab-vm__grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-8); }
.ab-vm__card { display: flex; flex-direction: column; min-width: 0; box-shadow: var(--shadow-sm), var(--shadow-lg); } /* a tight contact shadow under a soft wide one, both DS tokens (comment cmvkh3ob0nioy) */
.ab-vm__shot { aspect-ratio: var(--ty-le-tam_nhin); }
.ab-vm__body { flex: 1; padding: var(--space-6) var(--space-12) var(--space-8); background: var(--primary-300); }
.ab-vm__title { margin: 0; font-family: var(--font-heading); font-weight: 400; font-size: var(--h4-size); line-height: var(--h4-lh); text-transform: uppercase; color: var(--color-black); }
.ab-vm__text { margin: var(--space-1) 0 0; font-size: var(--body-size); line-height: var(--body-lh); text-align: justify; hyphens: none; color: var(--color-black); }

/* ---------- Come Meet Us: photos 413pt, gap 28pt, copy and form 403pt ---------- */
.ab-meet { padding: var(--space-12) 0 var(--space-24); }
/* Desktop: this block is narrower than the page frame. The file's body copy is about 25.4px at 1920 and the site's
   token is 24px, so the frame shrinks by the same 24/25.4 and the copy breaks where the file does (user 2026-10-22). */
@media (min-width: 1025px) {
  .ab-meet > .ab-wrap { width: min(73.75vw, 1416px); }
}
.ab-meet .ab-title { margin-bottom: var(--space-16); }
.ab-meet__grid { display: grid; grid-template-columns: minmax(0, 413fr) minmax(0, 403fr); column-gap: var(--space-12); align-items: start; }
.ab-meet__media { margin: 0; }
.ab-meet__photos { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-rows: repeat(2, minmax(0, 1fr)); gap: var(--space-4); aspect-ratio: var(--ty-le-gap_go); }
.ab-meet__main { grid-column: 1 / -1; }
.ab-meet__note { margin-top: var(--space-4); font-size: var(--h6-size); line-height: var(--h6-lh); font-weight: 300; font-style: italic; text-align: center; color: var(--color-black); }
.ab-meet__text { min-width: 0; }
.ab-form { margin-top: var(--space-12); }
.ab-form__title { margin: 0 0 var(--space-8); font-family: var(--font-body); font-size: var(--h2-size); line-height: var(--h2-lh); font-weight: 600; text-align: center; color: var(--color-black); }
.ab-form__grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-8) var(--space-16); }
.ab-field { position: relative; display: flex; flex-direction: column; min-width: 0; }
.ab-field--full { grid-column: 1 / -1; }
.ab-field label { font-size: var(--h6-size); line-height: var(--h6-lh); color: var(--color-black); }
.ab-field :is(input, textarea) { width: 100%; padding: var(--space-2) 0; border: 0; border-radius: 0; background: transparent; font: inherit; font-size: var(--h6-size); line-height: var(--h6-lh); color: var(--color-black); resize: none; }
.ab-field textarea { min-height: calc(var(--h6-lh) * 2 + var(--space-4)); }
/* The underline is its own layer so it can draw in, and turns gold while the field holds the focus. */
.ab-field::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px; background: var(--color-black); transform-origin: 0 50%; transition: background-color .2s ease; }
.ab-field:focus-within::after { height: 2px; background: var(--color-primary); }
.ab-field :is(input, textarea):focus { outline: none; }
.ab-field :is(input, textarea):focus-visible { outline: 2px solid var(--color-primary); outline-offset: 4px; }
.ab-field:has(:user-invalid)::after { background: var(--color-error); }
.ab-form__submit { display: block; width: 100%; margin-top: var(--space-6); padding: var(--space-3) var(--space-6); border: 0; border-radius: 0; background: var(--color-black); font: inherit; font-size: var(--h5-size); line-height: var(--h5-lh); font-weight: 600; color: var(--color-white); cursor: pointer; transition: background-color .25s ease, color .25s ease, transform .15s ease; }
.ab-form__submit:is(:hover, :focus-visible) { background: var(--color-primary); color: var(--color-black); }
.ab-form__submit:focus-visible { outline: 2px solid var(--color-black); outline-offset: 2px; }
.ab-form__submit:active { transform: scale(.98); }
.ab-form__submit:disabled { cursor: default; background: var(--color-black); color: var(--color-white); transform: none; }

/* ============================================================
   Motion. One language with the Remy and Quality pages: titles
   rise out of their masks and the gold bar swipes in, curtains
   lift off photos while the pictures settle, copy comes up.
   Every effect is transform / opacity, waits for scripting
   (.js) and drops out with reduced motion.
   ============================================================ */
@media (prefers-reduced-motion: no-preference) {
  /* The banner settles once on load. */
  .ab-banner__media img { animation: ab-settle 2s var(--ab-ease) both; }

  .js .ab-title[data-reveal] .ab-title__in { transform: translateY(105%); transition: transform 1.1s var(--ab-ease) var(--d, 0s); }
  .js .ab-title[data-reveal] .ab-title__t::before { transform: scaleX(0); transition: transform 1s var(--ab-ease) calc(var(--d, 0s) + .45s); }
  .js .ab-title[data-reveal].is-in .ab-title__in, .js .ab-title[data-reveal].is-in .ab-title__t::before { transform: none; }

  .js .ab-rise[data-reveal] { opacity: 0; transform: translateY(32px); transition: opacity .9s var(--ab-ease) var(--d, 0s), transform .9s var(--ab-ease) var(--d, 0s); }
  .js .ab-rise[data-reveal].is-in { opacity: 1; transform: none; }

  /* Photos: the curtain lifts, the picture settles from 1.14. */
  .js :is(.ab-shot[data-reveal], [data-reveal] .ab-shot)::after { transform: scaleY(1); transition: transform 1.2s var(--ab-ease) var(--d, 0s); }
  .js :is(.ab-shot[data-reveal].is-in, [data-reveal].is-in .ab-shot)::after { transform: scaleY(0); }
  /* transform is listed too: this rule outranks the hover rule below, and without it the hover zoom jumped with no easing. */
  .js :is(.ab-shot[data-reveal], [data-reveal] .ab-shot) img { scale: 1.14; transition: scale 1.8s var(--ab-ease) var(--d, 0s), transform 1s var(--ab-ease); }
  .js :is(.ab-shot[data-reveal].is-in, [data-reveal].is-in .ab-shot) img { scale: 1; }
  /* The office photo lifts upward out of the banner; the Partners photo uncovers from the left. */
  .ab-about__shot::after { transform-origin: bottom; }
  .js .ab-about__shot[data-reveal] { opacity: 0; transform: translateY(64px); transition: opacity 1s var(--ab-ease) .2s, transform 1.4s var(--ab-ease) .2s; }
  .js .ab-about__shot[data-reveal].is-in { opacity: 1; transform: none; }
  .ab-partners__shot::after { transform-origin: right; }
  .js .ab-partners__shot[data-reveal]::after { transform: scaleX(1); }
  .js .ab-partners__shot[data-reveal].is-in::after { transform: scaleX(0); }

  /* The gold line opens from its centre, then its words come up one after another (js/about.js splits them). */
  .js .ab-band[data-reveal] { transform: scaleX(0); transition: transform 1.2s var(--ab-ease); }
  .js .ab-band[data-reveal].is-in { transform: none; }
  .js .ab-band[data-reveal] .ab-w__in { transform: translateY(105%); transition: transform .9s var(--ab-ease) calc(.5s + var(--w, 0) * .07s); }
  .js .ab-band[data-reveal].is-in .ab-w__in { transform: none; }

  /* People: the portrait lands first, then the ten photos open out from behind it, clockwise from the top. */
  .js .ab-crowd[data-reveal] .ab-crowd__ceo { opacity: 0; scale: .86; transition: opacity .9s var(--ab-ease), scale 1.4s var(--ab-ease); }
  .js .ab-crowd[data-reveal] .ab-crowd__tile { opacity: 0; translate: calc(var(--fx) * -60%) calc(var(--fy) * -60%); scale: .55; transition: opacity .7s var(--ab-ease) calc(.25s + var(--i) * .07s), translate 1.3s var(--ab-ease) calc(.25s + var(--i) * .07s), scale 1.3s var(--ab-ease) calc(.25s + var(--i) * .07s); }
  .js .ab-crowd[data-reveal].is-in :is(.ab-crowd__ceo, .ab-crowd__tile) { opacity: 1; translate: none; scale: none; }
  .js .ab-crowd[data-reveal] .ab-crowd__cap { transform: translateY(100%); transition: transform .9s var(--ab-ease) .5s; }
  .js .ab-crowd[data-reveal].is-in .ab-crowd__cap { transform: none; }

  /* Journey: the line draws left to right; each dot pops as the line reaches it, then its year and card land. */
  .js .ab-years[data-reveal]::before { transform: scaleX(0); transition: transform 1.4s cubic-bezier(.65, 0, .35, 1) .2s; }
  .js .ab-years[data-reveal].is-in::before { transform: none; }
  .js .ab-years[data-reveal] .ab-year__dot { transform: scale(0); transition: transform .6s cubic-bezier(.34, 1.56, .64, 1) calc(.2s + var(--i) * .3s); }
  .js .ab-years[data-reveal] .ab-year__num { opacity: 0; transform: translateY(24px); transition: opacity .7s var(--ab-ease) calc(.35s + var(--i) * .3s), transform .9s var(--ab-ease) calc(.35s + var(--i) * .3s); }
  .js .ab-years[data-reveal] .ab-year__card { opacity: 0; transform: translateY(48px); transition: opacity .8s var(--ab-ease) calc(.45s + var(--i) * .3s), transform 1.1s var(--ab-ease) calc(.45s + var(--i) * .3s); }
  .js .ab-years[data-reveal] .ab-shot::after { transition-delay: calc(.5s + var(--i) * .3s); }
  .js .ab-years[data-reveal] .ab-shot img { transition-delay: calc(.5s + var(--i) * .3s), 0s; } /* the settle waits; the hover zoom does not */
  .js .ab-years[data-reveal].is-in :is(.ab-year__dot, .ab-year__num, .ab-year__card) { opacity: 1; transform: none; }

  /* Today: the map comes up, its labels follow; VIETNAM rises letter by letter, the count runs, the countries slide in. */
  .js .ab-map[data-reveal] img { opacity: 0; transform: translateY(32px) scale(.97); transition: opacity 1.1s var(--ab-ease), transform 1.4s var(--ab-ease); }
  .js .ab-map[data-reveal] .ab-map__labels li { opacity: 0; transform: translateY(8px); transition: opacity .6s var(--ab-ease) calc(.6s + var(--i) * .08s), transform .6s var(--ab-ease) calc(.6s + var(--i) * .08s); }
  .js .ab-map[data-reveal].is-in :is(img, .ab-map__labels li) { opacity: 1; transform: none; }
  .js .ab-reach[data-reveal] .ab-c__in { transform: translateY(105%); transition: transform 1s var(--ab-ease) calc(.2s + var(--c, 0) * .05s); }
  .js .ab-reach[data-reveal] :is(.ab-reach__serve, .ab-reach__list li) { opacity: 0; transform: translateX(-24px); transition: opacity .7s var(--ab-ease) calc(.6s + var(--i, 0) * .06s), transform .8s var(--ab-ease) calc(.6s + var(--i, 0) * .06s); }
  .js .ab-reach[data-reveal] .ab-reach__list li::before { transform: scale(0); transition: transform .5s cubic-bezier(.34, 1.56, .64, 1) calc(.8s + var(--i, 0) * .06s); }
  .js .ab-reach[data-reveal].is-in :is(.ab-c__in, .ab-reach__serve, .ab-reach__list li), .js .ab-reach[data-reveal].is-in .ab-reach__list li::before { opacity: 1; transform: none; }

  /* Figures: the band opens, the rules draw, each figure counts up (js/about.js) as it rises. */
  .js .ab-stats[data-reveal] { transform: scaleX(.92); opacity: 0; transition: transform 1.2s var(--ab-ease), opacity .8s var(--ab-ease); }
  .js .ab-stats[data-reveal] .ab-stat { opacity: 0; transform: translateY(24px); transition: opacity .8s var(--ab-ease) calc(.3s + var(--i) * .1s), transform .9s var(--ab-ease) calc(.3s + var(--i) * .1s); }
  .js .ab-stats[data-reveal] .ab-stat::before { transform: scaleY(0); transition: transform 1s var(--ab-ease) calc(.5s + var(--i) * .1s); }
  .js .ab-stats[data-reveal].is-in, .js .ab-stats[data-reveal].is-in .ab-stat, .js .ab-stats[data-reveal].is-in .ab-stat::before { opacity: 1; transform: none; }

  .js .ab-vm__card[data-reveal] { opacity: 0; transform: translateY(48px); transition: opacity .9s var(--ab-ease) var(--d, 0s), transform 1.1s var(--ab-ease) var(--d, 0s); }
  .js .ab-vm__card[data-reveal].is-in { opacity: 1; transform: none; }

  /* Meet Us: the three photos uncover in turn; the form's fields draw their underlines one after another. */
  .js .ab-meet__media[data-reveal] .ab-meet__left img, .js .ab-meet__media[data-reveal] .ab-meet__left::after { transition-delay: .15s; }
  .js .ab-meet__media[data-reveal] .ab-meet__right img, .js .ab-meet__media[data-reveal] .ab-meet__right::after { transition-delay: .3s; }
  .js .ab-meet__media[data-reveal] .ab-meet__note { opacity: 0; transition: opacity .9s var(--ab-ease) .6s; }
  .js .ab-meet__media[data-reveal].is-in .ab-meet__note { opacity: 1; }
  .js .ab-form[data-reveal] :is(.ab-form__title, .ab-field, .ab-form__submit) { opacity: 0; transform: translateY(24px); transition: opacity .7s var(--ab-ease) calc(var(--i, 0) * .07s), transform .8s var(--ab-ease) calc(var(--i, 0) * .07s); }
  .js .ab-form[data-reveal] .ab-field { transition-delay: calc(.1s + var(--i) * .07s); }
  .js .ab-form[data-reveal] .ab-form__submit { transition: opacity .7s var(--ab-ease) .55s, transform .8s var(--ab-ease) .55s, background-color .25s ease, color .25s ease; }
  .js .ab-form[data-reveal] .ab-field::after { transform: scaleX(0); transition: transform .9s var(--ab-ease) calc(.3s + var(--i) * .07s), background-color .2s ease; }
  .js .ab-form[data-reveal].is-in :is(.ab-form__title, .ab-field, .ab-form__submit), .js .ab-form[data-reveal].is-in .ab-field::after { opacity: 1; transform: none; }
}

/* Under the pointer: photos lean in a little. */
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  /* Same lean as the homepage cards (1.06 over 1s). Kept on its own layer so it does not snap sharper or shift a pixel at either end. */
  .ab-year__shot img, .ab-vm__shot img { transition: scale 1.8s var(--ab-ease), transform 1s var(--ab-ease); will-change: transform; }
  .ab-year__card:hover .ab-year__shot img, .ab-vm__card:hover .ab-vm__shot img { transform: scale(1.06); }
  .ab-crowd__tile:hover { z-index: 2; }
  /* No drift with the pointer (user 2026-10-22: the photos stay put); each photo still leans in on its own. */
  .ab-crowd__tile img { transition: transform .8s var(--ab-ease); }
  .ab-crowd__tile:hover img { transform: scale(1.08); }
}

/* Scroll scenes: desktop, motion welcome, and a browser that can tie an animation to the scroll position.
   The banner drifts down behind the page, the story photos and the showroom drift inside their frames,
   the terraces sink behind the map. No scroll listeners. */
@media (min-width: 1025px) and (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .ab-banner__media { animation: ab-drift-down linear both; animation-timeline: view(); animation-range: exit 0% exit 100%; }
    .ab-story__shot, .ab-partners__shot { --ab-par: 6%; }
    :is(.ab-story__shot, .ab-partners__shot) img { animation: ab-par linear both; animation-timeline: view(); animation-range: cover 0% cover 100%; }
    .ab-today__bg img { animation: ab-par-bg linear both; animation-timeline: view(); animation-range: cover 0% cover 100%; }
  }
}
@keyframes ab-settle { from { scale: 1.12; } }
@keyframes ab-drift-down { to { transform: translateY(30%); } }
/* The story photos are 1.14 at rest while their curtain is down, then 1; the drift stays inside that margin. */
@keyframes ab-par { from { transform: translateY(calc(var(--ab-par) * -1)) scale(1.12); } to { transform: translateY(var(--ab-par)) scale(1.12); } }
@keyframes ab-par-bg { from { transform: translateY(-4%) scale(1.08); } to { transform: translateY(4%) scale(1.08); } }

/* ============================================================
   Narrower screens
   ============================================================ */
/* A title that wraps keeps a bar under each of its lines (the bar rides on the text as a background),
   and rises as a whole: a one-line mask does not hold two lines. Partners' title from 1899px down, every title on a phone. */
@media (max-width: 1899px) {
  .ab-partners .ab-title__t { display: inline; padding: 0; overflow: visible; }
  .ab-partners .ab-title__t::before { content: none; }
  .ab-partners .ab-title__in { display: inline; padding-inline: .34em; white-space: normal; background: linear-gradient(var(--color-primary), var(--color-primary)) 0 100% / 100% .45em no-repeat; -webkit-box-decoration-break: clone; box-decoration-break: clone; }
}
@media (max-width: 1899px) and (prefers-reduced-motion: no-preference) {
  .js .ab-partners .ab-title[data-reveal] { opacity: 0; transform: translateY(24px); transition: opacity .8s var(--ab-ease), transform .9s var(--ab-ease); }
  .js .ab-partners .ab-title[data-reveal].is-in { opacity: 1; transform: none; }
  .js .ab-partners .ab-title[data-reveal] .ab-title__in { transform: none; }
}
@media (max-width: 1799px) {
  /* The figures keep to one row: smaller figures and labels. */
  .ab-stat__num { font-size: var(--h1-size); }
  .ab-stat__label { font-size: var(--body-size); line-height: var(--body-lh); }
  .ab-stat { padding-inline: var(--space-4); }
  .ab-stats { padding-inline: var(--space-8); }
}
/* The gold line stays on one line as in the file: one type step down each time the frame gets too narrow for it. */
@media (max-width: 1599px) {
  .ab-band { font-size: var(--h3-size); line-height: var(--h3-lh); }
  .ab-crowd__cap { font-size: var(--h5-size); line-height: var(--h5-lh); }
}
@media (max-width: 1439px) {
  .ab-copy { font-size: var(--h6-size); }
  .ab-reach__list { font-size: var(--h6-size); line-height: var(--h6-lh); }
  .ab-reach__count { font-size: var(--h3-size); }
  .ab-years { --ab-gap: var(--space-6); }
  .ab-form__title { font-size: var(--h3-size); line-height: var(--h3-lh); }
}
@media (max-width: 1365px) {
  .ab-band { font-size: var(--h4-size); line-height: var(--h4-lh); }
}
@media (max-width: 1279px) {
  .ab-crowd__cap { padding: var(--space-1) var(--space-2); font-size: var(--h6-size); line-height: var(--h6-lh); }
  /* Five narrow cards: a slimmer side inset keeps every milestone name on two lines at 20px. */
  .ab-year__body { padding-inline: var(--space-3); }
  /* Figures in two rows of three and two; a rule only between neighbours on the same row. */
  .ab-stats { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); row-gap: var(--space-8); padding: var(--space-12) var(--space-6); }
  .ab-stat { grid-column: span 2; }
  .ab-stat:nth-child(n + 4) { grid-column: span 3; }
  .ab-stat:nth-child(4)::before { content: none; }
  /* Map above, the reach beside nothing: centred under it. */
  .ab-today__grid { grid-template-columns: minmax(0, 1fr); row-gap: var(--space-12); justify-items: center; margin-bottom: calc(var(--space-24) * 3); }
  /* One column from here down (user 2026-10-22): the terraces are a strip of their own under the country list, never behind the text;
     its top fades into the silk and the figures band covers only its last 48px. */
  .ab-today__bg { top: auto; height: calc(var(--space-24) * 3 + var(--space-12));
    -webkit-mask-image: linear-gradient(to bottom, transparent, var(--color-black) var(--space-24));
            mask-image: linear-gradient(to bottom, transparent, var(--color-black) var(--space-24)); }
  .ab-stats { margin-bottom: calc(var(--space-12) - var(--ab-stats-h, var(--space-24))); }
  .ab-vm { padding-top: calc(var(--ab-stats-h, var(--space-24)) - var(--space-12) + var(--space-8)); }
  .ab-map { width: min(100%, 900px); }
  .ab-reach { text-align: center; }
  .ab-reach__list { justify-content: center; text-align: left; }
}
@media (max-width: 1024px) {
  .ab-wrap { width: calc(100% - 2 * var(--space-6)); }
  .ab-band { margin-top: var(--space-16); font-size: var(--h4-size); line-height: var(--h4-lh); }
  .ab-about__grid, .ab-story__row, .ab-story__row--flip, .ab-partners__grid, .ab-meet__grid { grid-template-columns: minmax(0, 1fr); row-gap: var(--space-8); }
  .ab-about__text { padding-top: 0; }
  .ab-copy p, .ab-year__text { text-wrap: pretty; }
  /* Copy first, then its photo, in both story rows. */
  .ab-story__row--flip .ab-story__text { grid-column: 1; grid-row: 1; }
  .ab-story__row--flip .ab-story__shot { grid-column: 1; grid-row: 2; }
  .ab-partners { padding-block: var(--space-16); }
  .ab-crowd { width: 100%; }
  /* Journey: the line runs down the left; each year sits beside it with its card. */
  .ab-years { grid-template-columns: minmax(0, 1fr); row-gap: var(--space-8); padding-left: var(--space-12); }
  .ab-years::before { top: var(--space-3); bottom: var(--space-3); left: calc(var(--space-3) - 1px); right: auto; width: 2px; height: auto; transform-origin: 50% 0; }
  .ab-year { position: relative; align-items: stretch; text-align: left; }
  .ab-year__dot { position: absolute; top: var(--space-4); left: calc(var(--space-12) * -1); }
  .ab-year__num { margin: 0 0 var(--space-3); }
  .ab-year__card { flex-direction: row; background: var(--surface-beige); }
  .ab-year__shot { flex: none; width: 40%; }
  .ab-year__body { align-self: center; padding: var(--space-4) var(--space-6); }
  .ab-year__title { font-size: var(--h6-size); line-height: var(--h6-lh); }
  .ab-year__text { font-size: var(--body-size); text-wrap: pretty; }
  .ab-vm__body { padding: var(--space-6); }
  .ab-meet__note { font-size: var(--body-size); line-height: var(--body-lh); }
}
@media (max-width: 1024px) and (prefers-reduced-motion: no-preference) {
  .js .ab-years[data-reveal]::before { transform: scaleY(0); }
}
@media (max-width: 767px) {
  .ab-wrap { width: calc(100% - 2 * var(--space-4)); }
  /* Titles that wrap keep a bar under each line. */
  .ab-title__t { display: inline; padding: 0; overflow: visible; }
  .ab-title__t::before { content: none; }
  .ab-title__in { display: inline; padding-inline: .34em; background: linear-gradient(var(--color-primary), var(--color-primary)) 0 100% / 100% .45em no-repeat; -webkit-box-decoration-break: clone; box-decoration-break: clone; }
  .ab-copy { font-size: var(--body-size); }
  .ab-copy p + p { margin-top: var(--space-6); }
  .ab-justify p, .ab-vm__text { text-align: left; }
  .ab-about { padding-bottom: var(--space-12); }
  .ab-about__shot { border-width: var(--space-2); }
  .ab-story { padding-top: var(--space-12); }
  .ab-story__row + .ab-story__row { margin-top: var(--space-12); }
  .ab-band { margin: var(--space-12) 0 calc(var(--ab-band-h, var(--space-16)) / -2); padding: var(--space-4); font-size: var(--h5-size); line-height: var(--h5-lh); }
  .ab-people { padding: calc(var(--ab-band-h, var(--space-16)) / 2 + var(--space-12)) 0 var(--space-12); }
  .ab-people .ab-title, .ab-vm .ab-title, .ab-meet .ab-title { margin-bottom: var(--space-8); }
  .ab-crowd__tile { border-width: 1px; }
  .ab-crowd__cap { padding: var(--space-1); font-size: var(--h5-size); line-height: var(--h5-lh); } /* 24px on phones under the full-width portrait (user 2026-10-22) */
  /* People (user 2026-10-22): the founder's portrait full width in the middle, five photos above and five below, none at
     the sides; three columns, a two-cell photo on row 2 (left) and row 4 (right) for the two widest pictures.
     Square cells set each row's height; her portrait keeps its own proportions, so her head is never cropped.
     --mfx / --mfy (the inline --fx / --fy stay for desktop): the photos above open upward, those below downward. */
  .ab-crowd { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-1); aspect-ratio: auto; }
  .ab-crowd > figure { position: static; width: auto; height: auto; min-width: 0; min-height: 0; }
  .ab-crowd > .ab-crowd__tile { aspect-ratio: var(--ty-le-cum_nhan_su_mobile); }
  /* The two-cell ones take the row's height from the square beside them: their photo leaves the flow so it cannot grow the row. */
  .ab-crowd > .ab-crowd__tile:is(:nth-child(3), :nth-child(4)) { position: relative; inset: auto; aspect-ratio: auto; } /* inset: the desktop --x / --y offsets */
  .ab-crowd > .ab-crowd__tile:is(:nth-child(3), :nth-child(4)) img { position: absolute; inset: 0; }
  .ab-crowd > figure:nth-child(9) { grid-area: 1 / 1; --mfx: -.7; --mfy: -.7; }     /* football */
  .ab-crowd > figure:nth-child(10) { grid-area: 1 / 2; --mfx: 0; --mfy: -1; }       /* factory */
  .ab-crowd > figure:nth-child(1) { grid-area: 1 / 3; --mfx: .7; --mfy: -.7; }      /* hair work */
  .ab-crowd > figure:nth-child(3) { grid-area: 2 / 1 / 3 / 3; --mfx: -.5; --mfy: -.85; } /* meeting, two cells */
  .ab-crowd > figure:nth-child(2) { grid-area: 2 / 3; --mfx: .85; --mfy: -.5; }     /* consultation */
  .ab-crowd > .ab-crowd__ceo { grid-area: 3 / 1 / 4 / 4; grid-template-rows: auto auto; }
  .ab-crowd > .ab-crowd__ceo img { height: auto; }
  .ab-crowd > figure:nth-child(8) { grid-area: 4 / 1; --mfx: -.85; --mfy: .5; }     /* training */
  .ab-crowd > figure:nth-child(4) { grid-area: 4 / 2 / 5 / 4; --mfx: .5; --mfy: .85; }   /* together, two cells */
  .ab-crowd > figure:nth-child(7) { grid-area: 5 / 1; --mfx: -.7; --mfy: .7; }      /* wefting */
  .ab-crowd > figure:nth-child(5) { grid-area: 5 / 2; --mfx: 0; --mfy: 1; }         /* packing */
  .ab-crowd > figure:nth-child(6) { grid-area: 5 / 3; --mfx: .7; --mfy: .7; }       /* beach */
  .ab-partners { padding-block: var(--space-12); }
  .ab-journey { padding: var(--space-12) 0 var(--space-16); }
  .ab-journey .ab-title { margin-bottom: var(--space-8); }
  .ab-year__num { font-size: var(--h3-size); line-height: var(--h3-lh); }
  .ab-year__dot { top: calc((var(--h3-lh) - var(--ab-dot)) / 2); } /* level with the middle of the smaller year */
  .ab-year__body { padding: var(--space-3) var(--space-4); }
  .ab-year__title { font-size: var(--body-size); line-height: var(--body-lh); }
  .ab-today { padding-top: var(--space-12); }
  .ab-today .ab-title { margin-bottom: var(--space-8); }
  /* Phones: a lower terraces strip (the strip itself is set up at ≤1279). */
  .ab-today__grid { row-gap: var(--space-8); margin-bottom: calc(var(--space-24) * 2); }
  .ab-today__bg { height: calc(var(--space-24) * 2 + var(--space-12)); }
  .ab-map__labels li { font-size: var(--caption-size); line-height: 1; }
  .ab-reach__vn { font-size: var(--h1-size); }
  .ab-reach__serve { font-size: var(--h6-size); }
  .ab-reach__count { font-size: var(--h4-size); }
  .ab-reach__list { font-size: var(--body-size); line-height: var(--body-lh); gap: var(--space-2) var(--space-6); }
  /* Figures: one column (user 2026-10-22); the gold rules lie across, a short one between each pair of figures. */
  /* Phones: the band overlaps only the last 48px of the terraces strip (user 2026-10-22: 'the black layer covers just a little of the photo'). */
  .ab-stats { grid-template-columns: minmax(0, 1fr); padding: var(--space-8) var(--space-6); row-gap: 0; margin-bottom: calc(var(--space-12) - var(--ab-stats-h, var(--space-24))); }
  .ab-stat, .ab-stat:nth-child(n + 4) { grid-column: 1; padding: var(--space-6) 0; }
  .ab-stat:first-child { padding-top: 0; }
  .ab-stat:last-child { padding-bottom: 0; }
  .ab-stat + .ab-stat::before, .ab-stat:nth-child(4)::before { content: ""; top: 0; bottom: auto; left: calc(50% - var(--space-8)); width: var(--space-16); height: 1px; }
  .ab-stat__num { font-size: var(--h1-size); } /* phones: 30 → 34px, the phone scale's h1 (user 2026-10-22: stay within the DS) */
  .ab-vm { padding: calc(var(--ab-stats-h, var(--space-24)) - var(--space-12) + var(--space-8)) 0 var(--space-16); }
  .ab-vm__grid { grid-template-columns: minmax(0, 1fr); gap: var(--space-6); }
  .ab-vm__title { font-size: var(--h5-size); line-height: var(--h5-lh); }
  .ab-meet { padding: var(--space-12) 0 var(--space-16); }
  .ab-form { margin-top: var(--space-8); }
  .ab-form__title { margin-bottom: var(--space-6); font-size: var(--h4-size); line-height: var(--h4-lh); }
  .ab-form__grid { grid-template-columns: minmax(0, 1fr); gap: var(--space-6); }
  .ab-field label { font-size: var(--body-size); line-height: var(--body-lh); }
  .ab-form__submit { font-size: var(--h6-size); line-height: var(--h6-lh); }
}
@media (max-width: 767px) and (prefers-reduced-motion: no-preference) {
  /* One-line masks do not hold a wrapped title: the whole title rises instead. */
  .js .ab-title[data-reveal] { opacity: 0; transform: translateY(24px); transition: opacity .8s var(--ab-ease), transform .9s var(--ab-ease); }
  .js .ab-title[data-reveal].is-in { opacity: 1; transform: none; }
  .js .ab-title[data-reveal] .ab-title__in { transform: none; }
  /* The grid cluster opens out along its own directions; the across rules of the figures draw sideways. */
  .js .ab-crowd[data-reveal] .ab-crowd__tile { translate: calc(var(--mfx, 0) * -60%) calc(var(--mfy, 0) * -60%); }
  .js .ab-stats[data-reveal] .ab-stat::before { transform: scaleX(0); }
}
@media (max-width: 599px) {
  .ab-year__card { flex-direction: column; }
  .ab-year__shot { width: 100%; }
  .ab-year__body { align-self: stretch; }
}
