/* ============================================================
   Manhattan Academy of Music and Language — landing page
   Single font: Be Vietnam Pro

   Style: Japanese / Swiss editorial restraint.
   Sharp slabs (border-radius 2px), thin rules, generous white
   space, no pills, no fat rounded cards, no colored washes.
   Color is a punctuation mark — used only on the swapped word
   inside a trailer card and the active card's 2px top hairline.
   ============================================================ */

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

:root {
  --bg: #ffffff;
  --ink: #0a0a0a;
  --ink-2: #2a2a2a;
  --muted: #6a6a6a;
  --muted-2: #9a9a9a;
  --line: #e6e6e6;
  --pill: #f3f3f3;
  --pill-hover: #ebebeb;

  --max: 1180px;
  --pad: clamp(20px, 4vw, 56px);

  /* Swiss restraint: every corner on the page is 2px. */
  --radius: 2px;

  --t-fast: 180ms cubic-bezier(.2, .7, .2, 1);
  --t-med:  360ms cubic-bezier(.2, .7, .2, 1);
}

html {
  background-color: var(--bg);
}

html, body {
  margin: 0;
  padding: 0;
  color: var(--ink);
  font-family: "Be Vietnam Pro", system-ui, -apple-system, sans-serif;
  font-weight: 400;
  font-size: 17px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

body {
  background-color: transparent;
}

/* Giấy gió trắng — real photographed paper, one piece covering the viewport
   (no tiling). Painted by a fixed pseudo-element behind all content so it
   can drift independently of scroll (JS adds a gentle parallax via
   --paper-y). The pseudo is oversized vertically (top/bottom inset) to give
   room for the translation without exposing edges. `background-attachment:
   fixed` is avoided because iOS Safari drops it and the paper starts
   scrolling in lockstep with text — `position: fixed` + `transform` works
   everywhere. Inner white slabs (cards, pillars, topbar) stay solid so
   type remains crisp. */
body::before {
  content: "";
  position: fixed;
  top: -200px;
  bottom: -200px;
  left: -140px;
  right: -140px;
  z-index: -1;
  pointer-events: none;
  background-image:
    /* ánh kim — cool→warm pearl shimmer, diagonal like light on xà cừ */
    linear-gradient(
      118deg,
      rgba(255, 248, 232, 0.55) 0%,
      rgba(255, 255, 255, 0) 26%,
      rgba(232, 240, 255, 0) 52%,
      rgba(250, 236, 248, 0.42) 78%,
      rgba(255, 250, 236, 0.38) 100%
    ),
    url("paper-giay-gio.jpg");
  background-repeat: no-repeat, no-repeat;
  background-size: 100% 100%, cover;
  background-position: center, center;
  /* Counter-clockwise tilt to level the natural fiber direction of
     the photographed sheet. scale(1.20) + expanded inset cover the
     corner triangles exposed by the 6° rotation. */
  transform: translate3d(0, var(--paper-y, 0px), 0) rotate(-6deg) scale(1.20);
  will-change: transform;
}

a {
  color: inherit;
  text-decoration: none;
}

/* ---------- Topbar ---------- */
.topbar {
  position: sticky;
  top: 0;
  z-index: 50;
  display: grid;
  grid-template-columns: max-content 1fr auto;
  align-items: center;
  gap: 24px;
  width: 100%;
  padding: 18px var(--pad);
  background: rgba(255, 255, 255, 0.92);
  backdrop-filter: saturate(140%) blur(10px);
  -webkit-backdrop-filter: saturate(140%) blur(10px);
  border-bottom: 1px solid var(--line);
}

.brand {
  display: flex;
  align-items: stretch;
  gap: 14px;
  min-height: 44px;
}

.brand-logo {
  align-self: stretch;
  height: auto;
  width: auto;
  min-height: 44px;
  max-height: 56px;
  aspect-ratio: 1 / 1;
  object-fit: contain;
  display: block;
  flex-shrink: 0;
}

.brand-name {
  display: flex;
  flex-direction: column;
  justify-content: center;
  line-height: 1.15;
}

.brand-en {
  font-weight: 800;
  font-size: 15px;
  letter-spacing: -0.01em;
  color: var(--ink);
  white-space: nowrap;
}

.brand-vi {
  font-weight: 400;
  font-size: 12px;
  color: var(--muted);
  font-style: italic;
  white-space: nowrap;
}

.topnav {
  grid-column: 3;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: center;
  gap: 0 4px;
  row-gap: 4px;
  justify-self: end;
  min-width: 0;
}

/* Topnav: no pills. Plain text with a subtle 1px underline on
   hover — Swiss editorial, no background washes. */
.topnav a {
  position: relative;
  padding: 8px 2px;
  margin: 0 10px;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--ink-2);
  transition: color var(--t-fast);
}

.topnav a::after {
  content: "";
  position: absolute;
  left: 2px;
  right: 2px;
  bottom: 4px;
  height: 1px;
  background: var(--ink);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--t-fast);
}

.topnav a:hover {
  color: var(--ink);
}

.topnav a:hover::after {
  transform: scaleX(1);
}

/* ---------- Main ---------- */
main {
  width: 100%;
  max-width: none;
  margin: 0;
  padding: 0;
}

/* ---------- Hero ---------- */
.hero {
  width: 100%;
  max-width: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: clamp(4px, 0.8vw, 12px) var(--pad) clamp(56px, 8vw, 96px);
}

/* ---------- Tôi Ăn Cơm blurb (bilingual verse) ----------
   Four poetic lines stacked vertically, each line rendered as
   a 2-column grid with English on the left and Vietnamese on
   the right. The columns meet at a center gutter — EN
   right-aligned, VI left-aligned — so the eye can jump across
   between languages line by line, the way a bilingual poem
   reads on a facing page. On narrow viewports the two columns
   collapse into a stacked EN/VI pair, centered. */
.tac-blurb {
  max-width: 1100px;
  margin: 0 auto 26px;
  padding: 0 var(--pad);
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.blurb-line {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(24px, 4vw, 64px);
  align-items: baseline;
}

.blurb-line .en {
  text-align: right;
  font-size: clamp(15px, 1.8vw, 20px);
  font-weight: 500;
  color: var(--ink);
  line-height: 1.4;
  letter-spacing: -0.005em;
}

.blurb-line .vi {
  text-align: left;
  font-size: clamp(13px, 1.55vw, 17px);
  font-weight: 400;
  font-style: italic;
  color: var(--muted);
  line-height: 1.5;
}

@media (max-width: 720px) {
  .blurb-line {
    grid-template-columns: 1fr;
    gap: 3px;
    text-align: center;
  }
  .blurb-line .en,
  .blurb-line .vi {
    text-align: center;
  }
}


/* ---------- Tôi Ăn Cơm trailer: shelf of swap modules ---------- */
.tac-stage {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  width: 100%;
}

.tac-library {
  position: relative;
  width: 100%;
  max-width: 1480px;
  height: 960px;
  perspective: 1800px;
  perspective-origin: 50% 50%;
}

/* Each card receives the following CSS custom properties from script.js,
   set per module from modules.js. No per-slot CSS rules — adding cards
   requires NO style edits.

   --accent       : per-card highlight color (becomes border + active word)
   --shelf-x      : px X offset on the shelf
   --shelf-y      : px Y offset
   --shelf-z      : px Z depth
   --shelf-roty   : deg Y rotation
   --shelf-rotz   : deg Z tilt
   --breathe-delay: animation-delay for the idle breathe pulse
*/

.tac-module {
  --accent: var(--ink);
  --shelf-x: 0px;
  --shelf-y: 0px;
  --shelf-z: 0px;
  --shelf-roty: 0deg;
  --shelf-rotz: 0deg;
  --breathe-delay: 0s;
  --shelf-z-index: 1;

  position: absolute;
  top: 50%;
  left: 50%;
  z-index: var(--shelf-z-index);
  width: 340px;
  height: 220px;
  padding: 22px 28px;
  background: #ffffff;

  /* RULE: restraint. Cards are sharp white slabs with subtle neutral
     elevation. The accent color appears ONLY on the swapped word
     itself (and as a single hairline accent on the active card's
     top edge). No colored glows, no halos, no shrines. Aim:
     Japanese / Swiss editorial — the page is mostly white space and
     typography. Don't reintroduce colored shadows. */
  border: none;
  border-radius: 2px;
  cursor: pointer;
  font-family: inherit;
  text-align: left;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  transform-style: preserve-3d;
  transform-origin: 50% 50%;
  backface-visibility: hidden;
  will-change: transform;
  box-shadow:
    0 1px 2px rgba(0, 0, 0, 0.04),
    0 14px 36px -14px rgba(0, 0, 0, 0.10);
  transition:
    transform 720ms cubic-bezier(.2, .7, .2, 1),
    box-shadow 720ms cubic-bezier(.2, .7, .2, 1);

  transform:
    translate(-50%, -50%)
    translateX(var(--shelf-x))
    translateY(var(--shelf-y))
    translateZ(var(--shelf-z))
    rotateY(var(--shelf-roty))
    rotateZ(var(--shelf-rotz));
}

/* Hover on ring cards: push forward in Z, soften the inward tilt.
   X/Y stay locked to the ring position — lifting a top-edge card
   would push it off-stage, so we don't touch them. */
.tac-module:hover:not(.is-active) {
  transform:
    translate(-50%, -50%)
    translateX(var(--shelf-x))
    translateY(var(--shelf-y))
    translateZ(calc(var(--shelf-z) + 70px))
    rotateY(calc(var(--shelf-roty) * 0.55))
    rotateZ(var(--shelf-rotz));
}

/* Active state — pulled forward to the very center, larger, with a
   refined neutral elevation. A single 2px accent hairline runs
   along the very top edge as the only colored signature.
   Width is fluid: grows to fit long swap variants (e.g. the full
   "tôi ăn cơm chưa" ADDREM sentence) up to a capped max so the
   shelf behind it stays visible. */
.tac-module.is-active {
  width: auto;
  min-width: 660px;
  max-width: min(92vw, 960px);
  height: auto;
  min-height: 320px;
  padding: 36px 52px 32px;
  transform: translate(-50%, -50%) translateZ(220px) rotateY(0deg) rotateZ(0deg) scale(1.1) !important;
  z-index: 100;
  box-shadow:
    0 1px 2px rgba(0, 0, 0, 0.05),
    0 40px 100px -28px rgba(0, 0, 0, 0.18);
  cursor: pointer;
  gap: 16px;
}

.tac-module.is-active::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: var(--accent);
}

/* When one card is active, the inactive ring cards stay SOLID (never
   transparent — text bleeding through is unreadable). They keep their
   ring position (X/Y) and just recede hard in Z so the active card
   clearly dominates the foreground without visually colliding with
   the surrounding frame. */
.tac-library.has-active .tac-module:not(.is-active) {
  transform:
    translate(-50%, -50%)
    translateX(var(--shelf-x))
    translateY(var(--shelf-y))
    translateZ(calc(var(--shelf-z) - 260px))
    rotateY(var(--shelf-roty))
    rotateZ(var(--shelf-rotz));
}

/* ----- Card content ----- */

/* Caption — the dim teaser line above the main sentence.
   Each module owns a bilingual `caption: { en, vi }` that
   hints at what the card is about to reveal without
   describing it mechanically. Rendered very faint so the
   transformed sentence stays the hero; when the card is
   active we bump opacity slightly so the caption becomes
   legible but still subordinate. */
.mod-caption {
  margin: 0 0 10px;
  padding: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  text-align: center;
  font-style: italic;
  line-height: 1.35;
  color: var(--muted-2);
  opacity: 0.42;
}

.mod-caption .en {
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.01em;
  color: var(--muted);
}

.mod-caption .vi {
  font-size: 11px;
  font-weight: 400;
  color: var(--muted-2);
}

.tac-module.is-active .mod-caption {
  opacity: 0.62;
  margin-bottom: 14px;
}

.tac-module.is-active .mod-caption .en {
  font-size: 13px;
}

.tac-module.is-active .mod-caption .vi {
  font-size: 12px;
}

/* Sentence layout is type-aware.
   - SWAP: 1fr | auto | 1fr grid — the active word stays pinned
     to the card's visual center because left and right contexts
     balance each other (rule: "swapped word always centered").
   - ADD:  flex row, the whole sentence centered as a unit inside
     .mod-word-active. The added particle can land anywhere in
     the sentence, sometimes as a pair (e.g. "có … đâu"), so a
     grid would force the particle off-center or cause overlap.
     Centering the sentence container avoids both. */
.mod-sentence {
  align-items: baseline;
  font-size: 28px;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.02em;
  margin: 6px 0;
  perspective: 900px;
  width: 100%;
  min-width: 0;
}

.tac-module[data-type="swap"] .mod-sentence {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
}

.tac-module[data-type="add"] .mod-sentence {
  display: flex;
  justify-content: center;
  flex-wrap: nowrap;
}

.mod-side {
  color: var(--ink);
  white-space: nowrap;
  font-weight: 800;
  opacity: 0.75;
}

/* SWAP sides: right-align left · left-align right so the sentence
   reads continuously around the centered active word. */
.tac-module[data-type="swap"] .mod-left  { text-align: right; padding-right: 0.36em; }
.tac-module[data-type="swap"] .mod-right { text-align: left;  padding-left:  0.36em; }

/* ADD sides are always empty (content lives inside
   .mod-word-active as segment children). Collapse them so the
   sentence flex doesn't reserve phantom space. */
.tac-module[data-type="add"] .mod-side { display: none; }

.mod-word-active {
  color: var(--accent);
  display: inline-block;
  white-space: nowrap;
  text-align: center;
  transform-origin: 50% 50%;
  backface-visibility: hidden;
  will-change: transform, opacity;
}

/* ADD: .mod-word-active is a segment container. Segments sit in a
   row with a natural word gap and rotate together with the parent
   during flip. A seg-hit is accent-colored; a seg-mute is neutral
   context. Supporting multiple seg-hit spans in one item is how
   template pairs like "có ... đâu" highlight both halves at once. */
.tac-module[data-type="add"] .mod-word-active {
  display: inline-flex;
  align-items: baseline;
  flex-wrap: nowrap;
  gap: 0.3em;
  white-space: nowrap;
}

.tac-module[data-type="add"] .mod-word-active .seg-hit {
  color: var(--accent);
  font-weight: 800;
}

.tac-module[data-type="add"] .mod-word-active .seg-mute {
  color: var(--ink);
  opacity: 0.75;
  font-weight: 800;
}

@keyframes mod-flip-out {
  0%   { transform: rotateX(0deg);   opacity: 1; }
  100% { transform: rotateX(-90deg); opacity: 0; }
}
@keyframes mod-flip-in {
  0%   { transform: rotateX(90deg);  opacity: 0; }
  100% { transform: rotateX(0deg);   opacity: 1; }
}

.mod-word-active.flip-out {
  animation: mod-flip-out 350ms cubic-bezier(.6, .02, .4, 1) forwards;
}
.mod-word-active.flip-in {
  animation: mod-flip-in 350ms cubic-bezier(.6, .02, .4, 1) forwards;
}

.mod-foot {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 2px;
  text-align: center;
  width: 100%;
  min-width: 0;
}

.mod-foot-en,
.mod-foot-vi {
  display: block;
  width: 100%;
  max-width: 100%;
  min-width: 0;
  overflow-wrap: break-word;
  word-break: break-word;
  hyphens: auto;
}

.mod-foot-en {
  font-size: 12px;
  font-weight: 700;
  color: var(--ink);
  line-height: 1.35;
}

.mod-foot-vi {
  font-size: 11px;
  font-style: italic;
  color: var(--muted);
  line-height: 1.35;
}

/* ----- Active-state typographic boost ----- */
.tac-module.is-active .mod-sentence {
  font-size: clamp(36px, 5vw, 48px);
  margin: 12px 0;
}

.tac-module.is-active .mod-foot-en {
  font-size: 14px;
}

.tac-module.is-active .mod-foot-vi {
  font-size: 12px;
}

.mod-mode {
  display: none;
  margin-top: 8px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted-2);
}

.tac-module.is-active .mod-mode {
  display: inline;
}

/* ----- Close (×) button ----- */
.mod-close {
  position: absolute;
  top: 12px;
  right: 12px;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: #ffffff;
  font-family: inherit;
  font-size: 18px;
  line-height: 1;
  color: var(--muted);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity 280ms ease, color 200ms ease, border-color 200ms ease, transform 200ms ease;
}

.tac-module.is-active .mod-close {
  opacity: 1;
  pointer-events: auto;
}

.mod-close:hover {
  color: var(--ink);
  border-color: var(--ink);
  transform: scale(1.08);
}

/* ----- Idle breath: a barely-perceptible elevation pulse ----- */
@keyframes tac-breathe {
  0%, 100% {
    box-shadow:
      0 1px 2px rgba(0, 0, 0, 0.04),
      0 14px 36px -14px rgba(0, 0, 0, 0.10);
  }
  50% {
    box-shadow:
      0 1px 2px rgba(0, 0, 0, 0.05),
      0 20px 48px -14px rgba(0, 0, 0, 0.14);
  }
}
.tac-library:not(.has-active) .tac-module {
  animation: tac-breathe 4.2s ease-in-out infinite;
  animation-delay: var(--breathe-delay);
}

.tac-hint {
  display: none;
  margin: 0;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  text-align: center;
}
.tac-hint .en {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
}
.tac-hint .vi {
  font-size: 11px;
  font-style: italic;
  color: var(--muted-2);
}

/* tac-link: plain text link with a 1px underline — not a pill. */
.tac-link {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  padding: 4px 0;
  border-bottom: 1px solid var(--ink);
  font-size: 13px;
  font-weight: 700;
  transition: opacity var(--t-fast);
}

.tac-link .en {
  color: var(--ink);
  font-weight: 700;
  letter-spacing: 0.02em;
}

.tac-link .vi {
  color: var(--muted);
  font-size: 11px;
  font-style: italic;
}

.tac-link:hover {
  opacity: 0.7;
}

/* ---------- Pillars ---------- */
/* Pillars: sharp white slabs divided by a 1px vertical rule,
   each titled with a thin top hairline. No rounded corners, no
   background tint, no lift on hover — the content does the work. */
.pillars {
  max-width: var(--max);
  margin: 0 auto;
  padding: 0 var(--pad) clamp(72px, 10vw, 140px);
  display: flex;
  flex-direction: row;
  align-items: stretch;
  gap: 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

.pillar {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  padding: clamp(40px, 5vw, 72px) clamp(28px, 4vw, 56px);
  background: #ffffff;
  border-radius: 0;
  display: flex;
  flex-direction: column;
}

.pillar + .pillar {
  border-left: 1px solid var(--line);
}

.pillar h2 {
  display: flex;
  flex-direction: column;
  margin: 0 0 28px;
  line-height: 1.1;
}

.pillar h2 .en {
  font-weight: 800;
  font-size: clamp(26px, 2.6vw, 34px);
  letter-spacing: -0.02em;
  color: var(--ink);
}

.pillar h2 .vi {
  font-weight: 400;
  font-size: 13px;
  color: var(--muted);
  font-style: italic;
  margin-top: 6px;
}

.pillar-body {
  margin: 0 0 14px;
  font-size: 16px;
  line-height: 1.6;
  color: var(--ink-2);
  max-width: 48ch;
}

.pillar-body-vi {
  margin: auto 0 0;
  padding-top: 20px;
  font-size: 14px;
  line-height: 1.6;
  color: var(--muted);
  font-style: italic;
  max-width: 52ch;
}

/* ---------- Genre / instrument emphasis ----------
   For Vietnamese musical genres and instruments (hát ru, vọng cổ,
   đàn tranh, hát xẩm, lý con sáo). Italic + bold + a marker-pen
   highlight underline. Color used here is intentional editorial
   punctuation, not a wash — only these named terms carry it. */
.dieu {
  font-style: italic;
  font-weight: 700;
  color: var(--ink);
  background: linear-gradient(
    180deg,
    transparent 0%,
    transparent 62%,
    rgba(232, 178, 56, 0.32) 62%,
    rgba(232, 178, 56, 0.32) 92%,
    transparent 92%
  );
  padding: 0 2px;
  border-radius: 1px;
}

.pillar-body-vi .dieu,
.pub-meta .dieu {
  font-weight: 600;
}

/* ---------- Publications ----------
   A long-form research list. Same Swiss/Japanese restraint as
   pillars: sharp white slabs, 1px hairline rules between items,
   year as a thin tag in the gutter, EN primary VI secondary.
   No rounded corners, no hover lift, no fill. */
.publications {
  max-width: var(--max);
  margin: 0 auto;
  padding: clamp(64px, 8vw, 120px) var(--pad);
  border-bottom: 1px solid var(--line);
}

.publications-head {
  margin: 0 0 clamp(36px, 4vw, 56px);
  max-width: 64ch;
}

.publications-head h2 {
  display: flex;
  flex-direction: column;
  margin: 0 0 20px;
  line-height: 1.1;
}

.publications-head h2 .en {
  font-weight: 800;
  font-size: clamp(26px, 2.6vw, 34px);
  letter-spacing: -0.02em;
  color: var(--ink);
}

.publications-head h2 .vi {
  font-weight: 400;
  font-size: 13px;
  color: var(--muted);
  font-style: italic;
  margin-top: 6px;
}

.publications-intro {
  margin: 0 0 6px;
  font-size: 16px;
  line-height: 1.6;
  color: var(--ink-2);
}

.publications-intro-vi {
  margin: 0;
  font-size: 14px;
  line-height: 1.6;
  color: var(--muted);
  font-style: italic;
}

.pub-list {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--line);
}

.pub-item {
  display: grid;
  grid-template-columns: 80px 1fr;
  gap: clamp(20px, 3vw, 40px);
  padding: clamp(20px, 2.4vw, 28px) 0;
  border-bottom: 1px solid var(--line);
}

.pub-year {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: var(--muted);
  padding-top: 3px;
}

.pub-body {
  min-width: 0;
}

.pub-title {
  font-size: 16px;
  font-weight: 600;
  line-height: 1.4;
  color: var(--ink);
  margin-bottom: 6px;
  max-width: 72ch;
}

.pub-meta {
  font-size: 13px;
  font-style: italic;
  line-height: 1.5;
  color: var(--muted);
  max-width: 72ch;
}

/* ---------- Apps ----------
   Same Swiss/Japanese restraint as publications: sharp white slabs,
   1px hairline rules between items, two-digit index in the gutter,
   title as a plain text link with a 1px underline on hover. No
   pills, no fill, no halos. Coming-soon items are non-interactive
   and muted — same row, marked by .is-soon. */
.apps {
  max-width: var(--max);
  margin: 0 auto;
  padding: clamp(64px, 8vw, 120px) var(--pad);
  border-bottom: 1px solid var(--line);
}

.apps-head {
  margin: 0 0 clamp(36px, 4vw, 56px);
  max-width: 64ch;
}

.apps-head h2 {
  display: flex;
  flex-direction: column;
  margin: 0 0 20px;
  line-height: 1.1;
}

.apps-head h2 .en {
  font-weight: 800;
  font-size: clamp(26px, 2.6vw, 34px);
  letter-spacing: -0.02em;
  color: var(--ink);
}

.apps-head h2 .vi {
  font-weight: 400;
  font-size: 13px;
  color: var(--muted);
  font-style: italic;
  margin-top: 6px;
}

.apps-intro {
  margin: 0 0 6px;
  font-size: 16px;
  line-height: 1.6;
  color: var(--ink-2);
}

.apps-intro-vi {
  margin: 0;
  font-size: 14px;
  line-height: 1.6;
  color: var(--muted);
  font-style: italic;
}

.app-list {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--line);
}

.app-item {
  display: grid;
  grid-template-columns: 80px 1fr;
  gap: clamp(20px, 3vw, 40px);
  padding: clamp(20px, 2.4vw, 28px) 0;
  border-bottom: 1px solid var(--line);
}

.app-num {
  font-family: "Be Vietnam Pro", system-ui, sans-serif;
  font-variant-numeric: tabular-nums;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: var(--muted);
  padding-top: 4px;
}

.app-body {
  min-width: 0;
}

a.app-title,
span.app-title {
  display: inline;
  font-size: 17px;
  font-weight: 700;
  line-height: 1.4;
  color: var(--ink);
  border-bottom: 1px solid transparent;
  transition: border-color var(--t-fast), color var(--t-fast);
}

a.app-title:hover {
  border-bottom-color: var(--ink);
}

.app-meta {
  font-size: 13px;
  font-style: italic;
  line-height: 1.5;
  color: var(--muted);
  margin-top: 6px;
  max-width: 72ch;
}

.app-item.is-soon .app-title {
  color: var(--muted-2);
  font-weight: 600;
}

.app-item.is-soon .app-num {
  color: var(--muted-2);
}

/* ---------- Books page ----------
   Same Swiss/Japanese restraint as the rest of the site. Sharp slabs,
   thin hairlines, EN primary VI secondary. The flipbook preview is
   intentionally small — enough to feel the design, not enough to
   read cover to cover. No zoom, no right-click save, no drag.
   For the full book in clear print, order a copy. */
.topnav a.is-active {
  color: var(--ink);
}
.topnav a.is-active::after {
  transform: scaleX(1);
}

.books-hero {
  max-width: var(--max);
  margin: 0 auto;
  padding: clamp(48px, 7vw, 96px) var(--pad) clamp(24px, 3vw, 40px);
  text-align: left;
}

.books-title {
  display: flex;
  flex-direction: column;
  margin: 12px 0 20px;
  line-height: 1.05;
}

.books-title .en {
  font-weight: 800;
  font-size: clamp(40px, 6vw, 72px);
  letter-spacing: -0.03em;
  color: var(--ink);
}

.books-title .vi {
  font-weight: 400;
  font-size: 14px;
  color: var(--muted);
  font-style: italic;
  margin-top: 8px;
}

.books-intro {
  max-width: 64ch;
  margin: 0 0 6px;
  font-size: 16px;
  line-height: 1.6;
  color: var(--ink-2);
}

.books-intro-vi {
  max-width: 64ch;
  margin: 0;
  font-size: 14px;
  line-height: 1.6;
  color: var(--muted);
  font-style: italic;
}

.books-section {
  max-width: var(--max);
  margin: 0 auto;
  padding: clamp(32px, 5vw, 64px) var(--pad) clamp(64px, 8vw, 120px);
  border-top: 1px solid var(--line);
}

.book-entry {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: clamp(28px, 4vw, 64px);
  padding: clamp(36px, 5vw, 64px) 0;
  border-bottom: 1px solid var(--line);
  align-items: start;
}

.book-entry:last-child {
  border-bottom: none;
}

/* --- Preview column --- */
.book-preview {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  width: min(560px, 100%);
  flex-shrink: 0;
}

.flipbook {
  position: relative;
  width: min(560px, 100%);
  max-width: 100%;
  background: #fafafa;
  border: 1px solid var(--line);
  overflow: hidden;
  user-select: none;
  -webkit-user-select: none;
}

.flipbook-img {
  display: block;
  width: 100%;
  height: auto;
  pointer-events: none;
  -webkit-user-drag: none;
  user-select: none;
  -webkit-user-select: none;
}

/* Transparent overlay that sits on top of the image and absorbs
   right-clicks and drags. Controls live OUTSIDE .flipbook so the
   guard doesn't block them. */
.flipbook-guard {
  position: absolute;
  inset: 0;
  background: transparent;
  cursor: default;
}

.flip-controls {
  display: flex;
  align-items: center;
  gap: 10px;
  font-variant-numeric: tabular-nums;
}

.flip-btn {
  width: 28px;
  height: 28px;
  border: 1px solid var(--line);
  background: #fff;
  color: var(--ink-2);
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  padding: 0;
  border-radius: var(--radius);
  transition: border-color var(--t-fast), color var(--t-fast);
}

.flip-btn:hover {
  border-color: var(--ink);
  color: var(--ink);
}

.flip-counter {
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
  min-width: 56px;
  text-align: center;
  letter-spacing: 0.04em;
}

.flip-note {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  margin: 0;
  font-size: 11px;
  text-align: center;
}

.flip-note .en {
  color: var(--muted);
  letter-spacing: 0.04em;
}

.flip-note .vi {
  color: var(--muted-2);
  font-style: italic;
}

/* --- Info column --- */
.book-info {
  min-width: 0;
  max-width: 56ch;
}

.book-kind {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  margin-bottom: 12px;
}

.book-kind .en {
  font-weight: 700;
  color: var(--ink-2);
}

.book-kind .vi {
  font-weight: 400;
  color: var(--muted);
  font-style: italic;
  text-transform: none;
  letter-spacing: 0.02em;
}

.book-title {
  margin: 0 0 8px;
  font-size: clamp(22px, 2.4vw, 30px);
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.2;
  color: var(--ink);
}

.book-authors {
  margin: 0 0 18px;
  font-size: 14px;
  font-style: italic;
  color: var(--muted);
}

.book-body {
  margin: 0 0 10px;
  font-size: 15px;
  line-height: 1.65;
  color: var(--ink-2);
}

.book-body-vi {
  margin: 0 0 24px;
  font-size: 13px;
  line-height: 1.65;
  color: var(--muted);
  font-style: italic;
}

.book-buy {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  padding: 10px 18px;
  border: 1px solid var(--ink);
  background: #fff;
  color: var(--ink);
  border-radius: var(--radius);
  transition: background var(--t-fast), color var(--t-fast);
}

.book-buy .en {
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.04em;
}

.book-buy .vi {
  font-size: 11px;
  font-style: italic;
  color: var(--muted);
  transition: color var(--t-fast);
}

.book-buy:hover {
  background: var(--ink);
  color: #fff;
}

.book-buy:hover .vi {
  color: var(--muted-2);
}

@media (max-width: 760px) {
  .book-entry {
    grid-template-columns: 1fr;
    justify-items: center;
    gap: 28px;
  }

  .book-info {
    text-align: left;
  }
}

/* ---------- Team page ---------- */
.team-hero {
  max-width: var(--max);
  margin: 0 auto;
  padding: clamp(48px, 7vw, 96px) var(--pad) clamp(24px, 3vw, 40px);
  text-align: left;
}

.team-title {
  display: flex;
  flex-direction: column;
  margin: 12px 0 20px;
  line-height: 1.05;
}

.team-title .en {
  font-weight: 800;
  font-size: clamp(40px, 6vw, 72px);
  letter-spacing: -0.03em;
  color: var(--ink);
}

.team-title .vi {
  font-weight: 400;
  font-size: 14px;
  color: var(--muted);
  font-style: italic;
  margin-top: 8px;
}

.team-intro {
  max-width: 64ch;
  margin: 0 0 6px;
  font-size: 16px;
  line-height: 1.6;
  color: var(--ink-2);
}

.team-intro-vi {
  max-width: 64ch;
  margin: 0;
  font-size: 14px;
  line-height: 1.6;
  color: var(--muted);
  font-style: italic;
}

/* Roster: full-bleed alternating rows. The <main> has no
   max-width, so rows span the whole viewport edge to edge.
   Each row is a 50/50 grid; the image column sits flush to
   the outside edge (no padding), the text column carries
   the inside padding. */
.team-roster {
  border-top: 1px solid var(--line);
}

.team-row {
  display: grid;
  grid-template-columns: clamp(240px, 28vw, 360px) 1fr;
  align-items: center;
  gap: clamp(28px, 4vw, 72px);
  padding: clamp(48px, 6vw, 96px) var(--pad);
  border-bottom: 1px solid var(--line);
}

.team-row:last-child {
  border-bottom: none;
}

.team-row[data-side="right"] {
  grid-template-columns: 1fr clamp(240px, 28vw, 360px);
}

.team-row[data-side="right"] .team-photo {
  order: 2;
}

.team-row[data-side="right"] .team-info {
  order: 1;
}

.team-photo {
  background: #fafafa;
}

.team-photo img {
  display: block;
  width: 100%;
  height: auto;
  pointer-events: none;
  -webkit-user-drag: none;
  user-select: none;
  -webkit-user-select: none;
}

.team-info {
  display: flex;
  flex-direction: column;
  justify-content: center;
  max-width: 64ch;
}

.team-kind {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  margin-bottom: 12px;
}

.team-kind .en {
  font-weight: 700;
  color: var(--ink-2);
}

.team-kind .vi {
  font-weight: 400;
  color: var(--muted);
  font-style: italic;
  text-transform: none;
  letter-spacing: 0.02em;
}

.team-name {
  margin: 0 0 6px;
  font-size: clamp(26px, 3vw, 38px);
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.15;
  color: var(--ink);
}

.team-roles {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0 0 20px;
  font-size: 13px;
}

.team-roles .en {
  color: var(--ink-2);
  font-weight: 600;
}

.team-roles .vi {
  color: var(--muted);
  font-style: italic;
}

.team-body {
  margin: 0 0 10px;
  font-size: 15px;
  line-height: 1.65;
  color: var(--ink-2);
}

.team-body-vi {
  margin: 0 0 24px;
  font-size: 13px;
  line-height: 1.65;
  color: var(--muted);
  font-style: italic;
}

.team-link {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  padding: 10px 18px;
  border: 1px solid var(--ink);
  background: #fff;
  color: var(--ink);
  border-radius: var(--radius);
  align-self: flex-start;
  transition: background var(--t-fast), color var(--t-fast);
}

.team-link .en {
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.04em;
}

.team-link .vi {
  font-size: 11px;
  font-style: italic;
  color: var(--muted);
  transition: color var(--t-fast);
}

.team-link:hover {
  background: var(--ink);
  color: #fff;
}

.team-link:hover .vi {
  color: var(--muted-2);
}

@media (max-width: 820px) {
  .team-row,
  .team-row[data-side="right"] {
    grid-template-columns: 1fr;
    justify-items: start;
    gap: 24px;
  }

  .team-row[data-side="right"] .team-photo,
  .team-row[data-side="right"] .team-info {
    order: initial;
  }

  .team-photo {
    width: min(320px, 80%);
  }

  .team-info {
    max-width: none;
  }
}

/* ---------- Contact ---------- */
.contact {
  max-width: var(--max);
  margin: 0 auto;
  padding: clamp(64px, 8vw, 120px) var(--pad);
  border-bottom: 1px solid var(--line);
}

.contact-head {
  margin: 0 0 clamp(36px, 4vw, 56px);
  max-width: 64ch;
}

.contact-head h2 {
  display: flex;
  flex-direction: column;
  margin: 0;
  line-height: 1.1;
}

.contact-head h2 .en {
  font-weight: 800;
  font-size: clamp(26px, 2.6vw, 34px);
  letter-spacing: -0.02em;
  color: var(--ink);
}

.contact-head h2 .vi {
  font-weight: 400;
  font-size: 13px;
  color: var(--muted);
  font-style: italic;
  margin-top: 6px;
}

.contact-list {
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

.contact-row {
  display: grid;
  grid-template-columns: minmax(120px, 180px) 1fr;
  gap: 32px;
  padding: 24px 0;
  border-top: 1px solid var(--line);
}

.contact-row:last-child {
  border-bottom: 1px solid var(--line);
}

.contact-label {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.contact-label .en {
  font-weight: 700;
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink);
}

.contact-label .vi {
  font-weight: 400;
  font-size: 12px;
  color: var(--muted);
  font-style: italic;
}

.contact-value {
  margin: 0;
  font-size: 16px;
  line-height: 1.6;
  color: var(--ink-2);
}

.contact-value a {
  position: relative;
  color: var(--ink);
  font-weight: 600;
}

.contact-value a::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -2px;
  height: 1px;
  background: var(--ink);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--t-fast);
}

.contact-value a:hover::after {
  transform: scaleX(1);
}

@media (max-width: 640px) {
  .contact-row {
    grid-template-columns: 1fr;
    gap: 10px;
  }
}

/* ============================================================
   Repertoire — index grid + per-piece subpages + lightbox
   Full-bleed tablature, lazy YouTube facades, mobile pinch-zoom
   ============================================================ */

/* ---------- Shared: hero ---------- */
.rep-hero {
  max-width: var(--max);
  margin: 0 auto;
  padding: clamp(36px, 5vw, 72px) var(--pad) clamp(24px, 3vw, 40px);
}

.rep-back {
  display: inline-block;
  margin-bottom: clamp(20px, 3vw, 36px);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  border-bottom: 1px solid transparent;
  transition: color var(--t-fast), border-color var(--t-fast);
}
.rep-back:hover { color: var(--ink); border-bottom-color: var(--ink); }

.rep-eyebrow {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0 0 14px;
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.rep-eyebrow .en { font-weight: 700; color: var(--ink-2); }
.rep-eyebrow .vi { font-weight: 400; color: var(--muted); font-style: italic; text-transform: none; letter-spacing: 0.02em; }

.rep-title {
  display: flex;
  flex-direction: column;
  margin: 0 0 clamp(20px, 3vw, 32px);
  line-height: 1.02;
}
.rep-title .vi-big {
  font-weight: 800;
  font-size: clamp(36px, 6vw, 72px);
  letter-spacing: -0.03em;
  color: var(--ink);
}
.rep-title .en-small {
  margin-top: 10px;
  font-weight: 400;
  font-size: clamp(14px, 1.4vw, 17px);
  color: var(--muted);
  font-style: italic;
}

.rep-blurb {
  max-width: 62ch;
  margin: 0 0 6px;
  font-size: 16px;
  line-height: 1.65;
  color: var(--ink-2);
}
.rep-blurb-vi {
  max-width: 62ch;
  margin: 0 0 clamp(24px, 3vw, 40px);
  font-size: 14px;
  line-height: 1.65;
  color: var(--muted);
  font-style: italic;
}

.rep-terms {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 4px 28px;
  margin: 0;
  padding: 20px 0 0;
  border-top: 1px solid var(--line);
  max-width: 880px;
}
.rep-terms dt {
  font-weight: 700;
  font-size: 14px;
  color: var(--ink);
}
.rep-terms dd {
  margin: 0 0 10px;
  font-size: 13px;
  color: var(--muted);
  font-style: italic;
}

/* ---------- Shared: tablature image + sections ---------- */
.rep-overview,
.rep-level,
.rep-performance {
  padding: clamp(28px, 4vw, 56px) 0 clamp(12px, 2vw, 24px);
  border-top: 1px solid var(--line);
}

.rep-level-title {
  max-width: var(--max);
  margin: 0 auto clamp(16px, 2vw, 24px);
  padding: 0 var(--pad);
  font-size: clamp(13px, 1.4vw, 15px);
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}
.rep-level-title .n { color: var(--ink); }

.rep-tab-btn {
  display: block;
  width: 100%;
  padding: 0;
  margin: 0;
  border: none;
  background: transparent;
  cursor: zoom-in;
}
.rep-tab {
  display: block;
  width: 100%;
  max-width: 1400px;
  margin: 0 auto;
  height: auto;
  /* Tablature images are wide and thin; let them breathe */
  background: #fafafa;
}

.rep-caption {
  max-width: var(--max);
  margin: clamp(14px, 2vw, 22px) auto clamp(16px, 2vw, 24px);
  padding: 0 var(--pad);
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.rep-caption .en {
  font-size: 14px;
  color: var(--ink-2);
  line-height: 1.55;
}
.rep-caption .vi {
  font-size: 12px;
  color: var(--muted);
  font-style: italic;
  line-height: 1.55;
}

/* ---------- Video strip: signal | video | signal | video | ... ---------- */
/* Free horizontal scroll, thin scrollbar, no arrow controls. Pattern
   borrowed from anhthuphan.com/media.html band-track: each video is
   preceded by a small signal slot that marks which phrase comes next. */
.rep-strip-wrap {
  /* Full-bleed: the strip extends past the page max-width to hint at
     more content off-screen. Scrollbar belongs to this wrapper. */
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: thin;
  scrollbar-color: var(--muted-2) transparent;
  -webkit-overflow-scrolling: touch;
  padding-bottom: 10px;
  /* Reserve horizontal pan for this container. Without pan-x, iOS Safari
     cannot tell a horizontal strip-swipe from a vertical page-scroll and
     the horizontal gesture is lost to the page. */
  touch-action: pan-x;
  overscroll-behavior-x: contain;
}
.rep-strip-wrap::-webkit-scrollbar {
  height: 6px;
}
.rep-strip-wrap::-webkit-scrollbar-track {
  background: transparent;
}
.rep-strip-wrap::-webkit-scrollbar-thumb {
  background: var(--muted-2);
  border-radius: 3px;
}

.rep-strip {
  display: flex;
  align-items: stretch;
  gap: 12px;
  width: max-content;
  padding: 0 var(--pad);
}

/* All slots share the same height so video + signal line up cleanly. */
.rep-slot {
  --slot-h: clamp(180px, 22vw, 240px);
  flex-shrink: 0;
  height: var(--slot-h);
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  position: relative;
}

.rep-slot-video {
  /* 16:9 width derived from height so video face fits exactly */
  width: calc(var(--slot-h) * 16 / 9);
}
.rep-slot-video .yt-facade {
  width: 100%;
  height: 100%;
  /* Strip mode: the parent slot already enforces 16:9 via its fixed
     height × 16/9 width; the facade just fills. Kill padding-top so
     the facade doesn't add extra ratio on top. */
  padding-top: 0;
  border: none;
  border-radius: 0;
}
.rep-slot-video .yt-iframe {
  width: 100%;
  height: 100%;
}

/* Signal slot — the "nhỏ thôi" block before each video */
.rep-slot-signal {
  width: clamp(72px, 9vw, 96px);
  padding: 14px 10px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  background: #fafafa;
  text-align: center;
}
.rep-sig-mark {
  font-size: 8px;
  line-height: 1;
  color: var(--muted-2);
  margin-bottom: 2px;
}
.rep-sig-head {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}
.rep-sig-num {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: clamp(14px, 1.4vw, 17px);
  font-weight: 700;
  color: var(--ink);
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}

/* Alias slot — "same as Phrase 1" placeholder */
.rep-slot-alias {
  width: calc(var(--slot-h) * 16 / 9);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  border-style: dashed;
  background: #fafafa;
}
.rep-slot-alias span {
  font-size: 12px;
  color: var(--muted);
  font-style: italic;
  text-align: center;
  line-height: 1.5;
}

/* ---------- YouTube facade (thumbnail + play, iframe on click) ---------- */
/* Uses padding-top: 56.25% for a 16:9 box, same reason as .rep-card-img-wrap:
   aspect-ratio has lazy-load edge cases on older iPadOS Safari where the
   facade briefly collapses before the thumbnail loads. The .rep-slot-video
   override (strip mode) uses `aspect-ratio: unset` + explicit height, so
   its `.yt-facade` flows from the parent slot height instead. */
.yt-facade {
  position: relative;
  width: 100%;
  padding-top: 56.25%;
  background: #000;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  cursor: pointer;
}
.yt-thumb {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0.88;
  transition: opacity var(--t-fast);
}
.yt-facade:hover .yt-thumb { opacity: 1; }
.yt-play {
  position: absolute;
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  width: 56px; height: 56px;
  border: none;
  background: rgba(0, 0, 0, 0.72);
  color: #fff;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  border-radius: var(--radius);
  transition: background var(--t-fast), transform var(--t-fast);
}
.yt-facade:hover .yt-play {
  background: rgba(0, 0, 0, 0.88);
  transform: translate(-50%, -50%) scale(1.04);
}
.yt-iframe {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}

.rep-performance .yt-facade {
  max-width: min(920px, 100%);
  margin: 0 auto;
}

/* Overview block — image + caption + video stacked */
.rep-overview .yt-facade {
  max-width: min(920px, 100%);
  margin: clamp(8px, 1.2vw, 16px) auto 0;
  margin-left: auto;
  margin-right: auto;
  display: block;
}
/* ---------- Lightbox ---------- */
/* Three ways to exit: × button, tap image (1 finger), tap backdrop, Esc.
   Pinch is two-finger so it zooms without triggering close. The top bar
   is pinned to viewport so the × stays reachable at any zoom level. */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: rgba(10, 10, 10, 0.96);
  display: none;
  opacity: 0;
  transition: opacity var(--t-fast);
}
.lightbox.is-open {
  display: block;
  opacity: 1;
}

.lightbox-bar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: max(12px, env(safe-area-inset-top)) max(16px, env(safe-area-inset-right)) 12px max(16px, env(safe-area-inset-left));
  background: linear-gradient(to bottom, rgba(0,0,0,0.75), rgba(0,0,0,0));
  pointer-events: none;
}
.lightbox-bar > * { pointer-events: auto; }

.lightbox-hint {
  display: flex;
  flex-direction: column;
  gap: 1px;
  font-size: 11px;
  letter-spacing: 0.06em;
  color: rgba(255, 255, 255, 0.75);
  max-width: calc(100% - 68px);
}
.lightbox-hint .en {
  font-weight: 600;
  text-transform: uppercase;
  color: #fff;
}
.lightbox-hint .vi {
  font-style: italic;
  color: rgba(255, 255, 255, 0.6);
  font-size: 10px;
}

.lightbox-close {
  flex-shrink: 0;
  width: 48px;
  height: 48px;
  border: 1px solid rgba(255, 255, 255, 0.4);
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  font-size: 26px;
  line-height: 1;
  cursor: pointer;
  border-radius: var(--radius);
  font-family: inherit;
}
.lightbox-close:hover {
  border-color: #fff;
  background: rgba(0, 0, 0, 0.85);
}

.lightbox-scroll {
  position: absolute;
  inset: 0;
  overflow: auto;
  touch-action: pinch-zoom;
  -webkit-overflow-scrolling: touch;
}
.lightbox-img {
  display: block;
  min-width: 100%;
  min-height: 100%;
  max-width: none;
  height: auto;
  margin: auto;
  cursor: zoom-out;
}
body.lightbox-open {
  overflow: hidden;
}

/* ---------- Repertoire index grid ---------- */
.rep-index {
  max-width: var(--max);
  margin: 0 auto;
  padding: 0 var(--pad) clamp(64px, 8vw, 120px);
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: clamp(24px, 3vw, 40px);
}

.rep-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
  color: var(--ink);
  transition: transform var(--t-fast);
}
.rep-card:hover { transform: translateY(-2px); }

.rep-card-img-wrap {
  /* Square 1:1 wrapper, bulletproof on every browser. padding-top:100%
     forces the box height = width; the img inside is absolutely
     positioned to fill. This avoids the aspect-ratio inconsistency seen
     on older iPadOS Safari where lazy-loaded images would render at
     natural size inside the aspect-ratio box. */
  width: 100%;
  padding-top: 100%;
  position: relative;
  background: #fafafa;
  border: 1px solid var(--line);
  overflow: hidden;
}
.rep-card-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform var(--t-med);
}
.rep-card:hover .rep-card-img { transform: scale(1.02); }

.rep-card-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.rep-card-vi {
  font-weight: 700;
  font-size: 16px;
  color: var(--ink);
  letter-spacing: -0.01em;
}
.rep-card-en {
  font-weight: 400;
  font-size: 13px;
  font-style: italic;
  color: var(--muted);
}

/* ---------- Basics subpage article ---------- */
/* Basics pages are traditional articles — heading, paragraph, image,
   video interleaved. Share hero typography with .rep-hero. */
.basics-article {
  max-width: 760px;
  margin: 0 auto;
  padding: clamp(28px, 4vw, 56px) var(--pad) clamp(64px, 8vw, 120px);
  border-top: 1px solid var(--line);
}

.basics-h2 {
  margin: clamp(36px, 5vw, 64px) 0 18px;
  font-size: clamp(22px, 2.4vw, 30px);
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.2;
  color: var(--ink);
}
.basics-h2:first-child { margin-top: 0; }

.basics-h3 {
  margin: clamp(28px, 4vw, 44px) 0 12px;
  font-size: clamp(16px, 1.6vw, 19px);
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.3;
  color: var(--ink);
}

.basics-h4 {
  margin: 24px 0 8px;
  font-size: 14px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
}

.basics-p {
  margin: 0 0 16px;
  font-size: 16px;
  line-height: 1.7;
  color: var(--ink-2);
}

/* Images in the article are click-to-zoom, full-column width */
.basics-img-btn {
  display: block;
  width: 100%;
  padding: 0;
  margin: clamp(18px, 2.6vw, 28px) 0;
  border: none;
  background: transparent;
  cursor: zoom-in;
}
.basics-img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: #fafafa;
}

/* Video wrapper — the facade carries its own 16:9 via padding-top */
.basics-video {
  margin: clamp(18px, 2.6vw, 28px) 0;
}

/* Mobile polish — repertoire + basics + shared */
@media (max-width: 760px) {
  .rep-terms {
    grid-template-columns: 1fr;
  }
  .lightbox-hint .vi { display: none; }

  /* Basics article — tighten padding and let long headings wrap */
  .basics-article {
    padding: clamp(20px, 5vw, 36px) var(--pad) clamp(48px, 8vw, 80px);
  }
  .basics-h2,
  .basics-h3,
  .basics-h4,
  .basics-p {
    /* Long English-Vietnamese title fragments ("Where to Pluck with
       Your Index, Middle, and Thumb") must break mid-word on narrow
       screens instead of forcing horizontal overflow. */
    overflow-wrap: break-word;
    word-break: break-word;
  }
  .basics-h2 {
    margin-top: clamp(28px, 5vw, 40px);
  }

  /* Shared hero (used by basics + repertoire + subpages): tighter top
     padding on mobile so the title isn't pushed below the fold. */
  .rep-hero {
    padding-top: clamp(24px, 5vw, 48px);
  }
  .rep-title .vi-big {
    font-size: clamp(30px, 9vw, 48px);
  }
}

/* ---------- Footer ---------- */
.footer {
  max-width: var(--max);
  margin: 0 auto;
  padding: 32px var(--pad) 48px;
  border-top: 1px solid var(--line);
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
  font-size: 12px;
  color: var(--muted);
}

.footer .en {
  font-weight: 600;
  color: var(--ink-2);
}

.footer .vi {
  font-style: italic;
  color: var(--muted-2);
}

/* ---------- Courses page ----------
   Two parallel tracks (đàn tranh + đàn bầu) share one schedule.
   The only color on the page comes from the two instrument
   accents, mirroring the tones used on the printed flyer. */
:root {
  --tranh: #246b78;   /* cool teal — đàn tranh zither */
  --bau:   #9b3f26;   /* warm ochre-red — đàn bầu monochord */
}

.dieu-tranh,
.dieu-bau {
  font-style: italic;
  font-weight: 700;
}
.dieu-tranh { color: var(--tranh); }
.dieu-bau   { color: var(--bau); }

.courses-hero {
  max-width: var(--max);
  margin: 0 auto;
  padding: clamp(48px, 7vw, 96px) var(--pad) clamp(24px, 3vw, 40px);
  text-align: left;
}

.courses-title {
  display: flex;
  flex-direction: column;
  margin: 12px 0 20px;
  line-height: 1.05;
}

.courses-title .en {
  font-weight: 800;
  font-size: clamp(40px, 6vw, 72px);
  letter-spacing: -0.03em;
  color: var(--ink);
}

.courses-title .vi {
  font-weight: 400;
  font-size: 14px;
  color: var(--muted);
  font-style: italic;
  margin-top: 8px;
}

.courses-intro {
  max-width: 64ch;
  margin: 0 0 6px;
  font-size: 16px;
  line-height: 1.6;
  color: var(--ink-2);
}

.courses-intro-vi {
  max-width: 64ch;
  margin: 0;
  font-size: 14px;
  line-height: 1.6;
  color: var(--muted);
  font-style: italic;
}

.course-entry {
  max-width: var(--max);
  margin: 0 auto;
  padding: clamp(32px, 5vw, 64px) var(--pad) clamp(32px, 5vw, 64px);
  border-top: 1px solid var(--line);
  display: grid;
  grid-template-columns: minmax(280px, 420px) 1fr;
  gap: clamp(28px, 4vw, 64px);
  align-items: start;
}

.course-flyer {
  width: 100%;
  max-width: 420px;
}

.course-flyer-img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  user-select: none;
  -webkit-user-drag: none;
  -webkit-user-select: none;
}

.course-info {
  min-width: 0;
  max-width: 64ch;
}

.course-kind {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  margin-bottom: 12px;
}

.course-kind .en {
  font-weight: 700;
  color: var(--ink-2);
}

.course-kind .vi {
  font-weight: 400;
  color: var(--muted);
  font-style: italic;
  text-transform: none;
  letter-spacing: 0.02em;
}

.course-title {
  margin: 0 0 4px;
  font-size: clamp(28px, 3.2vw, 44px);
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.15;
  display: flex;
  flex-wrap: wrap;
  gap: 0.35em;
  align-items: baseline;
}

.course-title .course-amp {
  color: var(--muted-2);
  font-weight: 300;
  font-style: normal;
}

.course-sub {
  margin: 0 0 20px;
  font-size: 13px;
  font-style: italic;
  color: var(--muted);
}

.course-blurb {
  font-size: 15px;
  line-height: 1.65;
  color: var(--ink-2);
  margin-bottom: 24px;
}

.course-blurb p {
  margin: 0 0 12px;
}

.course-blurb p:last-child {
  margin-bottom: 0;
}

.course-blurb h1,
.course-blurb h2,
.course-blurb h3 {
  font-size: 15px;
  font-weight: 800;
  margin: 16px 0 6px;
  letter-spacing: -0.01em;
  color: var(--ink);
}

.course-blurb ul,
.course-blurb ol {
  margin: 0 0 12px;
  padding-left: 1.2em;
}

.course-blurb li {
  margin: 0 0 4px;
}

.course-blurb a {
  color: var(--tranh);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.course-blurb strong {
  font-weight: 700;
  color: var(--ink);
}

.course-blurb em {
  font-style: italic;
}

.course-loading {
  color: var(--muted-2);
  font-style: italic;
}

.course-meta {
  margin: 0;
  padding: 18px 0 0;
  border-top: 1px solid var(--line);
  display: grid;
  gap: 14px;
  font-size: 13px;
}

.course-meta > div {
  display: grid;
  grid-template-columns: 120px 1fr;
  gap: 16px;
  align-items: start;
}

.course-meta dt {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.course-meta dt .en {
  font-weight: 700;
  color: var(--ink-2);
}

.course-meta dt .vi {
  font-weight: 400;
  color: var(--muted);
  font-style: italic;
  text-transform: none;
  letter-spacing: 0.02em;
}

.course-meta dd {
  margin: 0;
  color: var(--ink-2);
  line-height: 1.6;
}

.dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  margin-right: 6px;
  vertical-align: 0;
  border-radius: 50%;
}

.dot-tranh { background: var(--tranh); }
.dot-bau   { background: var(--bau); }

.course-schedule-section {
  max-width: var(--max);
  margin: 0 auto;
  padding: clamp(40px, 6vw, 80px) var(--pad) clamp(64px, 8vw, 120px);
  border-top: 1px solid var(--line);
}

.schedule-head {
  margin-bottom: 28px;
}

.schedule-title {
  display: flex;
  flex-direction: column;
  margin: 0 0 12px;
  line-height: 1.05;
}

.schedule-title .en {
  font-weight: 800;
  font-size: clamp(28px, 3.6vw, 44px);
  letter-spacing: -0.02em;
  color: var(--ink);
}

.schedule-title .vi {
  font-weight: 400;
  font-size: 13px;
  color: var(--muted);
  font-style: italic;
  margin-top: 6px;
}

.schedule-note {
  margin: 0 0 2px;
  font-size: 14px;
  color: var(--ink-2);
  max-width: 64ch;
}

.schedule-note-vi {
  margin: 0 0 18px;
  font-size: 13px;
  color: var(--muted);
  font-style: italic;
  max-width: 64ch;
}

/* "I have a code" CTA — opens the password modal. Sits on the
   right edge of the schedule head so the schedule headline
   reads first, the CTA second. */
.code-cta {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0;
  padding: 10px 18px;
  border: 1px solid var(--ink);
  background: #fff;
  color: var(--ink);
  cursor: pointer;
  border-radius: var(--radius);
  font: inherit;
  transition: background var(--t-fast), color var(--t-fast);
}

.code-cta .en {
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.04em;
}

.code-cta .vi {
  font-size: 11px;
  font-style: italic;
  color: var(--muted);
  transition: color var(--t-fast);
}

.code-cta:hover {
  background: var(--ink);
  color: #fff;
}

.code-cta:hover .vi {
  color: var(--muted-2);
}

/* --- Password modal ---
   Backdrop + centered card. Built once by courses.js on the
   first click and re-used across opens. Uses the same Swiss
   restraint as the rest of the page: sharp slabs, hairlines,
   color reserved for state (tranh = ok, bau = err). */
.code-modal {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

.code-modal[hidden] {
  display: none;
}

.code-modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(10, 10, 10, 0.42);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
}

.code-modal-card {
  position: relative;
  z-index: 1;
  background: #fff;
  border: 1px solid var(--ink);
  border-radius: var(--radius);
  padding: 32px 36px 28px;
  width: min(440px, 100%);
  display: flex;
  flex-direction: column;
  gap: 12px;
  box-shadow: 0 24px 60px rgba(10, 10, 10, 0.18);
}

@keyframes code-modal-shake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-6px); }
  40% { transform: translateX(6px); }
  60% { transform: translateX(-4px); }
  80% { transform: translateX(4px); }
}

.code-modal-card.is-shake {
  animation: code-modal-shake 280ms cubic-bezier(.4, .1, .2, 1);
}

.code-modal-close {
  position: absolute;
  top: 8px;
  right: 10px;
  width: 28px;
  height: 28px;
  background: transparent;
  border: none;
  font-size: 22px;
  line-height: 1;
  color: var(--muted);
  cursor: pointer;
  padding: 0;
  border-radius: var(--radius);
  transition: color var(--t-fast);
}

.code-modal-close:hover {
  color: var(--ink);
}

.code-modal-eyebrow {
  display: flex;
  flex-direction: column;
  gap: 1px;
  margin: 0;
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.code-modal-eyebrow .en {
  font-weight: 700;
  color: var(--bau);
}

.code-modal-eyebrow .vi {
  font-weight: 400;
  font-style: italic;
  color: var(--muted);
  text-transform: none;
  letter-spacing: 0.02em;
}

.code-modal-title {
  margin: 0;
  font-size: 22px;
  font-weight: 800;
  letter-spacing: -0.01em;
  line-height: 1.2;
  color: var(--ink);
}

.code-modal-title .en {
  display: block;
}

.code-modal-title .vi {
  display: block;
  font-size: 13px;
  font-weight: 400;
  font-style: italic;
  color: var(--muted);
  margin-top: 4px;
  letter-spacing: 0;
}

.code-modal-note {
  margin: 4px 0 8px;
  font-size: 13px;
  line-height: 1.55;
  color: var(--ink-2);
}

.code-modal-input {
  width: 100%;
  padding: 12px 14px;
  border: 1px solid var(--line);
  background: #fff;
  font: inherit;
  font-size: 16px;
  border-radius: var(--radius);
  letter-spacing: 0.08em;
}

.code-modal-input:focus {
  outline: none;
  border-color: var(--ink);
}

.code-modal-row {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  margin-top: 4px;
}

.code-modal-submit {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0;
  padding: 10px 18px;
  border: 1px solid var(--ink);
  background: var(--ink);
  color: #fff;
  cursor: pointer;
  border-radius: var(--radius);
  font: inherit;
  transition: background var(--t-fast), color var(--t-fast);
}

.code-modal-submit .en {
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.04em;
}

.code-modal-submit .vi {
  font-size: 11px;
  font-style: italic;
  color: var(--muted-2);
}

.code-modal-submit:hover {
  background: #fff;
  color: var(--ink);
}

.code-modal-submit:hover .vi {
  color: var(--muted);
}

.code-modal-status {
  font-size: 12px;
  color: var(--muted);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.code-modal-status[data-kind="ok"]  { color: var(--tranh); }
.code-modal-status[data-kind="err"] { color: var(--bau); }

body.code-modal-open {
  overflow: hidden;
}

.schedule-list {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--line);
}

/* One entry per session. A session is a single unit of
   content taught twice — once on sunday and once on thursday.
   Inside each block, two instrument rows (đàn tranh, đàn bầu)
   each show their topic and two video slots, one per day. */
.session-block {
  list-style: none;
  padding: 24px 0 28px;
  border-bottom: 1px solid var(--line);
}

.session-header {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 14px 24px;
  margin-bottom: 16px;
}

.session-num {
  font-weight: 800;
  font-size: 22px;
  letter-spacing: -0.01em;
  color: var(--ink);
  text-transform: lowercase;
}

.session-dates {
  display: inline-flex;
  align-items: baseline;
  gap: 10px;
  font-variant-numeric: tabular-nums;
  font-size: 13px;
  color: var(--ink-2);
}

.session-date {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
}

.session-day {
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}

.session-date-sep {
  color: var(--muted-2);
}

.session-instruments {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 14px;
}

.instrument-row {
  display: grid;
  grid-template-columns: minmax(260px, 1.1fr) minmax(0, 1.2fr) auto;
  gap: 20px;
  padding: 12px 0 12px 14px;
  align-items: start;
  position: relative;
}

.instrument-row::before {
  content: "";
  position: absolute;
  left: 0;
  top: 16px;
  bottom: 12px;
  width: 2px;
  background: var(--accent, var(--line));
}

.instrument-row.inst-dan-tranh { --accent: var(--tranh); }
.instrument-row.inst-dan-bau   { --accent: var(--bau); }

.instrument-head {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

.instrument-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
  margin-bottom: 2px;
}

.instrument-name {
  font-style: italic;
  font-weight: 700;
  color: var(--accent);
  font-size: 16px;
}

.instrument-times {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px 10px;
  font-size: 11px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.instrument-time {
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
}

.instrument-day {
  font-size: 9px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted-2);
}

.instrument-time-sep {
  color: var(--muted-2);
}

.instrument-topic {
  font-size: 14px;
  color: var(--ink-2);
  line-height: 1.5;
  padding-top: 2px;
}

.instrument-videos {
  display: flex;
  flex-direction: column;
  gap: 6px;
  justify-self: end;
}

.video-slot {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 6px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  font-size: 11px;
  color: var(--muted);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  min-width: 132px;
  justify-content: space-between;
  text-decoration: none;
  transition:
    background var(--t-fast),
    color var(--t-fast),
    border-color var(--t-fast);
}

.video-slot .video-day {
  font-weight: 700;
  color: var(--ink-2);
  font-size: 9px;
  letter-spacing: 0.14em;
}

.video-slot.is-pending {
  border-style: dashed;
  color: var(--muted-2);
}

.video-slot.is-pending .video-day {
  color: var(--muted-2);
}

.video-slot.is-locked {
  border-color: var(--line);
  color: var(--muted);
}

.video-slot.is-unlocked {
  border-color: var(--accent);
  color: var(--accent);
  font-weight: 700;
}

.video-slot.is-unlocked .video-day {
  color: var(--accent);
}

.video-slot.is-unlocked:hover {
  background: var(--accent);
  color: #fff;
}

.video-slot.is-unlocked:hover .video-day {
  color: #fff;
}

.schedule-loading,
.schedule-empty {
  padding: 24px 0;
  color: var(--muted-2);
  font-style: italic;
  list-style: none;
}

/* ---------- Responsive ---------- */
/* ---------- Trailer: tablet/phone carousel ----------
   Desktop's rectangular 3D ring is a widescreen-only paradigm.
   Below 1024px we swap to a horizontal scroll-snap carousel:
   native touch momentum, per-card snap, every card visible at
   small size, user swipes. Every 3D/perspective rule is killed
   via !important because JS still sets shelf CSS vars inline —
   CSS needs to win to flatten the stage.

   The flip animation (rotate .mod-word-active) still works
   because that's internal to the card, independent of the
   card's own 3D transform. */
@media (max-width: 1280px) {
  /* Prevent the horizontal card row from pushing the whole page
     wider than the viewport. Without this, a flex container with
     14×300px children = 4200px of intrinsic width can leak out
     and force body-level horizontal scroll, which is exactly the
     "cards scattered everywhere, taps don't stick, jumps like a
     frog" symptom we saw. overflow-x: clip on body + max-width:
     100vw on library together guarantee the scroll is internal
     to the library only. */
  html, body {
    overflow-x: clip;
    max-width: 100vw;
  }

  main, .hero, .tac-stage {
    max-width: 100vw;
    overflow-x: clip;
  }

  .tac-stage {
    align-items: stretch;
  }

  .tac-library {
    /* flatten the 3D stage */
    perspective: none;
    width: 100%;
    max-width: 100vw;
    min-width: 0;
    height: auto;
    min-height: 0;

    /* horizontal scroll-snap row */
    display: flex;
    flex-direction: row;
    flex-wrap: nowrap;
    gap: 14px;
    padding: 24px 20px 32px;
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x mandatory;
    scroll-padding-inline: 20px;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .tac-library::-webkit-scrollbar {
    display: none;
  }

  .tac-module {
    /* flow item, not absolute-positioned 3D object */
    position: relative !important;
    top: auto !important;
    left: auto !important;
    transform: none !important;
    transform-style: flat !important;

    width: 380px !important;
    min-width: 380px !important;
    max-width: 380px !important;
    height: auto !important;
    min-height: 220px !important;
    flex: 0 0 auto;

    scroll-snap-align: center;
    animation: none !important;
  }

  /* Trackpad tablets still fire hover — kill the desktop 3D
     hover transform so it doesn't jolt the carousel row. */
  .tac-module:hover:not(.is-active) {
    transform: none !important;
  }

  /* Active state: no 3D pull, no size grow, no forced width.
     The card already carries its accent hairline via ::before,
     which is enough of a signal. The user is looking directly
     at whichever card they tapped. */
  .tac-module.is-active {
    width: 380px !important;
    min-width: 380px !important;
    max-width: 380px !important;
    padding: 22px 24px !important;
    transform: none !important;
    z-index: 2;
  }

  /* Sentence font stays BIG on tablet/phone — the sentence is
     the hero of the card, so we don't scale it down. Instead we
     let it wrap onto multiple lines when it exceeds the card
     width. Desktop's nowrap rules (on .mod-sentence and
     .mod-word-active segments) are relaxed here so flex can
     break between segments. Card height grows naturally to fit
     the wrapped content. */
  .tac-module .mod-sentence,
  .tac-module.is-active .mod-sentence {
    font-size: clamp(28px, 3.6vw, 36px) !important;
    margin: 10px 0 !important;
    white-space: normal !important;
    flex-wrap: wrap !important;
    justify-content: center;
  }

  .tac-module[data-type="add"] .mod-word-active {
    flex-wrap: wrap !important;
    white-space: normal !important;
    justify-content: center;
    row-gap: 4px;
  }

  /* Bump the supporting text (caption teaser, footer nuance,
     mode indicator) so everything on the card stays readable at
     arm's length. Desktop defaults are 10-12px which is tight
     on a phone. */
  .tac-module .mod-caption .en,
  .tac-module.is-active .mod-caption .en {
    font-size: 14px;
  }
  .tac-module .mod-caption .vi,
  .tac-module.is-active .mod-caption .vi {
    font-size: 13px;
  }
  .tac-module .mod-foot-en,
  .tac-module.is-active .mod-foot-en {
    font-size: 14px;
  }
  .tac-module .mod-foot-vi,
  .tac-module.is-active .mod-foot-vi {
    font-size: 12px;
  }
  .tac-module .mod-mode {
    font-size: 11px;
  }

  /* Inactive cards while one is active: stay put in the row.
     The eye tracks via horizontal position, not Z-depth. */
  .tac-library.has-active .tac-module:not(.is-active) {
    transform: none !important;
  }

  /* Kill the idle breathe animation — battery + perceptual
     noise on small screens. */
  .tac-library:not(.has-active) .tac-module {
    animation: none !important;
  }
}

/* ---------- Trailer: phone-specific carousel sizing ----------
   Under 640px we give the card 92vw so the longest sentences
   (e.g. "làm sao mà tôi ăn cơm được", "chừng nào tôi ăn cơm?")
   have room to breathe, while still leaving a 4vw peek of the
   next card on each edge — the peek is the swipe affordance.
   Font is scaled down further so those long sentences don't
   overflow. */
@media (max-width: 640px) {
  .tac-module,
  .tac-module.is-active {
    width: 92vw !important;
    min-width: 92vw !important;
    max-width: 92vw !important;
    padding: 20px 22px !important;
  }

  .tac-module .mod-sentence,
  .tac-module.is-active .mod-sentence {
    font-size: clamp(28px, 7.5vw, 40px) !important;
  }

  /* Phone gets an extra bump for the supporting text so the
     whole card is legible at arm's length without squinting. */
  .tac-module .mod-caption .en,
  .tac-module.is-active .mod-caption .en {
    font-size: 15px;
  }
  .tac-module .mod-caption .vi,
  .tac-module.is-active .mod-caption .vi {
    font-size: 14px;
  }
  .tac-module .mod-foot-en,
  .tac-module.is-active .mod-foot-en {
    font-size: 15px;
  }
  .tac-module .mod-foot-vi,
  .tac-module.is-active .mod-foot-vi {
    font-size: 13px;
  }
  .tac-module .mod-mode {
    font-size: 12px;
  }

  .tac-library {
    padding: 18px 4vw 28px;
    gap: 10px;
    scroll-padding-inline: 4vw;
  }
}

@media (max-width: 720px) {
  .topbar {
    grid-template-columns: auto auto;
    grid-template-rows: auto auto;
    row-gap: 12px;
  }

  .topnav {
    grid-column: 1 / -1;
    justify-self: start;
    flex-wrap: wrap;
    gap: 0;
  }

  .topnav a:first-child { margin-left: 0; }

  .brand-vi { font-size: 13px; }
  .brand-en { font-size: 11px; }

  .pillars {
    grid-template-columns: 1fr;
  }

  .pillar + .pillar {
    border-left: none;
    border-top: 1px solid var(--line);
  }

  .pub-item {
    grid-template-columns: 1fr;
    gap: 6px;
  }

  .pub-year {
    padding-top: 0;
  }

  .app-item {
    grid-template-columns: 1fr;
    gap: 6px;
  }

  .app-num {
    padding-top: 0;
  }

  .course-entry {
    grid-template-columns: 1fr;
  }

  .course-flyer {
    max-width: 100%;
  }

  .instrument-row {
    grid-template-columns: 1fr;
    gap: 10px;
    padding-left: 12px;
  }

  .instrument-videos {
    justify-self: start;
    flex-direction: row;
    flex-wrap: wrap;
  }

  .session-header {
    gap: 8px 18px;
  }

  .session-num {
    font-size: 18px;
  }
}

/* ============================================================
   Photos — editorial vertical stream
   Single centered column, large imagery, minimal chrome.
   Full-bleed background is plain white; the only color cues
   are a hairline rule above each caption and the numeric index.
   ============================================================ */

.photos-hero {
  max-width: 760px;
  margin: 0 auto;
  padding: clamp(56px, 9vw, 120px) var(--pad) clamp(48px, 7vw, 88px);
  text-align: center;
}

.photos-title {
  margin: 10px 0 28px;
  font-size: clamp(44px, 7vw, 88px);
  font-weight: 800;
  line-height: 1.02;
  letter-spacing: -0.025em;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}

.photos-title .en { color: var(--ink); }
.photos-title .vi {
  font-weight: 400;
  font-style: italic;
  font-size: 0.42em;
  color: var(--muted);
  letter-spacing: 0;
}

.photos-intro,
.photos-intro-vi {
  max-width: 56ch;
  margin: 0 auto 10px;
  color: var(--ink-2);
  font-size: 16px;
  line-height: 1.65;
}

.photos-intro-vi {
  font-style: italic;
  color: var(--muted);
}

/* ---------- Photos: horizontal accordion ----------
   A wide rack that scrolls horizontally. Each item is a vertical
   column: compressed items are thin 44px slivers, the active item
   expands into a tall portrait-friendly column. object-fit: contain
   on the active image guarantees portraits AND landscapes both show
   in full with no crop — that was the whole reason for flipping
   from vertical accordion. The rack itself scrolls horizontally;
   the vertical page scroll (hero → rack → footer) is unchanged. */

.photos-accordion {
  --header-h: 84px;
  /* Collapsed sliver width — JS reads this via getComputedStyle
     to stay in sync with CSS breakpoints (36px on mobile). */
  --item-w: 44px;
  margin: 0;
  padding: 0;
  max-width: none;
  width: 100%;
  height: calc(100vh - var(--header-h));
  overflow: hidden;
  background: #0a0a0a;
  position: relative;
}

/* Inner track — JS translates this horizontally so the active
   item sits at the viewport center. Using transform instead of
   scrollLeft means no boundary clamping — items at the start or
   end of the row center the same way as items in the middle. */
.photos-track {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  display: flex;
  flex-direction: row;
  gap: 2px;
  height: 100%;
  will-change: transform;
  transition: transform 900ms cubic-bezier(.72, 0, .12, 1);
}

.pa-item {
  position: relative;
  margin: 0;
  flex: 0 0 auto;
  width: var(--item-w, 44px);
  height: 100%;
  overflow: hidden;
  background: #0a0a0a;
  cursor: pointer;
  outline: none;
  transition: width 900ms cubic-bezier(.72, 0, .12, 1);
  will-change: width;
}

.pa-item:focus-visible {
  box-shadow: inset 0 0 0 2px #fff;
}

.pa-item.is-active {
  /* Width follows the photo's NATIVE aspect × rack height, so the
     expanded column is exactly the photo's own shape — no black
     bars on the sides, no cropping. JS writes --aspect
     (naturalWidth / naturalHeight) on image load; the fallback
     0.67 is a portrait guess used for the split second before the
     image arrives. Capped at 98vw so very wide panoramas still
     fit the viewport (those accept a small top/bottom crop from
     object-fit: cover). */
  width: min(calc(var(--aspect, 0.67) * (100vh - var(--header-h))), 98vw);
}

.pa-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* cover everywhere — on compressed slivers it shows a center slice,
     and on the active column the container already matches the photo's
     aspect, so cover renders the full frame with no visible crop. */
  object-fit: cover;
  object-position: center;
  display: block;
  transform-origin: 50% 50%;
  transform: scale(1.05);
  filter: brightness(0.45) saturate(0.8);
  transition: transform 1600ms cubic-bezier(.72, 0, .12, 1),
              filter 900ms cubic-bezier(.72, 0, .12, 1);
  will-change: transform, filter;
  backface-visibility: hidden;
  pointer-events: none;
}

.pa-item.is-active .pa-img {
  filter: brightness(1) saturate(1);
  animation: paZoom 9s ease-in-out 200ms infinite alternate;
}

/* Slow zoom on the active frame, Camille Mormal-style. */
@keyframes paZoom {
  0%   { transform: scale(1);    }
  100% { transform: scale(1.05); }
}

/* Caption on the active frame: large, anchored to the bottom. */
.pa-cap {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2;
  padding: clamp(20px, 3vw, 40px) clamp(22px, 4vw, 56px);
  display: flex;
  align-items: flex-end;
  gap: clamp(16px, 2.4vw, 32px);
  color: #fff;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.78) 0%, rgba(0, 0, 0, 0) 100%);
  opacity: 0;
  transform: translateY(12px);
  transition: opacity 500ms ease 250ms, transform 500ms ease 250ms;
  pointer-events: none;
}

.pa-item.is-active .pa-cap {
  opacity: 1;
  transform: translateY(0);
}

.pa-num {
  font-variant-numeric: tabular-nums;
  font-size: 11px;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.72);
  white-space: nowrap;
  padding-bottom: 4px;
}

.pa-title {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
  flex: 1 1 auto;
}

.pa-year {
  font-variant-numeric: tabular-nums;
  font-size: 11px;
  letter-spacing: 0.22em;
  color: rgba(255, 255, 255, 0.6);
  text-transform: uppercase;
}

.pa-text {
  font-weight: 600;
  font-size: clamp(16px, 2vw, 24px);
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: #fff;
}

.pa-text::first-letter {
  text-transform: uppercase;
}

/* Vertical label for compressed slivers — rotated so it reads
   bottom-to-top along the slim column, like a book spine. */
.pa-edge {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  letter-spacing: 0.14em;
  color: rgba(255, 255, 255, 0.82);
  white-space: nowrap;
  overflow: hidden;
  font-variant-numeric: tabular-nums;
  text-transform: lowercase;
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  padding: 16px 0;
  transition: opacity 300ms ease;
  pointer-events: none;
  background: linear-gradient(to bottom, rgba(0, 0, 0, 0.55), rgba(0, 0, 0, 0.05));
}

.pa-item.is-active .pa-edge {
  opacity: 0;
}

/* Hover preview: a touch of extra width on compressed slivers so
   the column feels responsive before clicking. */
.pa-item:hover:not(.is-active) {
  width: 64px;
}

.pa-item:hover:not(.is-active) .pa-img {
  filter: brightness(0.72) saturate(0.9);
}

@media (prefers-reduced-motion: reduce) {
  .pa-item,
  .pa-img,
  .pa-cap {
    transition: none;
  }
  .pa-item.is-active .pa-img {
    animation: none;
  }
}

/* ---------- Photos: mobile/tablet vertical scroll ----------
   Below 1024px the horizontal accordion is replaced by a simple
   vertical column where each photo is one block at its NATIVE
   aspect ratio, full viewport width. Why:
     - touch can't do hover; horizontal swipe fights vertical
       scroll; an absolute-positioned slideshow runs out of
       memory and crashes mobile Safari
     - vertical scroll is the most natural mobile photo gesture
     - native loading="lazy" lets the browser GC off-screen
       bitmaps automatically — no JS virtualization needed
     - portraits AND landscapes both show in full at their own
       aspect ratio, no letterbox, no cropping
   Animation: each block fades up as it enters the viewport via
   IntersectionObserver (added by JS). The currently most-visible
   block also gets a subtle ken-burns scale while it sits in the
   middle of the screen — same Camille-Mormal-style movement as
   desktop, but driven by scroll position instead of a timer. */

@media (max-width: 1024px) {
  .photos-accordion {
    height: auto !important;
    width: 100%;
    margin: 0;
    padding: 0 0 40px;
    overflow: visible;
    background: #0a0a0a;
    position: static;
    cursor: default;
  }

  .photos-track {
    position: static !important;
    display: block !important;
    height: auto !important;
    width: 100%;
    transform: none !important;
    transition: none !important;
    will-change: auto;
    gap: 0;
  }

  .pa-item,
  .pa-item.is-active,
  .pa-item:hover:not(.is-active) {
    position: relative;
    display: block;
    width: 100% !important;
    height: auto !important;
    margin: 0 0 36px;
    inset: auto;
    background: #0a0a0a;
    overflow: hidden;
    cursor: default;
    pointer-events: auto;
    opacity: 0;
    transform: translateY(28px);
    transition: opacity 800ms cubic-bezier(.2, .7, .2, 1),
                transform 800ms cubic-bezier(.2, .7, .2, 1);
    will-change: opacity, transform;
    z-index: auto;
  }

  /* Added by IntersectionObserver when the block enters the
     viewport — triggers the fade-up. */
  .pa-item.is-visible {
    opacity: 1;
    transform: translateY(0);
  }

  .pa-img {
    position: relative !important;
    inset: auto;
    width: 100% !important;
    height: auto !important;
    display: block;
    object-fit: contain;
    background: #0a0a0a;
    filter: none !important;
    transform: scale(1);
    animation: none !important;
    transition: transform 1800ms cubic-bezier(.5, .1, .3, 1);
    will-change: auto;
  }

  /* Subtle ken-burns only on the photo nearest viewport center. */
  .pa-item.is-near .pa-img {
    transform: scale(1.04);
  }

  .pa-cap {
    position: relative;
    inset: auto;
    z-index: auto;
    background: transparent;
    color: #fff;
    padding: 14px 18px 0;
    display: flex;
    align-items: baseline;
    gap: 14px;
    opacity: 1 !important;
    transform: none !important;
    pointer-events: auto;
  }

  .pa-num {
    color: rgba(255, 255, 255, 0.45);
    font-size: 10px;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    padding-bottom: 0;
  }

  .pa-title {
    color: #fff;
  }

  .pa-year {
    color: rgba(255, 255, 255, 0.45);
    font-size: 10px;
    letter-spacing: 0.22em;
  }

  .pa-text {
    color: #fff;
    font-size: 14px;
    line-height: 1.35;
    font-weight: 500;
  }

  /* The spine label was for skinny slivers — meaningless in a
     vertical scroll layout. */
  .pa-edge {
    display: none !important;
  }
}

@media (max-width: 760px) {
  .photos-hero {
    padding-top: 36px;
    padding-bottom: 28px;
  }

  .pa-item {
    margin-bottom: 28px;
  }

  .pa-cap {
    padding: 12px 14px 0;
    gap: 12px;
  }
}
