/* ==========================================================================
   Sherry — Short-form Editor Portfolio
   Visual world: "Paper Pastel" (see DESIGN.md)
   Cream ground, two flooded pastel bands (rose and azure). No halo, no glass.
   Every colour comes from the six swatches the owner supplied, except the
   three text colours — the supplied palette has no dark value at all.
   ========================================================================== */

/* --- Tokens --------------------------------------------------------------- */
:root {
  /* Bảng màu Sherry chọn — sáu mã gốc, giữ nguyên. */
  --cream:  #F1E8D9;
  --blush:  #F8DAE9;
  --petal:  #F4C2D7;
  --rose:   #EFB0C9;
  --sky:    #B9D6F3;
  --azure:  #A1C9F1;

  /* Ba màu phải tự dẫn ra. Bảng gốc toàn màu sáng — không mã nào đặt chữ lên
     được, kể cả cỡ lớn. Tỉ lệ tương phản dưới đây đo trên cả sáu nền. */
  --text:       #33232C;   /* chữ chính  8.27 – 12.19 : 1 */
  --text-soft:  #42303B;   /* chữ phụ    6.83 – 10.06 : 1 */
  --accent-ink: #7E2549;   /* nhấn nhỏ   5.22 –  7.69 : 1 */

  /* Vai trò bề mặt, trỏ vào bảng trên. Đổi một dòng ở đây là đổi cả trang. */
  --ground:          var(--cream);
  /* Khung ảnh dùng chính màu nền: trên nền kem nó thành khung viền mảnh, trên
     hai dải màu nó nổi hẳn lên — giống khung ảnh ở phần liên hệ. */
  --raised:          var(--cream);
  --raised-hover:    var(--blush);
  --ghost:           var(--petal);
  --accent:          var(--rose);
  --accent-soft:     var(--petal);
  --accent-alt:      var(--azure);
  --accent-alt-soft: var(--sky);
  /* Kẻ chỉ cần thấy, không phải chữ — dùng chính màu chữ pha loãng nên nó
     hiện được trên cả nền kem lẫn hai dải màu. */
  --line: rgba(51, 35, 44, .16);

  --edge:  2px;
  --clip:  4px;

  --xs:   8px;
  --sm:  16px;
  --md:  32px;
  --lg:  64px;
  --xl: 120px;

  --display: 'Archivo', 'Arial Black', system-ui, sans-serif;
  --body:    'Be Vietnam Pro', system-ui, -apple-system, sans-serif;

  /* Eight steps, and nothing between them. A one-off size is a decision that
     was never made. */
  --t-label:   0.75rem;
  --t-small:   0.875rem;
  --t-body:    clamp(1rem, 0.97rem + 0.15vw, 1.125rem);
  --t-lead:    clamp(1.125rem, 1.6vw, 1.25rem);
  --t-h3:      clamp(1.5rem, 2.8vw, 2rem);
  --t-h2:      clamp(1.75rem, 4vw, 2.75rem);
  --t-h1:      clamp(2.25rem, 5.5vw, 3.5rem);
  --t-display: clamp(2.75rem, 9vw, 6rem);
  /* Ninth step, and the only one outside the reading hierarchy: the hero's
     ghost word is scenery, not type someone reads. */
  --t-ghost:   clamp(5.5rem, 23vw, 18rem);
  /* Tenth: the wordmark. Chosen live at 1.8x display and baked in here — it is
     a logotype, not a heading, so the 6rem reading cap does not bind it. */
  --t-mark:    clamp(5.5rem, 19vw, 13rem);
  /* Eleventh: the hero name. It sits between h1 and display because it shares
     one viewport with the portrait — every rem it gains, the portrait loses. */
  --t-hero:    clamp(2.5rem, 7vw, 4.5rem);

  /* How far the sticker tags sit off the wordmark's edges. One number moves
     the whole cluster; set live at 1.15. */
  --grip: 1.15;

  /* ---- Hero: sáu số chỉnh được bằng bảng điều khiển (dev-tune.js) --------
     Để trần không đơn vị cho thanh trượt và bộ lưu đọc/ghi dễ. Đơn vị được
     gắn ở chỗ dùng. Bấm LƯU trên bảng là ghi thẳng vào đây. */
  --hero-photo-h: 62;     /* chiều cao khung, vh          */
  --hero-photo-y: 0;      /* khung lên/xuống, px          */
  /* 86 = đúng tỉ lệ thật của file hero-final.png (661/770 = 0,858). Trùng tỉ lệ
     thì người được vẽ to hết cỡ trong vùng, không thừa dải trống hai bên. */
  --hero-ratio: 86;       /* tỉ lệ vùng ảnh, rộng/cao × 100 */
  --hero-img-scale: 100;  /* phóng ảnh, %                 */
  --hero-img-x: 0;        /* dịch ảnh ngang, %            */
  --hero-img-y: 0;        /* dịch ảnh dọc, %              */
  --hero-spill: 420;      /* cho tràn ra ngoài, px — 0 là xén gọn */
  --hero-shadow: 26;      /* đổ bóng bám viền người, px   */
  /* Cỡ đĩa tính theo % bề ngang vùng ảnh, mà bề ngang đổi mỗi lần đổi file, nên
     con số này phải chỉnh lại theo. Với hero-final.png: 104 đưa mép trên đĩa lên
     ngang vành nón — nón cắt qua mép đĩa, đó là chỗ sinh ra chiều sâu. Thử 92
     thì đĩa thụt hết ra sau lưng, nón lơ lửng ngoài đĩa; lớn hơn 118 thì đĩa
     nuốt mất chữ PORTFOLIO hồng phía sau. */
  --hero-disc-w: 104;     /* cỡ đĩa, %                    */
  --hero-disc-y: 6;       /* đĩa lên/xuống, %             */
  --hero-ghost-w: 14;     /* cỡ chữ PORTFOLIO, vw         */
  --hero-ghost-y: 52;     /* chữ lên/xuống, %             */

  /* ---- Mục "Tôi là ai": ảnh riêng, KHÔNG đụng biến --hero-* ở trên -------- */
  --intro-img-scale: 126; /* phóng ảnh, %                 */
  --intro-img-x: 0;       /* dịch ảnh ngang, %            */
  --intro-img-y: 2;       /* dịch ảnh dọc, %              */

  /* The world's one curve: a confident arrival, never a bounce. */
  --cut: cubic-bezier(0.16, 1, 0.3, 1);

  /* Bóng trên nền sáng phải nhạt và ám màu chữ; bóng đen của bản nền tối
     sẽ thành vết bẩn xám ở đây. */
  --lift: 0 14px 30px -14px rgba(51, 35, 44, 0.30);
}

/* --- Base ----------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--text);
  font-family: var(--body);
  font-size: var(--t-body);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

/* ---- Nền thử nghiệm: gradient pastel -------------------------------------
   Bật bằng cách thêm  data-bg="gradient"  vào thẻ <html> (nút bật/tắt trong
   dev-theme.js làm việc đó). Không có thuộc tính này thì trang giữ nền kem cũ,
   nên bản cũ chưa bị xoá đi đâu cả.

   Dựng bằng gradient chứ không dùng file ảnh: nét mượt ở mọi cỡ màn hình,
   không tải thêm file, và đổi màu chỉ là sửa một dòng.
   Lớp này cố định theo màn hình — trải theo chiều dài trang (hơn 8000px) thì
   các dải màu giãn ra tới mức không còn nhìn ra là gradient nữa. */
:root[data-bg="gradient"] body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  background: linear-gradient(
    180deg,
    #F7C9CB   0%,
    #FAD9BE  18%,
    #FAF3C2  38%,
    #C9F0DB  58%,
    #C6E8EE  78%,
    #C2DCEF 100%
  );
}

img, video { max-width: 100%; display: block; }

a { color: inherit; }

::selection { background: var(--accent); color: var(--text); }

:focus-visible {
  outline: 2px solid var(--text);
  outline-offset: 3px;
}

.skip {
  position: absolute;
  left: -9999px;
  top: var(--sm);
  z-index: 99;
  background: var(--text);
  color: var(--ground);
  padding: 12px 20px;
  font-weight: 600;
  text-decoration: none;
}
.skip:focus { left: var(--sm); }

/* Visually hidden but read aloud. */
.vh {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

.wrap {
  width: 100%;
  max-width: 1200px;
  margin-inline: auto;
  padding-inline: 20px;
}
@media (min-width: 720px) { .wrap { padding-inline: 32px; } }

/* --- Type ----------------------------------------------------------------- */
.h2, .h3 {
  font-family: var(--display);
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.05;
  margin: 0 0 var(--md);
}
.h2 { font-size: var(--t-h2); }
/* Bumped a full step at the owner's request. .h2 and .h3 now share a size,
   which is correct here: "Tôi là ai", "Kênh tôi tự xây" and "Clip tôi đã edit"
   are visual peers — the heading levels differ for the document outline, not
   for the eye. The step below them (.win__title, tool names) still holds
   --t-h3, so the hierarchy survives where it actually matters. */
.h3 { font-size: var(--t-h2); }

p { margin: 0 0 var(--sm); max-width: 68ch; }

strong { font-weight: 600; }

/* --- Sticker tag ---------------------------------------------------------- */
/* Rotation is required — a sticker sitting straight has lost the device. */
.sticker {
  display: inline-block;
  background: var(--accent);
  color: var(--text);
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  line-height: 1.2;
  padding: 6px 14px;
  border-radius: var(--edge);
  rotate: var(--rot, -3deg);
}
/* The hero's name tag: its own angle, so it never twins the divider stickers. */
.sticker--a { --rot: -2deg; }

/* --- Buttons -------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 18px 40px;
  border-radius: var(--edge);
  font-family: var(--body);
  font-size: var(--t-small);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  border: 1px solid transparent;
  transition: background-color .15s var(--cut), color .15s var(--cut), border-color .15s var(--cut);
}
.btn--primary { background: var(--accent); color: var(--text); }
.btn--primary:hover, .btn--primary:focus-visible { background: var(--text); color: var(--ground); }

.btn--ghost { border-color: var(--line); color: var(--text); }
.btn--ghost:hover, .btn--ghost:focus-visible { background: var(--text); color: var(--ground); border-color: var(--text); }

.btn__ext { font-size: var(--t-body); line-height: 1; }

/* --- Media frame + labelled placeholder ----------------------------------- */
.frame {
  position: relative;
  margin: 0;
  background: var(--raised);
  border: 1px solid var(--line);
  border-radius: var(--clip);
  overflow: hidden;
  box-shadow: var(--lift);
}
.frame--v { aspect-ratio: 9 / 16; }
.frame--h { aspect-ratio: 16 / 9; }
.frame--p { aspect-ratio: 4 / 5; }

.frame > video, .frame > img {
  width: 100%; height: 100%;
  object-fit: cover;
}

/* Empty slot: hatched, explicitly labelled, never mistaken for finished art. */
.frame[data-placeholder]::before {
  content: '';
  position: absolute; inset: 0;
  background-image: repeating-linear-gradient(
    -45deg,
    transparent 0 11px,
    rgba(51, 35, 44, .07) 11px 22px
  );
}
.frame__note {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  padding: var(--sm);
  font-size: var(--t-small);
  line-height: 1.4;
  color: var(--text-soft);
  /* The frame's own surface, not the page ground — a cream wash at the foot of
     a blue frame reads as a stain, not as a scrim. */
  background: linear-gradient(to top, var(--raised) 42%, transparent);
}
/* The one place the dark value is used as a fill: an empty slot is a warning,
   and on a page of pastels only the dark reads as one. */
.frame__tag {
  display: block;
  width: fit-content;
  margin-bottom: 6px;
  background: var(--text);
  color: var(--cream);
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 3px 8px;
  border-radius: var(--edge);
}

/* --- 1 · Hero ------------------------------------------------------------- */
/* The opening screen holds the portrait and the name and nothing else — the
   section claims a full viewport so the section below cannot peek in at the
   fold. `svh` rather than `vh`: on phones `100vh` is taller than the visible
   area while the browser bars are showing, which pushes the name off. */
.hero {
  position: relative;
  overflow: hidden;
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  align-content: center;
  padding: clamp(20px, 3vh, 40px) 0 clamp(24px, 4vh, 48px);
}

/* The ghost word the reference hero is built on: an oversized, barely-there
   layer that gives the most-viewed viewport a background to sit against. */
.hero__ghost {
  position: absolute;
  left: 50%;
  top: calc(var(--hero-ghost-y) * 1%);
  translate: -50% -50%;
  margin: 0;
  max-width: none;
  font-family: var(--display);
  font-weight: 800;
  font-size: clamp(2.5rem, calc(var(--hero-ghost-w) * 1vw), 18rem);
  line-height: 1;
  letter-spacing: -0.05em;
  color: var(--ghost);
  white-space: nowrap;
  pointer-events: none;
  user-select: none;
}

/* Photo-led hero: the portrait is the subject and the type sits above it,
   the way the pinned reference builds its opening. */
.hero__inner {
  position: relative;
  display: grid;
  gap: var(--md);
  justify-items: center;
  text-align: center;
}

.hero__name {
  font-family: var(--display);
  font-weight: 800;
  font-size: var(--t-hero);
  /* 0.92 floor: Vietnamese stacked diacritics need the room. */
  line-height: 0.98;
  letter-spacing: -0.032em;
  text-wrap: balance;
  margin: var(--sm) 0 12px;
}
.hero__alias {
  display: block;
  color: var(--accent-ink);
  letter-spacing: -0.04em;
}

.hero__after {
  font-size: var(--t-lead);
  color: var(--text-soft);
  margin-bottom: var(--sm);
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-bottom: var(--sm);
}

.hero__verify {
  font-size: var(--t-small);
  color: var(--text-soft);
  margin: 0;
  max-width: 46ch;
}
.hero__verify strong { color: var(--text); font-weight: 600; }
.hero__verify a { color: var(--text); text-underline-offset: 3px; }

/* The portrait: one frame, full width of the column, nothing beside it. */
/* Sized by viewport height, not width: a 4:5 frame given a free width will
   always grow taller than the screen and push the name and the action off
   the fold. Height leads, width follows the ratio. */
.hero__portrait {
  position: relative;
  aspect-ratio: calc(var(--hero-ratio) / 100);
  height: min(calc(var(--hero-photo-h) * 1vh), 620px);
  width: auto;
  max-width: 88vw;
  margin-top: 0;
  translate: 0 calc(var(--hero-photo-y) * 1px);
}
.hero__portrait .frame { width: 100%; height: 100%; }
.hero__portrait .frame { position: relative; z-index: 1; }
/* No box around the cutout: the subject stands on the disc, it does not sit
   in a window. An opaque frame here would hide the disc completely. */
/* Ảnh đã tách nền: không khung, không nền, không bóng — người đứng TRÊN đĩa
   màu chứ không ngồi trong một cái cửa sổ. Đóng khung ở đây là che mất đĩa. */
.frame--cut {
  /* The ratio lives on .hero__portrait so height can drive the size. */
  aspect-ratio: auto;
  background: transparent;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  overflow: visible;
  /* Một con số điều khiển cả hai kiểu: 0 thì xén đúng mép khung, càng lớn thì
     người càng được tràn ra ngoài. `overflow` không nhận biến nên dùng
     clip-path, cùng tác dụng mà chỉnh được bằng thanh trượt. */
  clip-path: inset(calc(var(--hero-spill) * -1px));
}
/* `contain` giữ nguyên tỉ lệ người, không bao giờ méo. Phóng và dịch nằm ở
   `scale` / `translate` nên cắt/tràn được mà hình không biến dạng.
   `drop-shadow` bám theo đúng viền đã tách nền — không phải cái hộp vuông như
   `box-shadow` — nên người nổi hẳn khỏi mặt phẳng đĩa. Bóng có độ lệch thật,
   không phải quầng sáng (DESIGN.md · The No Halo Rule). */
.frame--cut > img {
  object-fit: contain;
  object-position: center bottom;
  scale: calc(var(--hero-img-scale) / 100);
  translate: calc(var(--hero-img-x) * 1%) calc(var(--hero-img-y) * 1%);
  filter: drop-shadow(
    calc(var(--hero-shadow) * 0.35px)
    calc(var(--hero-shadow) * 0.55px)
    calc(var(--hero-shadow) * 1px)
    rgba(51, 35, 44, .30)
  );
  /* Không dùng mask ở đây. Mặt nạ CSS luôn cắt theo đúng hộp viền của thẻ, nên
     nó dựng lại chính cái khung chữ nhật vừa gỡ đi ở trên — thử rồi, hỏng. */
}

/* The disc already marks where the subject stands, so the empty slot does not
   need the hatch on top of it — it needs one legible chip that says what is
   missing, readable over red or over ink. */
.frame--cut[data-placeholder]::before { display: none; }
.frame--cut .frame__note {
  top: 50%;
  bottom: auto;
  left: 50%;
  right: auto;
  translate: -50% -50%;
  width: max-content;
  max-width: 88%;
  background: var(--ground);
  border: 1px solid var(--line);
  border-radius: var(--clip);
  padding: 14px 16px;
  text-align: center;
}
.frame--cut .frame__tag { margin-inline: auto; }
.frame--cut code {
  font-family: inherit;
  color: var(--text);
  border-bottom: 1px solid var(--line);
}

/* Flat carmine disc behind the subject — the reference's glow, rebuilt as
   flooded colour because The No Halo Rule forbids the bloom. */
.hero__disc {
  position: absolute;
  left: 50%;
  bottom: calc(var(--hero-disc-y) * 1%);
  translate: -50% 0;
  /* Đủ lớn để ôm từ đầu tới quá nửa thân — nhỏ hơn thì nó thành cái chấm sau
     lưng chứ không phải nền đỡ. */
  width: calc(var(--hero-disc-w) * 1%);
  aspect-ratio: 1;
  border-radius: 50%;
  /* Blue, not pink: the ghost word behind it is already pink, and two pink
     layers on a cream ground merge into one shape with no depth. */
  background: var(--accent-alt);
  background-image: linear-gradient(150deg, var(--accent-alt-soft) 0%, var(--accent-alt) 100%);
}

/* Trên màn hẹp chữ ghost thu lại, còn vị trí vẫn theo số chỉnh được ở trên. */
@media (max-width: 899px) {
  .hero__ghost { font-size: clamp(3.5rem, 18vw, 6rem); }
}

/* --- 2 · Intro ------------------------------------------------------------ */
/* Một màn hình riêng, nội dung canh giữa — khoảng trống trên dưới tự sinh ra
   từ đó, nên nó tách hẳn khỏi màn hình đầu thay vì dính sát mép. */
.intro {
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  align-content: center;
  padding-block: var(--xl);
}
.intro__grid { display: grid; gap: var(--lg); }
/* Cỡ chữ và ảnh nâng lên cho cân với một màn hình đầy — ở cỡ cũ khối này nổi
   lửng giữa màn, quanh toàn khoảng trống. */
.intro__text .h2 { font-size: var(--t-h1); }
.intro__text p {
  color: var(--text-soft);
  font-size: var(--t-lead);
  max-width: 60ch;
  text-align: justify;
}
.intro__last { font-size: clamp(1.2rem, 2.1vw, 1.55rem) !important; }
.intro__text a { color: var(--text); text-underline-offset: 3px; }
.intro__last {
  /* Cùng màu với .hero__alias (chữ "Sherry" ở đầu trang). */
  color: var(--accent-ink) !important;
  background: var(--blush);
  border-radius: var(--clip);
  padding: var(--sm) var(--md);
  margin-top: 4px;
  text-align: center;
  text-wrap: balance;
}
.intro__media { max-width: 400px; }

/* Ảnh tách nền đứng trên đĩa, không khung — cùng công thức với ảnh đầu trang
   (.hero__portrait / .frame--cut), nhưng số đo riêng cho ảnh này nên không
   đụng tới các biến --hero-* (đổi ở đây không được làm lệch ảnh đầu trang). */
.intro__portrait {
  position: relative;
  width: 100%;
  /* Đúng tỉ lệ file cat-nen.png (1086×1448) để ảnh lấp đầy khung, không dư
     khoảng trống hai bên. */
  aspect-ratio: 1086 / 1448;
}
.intro__portrait .frame { width: 100%; height: 100%; position: relative; z-index: 1; }
.intro__portrait .frame--cut > img {
  object-fit: contain;
  object-position: center bottom;
  scale: calc(var(--intro-img-scale) / 100);
  translate: calc(var(--intro-img-x) * 1%) calc(var(--intro-img-y) * 1%);
  filter: drop-shadow(4px 8px 14px rgba(51, 35, 44, .3));
}

@media (min-width: 900px) {
  .intro__grid {
    /* Narrow column first, wide column second — media sits left, text sits
       right (order below), same width split as before, just mirrored.
       Canh top thay vì center: mép trên ảnh thẳng hàng với mép trên tiêu đề
       "Tôi là ai", không lấy tâm khối chữ làm chuẩn nữa. */
    grid-template-columns: minmax(340px, 460px) 1fr;
    gap: var(--xl);
    align-items: start;
  }
  .intro__media { max-width: none; order: -1; }
  /* Trải hết cả hai cột, xuống hàng riêng bên dưới cả ảnh lẫn cột chữ — không
     còn bó trong cột chữ hẹp bên phải nữa. */
  .intro__last { grid-column: 1 / -1; }
}

/* --- 3 · Điểm mạnh -------------------------------------------------------- */
/* Claims a full screen, like the opening — the wordmark is a held moment, not
   a row of content to scroll past. */
.strengths {
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  align-content: center;
  padding-block: var(--xl);
}

.mark { position: relative; text-align: center; padding: var(--md) 0; }

/* Ba kiểu cho tên nhỏ phía trên chữ "sherry". Đổi bằng nút ở góc dưới phải
   (dev-theme.js) hoặc đặt thẳng data-mark="1|2|3" vào thẻ <html>.
   Mặc định là kiểu 1. */
/* `block` + fit-content, not `inline-block`: as an inline-block it shared a
   line with .mark__lock and sat to the LEFT of "sherry" instead of above it. */
.mark__name {
  font-family: var(--display);
  font-weight: 700;
  font-size: var(--t-h2);
  color: var(--accent-ink);
  letter-spacing: -0.02em;
  margin: 0 auto 10px;
  max-width: none;
  display: block;
  width: fit-content;
  /* Dương = nghiêng xuống về bên phải, tức đổ về phía chữ "Nhi". */
  rotate: 4deg;
}
/* 2 — nghiêng ngược, lệch sang trái, ôm sát chữ sherry */
:root[data-mark="2"] .mark__name {
  rotate: 3deg;
  translate: -18% 8px;
  font-size: var(--t-h2);
}
/* 3 — nghiêng mạnh, nằm đè lên vai trái chữ sherry */
:root[data-mark="3"] .mark__name {
  rotate: -9deg;
  translate: -26% 22px;
  font-size: var(--t-h2);
  position: relative;
  z-index: 2;
}
/* The tags hang off the wordmark's own box, not off the section, so they
   travel with the word at any size. This is why .mark__lock exists: it is an
   inline-block that shrink-wraps the glyphs and gives the tags something the
   width of the word to anchor to. */
.mark__lock { position: relative; display: inline-block; }

.mark__word {
  font-family: var(--display);
  font-weight: 800;
  font-size: var(--t-mark);
  line-height: 0.92;
  letter-spacing: -0.045em;
  margin: 0;
  max-width: none;
}

.mark__tags {
  list-style: none;
  margin: 18px 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
}

@media (min-width: 760px) {
  .mark { padding: var(--lg) 0 calc(var(--lg) * 1.6); }
  .mark__tags {
    display: block;
    margin: 0;
    height: 0;
  }
  /* Stuck onto the word, not laid out in a row. Offsets are multiples of the
     grip step so the whole cluster tightens or loosens from one number. */
  .mark__tags .sticker {
    position: absolute;
    rotate: none;
    transform: translate(-50%, -50%) rotate(var(--r));
    white-space: nowrap;
  }
  .mark__tags .sticker:nth-child(1) { --r: -3deg;   left: calc(0%   - var(--grip) * 46px); top: 4%; }
  .mark__tags .sticker:nth-child(2) { --r:  3.5deg; left: calc(100% + var(--grip) * 50px); top: 24%; }
  .mark__tags .sticker:nth-child(3) { --r: -2.5deg; left: calc(0%   - var(--grip) * 66px); top: 60%; }
  .mark__tags .sticker:nth-child(4) { --r:  3deg;   left: calc(100% + var(--grip) * 44px); top: 80%; }
  .mark__tags .sticker:nth-child(5) { --r: -4deg;   left: 50%; top: calc(100% + var(--grip) * 30px); }
}

.strengths__gloss {
  font-size: var(--t-lead);
  color: var(--text-soft);
  max-width: 60ch;
  margin-inline: auto;
  text-align: center;
}
/* Cụm ô hồng to theo, để cả vùng 13–16 cùng một mức. */
.mark__tags .sticker { font-size: var(--t-small); padding: 8px 16px; }

/* --- 4/5 · Divider (the flooded band) ------------------------------------- */
/* Every flooded band is blue: the owner asked for the whole lower half of the
   page to read cool, so pink now survives only as accent (sticker tags) and as
   the hero's ghost word. */
.divider {
  position: relative;
  background: var(--accent-alt);
  background-image: linear-gradient(115deg, var(--accent-alt-soft) 0%, var(--accent-alt) 100%);
  overflow: hidden;
}
/* padding-block only: the inline gutter belongs to .wrap, so this band lines
   up with the section beneath it. The extra right padding clears the rail. */
.divider__inner {
  padding-block: clamp(64px, 11vw, 128px);
  padding-right: clamp(60px, 15vw, 190px);
}

.divider__idx {
  font-family: var(--display);
  font-weight: 600;
  font-size: var(--t-h1);
  line-height: 1;
  /* Cream on a pastel band is 1.4:1 — the index has to carry the deep value
     to stay a mark rather than a smudge. */
  color: var(--accent-ink);
  margin: 0 0 var(--sm);
  max-width: none;
}
.divider__title {
  font-family: var(--display);
  font-weight: 800;
  font-size: var(--t-display);
  /* Uppercase moves every diacritic on top of a capital, so the line needs
     room a lowercase setting never asked for. 0.94 clips Ế and Ộ outright. */
  line-height: 1.02;
  letter-spacing: -0.03em;
  text-transform: uppercase;
  margin: 0;
}
.divider__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 6px var(--lg);
  margin-top: clamp(48px, 9vw, 104px);
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-soft);
}
.divider__meta span:first-child { color: var(--text); }

/* The vertical rule and its four-point spark, lifted from the reference deck.
   Both sit below the sweep layer, so the cut reveals them. The spark is the
   deck's only ornament and the rail is the spine of its composition — neither
   is optional, and neither disappears on a phone. Every flooded carmine band
   carries them, so the two dividers and the close read as one family. */
.divider::after,
.contact::after {
  content: '';
  position: absolute;
  top: 0; bottom: 0;
  right: clamp(28px, 8vw, 120px);
  width: 2px;
  background: var(--text);
  opacity: .55;
}
.divider::before,
.contact::before {
  content: '';
  position: absolute;
  top: clamp(34px, 8vw, 92px);
  right: clamp(28px, 8vw, 120px);
  width: clamp(26px, 4vw, 40px);
  aspect-ratio: 1;
  translate: calc(50% - 1px) 0;
  background: var(--text);
  clip-path: polygon(
    50% 0%, 57% 43%, 100% 50%, 57% 57%,
    50% 100%, 43% 57%, 0% 50%, 43% 43%
  );
}

/* --- Work sections -------------------------------------------------------- */
.work { padding: var(--xl) 0; }
/* No rule here any more. Selected by adjacency rather than a class, so the
   name can't outlive the thing it described. The tools section already carries
   120px below it; 64px more matches the gap above the KOL block. */
.tools + .work { padding-top: var(--lg); }
.work__head { margin-bottom: var(--lg); }
.work__more { margin-top: var(--lg); text-align: center; }
.work__lede { color: var(--text-soft); }

.channel {
  display: grid;
  gap: 6px;
  padding: var(--md);
  margin-bottom: var(--lg);
  /* Rose, not the raised blue: this card is the page's one piece of verifiable
     proof, and being the only pink field below the first divider is what makes
     it read as different from the clip wells around it. */
  background: var(--rose);
  border: 1px solid var(--line);
  border-radius: var(--clip);
  transition: border-color .15s var(--cut), background-color .15s var(--cut);
}
/* Hover shifts to the lighter pink and darkens the border — an azure hover on
   a rose card would read as a different component, not as a state. */
.channel:hover, .channel:focus-visible { border-color: var(--text); background: var(--petal); }

/* Tên kênh và số liệu nằm chung một hàng — tên bên trái, số liệu dạt sang
   phải, xuống dòng khi không đủ chỗ (mobile hẹp). */
.channel__top {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sm) var(--md);
}
.channel__handle {
  font-family: var(--display);
  font-weight: 800;
  font-size: var(--t-h3);
  letter-spacing: -0.03em;
}
.channel__nums {
  display: flex;
  flex-wrap: wrap;
  gap: var(--md);
}
.channel__stat {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--text-soft);
  font-size: var(--t-small);
}
.channel__stat b { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }
.channel__stat-icon { width: 16px; height: 16px; flex: none; }
/* Owner's own line about the channel, below the figures. Marked as unfilled
   the same way every other placeholder is, so it cannot ship by accident. */
.channel__note {
  margin-top: var(--xs);
  font-size: var(--t-small);
  color: var(--accent-ink);
}
/* Giờ là link thật duy nhất trong thẻ — chỉ bấm được đúng chỗ chữ này, chứ
   không phải bấm đâu trong khung cũng mở kênh. */
.channel__go {
  display: inline-block;
  margin-top: var(--sm);
  color: var(--accent-ink);
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
}
.channel__go:hover, .channel__go:focus-visible {
  color: var(--text);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* Aspect drives the grid, not column count. */
.grid { display: grid; gap: var(--sm); }
.grid--v { grid-template-columns: repeat(2, 1fr); }
/* One per row at every width: a 16:9 clip is the long-form piece, and it earns
   the same full width the four verticals above it occupy together. */
.grid--h { grid-template-columns: 1fr; }
/* Horizontal clips sit below the vertical ones, never in the same grid. */
.grid--v + .grid--h { margin-top: var(--md); }

@media (min-width: 720px) {
  .grid--v { grid-template-columns: repeat(3, 1fr); gap: var(--md); }
  .grid--h { gap: var(--md); }
}
@media (min-width: 1000px) {
  .grid--v { grid-template-columns: repeat(4, 1fr); }
}

.clip { position: relative; }
.clip__meta {
  margin-top: 10px;
  font-size: var(--t-small);
  line-height: 1.4;
  color: var(--text-soft);
}
/* Cắt còn hai dòng. Lời Sherry viết dưới clip dài ngắn rất khác nhau — có cái
   một dòng, có cái bốn — mà tám ô này nằm cạnh nhau thành lưới, để nguyên thì
   đáy các ô so le trông như xếp hỏng. Chữ đầy đủ vẫn nằm trong trang cho máy
   đọc màn hình và cho công cụ tìm kiếm, chỉ là mắt không phải đọc hết. */
.clip__title {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  color: var(--text);
  font-weight: 500;
}

/* Nút tròn góc dưới-phải khung video — video tự chạy câm khi lướt tới,
   không cần bấm gì. Bấm nút này: bật tiếng + mở luôn panel "Giới thiệu"
   (Vai trò/Nhiệm vụ) của đúng video đó cùng lúc. Bấm lại thì tắt tiếng,
   nút không biến mất (còn dùng lại được), panel giữ nguyên trạng thái mở. */
.clip__play-btn {
  position: absolute;
  right: 12px;
  bottom: 12px;
  z-index: 2;
  width: 40px;
  height: 40px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--ground);
  color: var(--text);
  border: 1px solid var(--line);
  box-shadow: var(--lift);
  cursor: pointer;
  transition: background-color .2s var(--cut), scale .2s var(--cut);
}
.clip__play-btn:hover,
.clip__play-btn:focus-visible { background: var(--accent); scale: 1.06; }
.clip__play-btn-icon--on { display: none; }
.clip__play-btn.is-on .clip__play-btn-icon--off { display: none; }
.clip__play-btn.is-on .clip__play-btn-icon--on { display: block; }
.clip__intro-panel {
  margin: 8px 0 0;
  font-size: var(--t-small);
  line-height: 1.5;
  color: var(--text-soft);
}
.clip__intro-panel[hidden] { display: none; }
.clip__intro-block { margin-top: 10px; }
.clip__intro-block:first-child { margin-top: 8px; }
.clip__intro-label {
  margin: 0 0 4px;
  color: var(--accent-ink);
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
/* Mỗi vai trò xuống hàng riêng — cùng kiểu với .clip__intro-tasks bên dưới. */
.clip__intro-roles,
.clip__intro-tasks {
  margin: 0;
  padding-left: 18px;
}
.clip__intro-roles li,
.clip__intro-tasks li { margin-bottom: 4px; }
.clip__intro-roles li:last-child,
.clip__intro-tasks li:last-child { margin-bottom: 0; }
.clip__intro-collapse {
  display: block;
  margin: 12px 0 0;
  padding: 0;
  background: none;
  border: none;
  color: var(--accent-ink);
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  cursor: pointer;
}
.clip__intro-collapse:hover,
.clip__intro-collapse:focus-visible { text-decoration: underline; }

/* --- Ô clip bấm sang TikTok ----------------------------------------------- */
/* Cả ô ảnh là một liên kết, không phải cái nút con giữa ảnh: trên điện thoại ô
   này chỉ rộng chừng 150px, đặt nút nhỏ trong đó là bấm trượt. */
.clip__go {
  position: absolute;
  inset: 0;
  display: block;
  overflow: hidden;
}
.clip__go > img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: scale .5s var(--cut);
}
.clip__go:hover > img,
.clip__go:focus-visible > img { scale: 1.05; }

/* Tam giác trên đĩa kem đặc: nói ngay đây là clip chứ không phải ảnh tĩnh.
   Đĩa đặc chứ không phải mảng mờ hay vầng sáng (DESIGN.md · The No Halo Rule). */
.clip__play {
  position: absolute;
  left: 50%;
  top: 50%;
  translate: -50% -50%;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  background: var(--ground);
  transition: background .25s var(--cut), scale .25s var(--cut);
}
.clip__play::before {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  /* -40% chứ không phải -50%: trọng tâm thị giác của một tam giác lệch về phía
     cạnh đáy, canh đúng giữa hình học thì mắt thấy nó lệch trái. */
  translate: -40% -50%;
  border-style: solid;
  border-width: 9px 0 9px 15px;
  border-color: transparent transparent transparent var(--text);
}
.clip__go:hover .clip__play,
.clip__go:focus-visible .clip__play {
  background: var(--accent);
  scale: 1.08;
}

/* Chip góc: báo trước rằng bấm vào là rời khỏi trang. Nền mực đậm để đọc được
   trên bất kỳ tấm bìa nào, kể cả bìa sáng trắng. */
.clip__badge {
  position: absolute;
  top: 8px;
  right: 8px;
  padding: 5px 8px;
  background: var(--text);
  color: var(--ground);
  border-radius: var(--edge);
  font-family: var(--body);
  font-size: var(--t-label);
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.clip__badge::after { content: ' ↗'; }

/* Lượt view: góc dưới trái, chữ trắng đè thẳng lên ảnh — không nền, không
   khung — y hệt cách TikTok hiện số view trên lưới video của một kênh. */
.clip__views {
  position: absolute;
  left: 8px;
  bottom: 8px;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: #fff;
  font-family: var(--body);
  font-size: var(--t-small);
  font-weight: 700;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .6), 0 0 1px rgba(0, 0, 0, .4);
}
.clip__views svg {
  width: 15px;
  height: 15px;
  fill: #fff;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .5));
}

.clip__go:focus-visible {
  outline: 2px solid var(--text);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .clip__go > img,
  .clip__play { transition: none; }
  .clip__go:hover > img,
  .clip__go:focus-visible > img { scale: 1; }
}

/* --- Thành tựu (khối con trong mục 01) ------------------------------------ */
/* Not a section of its own: these wins came from the owner's own channel, so
   they sit inside "Kênh tôi tự xây" and are separated by a rule, not by a gap. */
/* Không còn vạch ngăn: từ khi bỏ tiêu đề "Sau khi kênh lên", vạch này rơi
   đúng ngay trên chữ "Được mời làm KOL" và đọc ra như gạch chân ngược. */
.wins {
  margin-top: var(--xl);
  padding-top: var(--lg);
}
.wins__head { margin-bottom: var(--lg); }

/* The KOL proof runs the full content width — one wide frame instead of three
   narrow ones, because a screenshot of a message is read left to right. */
.win--wide { margin-bottom: var(--xl); }
/* Khoảng cách giữa đoạn mô tả và khung ảnh — bằng đúng khoảng cách giữa tiêu
   đề "Nhãn hàng liên hệ" và lưới logo bên dưới (.brands__grid), cho hai khối
   con này đọc cân nhau. */
.win--wide .win__say { margin-bottom: var(--md); }
/* Frame ratio matches the screenshot's own pixel ratio (1280×596) exactly, so
   the default cover fills it edge to edge — no letterboxed frame background
   showing through top or bottom. */
.win--wide .frame { aspect-ratio: 1280 / 596; }

.brands { margin-bottom: var(--xl); }
/* Dòng mô tả chờ điền, cùng kiểu đánh dấu "chưa điền" như .channel__note.
   Cách xa lưới logo bên trên một khoảng rõ (--lg), không sát ngay dưới. */
/* Nằm ngay dưới tiêu đề, trước lưới logo. */
/* max-width: none — cùng lý do với .win__say (rule "p" gốc chặn 68ch), để
   trải hết bề ngang y hệt. Cỡ chữ cũng bỏ --t-small, dùng cỡ mặc định như
   .win__say cho đồng bộ. */
.brands__note {
  margin-top: var(--md);
  margin-bottom: var(--lg);
  max-width: none;
  color: var(--accent-ink);
}

/* 8 logo, 2 hàng 4 cột trên màn rộng — không khung, không nền: logo nằm trần
   trên nền trang. */
.brands__grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--sm);
  /* Bằng đúng --win--wide .win__say ở trên — xem chú thích ở đó. */
  margin-top: var(--md);
}
@media (min-width: 720px) {
  .brands__grid { grid-template-columns: repeat(4, 1fr); gap: var(--md); }
}
/* Vị trí/kích cỡ/cắt từng logo kéo-thả trực tiếp trên trang (dev-drag.js,
   nút "SẮP HÌNH"): kéo = dịch chuyển, lăn chuột = phóng to/thu nhỏ — phóng
   quá cỡ khung thì phần dư bị khung cắt mất, đúng như cắt ảnh. Ba biến này
   mặc định đứng yên; giá trị thật ghi vào style="" khi bấm LƯU. */
.brands__logo {
  --x: 0;
  --y: 0;
  --s: 100;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}
.brands__logo img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  translate: calc(var(--x) * 1px) calc(var(--y) * 1px);
  scale: calc(var(--s) / 100);
}

/* "Sau khi kênh lên" is gone, so these two are now the section headings for
   this block — they take the same step as every other section heading. */
.win__title {
  font-family: var(--display);
  font-weight: 800;
  font-size: var(--t-h2);
  letter-spacing: -0.03em;
  line-height: 1.05;
  margin: var(--sm) 0 6px;
}
/* Câu tiêu đề KOL dài hơn một cụm ngắn — cỡ --t-h2 làm nó rớt xuống 2 dòng và
   lẻ một chữ ở cuối. Thu nhỏ riêng chỗ này (không đụng .win__title dùng
   chung với tiêu đề "Nhãn hàng liên hệ") và cân dòng nếu vẫn phải xuống. */
.win--wide .win__title {
  font-size: clamp(1.75rem, 3.8vw, 2.6rem);
  text-wrap: balance;
  /* Cách đoạn mô tả bên dưới xa hơn mức 6px mặc định của .win__title dùng
     chung — chỉ áp riêng cho khối KOL. */
  margin-bottom: var(--sm);
}
/* Justify từng bị thử ở đây nhưng đoạn này không đủ rộng để giãn đều — chữ
   rời rạc thành từng cụm ngắn trông rối mắt. Để lề trái tự nhiên, tràn hết
   bề ngang khung như ảnh bên dưới. */
/* max-width: none — mọi <p> mặc định bị chặn ở 68ch (xem rule "p" gốc), đó
   là lý do đoạn này luôn hẹp hơn khung ảnh KOL bên dưới dù đã thử nhiều cách.
   Bỏ trần ở đây để nó trải đúng hết bề ngang khối .win--wide, bằng với ảnh. */
/* Cùng màu với .brands__note (mục 38). */
.win__say { color: var(--accent-ink); margin: 0; max-width: none; text-align: justify; text-wrap: pretty; }

/* Placeholder copy has to look like a note to the owner, not like shipped
   prose — otherwise it goes live as body text and nobody notices. */
/* Không còn vạch bên trái — chỉ màu đánh dấu chỗ chưa điền. */
.win__say[data-placeholder],
.win__title[data-placeholder] {
  color: var(--accent-ink);
}

/* --- 7 · Công cụ ---------------------------------------------------------- */
/* This section follows a flooded band directly, and a band gives no bottom
   margin of its own — without this the heading sits against the colour. */
.tools { padding-top: var(--xl); }
.tools { padding-bottom: var(--xl); }

.tools__list {
  margin: 0 0 var(--lg);
  border-top: 1px solid var(--line);
}
.tool {
  display: grid;
  gap: 4px;
  padding: var(--md) 0;
  border-bottom: 1px solid var(--line);
}
.tool__name {
  display: flex;
  align-items: center;
  gap: 14px;
  font-family: var(--display);
  font-weight: 800;
  font-size: var(--t-h3);
  letter-spacing: -0.03em;
  /* Rose as literal text is 1.47:1 on cream — invisible. This is the same hue
     deepened until it reads: 7.69:1. */
  color: var(--accent-ink);
}

/* The icon slot holds the real app logo when one is present and falls back to
   the drawn glyph until then. `overflow: hidden` plus the slight scale is the
   "cắt viền" — it trims the flat margin those PNGs ship with. */
.tool__icon {
  position: relative;
  flex: none;
  display: grid;
  place-items: center;
  width: clamp(34px, 3.8vw, 44px);
  aspect-ratio: 1;
  border-radius: 22%;
  overflow: hidden;
  color: var(--accent-ink);
}
.tool__icon img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  scale: 1.06;
}
/* The `hidden` attribute alone loses to the rule above, so a missing logo kept
   painting its broken-image box over the fallback glyph. State it explicitly. */
.tool__icon img[hidden] { display: none; }
/* Sized by percentage inside the centred grid, not absolutely inset — an
   auto-sized absolute SVG gets cropped by the rounded overflow. */
.tool__icon svg {
  width: 74%;
  height: 74%;
  stroke-linecap: square;
  stroke-linejoin: miter;
}
/* Once a real logo has loaded, the drawn glyph must not show through it. */
.tool__icon:has(img:not([hidden])) svg { display: none; }
.tool__level {
  margin: 0;
  color: var(--accent-ink);
  font-size: var(--t-small);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.tool__say {
  margin: 0;
  color: var(--text-soft);
  text-align: justify;
  text-align-last: left;
}

@media (min-width: 800px) {
  .tool {
    grid-template-columns: minmax(200px, 1fr) 160px 1.4fr;
    align-items: baseline;
    gap: var(--md);
  }
  .tool__level { text-align: left; }
}

.tools__honest {
  color: var(--text);
  font-size: var(--t-lead);
  max-width: 58ch;
}

/* --- 7 · Liên hệ ---------------------------------------------------------- */
.contact {
  position: relative;
  overflow: hidden;
  background: var(--accent-alt);
  background-image: linear-gradient(115deg, var(--accent-alt-soft) 0%, var(--accent-alt) 100%);
}
/* The close is the heaviest band on the page: it is the only thing on this
   site that turns a viewer into a client, so it gets the room to say so. */
.contact__inner {
  position: relative;
  z-index: 2;
  padding-block: clamp(96px, 16vw, 168px);
  padding-right: clamp(60px, 15vw, 190px);
  display: grid;
  gap: var(--lg);
}
/* Every frame is cream now, so the contact frame needs no special case. */

.contact__h {
  font-family: var(--display);
  font-weight: 800;
  /* Sized to fill its column: the largest value that still lands on two lines
     once the photo takes its half of the band. */
  font-size: clamp(2.75rem, 6.2vw, 5.25rem);
  /* Vietnamese uppercase stacks marks above capitals — NẾU, HỢP, HỆ all need
     the extra leading. This is the heading that would have clipped worst. */
  line-height: 1.06;
  letter-spacing: -0.028em;
  text-transform: uppercase;
  /* No hard <br>: the text column narrows when the photo takes its half, and a
     forced break there just wraps a second time into ragged pairs. */
  text-wrap: balance;
  margin: 0 0 var(--md);
}
.contact__lede { color: var(--text-soft); max-width: 52ch; margin-bottom: var(--lg); }

/* 3 icon tròn bấm thẳng tới Facebook/Zalo/Email — gọn hơn danh sách chữ có
   nhãn trước đây. Nền cream nổi trên dải xanh, đảo màu khi hover/focus như
   các nút khác trên trang. */
.contact__socials { display: flex; gap: var(--sm); margin-top: var(--lg); }
.contact__social {
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: var(--raised);
  border: 1px solid var(--line);
  color: var(--text);
  transition: background-color .15s var(--cut), color .15s var(--cut), border-color .15s var(--cut);
}
.contact__social svg { width: 22px; height: 22px; }
.contact__social:hover, .contact__social:focus-visible {
  background: var(--text);
  color: var(--ground);
  border-color: var(--text);
}

/* --- Footer --------------------------------------------------------------- */
.foot { border-top: 1px solid var(--line); }
.foot__inner {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--xs);
  padding-block: var(--md);
  font-size: var(--t-small);
  color: var(--text-soft);
}
.foot__inner p { margin: 0; }
.foot__inner a { color: var(--text); text-underline-offset: 3px; }

/* ==========================================================================
   MOTION — "the page cuts like her edits"
   Material is the wipe (clip-path), not the fade. Hidden states exist only
   once JS has confirmed it can restore them (.js-motion on <html>).
   ========================================================================== */

/* The stage carries .is-in; the children carry the cut. A wiped element has
   no intersection area, so it can never be the thing that triggers itself. */
.js-motion [data-rv] {
  transition:
    clip-path .55s var(--cut),
    translate .55s var(--cut);
  transition-delay: var(--d, 0ms);
}

.js-motion [data-rv="wipe-l"] { clip-path: inset(0 100% 0 0); translate: -14px 0; }
.js-motion [data-rv="wipe-r"] { clip-path: inset(0 0 0 100%); translate: 14px 0; }
.js-motion [data-rv="wipe-u"] { clip-path: inset(100% 0 0 0); translate: 0 18px; }

/* A hard opacity cut, with no scale: the most literal cut there is, and it
   leaves `translate` free for elements that use it to sit where they sit. */
.js-motion [data-rv="snap"] {
  opacity: 0;
  transition: opacity .12s linear;
  transition-delay: var(--d, 0ms);
}

.js-motion .is-in [data-rv] {
  clip-path: inset(0 0 0 0);
  translate: none;
  opacity: 1;
}

/* Ngoại lệ duy nhất của luật trên, và là ngoại lệ bắt buộc.
   `inset(0)` là trạng thái nghỉ của mọi thẻ có hiệu ứng cuộn — nó xén đúng vào
   mép ô. Với bức ảnh đầu trang thì chính cái xén đó là "khung chữ nhật" bao
   quanh người: phóng ảnh lên là thấy ngay một cái hộp cắt cụt tay và chân.
   Ảnh tách nền không có khung nào cả, cái hộp là do dòng trên sinh ra.
   Nới đúng bằng số "cho tràn ra ngoài" để người được vượt khỏi ô của mình.
   Từ đây chỉ còn mép dưới của cả khối hero xén, mà đó là mép thật của trang.

   Mép trên để bằng % chứ không phải px: hiệu ứng cuộn chạy từ inset(100% …),
   nên nếu đích là -420px thì quãng chạy dài gấp đôi ô và cú quét lên xong veo
   trong một phần tư thời gian. -25% thì quãng chạy chỉ dài hơn một phần tư, cú
   quét giữ gần đúng nhịp cũ. Cần tới 25% vì file hero-final.png chỉ chừa 34
   trên tổng 770px trong suốt phía trên đầu (~25px khi lên màn hình) — phóng ảnh
   là đỉnh nón vượt mép ô ngay. 25% ≈ 140px, đủ tới cỡ phóng 150%. */
.js-motion .is-in .hero__portrait[data-rv] {
  clip-path: inset(
    -25%
    calc(var(--hero-spill) * -1px)
    calc(var(--hero-spill) * -1px)
  );
}

/* The two dividers are the page's hard cuts: carmine sweeps across, then the
   type lands. This is the one moment that repeats, and only twice. */
.js-motion .divider__sweep {
  position: absolute;
  inset: 0;
  background: var(--ground);
  transform-origin: right center;
  scale: 1 1;
  z-index: 1;
}
.js-motion .divider.is-cut .divider__sweep {
  scale: 0 1;
  transition: scale .5s var(--cut);
}
.divider__inner { position: relative; z-index: 2; }

@media (prefers-reduced-motion: reduce) {
  .js-motion [data-rv],
  .js-motion [data-rv].is-in {
    clip-path: none;
    translate: none;
    scale: 1;
    opacity: 1;
    transition: none;
  }
  .js-motion .divider__sweep { display: none; }
  * { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}
