// Method section — Six Principles with interactive SVG visualizations
// Mounts to #method-root

(function() {
  const { useState, useEffect, useRef } = React;

  // Language helpers
  function useLang() {
    const [lang, setLang] = useState(window.PilatesI18n?.getLang() || "en");
    useEffect(() => window.PilatesI18n?.subscribe(setLang), []);
    return lang;
  }
  function pick(obj, lang) { return obj && typeof obj === "object" && obj[lang] !== undefined ? obj[lang] : obj?.en ?? obj; }
  function tt(key) { return window.PilatesI18n?.t(key) || key; }

  /* ════════════════════════════════════════════════════
     PRINCIPLE DATA (bilingual)
     ════════════════════════════════════════════════════ */
  const PRINCIPLES = [
    {
      id: "filament", num: "01",
      title: { en: "Sliding Filament", th: "กลไกการหดตัวของกล้ามเนื้อระดับจิ๋ว" },
      tag: { en: "sarcomere mechanics", th: "กลไกของซาร์โคเมียร์" },
      body: {
        en: "Every cue translates into an electrical signal — the nervous system triggers a calcium (Ca²⁺) release from the sarcoplasmic reticulum. Calcium binds troponin, tropomyosin shifts, and the myosin-binding sites on actin are exposed. Fueled by ATP, myosin heads pull the actin filaments toward the center of the sarcomere — the basic functional unit of muscle. This microscopic sliding is the source of all human movement.",
        th: "ทุกครั้งที่เราสั่งร่างกาย จะมีกระแสไฟฟ้าส่งไปกระตุ้นให้มีการปล่อยแคลเซียม (Ca²⁺) ออกมาในกล้ามเนื้อ แคลเซียมไปจับกับ Troponin ทำให้ Tropomyosin เลื่อน เปิดทางให้ Myosin มาเกาะกับ Actin ได้ ด้วยพลังจาก ATP หัว Myosin จะดึง Actin เข้าหากึ่งกลางของซาร์โคเมียร์ — หน่วยทำงานพื้นฐานของกล้ามเนื้อนะ การเลื่อนเล็กๆ ในระดับจุลภาคนี่แหละคือต้นกำเนิดของทุกการเคลื่อนไหวเลยล่ะ",
      },
      cues: {
        en: ["Cue → electrical signal → Ca²⁺ release", "Myosin power-stroke against actin", "Sarcomere shortens · the muscle contracts"],
        th: ["สั่ง → สัญญาณไฟฟ้า → ปล่อย Ca²⁺", "Myosin ดึง Actin แบบ Power-stroke", "ซาร์โคเมียร์หดสั้น · กล้ามเนื้อหดตัวเลย"],
      },
    },
    {
      id: "size", num: "02",
      title: { en: "The Size Principle", th: "ลำดับการเรียกใช้งานกล้ามเนื้อ" },
      tag: { en: "motor-unit recruitment", th: "การเรียกใช้หน่วยมอเตอร์" },
      body: {
        en: "Muscle activation is governed by motor-unit size. The body recruits smaller, fatigue-resistant slow-twitch fibers first, before reaching for larger, more powerful fast-twitch units. Small motor units stabilize joints and maintain posture; large units generate raw force. We earn the right to ask for power — the smaller stabilizers must fire before the prime movers do.",
        th: "ร่างกายเราฉลาดนะ — มันจะเรียกใช้กล้ามเนื้อเล็กๆ ที่ทนทาน (slow-twitch) ก่อนเพื่อจัดท่าทางให้เป๊ะ แล้วค่อยเรียกกล้ามเนื้อใหญ่ที่ทรงพลัง (fast-twitch) ออกมาใช้พลังแรงๆ ทีหลัง หน่วยเล็กดูแลท่าทางและข้อต่อ ส่วนหน่วยใหญ่สร้างพลังดิบเลย เราเลยต้องค่อยๆ \"สร้างสิทธิ์\" ในการเรียกพลังนะ — กล้ามเนื้อค้ำจุนต้องทำงานก่อนกล้ามเนื้อหลักเสมอ",
      },
      cues: {
        en: ["Slow-twitch first · postural endurance", "Demand rises → fast-twitch joins", "Stabilize before you mobilize"],
        th: ["Slow-twitch มาก่อน · เน้นความทน", "งานหนักขึ้น → Fast-twitch มาช่วย", "ค้ำให้มั่นก่อน แล้วค่อยเคลื่อนนะ"],
      },
    },
    {
      id: "cylinder", num: "03",
      title: { en: "The Cylinder", th: "แกนกลางลำตัวทรงกระบอก" },
      tag: { en: "intra-abdominal pressure", th: "ความดันในช่องท้อง" },
      body: {
        en: "The deep core is a cylinder: diaphragm above, pelvic floor below, transversus abdominis wrapping the front and sides like a corset, multifidi behind. When all four co-contract, they create Intra-Abdominal Pressure (IAP) — a pneumatic brace for the spine. Proximal stability before distal mobility: every advanced movement of the limbs descends from a stable canister.",
        th: "นึกถึงถังทรงกระบอกที่ล้อมด้วยกะบังลมเป็นหลังคา อุ้งเชิงกรานเป็นพื้น Transversus abdominis ห่อหน้าและด้านข้างเหมือนรัดคอร์เซ็ต ส่วน Multifidi อยู่ด้านหลัง พอทั้งสี่หดร่วมกันก็จะสร้างแรงดันในช่องท้อง (IAP) — เหมือนเครื่องค้ำลมให้กระดูกสันหลังเลยค่ะ ถ้า \"ทรงกระบอก\" นี้แข็งแรง เราจะขยับแขนขาได้คล่องแคล่วและปลอดภัยสุดๆ เลย",
      },
      cues: {
        en: ["Diaphragm · roof", "Pelvic floor · base", "TVA + multifidi · walls", "Co-contract → IAP → spine braced"],
        th: ["กะบังลม · หลังคา", "อุ้งเชิงกราน · พื้น", "TVA + Multifidi · ผนัง", "หดร่วม → IAP → ค้ำกระดูกสันหลัง"],
      },
    },
    {
      id: "lever", num: "04",
      title: { en: "The Lever", th: "ร่างกายคือระบบคานดีๆ นี่เอง" },
      tag: { en: "biomechanics of force", th: "ชีวกลศาสตร์ของแรง" },
      body: {
        en: "Human movement runs on biomechanical levers — joint as fulcrum, muscle insertion as force, body weight or load as resistance. Most levers in the body are third-class: effort applied between fulcrum and resistance. Third-class levers operate at a mechanical disadvantage — the muscle generates more force than the load it moves — but trade that disadvantage for speed and range. Design every exercise from the joint outward.",
        th: "ข้อต่อเราคือจุดหมุน ส่วนกล้ามเนื้อคือแรงส่งค่ะ ส่วนน้ำหนักตัวหรือของหนักก็คือแรงต้านนะ คานในร่างกายส่วนใหญ่เป็นคานชั้นที่ 3 (แรงอยู่ระหว่างจุดหมุนกับแรงต้าน) แม้จะดูเหมือนต้องใช้แรงเยอะกว่าน้ำหนักที่ยก แต่ก็แลกมาด้วยความเร็วและความกว้างในการเคลื่อนไหวนะ ออกแบบทุกท่าฝึกโดยเริ่มจากข้อต่อก่อนเลยล่ะ",
      },
      cues: {
        en: ["Joint = axis · muscle = force · weight = resistance", "3rd-class lever dominates the body", "Force always greater than load · trade for speed"],
        th: ["ข้อต่อ = จุดหมุน · กล้ามเนื้อ = แรง · น้ำหนัก = แรงต้าน", "คานชั้นที่ 3 ครองร่างกายเลย", "แรงมากกว่าน้ำหนักเสมอ · แลกกับความเร็ว"],
      },
    },
    {
      id: "contraction", num: "05",
      title: { en: "Contraction Modes", th: "3 วิธีการทำงานของกล้ามเนื้อ" },
      tag: { en: "concentric · eccentric · isometric", th: "คอนเซนทริก · เอกเซนทริก · ไอโซเมตริก" },
      body: {
        en: "A muscle fibre contracts in three distinct modes. Concentric — it shortens while overcoming resistance, as in curling a weight up or pressing the Reformer carriage away. Eccentric — it lengthens while still producing tension, braking the load against gravity, as in lowering that weight under control; this is where joints are protected and real strength is forged. Isometric — it generates force with no change in length and no joint motion: the held Plank, the still centre. Every controlled repetition is a sequence of all three.",
        th: "กล้ามเนื้อทำงานได้ 3 แบบนะ คอนเซนทริก — เกร็งแล้วกล้ามเนื้อสั้นลง เช่นตอนยกของขึ้น หรือดันแคร่ Reformer ออกไป เอกเซนทริก — เกร็งแล้วยืดออก ต้านแรงโน้มถ่วงไว้ เช่นค่อยๆ วางน้ำหนักลง ตรงนี้แหละที่ปกป้องข้อต่อและสร้างความแข็งแรงที่แท้จริง และไอโซเมตริก — เกร็งค้างไว้เฉยๆ ไม่ขยับ เช่นท่าแพลงก์ ทุกท่าคือการฝึกที่ยอดเยี่ยม ทุกการเคลื่อนไหวที่ควบคุมได้ก็คือลำดับของทั้งสามแบบนี้ผสมกันเลย",
      },
      cues: {
        en: ["Concentric — shortens, overcomes the load", "Eccentric — lengthens, brakes against gravity", "Isometric — holds, force without motion"],
        th: ["คอนเซนทริก — หดสั้น เอาชนะแรงต้าน", "เอกเซนทริก — ยืดยาว เบรกสู้แรงโน้มถ่วง", "ไอโซเมตริก — ค้างไว้ มีแรงแต่ไม่ขยับ"],
      },
    },
    {
      id: "breath", num: "06",
      title: { en: "Lateral Breathing", th: "ฝึกหายใจเข้าซี่โครง (ไม่ใช่เข้าพุงนะ!)" },
      tag: { en: "bellows breathing", th: "การหายใจแบบสูบลม" },
      body: {
        en: "Also called bellows breathing — inhalation is directed into the sides and back of the ribcage instead of the belly. The ribs expand laterally like an accordion, allowing deep oxygen intake while the transversus abdominis and obliques stay engaged. On exhale, ribs funnel inward and downward, navel drawing toward the spine. Stabilizing the trunk while breathing fully under load is the signature of advanced practice.",
        th: "หรือที่เรียกว่า Bellows Breathing — เวลาหายใจเข้า ให้ส่งลมไปที่ซี่โครงด้านข้างและด้านหลัง ไม่ต้องดันท้องนะ ซี่โครงจะกางออกเหมือนหีบเพลง ทำให้รับออกซิเจนได้เต็มที่ ส่วนพุงให้เกร็งแบนไว้เหมือนเดิม ตอนหายใจออก ซี่โครงม้วนเข้าและลง สะดือดึงเข้าหากระดูกสันหลัง การที่ทำให้ลำตัวนิ่งและหายใจได้เต็มที่ใต้แรงโหลด คือเอกลักษณ์ของการฝึกขั้นสูงเลยล่ะ",
      },
      cues: {
        en: ["Inhale → ribs widen sideways and back", "Belly stays drawn in · core engaged", "Exhale → ribs funnel down · TVA tightens"],
        th: ["หายใจเข้า → ซี่โครงขยายด้านข้างและหลัง", "พุงเกร็งแบนไว้ · แกนกลางทำงาน", "หายใจออก → ซี่โครงม้วนลง · TVA หดแน่น"],
      },
    },
  ];

  // Display sequence — principles are presented (and numbered) in this order
  const METHOD_SEQUENCE = ["breath", "filament", "contraction", "size", "lever", "cylinder"];
  const ORDERED_PRINCIPLES = METHOD_SEQUENCE.map((id, i) => ({
    ...PRINCIPLES.find(p => p.id === id),
    num: String(i + 1).padStart(2, "0"),
  }));

  /* ════════════════════════════════════════════════════
     1. SLIDING FILAMENT
     ════════════════════════════════════════════════════ */
  function VisFilament() {
    const [contracted, setContracted] = useState(false);
    const zL = contracted ? 110 : 30;
    const zR = contracted ? 290 : 370;
    const actinShift = contracted ? 78 : 0;
    return (
      <>
        <svg viewBox="0 0 400 220" className="mth-vis-svg">
          {/* Z-disk labels */}
          <text x="20" y="22" fontSize="10" fill="var(--ink-3)" fontFamily="monospace">Z-disk</text>
          <text x="350" y="22" fontSize="10" fill="var(--ink-3)" fontFamily="monospace">Z-disk</text>
          {/* Sarcomere baseline */}
          <line x1={zL} y1="36" x2={zR} y2="36" stroke="var(--ink-3)" strokeDasharray="2,3" strokeWidth="0.6"/>
          {/* Z-disks (move) */}
          <line x1={zL} y1="40" x2={zL} y2="180" className="m-z-disk"/>
          <line x1={zR} y1="40" x2={zR} y2="180" className="m-z-disk"/>
          {/* Myosin (thick) - center, fixed */}
          <rect x="160" y="100" width="80" height="14" className="m-myosin" rx="2"/>
          {/* Myosin heads pointing outward */}
          {[0,1,2,3].map(i => (
            <g key={"hL"+i}>
              <line x1={170+i*16} y1="100" x2={166+i*16} y2={contracted ? 90 : 86} className={"m-myosin-head" + (contracted ? " bound" : "")}/>
              <line x1={170+i*16} y1="114" x2={166+i*16} y2={contracted ? 124 : 128} className={"m-myosin-head" + (contracted ? " bound" : "")}/>
            </g>
          ))}
          {/* Actin (thin) - left side, slides right when contracted */}
          <g style={{transform: `translateX(${actinShift}px)`, transition: "transform 0.9s cubic-bezier(.4,0,.3,1)"}}>
            <line x1={30} y1="86" x2={170} y2="86" className="m-actin"/>
            <line x1={30} y1="128" x2={170} y2="128" className="m-actin"/>
            {/* actin beads */}
            {Array.from({length:8}).map((_,i) => (
              <circle key={"aL"+i} cx={40 + i*16} cy="86" r="2.5" fill="var(--ink-2)"/>
            ))}
            {Array.from({length:8}).map((_,i) => (
              <circle key={"aL2"+i} cx={40 + i*16} cy="128" r="2.5" fill="var(--ink-2)"/>
            ))}
          </g>
          {/* Actin - right side, slides left */}
          <g style={{transform: `translateX(${-actinShift}px)`, transition: "transform 0.9s cubic-bezier(.4,0,.3,1)"}}>
            <line x1={230} y1="86" x2={370} y2="86" className="m-actin"/>
            <line x1={230} y1="128" x2={370} y2="128" className="m-actin"/>
            {Array.from({length:8}).map((_,i) => (
              <circle key={"aR"+i} cx={240 + i*16} cy="86" r="2.5" fill="var(--ink-2)"/>
            ))}
            {Array.from({length:8}).map((_,i) => (
              <circle key={"aR2"+i} cx={240 + i*16} cy="128" r="2.5" fill="var(--ink-2)"/>
            ))}
          </g>
          {/* Calcium ions burst */}
          {contracted && Array.from({length:10}).map((_,i) => {
            const angle = (i/10) * Math.PI * 2;
            const dx = Math.cos(angle) * 60;
            const dy = Math.sin(angle) * 40;
            return (
              <circle
                key={"ca"+i}
                cx="200" cy="107" r="3.5"
                className="m-calcium active"
                style={{"--dx": dx+"px", "--dy": dy+"px", animationDelay: (i*0.04)+"s"}}
              />
            );
          })}
          {/* Length readout */}
          <text x="200" y="200" textAnchor="middle" fontSize="10" fill="var(--ink-3)" fontFamily="monospace">
            sarcomere length · {Math.round((zR-zL))} a.u.
          </text>
        </svg>
        <div className="mth-vis-controls">
          <button className="mth-btn accent" data-on={contracted} onClick={() => setContracted(!contracted)}>
            {contracted ? "↺ relax" : "▸ fire Ca²⁺"}
          </button>
          <div className="mth-readout">
            <div className="mth-readout-item">
              <div className="mth-readout-label">state</div>
              <div className="mth-readout-val">{contracted ? "contracted" : "relaxed"}</div>
            </div>
          </div>
        </div>
      </>
    );
  }

  /* ════════════════════════════════════════════════════
     2. SIZE PRINCIPLE
     ════════════════════════════════════════════════════ */
  function VisSize() {
    const [effort, setEffort] = useState(15);
    // Motor units: smallest first (low threshold, slow-twitch), to largest (high threshold, fast-twitch)
    const UNITS = [
      { name: "MU-1", type: "S",  threshold: 8,  size: 28 },
      { name: "MU-2", type: "S",  threshold: 22, size: 38 },
      { name: "MU-3", type: "FR", threshold: 42, size: 52 },
      { name: "MU-4", type: "FR", threshold: 65, size: 72 },
      { name: "MU-5", type: "FF", threshold: 88, size: 100 },
    ];
    const recruited = UNITS.filter(u => effort >= u.threshold).length;
    return (
      <>
        <svg viewBox="0 0 400 240" className="mth-vis-svg">
          {/* Header labels */}
          <text x="10" y="16" fontSize="10" fill="var(--ink-3)" fontFamily="monospace">SLOW · fatigue-resistant</text>
          <text x="270" y="16" fontSize="10" fill="var(--ink-3)" fontFamily="monospace">FAST · powerful</text>
          {/* Threshold line */}
          <line x1={20 + (effort/100)*360} y1="28" x2={20 + (effort/100)*360} y2="220" stroke="var(--accent)" strokeWidth="1" strokeDasharray="3,2" opacity="0.7"/>
          <text x={20 + (effort/100)*360 + 4} y="32" fontSize="10" fill="var(--accent)" fontFamily="monospace">effort {effort}%</text>
          {/* Motor unit bars */}
          {UNITS.map((u, i) => {
            const y = 50 + i * 32;
            const active = effort >= u.threshold;
            const barW = u.size * 3;
            return (
              <g key={u.name}>
                {/* Background */}
                <rect x="20" y={y} width="360" height="22" fill="var(--rule)" opacity="0.4"/>
                {/* Active fill */}
                <rect x="20" y={y} width={active ? barW : 0} height="22" className="m-mu-bar" fill="var(--accent)" opacity={active ? 0.85 : 0}/>
                {/* Label */}
                <text x="26" y={y + 14} fontSize="11" fill={active ? "var(--paper)" : "var(--ink-3)"} fontFamily="monospace" fontWeight="600">{u.name}</text>
                <text x="68" y={y + 14} fontSize="9" fill={active ? "rgba(255,255,255,0.7)" : "var(--ink-3)"} fontFamily="monospace">type-{u.type}</text>
                {/* Threshold marker */}
                <line x1={20 + (u.threshold/100)*360} y1={y - 2} x2={20 + (u.threshold/100)*360} y2={y + 24} stroke="var(--ink-3)" strokeWidth="0.6" opacity="0.6"/>
                <text x={20 + (u.threshold/100)*360 - 14} y={y + 32} fontSize="8" fill="var(--ink-3)" fontFamily="monospace">{u.threshold}</text>
              </g>
            );
          })}
        </svg>
        <div className="mth-vis-controls">
          <div className="mth-slider-row">
            <span className="mth-slider-label">effort</span>
            <input type="range" min="0" max="100" value={effort} onChange={e => setEffort(parseInt(e.target.value))} className="mth-slider"/>
            <span className="mth-slider-val">{effort}%</span>
          </div>
          <div className="mth-readout">
            <div className="mth-readout-item">
              <div className="mth-readout-label">recruited</div>
              <div className="mth-readout-val">{recruited}/5</div>
            </div>
          </div>
        </div>
      </>
    );
  }

  /* ════════════════════════════════════════════════════
     3. THE CYLINDER
     ════════════════════════════════════════════════════ */
  function VisCylinder() {
    const [active, setActive] = useState({});
    const toggle = (k) => setActive(prev => ({...prev, [k]: !prev[k]}));
    const allOn = ["diaphragm","pelvic","tva","multifidi"].every(k => active[k]);
    const reset = () => setActive({});
    const allOn4 = (active.diaphragm && active.pelvic && active.tva && active.multifidi);
    return (
      <>
        <svg viewBox="0 0 400 280" className="mth-vis-svg">
          {/* Outer torso outline */}
          <path d="M120,30 C90,30 80,55 85,80 L85,200 C85,230 100,250 120,256 L280,256 C300,250 315,230 315,200 L315,80 C320,55 310,30 280,30 Z"
            fill="var(--paper)" stroke="var(--ink)" strokeWidth="1.4"/>
          {/* IAP fill (pressurized state) */}
          {allOn4 && (
            <path d="M105,90 L105,210 C105,228 118,240 130,242 L270,242 C282,240 295,228 295,210 L295,90 Z"
              fill="var(--accent)" fillOpacity="0.15" className="m-pressurized"/>
          )}
          {/* Diaphragm (top dome) */}
          <path d="M105,90 C105,55 295,55 295,90 L295,100 C295,75 105,75 105,100 Z"
            className={"m-cyl-region" + (active.diaphragm ? " active" : "")}
            onClick={() => toggle("diaphragm")}/>
          <text x="200" y="78" textAnchor="middle" fontSize="11" fill={active.diaphragm ? "var(--accent)" : "var(--ink-2)"} fontFamily="monospace" pointerEvents="none">diaphragm</text>
          {/* TVA — wraps around front (left + right side bands) */}
          <path d="M105,110 L105,200 L130,200 L130,110 Z"
            className={"m-cyl-region" + (active.tva ? " active" : "")}
            onClick={() => toggle("tva")}/>
          <path d="M270,110 L270,200 L295,200 L295,110 Z"
            className={"m-cyl-region" + (active.tva ? " active" : "")}
            onClick={() => toggle("tva")}/>
          <text x="120" y="160" textAnchor="middle" fontSize="10" fill={active.tva ? "var(--accent)" : "var(--ink-2)"} fontFamily="monospace" pointerEvents="none" transform="rotate(-90 120 160)">TVA</text>
          <text x="280" y="160" textAnchor="middle" fontSize="10" fill={active.tva ? "var(--accent)" : "var(--ink-2)"} fontFamily="monospace" pointerEvents="none" transform="rotate(90 280 160)">TVA</text>
          {/* Multifidi — back center band */}
          <path d="M150,110 L150,200 L250,200 L250,110 Z"
            className={"m-cyl-region" + (active.multifidi ? " active" : "")}
            onClick={() => toggle("multifidi")}/>
          <text x="200" y="160" textAnchor="middle" fontSize="11" fill={active.multifidi ? "var(--accent)" : "var(--ink-3)"} fontFamily="monospace" pointerEvents="none">multifidi (post.)</text>
          {/* Pelvic floor (bottom dome) */}
          <path d="M105,210 C105,245 295,245 295,210 L295,200 C295,225 105,225 105,200 Z"
            className={"m-cyl-region" + (active.pelvic ? " active" : "")}
            onClick={() => toggle("pelvic")}/>
          <text x="200" y="232" textAnchor="middle" fontSize="11" fill={active.pelvic ? "var(--accent)" : "var(--ink-2)"} fontFamily="monospace" pointerEvents="none">pelvic floor</text>
          {/* IAP indicator at center */}
          {allOn4 && (
            <g>
              <text x="200" y="158" textAnchor="middle" fontSize="14" fill="var(--accent)" fontFamily="Newsreader" fontStyle="italic" fontWeight="500">IAP active</text>
              <text x="200" y="174" textAnchor="middle" fontSize="9" fill="var(--accent)" fontFamily="monospace" letterSpacing="0.16em">SPINE BRACED</text>
            </g>
          )}
        </svg>
        <div className="mth-vis-controls">
          <button className="mth-btn" onClick={reset}>↺ reset</button>
          <span style={{fontFamily:"monospace",fontSize:"10px",color:"var(--ink-3)",letterSpacing:"0.06em"}}>↑ click each region to engage</span>
          <div className="mth-readout">
            <div className="mth-readout-item">
              <div className="mth-readout-label">cylinder</div>
              <div className="mth-readout-val">{Object.values(active).filter(Boolean).length}/4</div>
            </div>
          </div>
        </div>
      </>
    );
  }

  /* ════════════════════════════════════════════════════
     4. THE LEVER
     ════════════════════════════════════════════════════ */
  function VisLever() {
    const [load, setLoad] = useState(10); // kg at hand
    // Bicep insertion ~ 4cm from elbow; hand ~ 36cm from elbow → ratio 9:1
    const ratio = 9;
    const muscleForce = load * ratio;
    return (
      <>
        <svg viewBox="0 0 400 240" className="mth-vis-svg">
          {/* Upper arm (humerus) - stays vertical */}
          <line x1="80" y1="40" x2="80" y2="160" stroke="var(--ink)" strokeWidth="3" strokeLinecap="round"/>
          <text x="62" y="60" fontSize="9" fill="var(--ink-3)" fontFamily="monospace">humerus</text>
          {/* Elbow joint - fulcrum */}
          <circle cx="80" cy="160" r="8" fill="var(--paper)" stroke="var(--ink)" strokeWidth="2"/>
          <text x="50" y="178" fontSize="10" fill="var(--ink)" fontFamily="monospace" fontWeight="600">A · axis</text>
          {/* Forearm (lever) - tilts based on load */}
          <g style={{transform: `rotate(${-30 + load*0.3}deg)`, transformOrigin: "80px 160px", transition: "transform 0.4s"}}>
            <line x1="80" y1="160" x2="320" y2="160" stroke="var(--ink)" strokeWidth="3.5" strokeLinecap="round"/>
            {/* Bicep insertion point - F (force) */}
            <circle cx="106" cy="160" r="5" fill="var(--accent)"/>
            <line x1="106" y1="160" x2="106" y2="120" stroke="var(--accent)" strokeWidth="2"/>
            <path d="M101,124 L106,114 L111,124 Z" fill="var(--accent)"/>
            <text x="115" y="130" fontSize="11" fill="var(--accent)" fontFamily="monospace" fontWeight="600">F · {muscleForce}kg</text>
            {/* Hand (load) - R (resistance) */}
            <circle cx="320" cy="160" r="6" fill="var(--ink)"/>
            <line x1="320" y1="160" x2="320" y2={160 + load*1.4} stroke="var(--ink)" strokeWidth="2"/>
            <path d="M315,{160+load*1.4-4} L320,{160+load*1.4+4} L325,{160+load*1.4-4} Z" fill="var(--ink)"/>
            <text x="290" y={185 + load*1.4} fontSize="11" fill="var(--ink)" fontFamily="monospace" fontWeight="600">R · {load}kg</text>
            {/* Distance brackets */}
            <line x1="80" y1="200" x2="106" y2="200" stroke="var(--accent)" strokeWidth="0.8"/>
            <text x="83" y="214" fontSize="8" fill="var(--accent)" fontFamily="monospace">d₁=4cm</text>
            <line x1="80" y1="220" x2="320" y2="220" stroke="var(--ink-3)" strokeWidth="0.8"/>
            <text x="180" y="234" fontSize="8" fill="var(--ink-3)" fontFamily="monospace">d₂=36cm</text>
          </g>
          {/* Equation */}
          <text x="200" y="30" textAnchor="middle" fontSize="11" fill="var(--ink-2)" fontFamily="monospace">F × d₁ = R × d₂  →  F = R × {ratio}</text>
        </svg>
        <div className="mth-vis-controls">
          <div className="mth-slider-row">
            <span className="mth-slider-label">load (R)</span>
            <input type="range" min="1" max="30" value={load} onChange={e => setLoad(parseInt(e.target.value))} className="mth-slider"/>
            <span className="mth-slider-val">{load} kg</span>
          </div>
          <div className="mth-readout">
            <div className="mth-readout-item">
              <div className="mth-readout-label">muscle force (F)</div>
              <div className="mth-readout-val">{muscleForce} kg</div>
            </div>
            <div className="mth-readout-item">
              <div className="mth-readout-label">disadvantage</div>
              <div className="mth-readout-val">{ratio}×</div>
            </div>
          </div>
        </div>
      </>
    );
  }

  /* ════════════════════════════════════════════════════
     5. CONTRACTION MODES — concentric · eccentric · isometric
     ════════════════════════════════════════════════════ */
  function VisContraction() {
    const [mode, setMode] = useState("concentric");
    const [t, setT] = useState(0);
    const [running, setRunning] = useState(false);
    const rafRef = useRef();
    const startRef = useRef();
    const DUR = { concentric: 1700, eccentric: 4200, isometric: 2800 };

    useEffect(() => {
      if (!running) return;
      const duration = DUR[mode];
      startRef.current = performance.now();
      function tick(now) {
        const p = Math.min((now - startRef.current) / duration, 1);
        setT(p);
        if (p < 1) rafRef.current = requestAnimationFrame(tick);
        else setRunning(false);
      }
      rafRef.current = requestAnimationFrame(tick);
      return () => cancelAnimationFrame(rafRef.current);
    }, [running, mode]);

    const run = () => { setT(0); setRunning(true); };
    const reset = () => { setRunning(false); setT(0); };
    const pickMode = (m) => { setMode(m); setRunning(false); setT(0); };

    // mode-specific kinematics
    let angle, shorten, tension, motionWord, lenWord, caption;
    if (mode === "concentric") {
      angle = -95 * t;                                  // extends → curls up
      shorten = t;                                      // 0 long → 1 short
      tension = 60 + 28 * Math.sin(t * Math.PI);
      motionWord = "flexing";
      lenWord = "shortening";
      caption = "concentric — muscle shortens, lifts the load";
    } else if (mode === "eccentric") {
      angle = -95 * (1 - t);                            // curled → lowers down
      shorten = 1 - t;                                  // 1 short → 0 long
      tension = 94 - 16 * t;                            // stays high — the brake
      motionWord = "extending";
      lenWord = "lengthening";
      caption = "eccentric — muscle lengthens, brakes the load";
    } else {                                            // isometric
      angle = -52;                                      // held, no motion
      shorten = 0.5;
      tension = 72 + 16 * Math.sin(t * Math.PI * 8);    // pulsing hold
      motionWord = "none";
      lenWord = "constant";
      caption = "isometric — force held, no change in length";
    }

    // bicep belly on the fixed upper arm — bulges as it shortens
    const brx = 8 + shorten * 11;
    const bry = 36 - shorten * 15;

    return (
      <>
        <svg viewBox="0 0 400 240" className="mth-vis-svg">
          {/* torso + head */}
          <ellipse cx="70" cy="124" rx="26" ry="46" fill="var(--paper)" stroke="var(--ink)" strokeWidth="1.4"/>
          <circle cx="70" cy="64" r="14" fill="var(--paper)" stroke="var(--ink)" strokeWidth="1.4"/>
          {/* shoulder link */}
          <line x1="92" y1="86" x2="130" y2="82" stroke="var(--ink)" strokeWidth="3" strokeLinecap="round"/>
          {/* upper arm — fixed vertical */}
          <line x1="130" y1="82" x2="130" y2="170" stroke="var(--ink)" strokeWidth="5" strokeLinecap="round"/>
          <circle cx="130" cy="82" r="5" fill="var(--ink)"/>
          {/* bicep belly — morphs with contraction state */}
          <ellipse cx="117" cy="124" rx={brx} ry={bry}
            fill="var(--accent)" fillOpacity={0.18 + tension / 320}
            stroke="var(--accent)" strokeWidth="1.4"
            style={{transition: running ? "none" : "rx 0.35s, ry 0.35s"}}/>
          <text x="117" y="127" textAnchor="middle" fontSize="8" fill="var(--accent)" fontFamily="monospace" pointerEvents="none">biceps</text>
          {/* elbow fulcrum */}
          <circle cx="130" cy="170" r="7" fill="var(--paper)" stroke="var(--ink)" strokeWidth="2"/>
          {/* forearm — rotates at elbow */}
          <g style={{transform: `rotate(${angle}deg)`, transformOrigin: "130px 170px", transition: running ? "none" : "transform 0.4s"}}>
            <line x1="130" y1="170" x2="250" y2="170" stroke="var(--ink)" strokeWidth="4.5" strokeLinecap="round"/>
            <g transform="translate(250,170)">
              <rect x="-8" y="-17" width="16" height="34" rx="3" fill="var(--ink)"/>
              <rect x="-17" y="-8" width="34" height="16" rx="3" fill="var(--ink)"/>
            </g>
          </g>
          {/* motion arrow */}
          {mode !== "isometric" && running && (
            <path
              d={mode === "concentric" ? "M196,150 A56,56 0 0,1 150,104" : "M150,104 A56,56 0 0,1 196,150"}
              fill="none" stroke="var(--accent)" strokeWidth="1.6" strokeDasharray="3,3" opacity="0.7"/>
          )}
          {mode === "isometric" && running && (
            <circle cx="190" cy="170" r={9 + 3 * Math.sin(t * Math.PI * 8)} fill="none" stroke="var(--accent)" strokeWidth="1.4" opacity="0.6"/>
          )}
          {/* tension bar */}
          <text x="352" y="34" fontSize="9" fill="var(--ink-3)" fontFamily="monospace" textAnchor="middle">tension</text>
          <rect x="342" y="42" width="20" height="152" fill="var(--rule)" opacity="0.4"/>
          <rect x="342" y={42 + (152 - tension * 1.52)} width="20" height={tension * 1.52} fill="var(--accent)" opacity="0.85"/>
          <text x="352" y="208" fontSize="9" fill="var(--accent)" fontFamily="monospace" textAnchor="middle">{Math.round(tension)}%</text>
          {/* caption */}
          <text x="206" y="228" textAnchor="middle" fontSize="11" fill="var(--ink-2)" fontFamily="monospace">{caption}</text>
        </svg>
        <div className="mth-vis-controls">
          <button className="mth-btn" data-on={mode === "concentric"} onClick={() => pickMode("concentric")}>concentric</button>
          <button className="mth-btn" data-on={mode === "eccentric"} onClick={() => pickMode("eccentric")}>eccentric</button>
          <button className="mth-btn" data-on={mode === "isometric"} onClick={() => pickMode("isometric")}>isometric</button>
          <button className="mth-btn accent" onClick={running ? reset : run}>{running ? "↺ reset" : "▸ perform"}</button>
          <div className="mth-readout">
            <div className="mth-readout-item">
              <div className="mth-readout-label">muscle length</div>
              <div className="mth-readout-val">{lenWord}</div>
            </div>
            <div className="mth-readout-item">
              <div className="mth-readout-label">joint motion</div>
              <div className="mth-readout-val">{motionWord}</div>
            </div>
          </div>
        </div>
      </>
    );
  }

  /* ════════════════════════════════════════════════════
     6. LATERAL BREATHING — guided timer + singing-bowl cues
     ════════════════════════════════════════════════════ */
  function VisBreathing() {
    const [mode, setMode] = useState("lateral");
    const [sound, setSound] = useState(true);
    const [running, setRunning] = useState(false);
    const [elapsed, setElapsed] = useState(0);
    const [phase, setPhase] = useState("rest");
    const rafRef = useRef();
    const startRef = useRef(0);
    const phaseRef = useRef("rest");
    const audioRef = useRef(null);
    const soundRef = useRef(true);
    useEffect(() => { soundRef.current = sound; }, [sound]);

    // guided pattern — inhale 4s · hold 4s · exhale 6s
    const SEQ = [
      { name: "inhale", dur: 4000 },
      { name: "hold",   dur: 4000 },
      { name: "exhale", dur: 6000 },
    ];
    const CYCLE = 14000;
    const FREQ = { inhale: 466.16, hold: 392.00, exhale: 311.13, close: 233.08 };

    // Tibetan singing-bowl tone — synthesised with the Web Audio API (no audio files)
    function ensureAudio() {
      try {
        if (!audioRef.current) {
          const AC = window.AudioContext || window.webkitAudioContext;
          audioRef.current = new AC();
        }
        if (audioRef.current.state === "suspended") audioRef.current.resume();
      } catch (e) { /* audio unavailable */ }
    }
    function playBowl(base) {
      const ctx = audioRef.current;
      if (!ctx) return;
      try {
        const now = ctx.currentTime;
        const master = ctx.createGain();
        master.gain.setValueAtTime(0.0001, now);
        master.gain.exponentialRampToValueAtTime(0.40, now + 0.05);
        master.gain.exponentialRampToValueAtTime(0.0001, now + 5.4);
        master.connect(ctx.destination);
        // inharmonic partials + detuned pairs → the characteristic bowl shimmer
        const partials = [
          { r: 1.00, g: 1.00, d: 1.6 },
          { r: 2.74, g: 0.45, d: 2.6 },
          { r: 5.41, g: 0.22, d: 3.6 },
          { r: 8.88, g: 0.10, d: 4.6 },
        ];
        partials.forEach(p => {
          [-p.d, p.d].forEach(det => {
            const osc = ctx.createOscillator();
            osc.type = "sine";
            osc.frequency.value = base * p.r;
            osc.detune.value = det;
            const g = ctx.createGain();
            g.gain.value = p.g * 0.5;
            osc.connect(g); g.connect(master);
            osc.start(now);
            osc.stop(now + 5.6);
          });
        });
      } catch (e) { /* silent */ }
    }

    useEffect(() => {
      if (!running) return;
      startRef.current = performance.now();
      phaseRef.current = "rest";
      function tick() {
        const el = performance.now() - startRef.current;
        setElapsed(el);
        const cyc = el % CYCLE;
        let acc = 0, ph = "inhale";
        for (const s of SEQ) { if (cyc < acc + s.dur) { ph = s.name; break; } acc += s.dur; }
        if (ph !== phaseRef.current) {
          phaseRef.current = ph;
          setPhase(ph);
          if (soundRef.current) playBowl(FREQ[ph]);
        }
      }
      tick();
      rafRef.current = setInterval(tick, 80);
      return () => clearInterval(rafRef.current);
    }, [running]);

    const begin = () => { ensureAudio(); setElapsed(0); setPhase("inhale"); phaseRef.current = "rest"; setRunning(true); };
    const stop  = () => { setRunning(false); setPhase("rest"); setElapsed(0); if (soundRef.current) { ensureAudio(); playBowl(FREQ.close); } };
    const toggleSound = () => { ensureAudio(); setSound(s => !s); };

    // derive phase progress + a continuous breath fill (0 empty → 1 full)
    let phaseLeft = 0, breath = 0, phaseProg = 0;
    if (running) {
      const cyc = elapsed % CYCLE;
      let acc = 0;
      for (const s of SEQ) {
        if (cyc < acc + s.dur) {
          phaseProg = (cyc - acc) / s.dur;
          phaseLeft = Math.ceil((acc + s.dur - cyc) / 1000);
          breath = s.name === "inhale" ? phaseProg : s.name === "hold" ? 1 : 1 - phaseProg;
          break;
        }
        acc += s.dur;
      }
    }
    const cycleNum = running ? Math.floor(elapsed / CYCLE) + 1 : 0;
    const mm = String(Math.floor(elapsed / 60000)).padStart(2, "0");
    const ss = String(Math.floor((elapsed % 60000) / 1000)).padStart(2, "0");

    const isLateral = mode === "lateral";
    const rx = isLateral ? 80 + breath * 44 : 80 + breath * 10;
    const ry = isLateral ? 66 + breath * 8  : 66 + breath * 30;
    const tvaActive = isLateral;
    const phaseColor = phase === "exhale" ? "var(--ink-2)" : "var(--accent)";
    const RING = 2 * Math.PI * 27;

    return (
      <>
        <svg viewBox="0 0 400 260" className="mth-vis-svg">
          <text x="200" y="20" textAnchor="middle" fontSize="10" fill="var(--ink-3)" fontFamily="monospace">↓ ribcage · top-down view</text>
          {/* spine */}
          <rect x="195" y="106" width="10" height="54" fill="var(--ink)" rx="2"/>
          {/* ribcage */}
          <ellipse cx="200" cy="133" rx={rx} ry={ry} fill="none" stroke="var(--ink)" strokeWidth="1.6"
            style={{transition: "rx 0.16s linear, ry 0.16s linear"}}/>
          <ellipse cx="200" cy="133" rx={Math.max(rx - 18, 4)} ry={Math.max(ry - 16, 4)}
            fill="var(--accent)" fillOpacity={0.05 + breath * 0.20}
            stroke="var(--accent)" strokeWidth="0.8" strokeDasharray="2,2"
            style={{transition: "rx 0.16s linear, ry 0.16s linear, fill-opacity 0.16s"}}/>
          {[0,1,2,3,4].map(i => (
            <ellipse key={i} cx="200" cy={105 + i * 17} rx={Math.max(rx - 6, 4)} ry={6}
              fill="none" stroke="var(--ink-3)" strokeWidth="0.7" style={{transition: "rx 0.16s linear"}}/>
          ))}
          {/* TVA corset */}
          {tvaActive && <ellipse cx="200" cy="206" rx="58" ry="13" fill="none" stroke="var(--accent)" strokeWidth="2" strokeDasharray="4,3" opacity="0.6"/>}
          {tvaActive && <text x="200" y="226" textAnchor="middle" fontSize="9" fill="var(--accent)" fontFamily="monospace">TVA · engaged</text>}
          {!tvaActive && running && <text x="200" y="226" textAnchor="middle" fontSize="9" fill="var(--ink-3)" fontFamily="monospace">belly distends · TVA released</text>}
          {/* lateral widen arrows on inhale */}
          {running && phase === "inhale" && isLateral && (
            <g stroke="var(--accent)" strokeWidth="1.4" fill="none">
              <path d="M108,133 L86,133 M93,127 L86,133 L93,139"/>
              <path d="M292,133 L314,133 M307,127 L314,133 L307,139"/>
            </g>
          )}
          {/* phase label */}
          <text x="200" y="44" textAnchor="middle" fontSize="12" fill={running ? phaseColor : "var(--ink-3)"} fontFamily="monospace" fontWeight="600" letterSpacing="0.14em">
            {running ? phase.toUpperCase() : "READY · 4 · 4 · 6"}
          </text>
          {/* countdown dial */}
          {running && (
            <g>
              <circle cx="200" cy="133" r="27" fill="var(--paper)" opacity="0.85"/>
              <circle cx="200" cy="133" r="27" fill="none" stroke="var(--rule)" strokeWidth="3"/>
              <circle cx="200" cy="133" r="27" fill="none" stroke={phaseColor} strokeWidth="3" strokeLinecap="round"
                strokeDasharray={RING} strokeDashoffset={RING * (1 - phaseProg)} transform="rotate(-90 200 133)"
                style={{transition: "stroke-dashoffset 0.12s linear"}}/>
              <text x="200" y="142" textAnchor="middle" fontSize="30" fill={phaseColor} fontFamily="Newsreader" fontWeight="500">{phaseLeft}</text>
            </g>
          )}
        </svg>
        <div className="mth-vis-controls">
          <button className="mth-btn" data-on={mode === "lateral"} onClick={() => setMode("lateral")}>lateral · bellows</button>
          <button className="mth-btn" data-on={mode === "belly"} onClick={() => setMode("belly")}>belly breathing</button>
          <button className="mth-btn" data-on={sound} onClick={toggleSound}>{sound ? "♪ bowl on" : "♪ bowl off"}</button>
          <button className="mth-btn accent" onClick={running ? stop : begin}>{running ? "■ stop" : "▸ begin"}</button>
          <div className="mth-readout">
            <div className="mth-readout-item">
              <div className="mth-readout-label">time</div>
              <div className="mth-readout-val">{mm}:{ss}</div>
            </div>
            <div className="mth-readout-item">
              <div className="mth-readout-label">breath</div>
              <div className="mth-readout-val">{cycleNum}</div>
            </div>
          </div>
        </div>
      </>
    );
  }

  const VIS_MAP = {
    filament: VisFilament, size: VisSize, cylinder: VisCylinder,
    lever: VisLever, contraction: VisContraction, breath: VisBreathing,
  };

  /* ════════════════════════════════════════════════════
     MAIN COMPONENT
     ════════════════════════════════════════════════════ */
  function MethodSection() {
    const [active, setActive] = useState("breath");
    const lang = useLang();
    const p = ORDERED_PRINCIPLES.find(x => x.id === active);
    const Vis = VIS_MAP[active];
    return (
      <div className="method-wrap">
        <p className="method-lede">
          {lang === "th" ? (
            <>ให้มองว่าร่างกายเราคือระบบ <em>คานงัด</em> ที่ทำงานด้วย <em>แรงดัน</em> และมี <em>กลุ่มกล้ามเนื้อ</em> คอยควบคุมการเคลื่อนไหวอีกทีนะ หัวใจของพิลาทิสคือการฝึกกล้ามเนื้อทีละชั้นตามลำดับที่ถูกต้อง — โดยเริ่มตั้งแต่กะบังลม อุ้งเชิงกราน TVA ไปจนถึง Multifidi ให้ทำงานสอดประสานกันเหมือนเป็นก้อนเดียวกันเลย แล้วค่อยเริ่มขยับร่างกายกันนะ!</>
          ) : (
            <>The body is a system of <em>levers,</em> governed by <em>pressure,</em> executed by <em>motor units.</em> Pilates is the discipline of training each layer in the right order — diaphragm, pelvic floor, transversus and multifidi as one continuous cylinder, then ask it to do work.</>
          )}
        </p>

        <div className="mth-strip">
          {ORDERED_PRINCIPLES.map(pp => (
            <button
              key={pp.id} className="mth-tab"
              data-active={active === pp.id}
              onClick={() => setActive(pp.id)}
            >
              <span className="mth-tab-num">§ {pp.num}</span>
              <span className="mth-tab-title">{pick(pp.title, lang)}</span>
              <span className="mth-tab-tag">// {pick(pp.tag, lang)}</span>
            </button>
          ))}
        </div>

        <div className="mth-stage">
          <div className="mth-vis">
            <Vis />
          </div>
          <div className="mth-body">
            <div className="mth-body-meta">{lang === "th" ? "// หลักการ" : "// principle"} {p.num} · {pick(p.tag, lang)}</div>
            <div className="mth-body-title">{pick(p.title, lang)}</div>
            <p className="mth-body-text">{pick(p.body, lang)}</p>
            <div className="mth-body-cues">
              <div className="mth-body-cues-label">{lang === "th" ? "// สาระสำคัญ" : "// distillation"}</div>
              <ul className="mth-body-cues-list">
                {pick(p.cues, lang).map(c => <li key={c}>{c}</li>)}
              </ul>
            </div>
          </div>
        </div>
      </div>
    );
  }

  const root = document.getElementById("method-root");
  if (root) ReactDOM.createRoot(root).render(<MethodSection />);
})();
