/**
 * TONGRU — Shared component styles (Phase B: Homepage assembly)
 * Extracted from src/sections/{certifications,trust-bar,comparison-table,services-courses}.php
 * Loaded once per page (home-page-preview.php) — do NOT duplicate @font-face or :root tokens
 * inside individual section files anymore; sections should assume this file is already loaded.
 *
 * Token values below are the ACTUAL per-node colors returned by Figma get_design_context on the
 * Home Page frame (node 1219:746), which differ slightly from the generic swatch in CLAUDE.md
 * (e.g. neutral-100 here is #e5e5eb, not CLAUDE.md's #e5e5e5) — this project's established rule
 * (see comparison-table.php, services-courses.php comments) is that a node's real Figma value
 * always wins over the generic token when the two disagree on a design value.
 *
 * Grid: 12-column desktop (>=1024px) / 8-column tablet (768-1023px) / 4-column mobile (<768px),
 * per docs/wireframe-context/Homepage.md ("Grid: 12-column (desktop) / 8-column (tablet) /
 * 4-column (mobile)"). Breakpoints match Working Convention.md's standard px cuts.
 */

@font-face {
  font-family: 'Kanit';
  font-style: normal;
  font-weight: 400;
  font-display: block;
  src: url(https://fonts.gstatic.com/s/kanit/v17/nKKZ-Go6G5tXcraBGwCYdA.woff2) format('woff2');
  unicode-range: U+02D7, U+0303, U+0331, U+0E01-0E5B, U+200C-200D, U+25CC;
}
@font-face {
  font-family: 'Kanit';
  font-style: normal;
  font-weight: 500;
  font-display: block;
  src: url(https://fonts.gstatic.com/s/kanit/v17/nKKU-Go6G5tXcr5mOBWzVaF5NQ.woff2) format('woff2');
  unicode-range: U+02D7, U+0303, U+0331, U+0E01-0E5B, U+200C-200D, U+25CC;
}
@font-face {
  font-family: 'Kanit';
  font-style: normal;
  font-weight: 600;
  font-display: block;
  src: url(https://fonts.gstatic.com/s/kanit/v17/nKKU-Go6G5tXcr5KPxWzVaF5NQ.woff2) format('woff2');
  unicode-range: U+02D7, U+0303, U+0331, U+0E01-0E5B, U+200C-200D, U+25CC;
}
@font-face {
  font-family: 'Kanit';
  font-style: normal;
  font-weight: 700;
  font-display: block;
  src: url(https://fonts.gstatic.com/s/kanit/v17/nKKU-Go6G5tXcr4uPhWzVaF5NQ.woff2) format('woff2');
  unicode-range: U+02D7, U+0303, U+0331, U+0E01-0E5B, U+200C-200D, U+25CC;
}
@font-face {
  font-family: 'Kanit';
  font-style: normal;
  font-weight: 400;
  font-display: block;
  src: url(https://fonts.gstatic.com/s/kanit/v17/nKKZ-Go6G5tXcraVGwA.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Kanit';
  font-style: normal;
  font-weight: 500;
  font-display: block;
  src: url(https://fonts.gstatic.com/s/kanit/v17/nKKU-Go6G5tXcr5mOBWnVaE.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Kanit';
  font-style: normal;
  font-weight: 600;
  font-display: block;
  src: url(https://fonts.gstatic.com/s/kanit/v17/nKKU-Go6G5tXcr5KPxWnVaE.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Kanit';
  font-style: normal;
  font-weight: 700;
  font-display: block;
  src: url(https://fonts.gstatic.com/s/kanit/v17/nKKU-Go6G5tXcr4uPhWnVaE.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  /* Shades below that were already here (primary-900/800/600, accent-800/600/400, neutral-800/
     600/400/200/100/50) are the ACTUAL per-node colors measured via get_design_context on the
     Home Page frame — see the file docblock above. The shades added below (primary-400/200/100/
     50, accent-900/200/100/50, neutral-900, base-black, overlay, error) had never been measured
     against a real node before nav.php's mega menu needed --tg-primary-400 and found it silently
     undefined — so they're filled in from root CLAUDE.md's documented generic swatch instead,
     the same fallback rule CLAUDE.md itself specifies until a real per-node value overrides one. */
  --tg-primary-900: #06061a;
  --tg-primary-800: #111145;
  /* 2026-09-09: กลับมาเป็น #2a2a6b ตาม design system ของ Astra (get_variable_defs บน 2577:543
     ระบุ "Primary - 600":"#2A2A6B") — ค่า #2a2aab ที่ใช้ระหว่าง 2026-08-31 ถึง 2026-09-09 มาจาก
     mockup ชุดเก่า (node 2200:5118 "Test Design2") ที่ user ยืนยันตอนนั้นให้ใช้ทั้งระบบ แต่ตอนนี้
     แหล่งอ้างอิงเปลี่ยนเป็นไฟล์ Astra แล้ว (คำสั่ง "ทำให้เหมือน Figma 100%") · token นี้คุมสี
     eyebrow / section label / คำโปรย ทั้งเว็บ จึงเป็นสาเหตุหลักที่ user ทักว่า "สีหลายๆ ส่วนไม่ตรง" */
  --tg-primary-600: #2a2a6b;
  --tg-primary-400: #4d4d8f;
  --tg-primary-200: #a8a8cc;
  --tg-primary-100: #d4d4e6;
  --tg-primary-50: #ededf5;
  --tg-accent-900: #46380c;
  --tg-accent-800: #6e5814;
  --tg-accent-600: #9c7d1d;
  --tg-accent-400: #c9a227; /* gold — per Semantic Mapping.md, allowed use: 1px contact-icon circle border */
  --tg-accent-200: #e5c670;
  --tg-accent-100: #f2e1b3;
  --tg-accent-50: #fbf5e6;
  --tg-neutral-900: #1f1f1f;
  --tg-neutral-800: #3d3d45;
  --tg-neutral-600: #6a6a75;
  --tg-neutral-400: #9c9ca6;
  --tg-neutral-200: #cfcfd6;
  --tg-neutral-100: #e5e5eb;
  --tg-neutral-50: #f7f7fa;
  --tg-base-white: #ffffff;
  --tg-base-black: #000000;
  --tg-overlay: rgba(6, 6, 26, 0.6);
  --tg-error: #a33a31;
  --tg-font-family: 'Kanit', sans-serif;

  /* Breakpoints (documented for reference; media queries below hardcode the px values because
     CSS custom properties cannot be used inside @media conditions). */
  --tg-bp-tablet: 1023px;
  --tg-bp-mobile: 767px;

  /* ── Radius scale (2026-08-31) ──────────────────────────────────────────────────────────────
     Home Page had ~11 distinct border-radius values (4px through 100px) because each was copied
     literally from its own Figma node — and Figma's own mockup was never internally consistent
     about radius across sections to begin with (unlike color/type, which ARE consistent in
     Figma). Corner radius differs from color/spacing in one important way: a few px of radius
     is not visually distinguishable, so consolidating to a small named scale costs ~zero fidelity
     while paying off across every future page this project builds. Rule going forward: when a
     Figma node's own radius is within ~2-3px of one of these tiers, use the tier, not the literal
     px. When a node's radius is deliberately NOT a full pill even though the element is
     pill-shaped (e.g. Hero's CTA button — 20px on a 55px-tall control, well short of the 27.5px
     that would make it a true capsule), that's a real visual choice, not noise — keep the literal
     value (see --tg-radius-xl below) rather than forcing it into --tg-radius-pill. */
  --tg-radius-xs: 0.375rem; /* 6px — small icon chips, badge overlays, focus-outline corners */
  --tg-radius-sm: 0.5rem; /* 8px — cards, inputs, standard rectangular buttons (the default, already the most common value site-wide) */
  --tg-radius-lg: 1rem; /* 16px — larger cards, big badge boxes */
  --tg-radius-xl: 1.25rem; /* 20px — soft-capsule controls that are intentionally NOT a full pill (Hero CTA button, Hero contact cluster) */
  --tg-radius-pill: 9999px; /* fully-rounded pill/circle — used where a control's own radius was already >= half its shorter dimension (visually already a capsule/circle), so snapping to this is a zero-visual-change consolidation, not a redesign */

  /* ── Astra type scale (2026-09-08) ───────────────────────────────────────────────────────────
     สเกลของ redesign รอบ Astra — เป็น **ส่วนขยาย** ของ token ใน CLAUDE.md ไม่ใช่ตัวแทน
     (สี + font-family ยังใช้ของเดิมทุกตัว เปลี่ยนแค่ขนาด/น้ำหนัก/ระยะ)

     ทำไมต้องมีชุดนี้: สองวันแรกของ redesign เขียนค่าที่วัดจาก Figma ฝังลงในแต่ละไฟล์ section
     ผลคือ "หัวข้อ section" ซึ่งเป็นหน้าที่เดียวกัน ได้ 44px ที่ services-courses, 46px ที่
     case-study/team/contact/why-tongru, 42px ที่ blog และ 32px (token เก่า) ที่ certifications
     กับ gallery-reviews — ทั้งหมดในหน้าเดียวกัน

     ทำไมเลือก 44px: ตรวจ Figma ทีละ node แล้วพบว่า **แบบเองก็ไม่ตรงกัน** ไม่ใช่เราอ่านผิด —
       node 2455:604 (Gallery & Reviews, หน้าแรก) = 48px tracking -0.96px
       node 2455:535 (Team, หน้าแรก)              = 46px tracking -0.92px
       node 2535:593 (All Services)               = 44px tracking -0.88px
       node 2455:557 (Blog, หน้าแรก)              = สูง 50px ÷ lh 1.2 = 42px
     หัวข้อ section ในหน้าแรกหน้าเดียวห่างกันสุด 6px ทั้งที่ทำหน้าที่เดียวกัน
     Astra ใช้กฎ tracking = -2% ของ font-size สม่ำเสมอ แต่ไม่ได้ล็อกขนาดให้เท่ากัน
     44px เป็นค่ากลางของช่วง 42-48 และเป็นค่าที่ใช้เยอะสุดในโค้ดอยู่แล้ว → เลือกเป็นค่ากลาง
     ไม่ใช่การเบี่ยงจากแบบ แต่คือการเลือกหนึ่งในหลายค่าที่แบบเองให้มาไม่ตรงกัน
     บันทึกไว้ที่ docs/astra-system.md ข้อ 5

     ⚠️ ข้อยกเว้นที่ไม่รวมเข้า token นี้: **Certifications ใช้ 32px จริง** (node 2455:596) และ
     Figma ระบุว่าเป็น text style ชื่อ "Title/Section" ของ Design System เก่าที่ Astra หยิบมาใช้
     ตรงๆ + จัดกึ่งกลาง ซึ่งเป็นทรีตเมนต์คนละแบบกับหัวข้อ 2 คอลัมน์ชิดซ้ายของ section อื่น
     จึงคง `--tg-title-section` (32px) ไว้ที่ section นั้น ไม่ดันขึ้น 44px เพราะนั่นจะเป็น
     การตัดสินใจออกแบบของเราเอง ไม่ใช่ของ Astra

     ⚠️ ห้ามเขียน font-size/letter-spacing เป็น px หรือ rem ตรงๆ ในไฟล์ section อีก
     เจอค่าใหม่ใน Figma → ห่างจาก token ไหนไม่เกิน 2px ให้ใช้ token นั้น ถ้าเกินให้เพิ่ม token
     ที่นี่พร้อม node ที่วัดมา (ดูกติกาเต็มที่ docs/astra-system.md ข้อ 1) */
  --tg-astra-h1:           700 3.75rem/1.2 var(--tg-font-family);    /* 60px — H1 หน้าย่อย (node 2535:531) */
  --tg-astra-h1-tracking:  -1.2px;                                   /* -2% ของ 60px */
  --tg-astra-section:      700 2.75rem/1.2 var(--tg-font-family);    /* 44px — หัวข้อ section ทุก section ทุกหน้า */
  --tg-astra-section-tracking: -0.88px;                              /* -2% ของ 44px */
  --tg-astra-card-title:   600 1.5625rem/1.3 var(--tg-font-family);  /* 25px — หัวข้อการ์ด (node 2455:402 = 2535:1002) */
  --tg-astra-card-tracking: -0.25px;
  --tg-astra-subtitle:     600 1.75rem/1.3 var(--tg-font-family);    /* 28px — หัวข้อย่อยในบล็อก เช่น why-tongru */
  --tg-astra-lead:         500 1.3125rem/1.7 var(--tg-font-family);  /* 21px — คำโปรยใต้/ข้างหัวข้อ (node 2455:537) */
  --tg-astra-quote:        500 1.125rem/1.7 var(--tg-font-family);   /* 18px — ข้อความรีวิว (node 2535:793) */
  --tg-astra-body:         400 1rem/1.6 var(--tg-font-family);       /* 16px — เนื้อหาทั่วไป, ปุ่ม */
  --tg-astra-label:        500 0.9375rem/1.4 var(--tg-font-family);  /* 15px — ป้ายชื่อ section (node 2535:590) */
  --tg-astra-meta:         400 0.875rem/1.6 var(--tg-font-family);   /* 14px — breadcrumb, วันที่, ข้อมูลประกอบ */
  --tg-astra-eyebrow:      400 0.75rem/1.5 var(--tg-font-family);    /* 12px — eyebrow ในการ์ด (node 2535:1002) */
  /* ตัวเลขสถิติ (Trust bar) — ขนาดเท่า --tg-astra-section โดยบังเอิญ แต่แยก token เพราะเป็น
     คนละหน้าที่ ถ้าวันหน้าปรับหัวข้อ section ตัวเลขสถิติต้องไม่ขยับตาม (Figma node 2455:351) */
  --tg-astra-stat:         700 2.75rem/1.2 var(--tg-font-family);    /* 44px */

  /* ── Astra shape & spacing (2026-09-08) ──────────────────────────────────────────────────── */
  --tg-astra-radius:       0.75rem;   /* 12px — มุมมาตรฐานของรอบนี้ทั้งเว็บ (การ์ด รูป ปุ่มในการ์ด) */
  --tg-astra-section-pad:  5rem;      /* 80px — padding ของ section ทั้งแนวตั้งและแนวนอน */
  --tg-astra-section-gap:  2.25rem;   /* 36px — ระยะหัวข้อ → เนื้อหาภายใน section */
  --tg-astra-col-gap:      5rem;      /* 80px — ระยะระหว่างคอลัมน์ของหัวข้อ 2 คอลัมน์ */
  --tg-astra-card-pad:     1.75rem;   /* 28px — padding ภายในการ์ด */
}

/* ── Astra utility classes ─────────────────────────────────────────────────────────────────────
   ใช้คู่กับ token ข้างบน เพื่อไม่ต้องเขียน font + letter-spacing + margin ซ้ำทุกที่
   หมายเหตุ: ใช้ `font:` shorthand ซึ่งรีเซ็ต font-style/variant ด้วย — เป็นพฤติกรรมที่ต้องการ
   เพราะทุกจุดที่ใช้ token นี้ต้องได้ค่าเดียวกันเป๊ะ ไม่รับค่าตกทอดจาก selector อื่น */
.tg-astra-h1 { margin: 0; font: var(--tg-astra-h1); letter-spacing: var(--tg-astra-h1-tracking); }
.tg-astra-section-title { margin: 0; font: var(--tg-astra-section); letter-spacing: var(--tg-astra-section-tracking); }
.tg-astra-card-title { margin: 0; font: var(--tg-astra-card-title); letter-spacing: var(--tg-astra-card-tracking); }
.tg-astra-subtitle { margin: 0; font: var(--tg-astra-subtitle); }
.tg-astra-lead { margin: 0; font: var(--tg-astra-lead); }
.tg-astra-quote { margin: 0; font: var(--tg-astra-quote); }

html, body { margin: 0; padding: 0; font-size: 16px; }
/* background ต้องประกาศตรงๆ ห้ามพึ่งค่า default ของเบราว์เซอร์ (2026-09-11) — เดิมไม่ได้เขียนไว้
   เลยได้สีขาวมาจาก default ของเบราว์เซอร์เฉยๆ ซึ่งปลั๊กอินตัวไหนก็มาทับได้ · เจอของจริงบน staging:
   Elementor ใส่ `body.elementor-page-12 { background-color:#F1F1F1 }` จากค่า Page Settings ของหน้าแรก
   เก่า ทำให้พื้นหลังกลายเป็นเทา · ต้นเหตุแก้ที่ functions.php (ถอด CSS ก้อนนั้นทิ้ง) ส่วนบรรทัดนี้
   คือประกาศเจตนาให้ชัดว่าพื้นหลังเว็บนี้คือสีขาว ไม่ใช่ "บังเอิญขาวเพราะเบราว์เซอร์" */
body { font-family: var(--tg-font-family); background: var(--tg-base-white); }
*, *::before, *::after { box-sizing: border-box; }

/* ============ Layout primitives ============ */

/* Shared content container — CSS Grid centering pattern. Every section's outer <section> is
   full-bleed (100% width, so its background color reaches the viewport edge); .tg-container sits
   directly inside it and defines 3 columns: two equal 1fr "gutter" tracks and a centered middle
   track capped at 1440px (the Figma frame width). Direct children default into the middle track
   (grid-column: 2) — content never stretches past 1440px, and stays centered/symmetric at every
   scale. (An earlier version let carousel/marquee rows bleed into the right gutter track past the
   1440px cap via a `.tg-bleed-right` class — removed after visual review on real wide screens
   showed the asymmetry it created against the rest of the (symmetric) page. Every section is
   capped/centered uniformly now.)
   The middle track is `min(90rem, 100%)` — NOT `100% - 2*gutter` (an earlier version subtracted a
   clamp() gutter here). That subtraction silently shrank col2 below its full 100% width even at
   viewports UNDER 1440px, where the cap isn't even active yet and nothing should be subtracted at
   all. Individual sections still own their real edge-safety padding (hero's 200px, footer's
   64px, etc.) — the grid itself doesn't need to reserve any extra margin on top of that.
   CRITICAL: .tg-container must never be combined with a class that sets `display: flex` on the
   SAME element — that display would win the cascade (loaded later, equal specificity) and make
   `display: grid` (and therefore every grid-column placement below) silently inert, collapsing the
   1440px cap entirely. Nest a separate child element for section-specific flex/padding needs
   instead: <div class="tg-container"><div class="xxx-container">...</div></div> — see any
   src/sections/*.php for the pattern. */
.tg-container {
  display: grid;
  grid-template-columns: 1fr min(90rem, 100%) 1fr;
}
.tg-container > * {
  grid-column: 2;
}

/* 12/8/4-column responsive grid utility. Usage: <div class="tg-grid tg-grid--gap-lg"> with
   children carrying inline `grid-column: span N` (N counted against the current breakpoint's
   column count) — desktop N is authored as-is, tablet/mobile spans are set via
   data-span-tablet/data-span-mobile read through the [style] override pattern used per-section,
   OR simply let items wrap naturally when a section doesn't need explicit spans. */
.tg-grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 1.5rem; /* 24px */
  width: 100%;
}
.tg-grid--gap-sm { gap: 1.25rem; /* 20px */ }
@media (max-width: 1023px) {
  .tg-grid { grid-template-columns: repeat(8, 1fr); }
}
@media (max-width: 767px) {
  .tg-grid { grid-template-columns: repeat(4, 1fr); gap: 1rem; /* 16px */ }
}

/* ============ Typography ============ */

.tg-eyebrow {
  margin: 0;
  font-family: var(--tg-font-family);
  font-weight: 500;
  font-size: 0.875rem; /* 14px — deviation ตั้งใจจาก Figma (เดิม 13px), ดู CLAUDE.md/PIPELINE.md */
  line-height: 1.4;
  color: var(--tg-accent-800);
}

/* .tg-title-page = สงวนเฉพาะ H1 หน้าเดียวต่อหน้า (Gate B 10.3, CLAUDE.md "Title/Page ใช้หน้าละ 1 ครั้ง")
   .tg-title-page-scale = ใช้ scale 48px เดียวกัน แต่ "ไม่ผูก" semantic H1-only — สำหรับ section
   header อื่นๆ ที่ Figma ระบุ Title/Page style จริง (ยืนยันด้วย get_design_context ทีละ node ไม่ใช่
   เดา — พบว่า Homepage เองใช้ scale นี้ซ้ำกับหลาย section header โดยตั้งใจ: Team, Certifications,
   Case Study, Gallery, Reviews, Blog — ดู PIPELINE.md หัวข้อ heading bug fix)
   รวมกฎเดียวกันเพื่อไม่ให้ drift ถ้าแก้ font ทีหลัง — ต้องแก้แค่จุดเดียว ทั้งสอง class จะอัปเดตพร้อมกัน */
.tg-title-page,
.tg-title-page-scale {
  margin: 0;
  font-family: var(--tg-font-family);
  font-weight: 700;
  font-size: 3rem; /* 48px */
  line-height: 1.2;
  letter-spacing: -0.96px; /* actual per-node tracking, not the generic -2px token — see PIPELINE.md Phase A */
  color: var(--tg-primary-800);
}

.tg-title-section {
  margin: 0;
  font-family: var(--tg-font-family);
  font-weight: 700;
  font-size: 2rem; /* 32px */
  line-height: 1.3;
  letter-spacing: -0.32px; /* actual per-node tracking, not the generic -1px token */
  color: var(--tg-primary-800);
}

.tg-title-subsection {
  margin: 0;
  font-family: var(--tg-font-family);
  font-weight: 600;
  font-size: 1.125rem; /* 18px */
  line-height: 1.4;
  color: var(--tg-primary-800);
}

.tg-title-card-large {
  margin: 0;
  font-family: var(--tg-font-family);
  font-weight: 600;
  font-size: 1.5rem; /* 24px */
  line-height: 1.3;
  letter-spacing: -0.24px; /* actual per-node tracking, not the generic -1px token */
  color: var(--tg-primary-800);
}

.tg-title-card {
  margin: 0;
  font-family: var(--tg-font-family);
  font-weight: 600;
  font-size: 1rem; /* 16px */
  line-height: 1.4;
  color: var(--tg-primary-800);
}

.tg-body-large {
  margin: 0;
  font-family: var(--tg-font-family);
  font-weight: 400;
  font-size: 1rem; /* 16px */
  line-height: 1.7;
  color: var(--tg-neutral-600);
}

.tg-body-base {
  margin: 0;
  font-family: var(--tg-font-family);
  font-weight: 400;
  font-size: 1rem; /* 16px — deviation ตั้งใจจาก Figma (เดิม 14px), ดู CLAUDE.md/PIPELINE.md */
  line-height: 1.6;
  color: var(--tg-neutral-600);
}

.tg-body-small {
  margin: 0;
  font-family: var(--tg-font-family);
  font-weight: 400;
  font-size: 0.875rem; /* 14px — deviation ตั้งใจจาก Figma (เดิม 13px) */
  line-height: 1.6;
  color: var(--tg-neutral-600);
}

.tg-label-meta {
  margin: 0;
  font-family: var(--tg-font-family);
  font-weight: 400;
  font-size: 0.8125rem; /* 13px — deviation ตั้งใจจาก Figma (เดิม 12px) */
  line-height: 1.5;
  color: var(--tg-neutral-600);
}

/* ============ Section header (eyebrow + title + subtitle, centered) ============ */

/* ── 2026-09-08 (redesign Astra): หัว section ทุกอันในเว็บ ────────────────────────────────────
   คลาสนี้ใช้อยู่ 61 ไฟล์ (หน้าบริการย่อย 6 หน้า + หน้าคอร์ส 7 หน้า) และทุกที่ใช้โครงเดียวกันเป๊ะ:
     P.tg-eyebrow  |  H2.tg-title-section  |  P.tg-body-large (บางอันไม่มี)
   ซึ่งตรงกับแพทเทิร์นที่ Astra ใช้ซ้ำทุก section ของหน้าบริการย่อยพอดี (Figma node 2627:652 +
   2627:655 ของหน้า Private Training):
     Section label (ขีดทอง 24x3 + ข้อความ)   -> .tg-eyebrow
     หัวข้อซ้าย กว้าง 740px                   -> h2
     คำโปรยขวา กว้าง 479px เริ่มที่ x=804     -> p
   จึงแก้ที่คลาสนี้ที่เดียว แทนการไล่แก้ 61 ไฟล์

   เดิม: คอลัมน์เดียว จัดกึ่งกลาง -> ใหม่: label เต็มแถวบน แล้วหัวข้อ/คำโปรยเป็น 2 คอลัมน์ชิดซ้าย
   740 + 479 + ช่องว่าง 64 = 1283 พอดีกับความกว้างเนื้อหา จึงใช้ space-between ให้คำโปรยชิดขวาเสมอ

   ⚠️ Certifications ไม่ได้ใช้คลาสนี้ (ตรวจแล้ว) จึงยังเป็นหัวข้อ 32px จัดกึ่งกลางตามแบบของมันเอง */
.tg-section-header {
  display: grid;
  /* ใช้ fr ตามสัดส่วนจริงของแบบ (740 : 479) แทนความกว้างตายตัว — พอเนื้อหากว้างครบ 1283px
     จะได้ 740/479 เป๊ะ (1283 - gap 64 = 1219 = 740+479) และถ้า container แคบกว่านั้น
     สัดส่วนยังคงเดิมแทนที่จะหดลงเท่ากันทั้งสองคอลัมน์ */
  /* 2026-09-09: 740/479 + gap 64 -> 820/383 + gap 80 · ค่าเดิมวัดมาจาก section เดียวของหน้า
     Private Training (2617:6341) แล้วเหมาเป็นค่ากลางทั้งเว็บ · อ่าน get_design_context ซ้ำที่
     หัว section จริงของ Home (2455:392), All Services (2535:593) และทุก section ของหน้าคอร์ส
     (2623:626/632/638/644) ตรงกันหมดว่า หัวข้อ 820px + ช่องว่าง 80px + คำโปรย 383px
     (820+80+383 = 1283 พอดี) — 80px ยังตรงกับระยะระหว่างคอลัมน์ที่ Astra ใช้ทุก section ด้วย */
  grid-template-columns: minmax(0, 820fr) minmax(0, 383fr);
  grid-template-areas:
    "label label"
    "title lead";
  justify-content: space-between;
  column-gap: 5rem; /* 80px */
  /* 2026-09-09: 32px -> 36px · ทุก section ที่ตรวจซ้ำ (Home 2455:391, All Services 2535:589,
     หน้าคอร์ส 2578:557/2581:543/2581:564/2582:558) วาง label ที่ y=72 สูง 23 (จบ 95) แล้วหัวข้อ
     ที่ y=131 = ห่าง 36px เท่ากันหมด ตรงกับ gap ของ container ด้วย */
  row-gap: 2.25rem; /* 36px */
  align-items: start;
  width: 100%;
  text-align: left;
}
/* ⚠️ ยกเลิก max-width ที่แต่ละ section ตั้งไว้เอง — ค่าพวกนั้น (368px / 508px / 512px / 700px /
   744px / 912px) มาจาก wireframe ชุดเก่า (canvas 1635) ที่หัวข้อเป็นคอลัมน์เดียวจัดกึ่งกลาง
   จึงกำหนดความกว้างกล่องข้อความไว้ต่างกันรายอัน · แบบของ Astra ใช้หัวเต็มความกว้างเนื้อหา (1283px)
   แล้วแบ่งเป็น 2 คอลัมน์เสมอ ถ้าปล่อยค่าเดิมไว้ คอลัมน์คำโปรยจะเหลือแค่ ~130px ในบาง section
   ใช้ selector 2 คลาสเพื่อให้ชนะ .xxx-header ของแต่ละไฟล์โดยไม่ต้องใช้ !important
   และไม่ต้องไล่แก้ทีละไฟล์กว่า 40 ไฟล์ */
/* ⚠️ ต้องมี `width: 100%` คู่กับ `max-width: none` เสมอ ห้ามใส่แค่อย่างเดียว —
   5 ไฟล์ตั้งหัว section เป็น `width: 52.75rem; max-width: 100%` (video-testimonial,
   video-stories ×3, private-training/case-study) ซึ่งเดิมปลอดภัยเพราะ max-width: 100% คุมไว้
   พอสั่ง max-width: none อย่างเดียวจะเท่ากับ "ปลดตัวคุมออก" แล้ว width 844px ชนะทันที
   ทำให้หัว section ล้นขอบจอบนมือถือ (เจอจริงที่ 416px: ล้น 214px) */
.tg-container .tg-section-header { max-width: none; width: 100%; }

/* ── ระยะขอบซ้าย/ขวาของทุก section ให้ตรงกัน 80px (2026-09-08) ──────────────────────────────
   สำรวจแล้วพบว่าหน้าเว็บแบ่งเป็น 2 กลุ่มชัดเจน:
     80px  = section ที่รื้อเป็น Astra แล้ว (nav, breadcrumb, hero, trust bar, comparison,
             FAQ, contact form, footer) — ตรงกับ Figma ที่ใช้ padding 80px ทุกเฟรม
     120px = section จาก wireframe ชุดเก่าที่ยังไม่ถึงคิวรื้อ (mirror, scope, who, process,
             pricing, results, testimonial ฯลฯ ของหน้าบริการย่อยและหน้าคอร์ส)
   ผลคือขอบเนื้อหาไม่ตรงกันในแนวตั้งเมื่อเลื่อนหน้า ซึ่ง user เคยทักเรื่องนี้มาแล้วรอบก่อน
   ("แนวขอบ section ให้ตรงกันทั้งหน้า") — ตั้งค่ากลางที่นี่ที่เดียวแทนการไล่แก้กว่า 40 ไฟล์
   section ที่เป็น 80px อยู่แล้วได้ค่าเดิมไม่เปลี่ยน */
.tg-container > [class*="-container"] {
  padding-left: var(--tg-astra-section-pad); /* 80px */
  padding-right: var(--tg-astra-section-pad);
}
/* ⚠️ ต้องมี 2 บล็อกนี้เสมอ: กฎด้านบนมี specificity 0,2,0 ซึ่ง **สูงกว่า** media query ของแต่ละ
   section ที่เขียนไว้เป็นคลาสเดียว (0,1,0) ถ้าไม่ทับคืนที่นี่ ค่ามือถือของทุก section จะถูก
   80px ทับหมด แล้วเนื้อหาเหลือกว้าง 230px บนจอ 390px จนล้นขอบจอ (เจอจริงตอนตรวจ: ล้น 25px)
   ค่าที่ใช้ตรงกับ breakpoint เดิมของโปรเจกต์ (1023/767) และตรงกับที่ section ส่วนใหญ่ตั้งไว้อยู่แล้ว */
@media (max-width: 1023px) {
  .tg-container > [class*="-container"] { padding-left: 2.5rem; padding-right: 2.5rem; /* 40px */ }
}
@media (max-width: 767px) {
  .tg-container > [class*="-container"] { padding-left: 1.5rem; padding-right: 1.5rem; /* 24px */ }
}

.tg-section-header > .tg-eyebrow { grid-area: label; }
.tg-section-header > h2 { grid-area: title; }
.tg-section-header > p:not(.tg-eyebrow) { grid-area: lead; }

/* หัวข้อในหัว section = 44px ตาม Astra — scope ไว้ในหัว section เท่านั้น ไม่แตะ .tg-title-section
   ที่ใช้นอกบริบทนี้ (เช่น Certifications ซึ่ง Figma ยืนยันว่าเป็น 32px จริง) */
.tg-section-header > h2.tg-title-section {
  font: var(--tg-astra-section);
  letter-spacing: var(--tg-astra-section-tracking);
  color: var(--tg-primary-800);
}
/* คำโปรยขวา 18px Medium neutral-600 (Figma node 2627:657) */
.tg-section-header > p:not(.tg-eyebrow) {
  font: 500 1.125rem/1.7 var(--tg-font-family);
  color: var(--tg-neutral-600);
}

/* Section label: ขีดทอง 24x3 นำหน้าข้อความ — ทำด้วย ::before ไม่เพิ่ม element ใน DOM
   (ของตกแต่งล้วน ไม่ควรให้ screen reader อ่าน) ค่าตรงกับ component-section-label.php */
.tg-section-header > .tg-eyebrow {
  display: flex;
  align-items: center;
  gap: 0.75rem; /* 12px — Figma: ขีดที่ x=0 กว้าง 24 ข้อความเริ่มที่ x=36 */
  font-size: 0.9375rem; /* 15px */
  color: var(--tg-primary-600); /* เดิม accent-800 — Astra ใช้ navy อ่อนกับ label */
}
.tg-section-header > .tg-eyebrow::before {
  content: '';
  flex-shrink: 0;
  width: 1.5rem; /* 24px */
  height: 3px;
  border-radius: 1px;
  background: var(--tg-accent-400);
}

.tg-section-header--tight { row-gap: 1.5rem; /* 24px */ }

@media (max-width: 1023px) {
  .tg-section-header {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "label" "title" "lead";
    row-gap: 1rem;
  }
}

/* ============ Buttons / links ============ */

.tg-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  font-family: var(--tg-font-family);
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  border: none;
}

.tg-btn--primary {
  height: 2.5625rem; /* 41px */
  padding: 0 2rem; /* 32px */
  background: var(--tg-primary-800);
  border-radius: var(--tg-radius-sm);
  font-weight: 600;
  font-size: 1rem; /* 16px */
  line-height: 1;
  color: var(--tg-base-white);
}

.tg-btn--outline {
  height: 2.5rem; /* 40px, pinned: the "→" glyph isn't in Kanit's web-font subsets and falls
    back to a system font with taller metrics — pinning height keeps this pixel-accurate */
  overflow: hidden;
  padding: 0 1.25rem; /* 20px */
  border: 1px solid var(--tg-primary-800);
  border-radius: var(--tg-radius-sm);
  background: transparent;
  font-weight: 500;
  font-size: 0.875rem; /* 14px */
  line-height: 1.4;
  color: var(--tg-primary-800);
}

.tg-btn--outline-sm {
  height: 2.0625rem; /* 33px, same fallback-glyph fix as .tg-btn--outline */
  overflow: hidden;
  padding: 0 0.875rem; /* 14px */
  border: 1px solid var(--tg-primary-800);
  border-radius: var(--tg-radius-xs);
  background: transparent;
  font-weight: 500;
  font-size: 0.75rem; /* 12px */
  line-height: 1.4;
  color: var(--tg-primary-800);
}

/* 2026-08-31: new addition (not a rewrite of an existing class, zero blast radius) — the "Subtle"
   button variant that recurs across the Home Page mockup's "view all" links (Services, Team,
   Blog, Gallery — get_design_context literally names it variant="Subtle" on these instances):
   a light gradient pill, not a solid border-outline like .tg-btn--outline. */
.tg-btn--subtle {
  height: 3.375rem; /* 54px */
  padding: 0 1.25rem; /* 20px */
  border: 1px solid var(--tg-neutral-50);
  border-radius: var(--tg-radius-xl); /* 20px, intentionally short of a true 27px half-height pill — matches the Hero CTA/contact-cluster's same soft-capsule choice */
  background: linear-gradient(to bottom, var(--tg-neutral-50), var(--tg-primary-50));
  box-shadow: 0 0.125rem 0.375rem 0 rgba(0, 0, 0, 0.1);
  font-weight: 500;
  font-size: 0.875rem; /* 14px */
  line-height: 1.4;
  color: var(--tg-primary-800);
  transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
}
/* 2026-08-31 (round 8b): "ดูบริการทั้งหมด/ดูคอร์สเรียนทั้งหมด →" and every other .tg-btn--subtle
   instance (Team/Blog/Gallery view-all links) had no hover feedback — same lift+shadow language
   as the other buttons in this file already use, for consistency across the page. */
.tg-btn--subtle:hover {
  transform: translateY(-2px);
  box-shadow: 0 0.375rem 0.875rem 0 rgba(17, 17, 69, 0.18);
  border-color: var(--tg-primary-200);
}
.tg-btn--subtle:active { transform: translateY(0); }

/* ============ Cards ============ */

.tg-card {
  background: var(--tg-base-white);
  border: 1px solid var(--tg-neutral-100);
  border-radius: var(--tg-radius-sm);
}

/* ============ Tabs ============ */

.tg-tabs {
  display: flex;
  /* align-items: flex-start; tabs are not always the same height */
  border: 1px solid var(--tg-neutral-100);
  border-radius: var(--tg-radius-sm);
  overflow: hidden;
}
.tg-tab {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0.625rem 1.5rem; /* 10px 24px */
  border-radius: var(--tg-radius-sm);
  border: none;
  background: none;
  cursor: pointer;
  font-family: var(--tg-font-family);
  white-space: nowrap;
  transition: background 0.15s ease;
}
.tg-tab--active { background: var(--tg-primary-800); }
.tg-tab--active .tg-tab-label { font-weight: 600; font-size: 0.875rem; line-height: 1; color: var(--tg-base-white); }
.tg-tab--inactive { background: var(--tg-neutral-50); }
.tg-tab--inactive .tg-tab-label { font-weight: 500; font-size: 0.875rem; line-height: 1.4; color: var(--tg-neutral-600); }
/* 2026-08-31 (round 8b): inactive tab had zero hover feedback — nothing told a viewer it was
   clickable ("ไม่รู้ว่าสามารถกดได้ไหม"). Pure addition, .tg-tab--active is unaffected (it's
   already visually "selected", doesn't need a hover cue). */
.tg-tab--inactive:hover { background: var(--tg-neutral-100); }
.tg-tab--inactive:hover .tg-tab-label { color: var(--tg-primary-800); }
.tg-tab-label { margin: 0; font-family: var(--tg-font-family); white-space: nowrap; }

/* Pill-shaped filter tabs (Blog category filter) — distinct radius/spacing from .tg-tabs */
.tg-pill-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem; /* 12px */
}
.tg-pill-tab {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0.75rem 1.5rem; /* 12px 24px */
  border-radius: var(--tg-radius-pill); /* was a literal 100px, already far past half its own ~51px height (already a true pill) — token snap is a zero-visual-change consolidation */
  border: none;
  cursor: pointer;
  font-family: var(--tg-font-family);
  font-weight: 400;
  font-size: 1rem; /* 16px */
  line-height: 1.7;
  white-space: nowrap;
}
.tg-pill-tab--active { background: var(--tg-primary-800); color: var(--tg-neutral-50); }
.tg-pill-tab--inactive { background: var(--tg-neutral-100); color: var(--tg-primary-800); }

/* ============ Pagination (circular prev/next + dots) ============ */

.tg-pagination {
  display: flex;
  align-items: center;
  gap: 1rem; /* 16px */
}
.tg-pagination-btn {
  width: 2.25rem; /* 36px */
  height: 2.25rem;
  flex-shrink: 0;
  border: 1px solid var(--tg-neutral-100);
  border-radius: var(--tg-radius-pill); /* was a literal 18px — exactly half of this button's own 36px box (already a perfect circle), token snap is a zero-visual-change consolidation */
  background: var(--tg-base-white);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  padding: 0;
  transition: background 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
}
/* 2026-08-31 (round 8, taste-skill pass): had zero hover feedback — a pure addition (no existing
   rule touched), zero blast radius across the 3 other places this shared class renders (Blog Hub,
   Gallery Hub, Services & Courses). */
.tg-pagination-btn:hover:not(:disabled) {
  background: var(--tg-primary-800);
  border-color: var(--tg-primary-800);
}
.tg-pagination-btn:hover:not(:disabled) .tg-pagination-btn-label { color: var(--tg-base-white); }
.tg-pagination-btn:active:not(:disabled) { transform: scale(0.94); }
.tg-pagination-btn:disabled { opacity: 0.4; cursor: default; }
.tg-pagination-btn-label {
  margin: 0;
  font-family: var(--tg-font-family);
  font-weight: 600;
  font-size: 1rem; /* 16px */
  line-height: 1;
  color: var(--tg-primary-800);
  transition: color 0.15s ease;
}
.tg-pagination-dots { display: flex; align-items: center; gap: 0.375rem; /* 6px */ }
.tg-pagination-dot { width: 0.5rem; height: 0.5rem; border-radius: 50%; background: var(--tg-neutral-200); flex-shrink: 0; padding: 0; border: none; cursor: pointer; transition: background 0.15s ease, transform 0.15s ease; }
.tg-pagination-dot:hover:not(.tg-pagination-dot--active) { background: var(--tg-primary-200); }
.tg-pagination-dot--active { background: var(--tg-primary-800); transform: scale(1.25); }

/* ============ Divider ============ */

.tg-divider {
  border: none;
  border-top: 1px solid var(--tg-neutral-200);
  width: 100%;
  margin: 0;
}

/* ============ Badge / chip ============ */

.tg-badge {
  display: inline-flex;
  align-items: center;
  padding: 0.25rem 0.5rem; /* 4px 8px */
  border-radius: var(--tg-radius-xs); /* was a literal 4px, bumped 2px to fit the scale — imperceptible on a chip this small */
  background: var(--tg-primary-800);
  font-family: var(--tg-font-family);
  font-weight: 500;
  font-size: 0.8125rem; /* 13px — deviation ตั้งใจจาก Figma (เดิม 12px) */
  line-height: 1.4;
  color: var(--tg-base-white);
  white-space: nowrap;
}

/* ============ Star rating ============ */

.tg-stars { display: inline-flex; align-items: center; gap: 0.125rem; /* 2px */ }
.tg-star { width: 1rem; height: 1rem; color: var(--tg-accent-600); flex-shrink: 0; }

/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   DECORATION LAYER — Batch 0 (2026-09-01)
   ─────────────────────────────────────────────────────────────────────────────────────────────
   ที่มา: Home Page ผ่าน taste pass มาหลายรอบจนมีภาษาการตกแต่งชัดเจน แต่เนื้อในของอีก 16 หน้า
   (106 ไฟล์ใน template-parts/{services,courses,about}/) แทบไม่มี decoration เลย — วัดได้:
   linear-gradient 1/106 ไฟล์, box-shadow 3/106, :hover 9/106, ::after 0/106, radius token 0/106
   ชั้นนี้คือ "คำศัพท์" ที่ยกมาจากโค้ด Home จริงเพื่อให้หน้าอื่นเรียกใช้ได้โดยไม่ต้องเขียน CSS ซ้ำ

   ⚠️ กฎเหล็กของชั้นนี้: เพิ่มอย่างเดียว ห้ามแก้ rule เดิมเหนือบรรทัดนี้
   เพราะ 4 section ของ Home ผ่าน Quality Gate A แล้วโดยวัด DFS จริง (Certifications 100,
   Trust Bar 100, Comparison Table 99, Services & Courses 99) และกิน rule จากไฟล์นี้อยู่
   การเพิ่ม class ใหม่ไม่แตะของเดิม = DFS ไม่ขยับ · ดู PIPELINE.md หัวข้อ Quality Gate A

   ⚠️ ทุกค่าในชั้นนี้ยกมาจากโค้ดที่ใช้งานจริงบน Home ไม่มีค่าที่คิดขึ้นเอง — ที่มากำกับไว้ทุก token
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */

/* ── กลไก A · global rule (ไม่ต้องแก้ไฟล์ไหนเลย มีผลทั้ง 17 หน้าทันที) ────────────────────────── */

/* Gate B ข้อ 5 บังคับว่าทุก element ที่โต้ตอบได้ต้องมี :focus-visible ที่มองเห็นชัด — ก่อนหน้านี้
   มีเฉพาะที่เขียนเองรายจุด 11 แห่ง (nav, faq, breadcrumb, hero CTA, contact cluster, video modal,
   client-success arrow/dot, services rail) ที่เหลือทั้งเว็บพึ่ง default ของเบราว์เซอร์
   สีที่ใช้คือ navy --tg-primary-800 offset 2px เพราะเป็น pattern ที่ธีมตั้งไว้แล้วจริง 8 ใน 11 จุด
   (เช่น component-faq-accordion.php:185, component-breadcrumb.php:105, section-nav.php:505)
   ไม่ใช่ทอง — ทองถูกใช้จุดเดียวคือ Hero CTA ที่พื้นหลังเป็นทอง/navy เข้ม (section-hero.php:199) */
a:focus-visible,
button:focus-visible,
summary:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--tg-primary-800);
  outline-offset: 2px;
}
/* บนพื้นเข้ม navy ring จะจมหายไป — สลับเป็นขาวตาม precedent ที่มีอยู่แล้วใน
   section-video-placeholder.php:218 (.video-modal-close บนพื้น overlay เข้ม) */
.tg-surface-gradient a:focus-visible,
.tg-surface-gradient button:focus-visible,
.tg-surface-gradient [tabindex]:focus-visible {
  outline-color: var(--tg-base-white);
}

/* Gate B ข้อ 6 + กฎ a11y: ก่อนหน้านี้มีแค่ 2 จาก 106 ไฟล์หน้าย่อยที่ guard reduced-motion ไว้
   ก้อนเดียวนี้ครอบ transition/animation ที่เขียน inline ไว้ในทุกไฟล์ รวมถึงของชั้นนี้เอง
   (trust-bar มี guard ของตัวเองอยู่แล้วที่ section-trust-bar.php:196-198 — ไม่ขัดกัน เสริมกัน) */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ── กลไก B · token (สำหรับสลับค่าในไฟล์แบบบรรทัดเดียว ไม่ต้อง swap class) ───────────────────── */

:root {
  /* gradient หลักของแบรนด์ — ยกจาก section-hero.php:124 (.hero-title) และ
     section-trust-bar.php:165 ซึ่งใช้ค่าเดียวกันเป๊ะ · เป็นสีที่ทำให้หน้า "อ่านว่าตกแต่งแล้ว"
     มากที่สุดเมื่อแทนที่พื้น --tg-primary-800 ทึบ */
  --tg-gradient-brand: linear-gradient(to left, var(--tg-primary-400), var(--tg-primary-800));
  /* gradient อ่อนสำหรับพื้นผิวรอง — ยกจาก components.css:369 (.tg-btn--subtle) */
  --tg-gradient-tint: linear-gradient(to bottom, var(--tg-neutral-50), var(--tg-primary-50));
  /* scrim ทับภาพให้ดึงกลับเข้าโทนแบรนด์ — ยกจาก section-services-courses.php:361
     (.services-card-image::after) เข้มสุดที่ขอบล่าง จางหมดที่กึ่งกลางแนวตั้ง */
  --tg-scrim-navy: linear-gradient(to top, rgba(17, 17, 69, 0.32), transparent 55%);

  /* เงา 4 ระดับ — ยกจากโค้ดจริงทั้งหมด ไม่มีค่าที่แต่งขึ้น */
  --tg-shadow-sm: 0 0.125rem 0.375rem 0 rgba(0, 0, 0, 0.1);         /* components.css:370 (.tg-btn--subtle base) */
  --tg-shadow-md: 0 0.375rem 0.875rem 0 rgba(17, 17, 69, 0.18);     /* components.css:381 (.tg-btn--subtle:hover) */
  --tg-shadow-lg: 0 0.75rem 1.75rem -0.5rem rgba(17, 17, 69, 0.28); /* section-services-courses.php:336 */
  --tg-shadow-menu: 0 1.0625rem 1.875rem rgba(6, 6, 26, 0.18);      /* section-nav.php:304 — เงาจริงจาก Figma */

  /* จังหวะ transition — ยกจากค่าที่ใช้ซ้ำมากที่สุดในไฟล์ Home
     fast = section-hero.php:236 (icon zoom) · base = section-services-courses.php:332 (card)
     slow = section-services-courses.php:371 (image ken-burns) */
  --tg-t-fast: 0.15s ease;
  --tg-t: 0.25s ease;
  --tg-t-slow: 0.4s ease;
}

/* ── กลไก C · utility class ─────────────────────────────────────────────────────────────────────
   ทุกตัวลงบน property ที่ไฟล์หน้าย่อยแทบไม่ประกาศเอง (::after 0/106, gradient 1/106, shadow 3/106)
   จึงไม่ชน cascade แม้ <style> ของแต่ละไฟล์จะอยู่ใน <body> (มาทีหลัง components.css ใน <head>)
   ยกเว้น 2 ตัวที่ชนแน่: border-radius (91 ไฟล์) กับ background — สองอันนั้นใช้ token ข้างบนสลับ
   ค่าในไฟล์แทน ห้ามทำเป็น class */

/* ---- Elevation & lift ---- */

/* .tg-lift — การ์ดที่กดได้/เป็นหน่วยเรียกดู ยกจาก section-services-courses.php:334-339
   ⚠️ สัญญาการใช้งาน: ห้ามใช้กับการ์ดที่อยู่ใน .tg-scroll-rail หรือแถวที่ลากได้
   ผู้ใช้ตัดสินใจแล้ว (2026-09-01) ว่าปิด hover-lift บน .services-card เพราะ transform ชนกับการลาก
   และแบบไม่มี lift โอเคกว่า — กฎนี้เป็นข้อบังคับ ไม่ใช่คำแนะนำ */
.tg-lift {
  transition: transform var(--tg-t), box-shadow var(--tg-t), border-color var(--tg-t);
}
.tg-lift:hover {
  transform: translateY(-4px);
  box-shadow: var(--tg-shadow-lg);
  border-color: var(--tg-primary-200);
}
.tg-lift:active { transform: translateY(0); }

/* .tg-lift-sm — ขนาดคอนโทรล (ปุ่ม/ชิป) ยกจาก components.css:378-385 (.tg-btn--subtle:hover) */
.tg-lift-sm {
  transition: transform var(--tg-t), box-shadow var(--tg-t), border-color var(--tg-t);
}
.tg-lift-sm:hover {
  transform: translateY(-2px);
  box-shadow: var(--tg-shadow-md);
  border-color: var(--tg-primary-200);
}
.tg-lift-sm:active { transform: translateY(0); }

/* .tg-elevate — ยกลอยแบบนิ่ง ไม่มี motion (ใช้กับกล่องที่ไม่ได้กด เช่นการ์ดราคาใบที่เน้น) */
.tg-elevate { box-shadow: var(--tg-shadow-sm); }
.tg-elevate--md { box-shadow: var(--tg-shadow-md); }

/* ---- Gradient text ---- */

/* .tg-title-gradient — ประกอบทับ .tg-title-* ได้ (ไม่ตั้ง font เอง)
   ⚠️ ห้ามใช้กับหัวข้อของ 4 section ที่ผ่าน Gate A แล้ว (certifications/trust-bar/
   comparison-table/services-courses) เพราะเปลี่ยนภาพนิ่ง = DFS ขยับ · ใช้กับหน้าย่อยเท่านั้น
   ต่างจากต้นฉบับ (section-hero.php:124-127) ตรงที่ห่อ @supports ไว้ — ต้นฉบับตั้ง
   color:transparent ตรงๆ ซึ่งถ้าเบราว์เซอร์ไม่รองรับ background-clip:text ข้อความจะหายไปทั้งบรรทัด
   ผลลัพธ์ที่ตาเห็นบนเบราว์เซอร์ที่รองรับเหมือนกันเป๊ะ นี่คือการเสริมความทนทาน ไม่ใช่เปลี่ยนดีไซน์ */
/* 2026-09-08 (redesign Astra, ขั้นวาง token): ถอด gradient ออกจากทั้ง 2 คลาส เหลือสีทึบ
   ที่แต่ละคลาสประกาศไว้เป็น fallback อยู่แล้ว — ไม่ได้ลบคลาสทิ้งเพราะยังมีมาร์กอัปอ้างอยู่
   (hero 2 ตัว + เลขบท syllabus ของหน้าคอร์ส 5 หน้า ซึ่งจงใจถอด color ของตัวเองออกไปแล้ว
   เพื่อรับสีจากคลาสนี้ ถ้าลบคลาสทิ้งเลยข้อความจะไม่มีสีกำหนด)
   เหตุผล: แบบของ Astra ไม่ใช้ gradient กับตัวอักษรเลยสักจุดทั้งเว็บ — ยืนยันจาก node 2535:531
   (H1 หน้า All Services เป็นสีทึบ) และเป็นข้อที่ทำให้เว็บเดิม "อ่านออกว่าทำด้วย AI" ชัดที่สุด
   ขั้น 1 ของ redesign ถอดไปแล้ว 6 จุดที่เขียนสีไว้ในไฟล์ section (commit be72af1) แต่ตกคลาส
   กลาง 2 ตัวนี้ไว้ จึงยังมี gradient หลงเหลือในหน้าที่ยังไม่ถึงคิวรื้อ */
.tg-title-gradient { color: var(--tg-primary-800); }

/* .tg-numeral-gradient — ตัวเลขใหญ่ (01-05 ของ scope/process, สถิติ) */
.tg-numeral-gradient { color: var(--tg-primary-400); }

/* ---- Surfaces ---- */

.tg-surface-gradient {
  background: var(--tg-gradient-brand);
  color: var(--tg-base-white);
}
.tg-surface-tint {
  background: var(--tg-gradient-tint);
  border: 1px solid var(--tg-neutral-50);
}

/* ---- Media ---- */

/* .tg-media — กล่องภาพมาตรฐาน · ใส่อัตราส่วนผ่าน aspect-ratio ที่ไฟล์ปลายทาง
   โครงลูกที่รองรับ: <img> ตัวเดียวเป็นลูกโดยตรง (ไม่ต้องมี wrapper เพิ่ม) */
/* min-width/min-height: 0 — บั๊กที่เจอจริงตอน Batch 2 (courses/instructors.php): กล่องที่มีทั้ง
   aspect-ratio (inline จาก tg_render_media()) และ height ตายตัวจากไฟล์ปลายทาง พอไปอยู่ใน CSS Grid
   คอลัมน์ 1fr เบราว์เซอร์คำนวณ min-content โดยไม่รู้ความกว้างจริง (width:100% resolve ไม่ได้ตอนนั้น)
   จึงถอย fallback ไปใช้ height × aspect-ratio เป็นความกว้างขั้นต่ำแทน — ดันให้ grid ล้นออกนอกกรอบ
   ทั้งที่ทุก breakpoint กำหนด column ไว้ถูกต้องแล้ว (วัดจริง: 281px × 4/3 = 374.67px/คอลัมน์ × 3 = ล้น)
   min-width/min-height: 0 คือสูตรมาตรฐานของบั๊กคลาสนี้ ไม่กระทบขนาดที่ตาเห็นเลยเมื่อพื้นที่พอ
   (แค่เปลี่ยนขั้นต่ำจาก auto เป็น 0) แก้ที่นี่ครั้งเดียวกัน tg_render_media() ทุกจุดที่อยู่ใน grid/flex */
.tg-media {
  position: relative;
  overflow: hidden;
  min-width: 0;
  min-height: 0;
  border-radius: var(--tg-radius-sm);
  background: var(--tg-neutral-100);
  /* 2026-09-02 (Batch 3 review fix): อ่าน aspect-ratio ผ่าน custom property แทนรับ inline
     aspect-ratio ตรงๆ จาก tg_render_media() (ดู template-parts/component-media.php) — ให้ไฟล์
     ปลายทางที่ต้องการอัตราส่วนต่างกันต่อ breakpoint override ได้ด้วย
     `.some-class { --tg-media-ratio: 4/3; }` ใน media query ของตัวเอง (ชนะตามกฎ cascade ปกติ
     เพราะมาทีหลัง ไม่ใช่ inline ที่ override ไม่ได้เลย) */
  aspect-ratio: var(--tg-media-ratio, 16 / 9);
}
.tg-media > img {
  display: block;
  width: 100%;
  height: 100%;
  /* 2026-09-04: อ่านผ่าน custom property เพื่อให้ manifest ตั้ง fit ต่อ slot ได้
     (contain = เห็นรูปทั้งใบ ยอมมีขอบว่าง · cover = เต็มกล่อง ยอมโดนครอป)
     ค่าเริ่มต้นยังเป็น cover เหมือนเดิม ทุก call site เดิมจึงไม่เปลี่ยนพฤติกรรม */
  object-fit: var(--tg-media-fit, cover);
  /* 2026-09-03: object-position ปรับได้ต่อจุด — เดิม cover ครอปจากกึ่งกลางเสมอ ทำให้ภาพคน
     แนวตั้งถูกตัดหัวทิ้ง (เจอจริงที่การ์ดผู้สอน) · ธีมรู้วิธีแก้นี้อยู่แล้วและใช้อยู่ 2 จุด
     (section-team.php, single-tg_person.php ใช้ `object-position: top center` พร้อมคอมเมนต์
     "ภาพต้นฉบับเป็น portrait ครึ่งตัว เอาหน้าไว้ในเฟรมเสมอ") แต่ .tg-media ซึ่งเป็นระบบกลางที่
     รูปเกือบทั้งเว็บใช้กลับไม่มี — เปิดเป็น custom property ให้ตั้งได้ 2 ทาง: ไฟล์ section
     ตั้งเองใน CSS, หรือส่ง 'pos' เข้า tg_render_media() (เช่นการ์ดผู้สอน — ดู
     template-parts/courses/instructors.php และไฟล์คู่กัน) */
  object-position: var(--tg-media-pos, 50% 50%);
}
/* .tg-media--scrim — ยกจาก section-services-courses.php:355-365 · เป็น ::after ไม่ใช่ filter บน
   <img> เพื่อไม่แตะพิกเซลจริงของภาพ (ภาพการตลาดหลายใบมี stat overlay อบไว้ในไฟล์อยู่แล้ว) */
.tg-media--scrim::after {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--tg-scrim-navy);
  pointer-events: none;
}
/* .tg-media--zoom — ken-burns ยกจาก section-services-courses.php:366-372
   ทำงานทั้งเมื่อ hover ตัวเองและเมื่อ hover การ์ดแม่ที่มี .tg-lift */
.tg-media--zoom > img { transition: transform var(--tg-t-slow); }
.tg-media--zoom:hover > img,
.tg-lift:hover .tg-media--zoom > img { transform: scale(1.06); }

/* .tg-media-placeholder — พื้นผิว "รอ asset จริง" ที่ตกแต่งตามแบรนด์ แทนกล่องเทาเปล่า
   เหตุผล: รูปจริงราว 60-70 ชิ้นยังผลิตไม่เสร็จและจะมาถึงท้ายสุด ถ้าปล่อยเป็นกล่องเทาหน้าจะอ่านว่า
   ยังไม่เสร็จ ทั้งที่ layout เสร็จแล้ว · กล่องที่ประกาศตัวตรงๆ ว่ารอภาพ = ซื่อสัตย์
   ส่วนการยัดภาพสต็อกปลอมละเมิดกฎ Working Convention.md บรรทัด 167 */
.tg-media-placeholder {
  display: grid;
  place-items: center;
  align-content: center;
  gap: 0.5rem; /* 8px */
  padding: 1rem; /* 16px */
  background: var(--tg-gradient-tint);
  box-shadow: inset 0 0 0 1px var(--tg-primary-100);
  text-align: center;
}
.tg-media-placeholder-label {
  margin: 0;
  font-family: var(--tg-font-family);
  font-weight: 400;
  font-size: 0.8125rem; /* 13px — ตรงกับ --tg-label-meta */
  line-height: 1.5;
  color: var(--tg-primary-400);
}

/* ---- Icon well ---- */

/* .tg-icon-well — วงกลมครอบไอคอน ยกทรงจาก section-case-study.php:87 / section-footer.php:77
   ที่ใช้ border-radius:50% อยู่แล้ว · ตัวนี้เป็นทางเลือกแทน "รูปประกอบ" ในหลาย section
   (process step, who-is-it-for) ซึ่งเป็นภาษาที่เว็บนี้ใช้อยู่แล้วและไม่ต้องรอ asset */
.tg-icon-well {
  display: grid;
  place-items: center;
  flex-shrink: 0;
  width: 3rem; /* 48px */
  height: 3rem;
  border-radius: var(--tg-radius-pill);
  background: var(--tg-primary-50);
  color: var(--tg-primary-800);
}
.tg-icon-well--brand {
  background: var(--tg-gradient-brand);
  color: var(--tg-base-white);
}
.tg-icon-well--sm { width: 2.25rem; height: 2.25rem; /* 36px */ }
.tg-icon-well--lg { width: 4rem; height: 4rem; /* 64px */ }

/* .tg-icon-zoom — ยกจาก section-hero.php:234-238 (.hero-contact-item:hover img) */
.tg-icon-zoom img,
.tg-icon-zoom svg { transition: transform var(--tg-t-fast); }
.tg-icon-zoom:hover img,
.tg-icon-zoom:hover svg { transform: scale(1.08); }

/* ---- Scroll rail ---- */

/* .tg-scroll-rail — แถวเลื่อน/ลากได้ ยกจาก section-services-courses.php:255-288
   ⚠️ ห้ามตั้ง padding บน element นี้เด็ดขาด (กฎ CLAUDE.md หัวข้อ Scroll-snap carousel) —
   ระยะเยื้องหัว/ท้ายให้ใช้ margin ที่การ์ดใบแรก/ใบสุดท้ายแทน เช่น
   .tg-scroll-rail > *:first-child { margin-inline-start: <gutter> }
   ⚠️ ไม่มี scroll-snap โดยตั้งใจ — รอบ 10 ของ Home พิสูจน์ด้วยการวัดจริงแล้วว่า snap (ทั้ง
   mandatory และ proximity) ดึงตำแหน่งหลังปล่อยเมาส์เสมอ และค้างกลางการ์ดที่ปลายแถว
   ดู docblock เต็มที่ section-services-courses.php:258-270 ก่อนคิดจะใส่กลับ */
.tg-scroll-rail {
  display: flex;
  overflow-x: auto;
  scrollbar-width: none;
  cursor: grab;
  -webkit-overflow-scrolling: touch;
}
.tg-scroll-rail::-webkit-scrollbar { display: none; }
.tg-scroll-rail.is-dragging {
  cursor: grabbing;
  user-select: none;
}

/* .tg-edge-fade — ขอบขวาจางบอกว่ายังเลื่อนต่อได้ ยกจาก section-services-courses.php:239-251
   ใส่ที่ตัวห่อของ rail (ต้องเป็น position:relative) ไม่ใช่ที่ rail เอง
   สีปลายทางตั้งผ่าน --tg-edge-fade-color ให้ตรงกับพื้นหลัง section นั้น (ค่าเริ่มต้น neutral-50
   ตามพื้นของ Home section ต้นทาง) · JS เติม .is-at-end เพื่อซ่อนตอนเลื่อนสุดแล้ว */
.tg-edge-fade { position: relative; }
.tg-edge-fade::after {
  content: '';
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 4rem; /* 64px */
  background: linear-gradient(to right, transparent, var(--tg-edge-fade-color, var(--tg-neutral-50)));
  pointer-events: none;
  opacity: 1;
  transition: opacity 0.2s ease;
}
.tg-edge-fade.is-at-end::after { opacity: 0; }

/* .tg-marquee-mask — จางสองข้าง ยกจาก section-trust-bar.php:181-182 */
.tg-marquee-mask {
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 3rem, #000 calc(100% - 3rem), transparent 100%);
  mask-image: linear-gradient(to right, transparent 0, #000 3rem, #000 calc(100% - 3rem), transparent 100%);
}

/* ---- Badge variants ---- */

/* .tg-badge เดิมเป็น navy ทึบ/ตัวอักษรขาว — 2 variant นี้เป็นชิปอ่อนสำหรับกรณีที่ navy ทึบหนักไป
   gold variant ใช้ accent-100 เป็นพื้น + accent-900 เป็นตัวอักษร (ผ่าน AA) และเป็นชิปเล็ก
   จึงอยู่ในงบทอง ≤10% ต่อหน้าตาม CLAUDE.md */
.tg-badge--soft {
  background: var(--tg-primary-50);
  color: var(--tg-primary-800);
}
.tg-badge--gold {
  background: var(--tg-accent-100);
  color: var(--tg-accent-900);
}

/* ---- Divider & eyebrow ---- */

/* เส้นคั่นที่จางหายที่ปลายทั้งสองข้าง แทนเส้นทึบเต็มความกว้าง */
.tg-divider--gradient {
  border: none;
  height: 1px;
  background: linear-gradient(to right, transparent, var(--tg-neutral-200), transparent);
}

/* ⚠️ เป็น modifier แบบ opt-in ไม่ใช่การแก้ .tg-eyebrow เดิม — เพราะ Certifications (Gate A DFS 100)
   ใช้ .tg-eyebrow อยู่ และการเติม ::before คือการเปลี่ยนภาพนิ่ง = DFS ขยับ */
.tg-eyebrow--rule {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem; /* 8px */
}
.tg-eyebrow--rule::before {
  content: '';
  width: 1.5rem; /* 24px */
  height: 2px;
  background: var(--tg-accent-400);
  flex-shrink: 0;
}

/* ---- Pending note ---- */

/* .tg-note--pending — แถบ "เนื้อหานี้ยังเป็น placeholder" ที่ดูตั้งใจ แทนกรอบเส้นประสีเทา
   ที่ตอนนี้กระจายอยู่ 13 ไฟล์ (ทุกไฟล์เขียน CSS ของตัวเองซ้ำกัน) — นำไปใช้จริงใน Batch 1 */
.tg-note--pending {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem; /* 8px */
  width: 100%;
  margin: 0;
  padding: 0.75rem 1rem; /* 12px 16px */
  border: none;
  border-radius: var(--tg-radius-sm);
  background: var(--tg-gradient-tint);
  box-shadow: inset 0 0 0 1px var(--tg-primary-100);
  font-family: var(--tg-font-family);
  font-weight: 400;
  font-size: 0.8125rem; /* 13px */
  line-height: 1.6;
  color: var(--tg-primary-400);
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   BATCH 1 (2026-09-01) — state ที่ขาดของ shared control
   ทั้งหมดเป็น state-only (:hover/:active/transition) จึงไม่เปลี่ยนภาพนิ่ง → Gate A ไม่กระทบ
   (พิสูจน์ซ้ำได้ด้วย tests/section/verify-additive.js)
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */

/* .tg-btn--primary เป็นปุ่มหลักที่ใช้ร่วมกัน **15 ไฟล์** (nav, page-hero, course-hero,
   closing-cta, commerce-grid, cta-band ของ 3 หน้า, pricing-recap ของ 3 คอร์ส, single*.php)
   แต่ base rule ที่ components.css:322 มีแค่พื้น navy ทึบ ไม่มี hover/active/transition เลย —
   ผู้ใช้กดแล้วไม่มีอะไรตอบสนอง ต่างจาก .tg-btn--subtle ที่ได้ hover ไปแล้วตั้งแต่รอบ 8b
   ใช้ภาษาเดียวกับ .tg-btn--subtle (ยก/เงา/กดแล้วยุบ) เพื่อให้ปุ่มทั้งไฟล์พูดจังหวะเดียวกัน
   ยืนยันแล้วว่าไม่มี .tg-btn--primary อยู่ใน 4 section ที่ผ่าน Gate A (certifications/trust-bar/
   comparison-table/services-courses) — และ Hero ของหน้าแรกมี hover ของตัวเองที่ specificity
   สูงกว่า (.hero-cta-row .tg-btn--primary:hover) จึงไม่ถูกทับ */
.tg-btn--primary {
  transition: transform var(--tg-t), box-shadow var(--tg-t), background var(--tg-t);
}
.tg-btn--primary:hover {
  transform: translateY(-2px);
  box-shadow: var(--tg-shadow-md);
}
.tg-btn--primary:active { transform: translateY(0); }

/* .tg-btn--outline ก็ไม่มี hover เหมือนกัน — ใช้จังหวะเดียวกันแต่เบากว่า (ปุ่มรอง) */
.tg-btn--outline,
.tg-btn--outline-sm {
  transition: transform var(--tg-t), box-shadow var(--tg-t), border-color var(--tg-t);
}
.tg-btn--outline:hover,
.tg-btn--outline-sm:hover {
  transform: translateY(-1px);
  box-shadow: var(--tg-shadow-sm);
  border-color: var(--tg-primary-800);
}
.tg-btn--outline:active,
.tg-btn--outline-sm:active { transform: translateY(0); }
