ข้ามไปยังเนื้อหา
Tayakorn
← คู่มือทั้งหมด
devอัปเดต 2026-08-24อ่านฟรี

HyperFrames Handbook

ทำวิดีโอจาก HTML ที่แก้ได้เหมือนโค้ด — เข้าใจกลไกเฟรมคือฟังก์ชันของเวลา จนเขียน ตรวจ และเรนเดอร์วิดีโอเองได้ทั้งวงจร

#hyperframes#video#html#gsap#motion-design

คู่มือที่พาคุณจากคนไม่เคยทำวิดีโอด้วยโค้ด ไปจนเขียน ตรวจ และเรนเดอร์วิดีโอจากเว็บเพจได้เองครบวงจร — เน้นกลไกแกนที่ทำให้กฎทุกข้อของ framework กลายเป็นเรื่อง "เดาได้" ไม่ใช่ท่องจำ

  • ระดับ: เริ่มจากศูนย์ (ขอแค่เคยแตะ HTML/CSS) → ใช้งานจริง
  • เวลาอ่าน: ~60 นาที
  • ภาษา: ไทย พร้อมไดอะแกรม
  • ทดสอบกับ: hyperframes v0.8.8 บน Windows 11 — ทุก output ในเล่มรันจริง

LEVEL 0 · ปูพื้นจากศูนย์

ทำไมวิดีโอถึงเป็นเว็บเพจได้ — แล้วทำไมควรเป็น#

ลองนึกถึงวันที่ตัดคลิปแนะนำทีมเสร็จ export รอครึ่งชั่วโมง ส่งเข้ากลุ่มไปแล้ว เพิ่งเห็นว่าสะกดชื่อหัวหน้าทีมผิดหนึ่งตัวอักษร ในโปรแกรมตัดต่อ การแก้ตัวอักษรตัวเดียวแปลว่าเปิดโปรเจกต์ ไล่หากล่องข้อความบน timeline แก้คำ แล้ว export ใหม่ทั้งไฟล์อีกรอบ

ถ้าเป็นเอกสาร คุณแก้คำแล้วกดเซฟ จบในสิบวินาที ที่วิดีโอทำแบบนั้นไม่ได้เพราะไฟล์ .mp4 เก็บเฉพาะผลลัพธ์ปลายทาง คือพิกเซลนับล้านจุด ส่วนต้นทางที่มนุษย์แก้เป็น — ข้อความ สี ตำแหน่ง จังหวะ — ถูกขังไว้ในไฟล์โปรเจกต์ของโปรแกรมตัดต่อ ที่เปิดได้ด้วยโปรแกรมเดียวและเทียบความต่างระหว่างเวอร์ชันไม่ได้เลย

HyperFrames แก้ที่ต้นเหตุ: เก็บต้นทางเป็นเว็บเพจ ตัวหนังสือเป็น text จริงใน HTML สีและตำแหน่งเป็น CSS จังหวะเวลาเป็น attribute ชื่อ data-* แล้วให้เครื่องเรนเดอร์ผลิตไฟล์วิดีโอจากต้นทางนี้ทุกครั้งที่สั่ง การแก้ชื่อที่สะกดผิดจึงเหลือแค่แก้ text หนึ่งคำ สั่งเรนเดอร์ใหม่ จบ

เมื่อวิดีโอทั้งตัวเป็นไฟล์ text สามอย่างที่โปรแกรมตัดต่อให้ไม่ได้ก็ตามมาทันที: เก็บลง Git แล้ว diff ดูได้ว่าเวอร์ชันนี้ต่างจากเมื่อวานตรงไหน · ผลิตซ้ำเป็นระบบได้ เช่นคลิปโครงเดียวกันร้อยชื่อร้อยเวอร์ชัน (บทที่ 12) · และ AI อ่าน แก้ หรือเขียนให้ได้ทั้งตัว เพราะทุกอย่างคือโค้ดที่มันถนัดที่สุดอยู่แล้ว

1.1 สองวิธีใช้ — เขียนเอง กับ กำกับ AI

HyperFrames ถูกออกแบบมาให้ AI ทำงานด้วยตั้งแต่แรก ตัว framework แถมชุดคู่มือสำหรับ AI agent มาในตัว และในงานจริงคุณจะสั่ง AI เขียน composition บ่อยกว่าพิมพ์เองทีละบรรทัด

สองทางนี้ไม่ได้แข่งกัน คนที่กำกับ AI ได้ดีคือคนที่อ่านงานของเครื่องออก — รู้ว่ารายงานตรวจกำลังฟ้องอะไร ดูเฟรมแล้วบอกได้ว่าผิดเพราะกลไกข้อไหน เล่มนี้จึงสอนชั้นกลไก: เข้าใจแล้วจะลงมือเขียนเองก็ได้ หรือสั่งให้คนอื่นเขียนแล้วตรวจรับเป็น ก็ได้ทั้งคู่

1.2 ติดตั้งและตรวจเครื่อง

ของที่ต้องมีในเครื่องมีสองอย่าง: Node.js 22 ขึ้นไป และ FFmpeg จากนั้นตรวจความพร้อมทั้งหมดด้วยคำสั่งเดียว:

$ npx hyperframes doctor

เครื่องที่ใช้เขียนเล่มนี้ตอบกลับมาว่า FFmpeg 8.0 พร้อม FFprobe พร้อม และ Chrome headless พร้อม — ตัวสุดท้ายคือเบราว์เซอร์ไร้หน้าจอที่ HyperFrames ใช้ถ่ายเฟรม ดาวน์โหลดให้อัตโนมัติในการใช้งานครั้งแรก ไม่ต้องติดตั้งเอง

1.3 เปิดโปรเจกต์แรก

$ npx hyperframes init my-first-video --example blank

ได้โฟลเดอร์ที่มีไฟล์สำคัญไฟล์เดียวคือ index.html ที่เหลือเป็นของประกอบ (hyperframes.json ตั้งค่าโปรเจกต์ package.json เก็บทางลัดคำสั่ง) เว็บเพจไฟล์นี้แหละคือวิดีโอทั้งตัว บทที่ 3 จะเปิดผ่าดูทีละบรรทัด

ส่วนปลายทางของเล่ม ขอสปอยล์ให้เห็นก่อน: การ์ดนัดประชุมทีมยาว 10 วินาที ที่คุณจะสร้างเองครบวงจรในบท 3–6 เครื่องที่เขียนเล่มนี้เรนเดอร์มันด้วยคำสั่งเดียว ได้ผลจริงแบบนี้:

◇  renders\handbook-lab_2026-08-22_16-48-08.mp4
   326.3 KB · 10.0s video · rendered in 59.3s

วิดีโอ 10 วินาที 300 เฟรม เสร็จในหนึ่งนาที และไม่ว่าจะกลับมาแก้คำผิดอีกกี่รอบ ก็จ่ายเวลาเท่านี้เสมอ

เฟรมคือฟังก์ชันของเวลา#

บทนี้สำคัญที่สุดในเล่ม แนวคิดเดียวข้างล่างนี้คือเหตุผลเบื้องหลังกฎแทบทุกข้อของ HyperFrames เข้าใจมันแล้ว กฎทั้งหมดจะเปลี่ยนจาก "ข้อห้ามที่ต้องท่อง" เป็น "เรื่องที่เดาได้เอง"

2.1 ไม่มีการอัดหน้าจอ — มีแต่การถาม

ความเข้าใจผิดที่พบบ่อยคือคิดว่า HyperFrames "อัด" เว็บเพจแบบโปรแกรมอัดหน้าจอ: เปิดเพจ ปล่อยให้ animation เล่นไป แล้วบันทึกสิ่งที่เห็น ความจริงตรงกันข้าม — ระหว่างเรนเดอร์ ไม่มีเวลาจริงไหลผ่านเลยสักวินาที

เครื่องเรนเดอร์ทำแบบนี้แทน: ถามเพจว่า "วินาทีที่ 0.000 หน้าตาเป็นยังไง" แล้วถ่ายรูปเก็บ ถามต่อ "วินาทีที่ 0.033 ล่ะ" ถ่ายอีกรูป ไล่ถามอย่างนี้ 300 ครั้งสำหรับคลิป 10 วินาทีที่ 30 เฟรมต่อวินาที (เลข 300 นี้อยู่ใน log จริงของการเรนเดอร์บทที่ 1) เสร็จแล้วส่งรูปทั้งกองให้ FFmpeg เย็บเป็นไฟล์วิดีโอ

วิดีโอใน HyperFrames ไม่ใช่สิ่งที่ถูกอัด แต่คือคำตอบของฟังก์ชัน: ใส่เวลา t เข้าไป ได้ภาพหนึ่งเฟรมออกมา — และต้องได้ภาพเดิมทุกครั้งที่ถาม t เดิม

เครื่องเรนเดอร์producerถาม f(t)ภาพนิ่ง 1 เฟรมเว็บเพจ (composition)timeline หยุดนิ่งรอถูก seek×300กองเฟรมFFmpeg.mp4วน 300 รอบ — ลำดับไหนก็ได้ถาม t เดิม ต้องได้ภาพเดิมเป๊ะทุกครั้ง — ทุกกฎของ framework ไถลออกมาจากประโยคนี้
FIG 2.1 ไม่มีการอัดหน้าจอ — เครื่องเรนเดอร์ถาม f(t) ทีละเฟรม (คลิป 10 วิ ที่ 30 เฟรม/วิ = ถาม 300 ครั้ง) แล้วส่งภาพทั้งกองให้ FFmpeg เย็บเป็นไฟล์ · สัญญาเดียวที่ทุกกฎของ framework ห้อยอยู่: ถาม t เดิม ได้ภาพเดิมเป๊ะ

2.2 ผลข้อแรก: เวลาถูก "ประกาศ" ไม่ใช่ถูก "สั่งเล่น"

การจะตอบ "วินาที 7.2 หน้าตาเป็นยังไง" ได้ทันที เพจต้องรู้อนาคตทั้งเรื่องล่วงหน้า: อะไรโผล่เมื่อไหร่ อยู่นานแค่ไหน กำลังเคลื่อนที่ถึงจุดไหน ณ เสี้ยววินาทีนั้น ใน HyperFrames จึงไม่มีโค้ดสั่ง "เล่นเดี๋ยวนี้" มีแต่การประกาศแผนเวลาไว้ล่วงหน้า สองชั้น:

  • ชั้น DOM — แต่ละ element ประกาศช่วงเวลาของตัวเองตรง ๆ บน HTML: "ฉันอยู่บนจอตั้งแต่วินาที 2.5 นาน 7.5 วินาที" ผ่าน data-start และ data-duration (บท 3–4)
  • ชั้น animation — timeline ประกาศการเคลื่อนไหวทั้งเส้นจบตั้งแต่เพจโหลด แล้วหยุดนิ่งรอ ไม่เล่นเอง คนหมุนเข็มเวลาไปยังจุดที่ต้องการคือเครื่องเรนเดอร์ ไม่ใช่ตัวเพจ (บท 5)

มองผ่านเลนส์นี้ DOM ของ composition ก็คือ timeline ที่ประกาศตัวเอง — สิ่งที่โปรแกรมตัดต่อวาดเป็นแถบสีบนราง HyperFrames เขียนเป็น attribute บน element

2.3 ผลข้อสอง: อะไรที่ผันแปรตามโลกจริง คือบั๊กทั้งหมด

ถ้าเฟรมคือฟังก์ชันของเวลา แล้วมีบางอย่างในเพจตอบไม่เหมือนเดิมเมื่อถูกถาม t เดิม วิดีโอจะเสียความน่าเชื่อถือทันที — เฟรมใน preview กับเฟรมในไฟล์จริงจะไม่ใช่ภาพเดียวกัน เล่มนี้ขอปลูกเรื่องนี้เป็นปริศนาสามข้อ อ่านถึงบทที่ไขแล้วจะกระจ่างเอง:

  • ทำไมห้ามใช้ Math.random() และนาฬิกาจริง ทั้งที่โค้ดเว็บทั่วไปใช้กันปกติ (บทที่ 11 ไข)
  • ทำไมเพจถูกห้ามเล่นไฟล์ <video> และ <audio> เอง ต้องยกสิทธิ์ให้ framework คุม (บทที่ 7 ไข)
  • ทำไมเครื่องตรวจปฏิเสธ animation ที่วนลูปไม่รู้จบ (บทที่ 11 ไข)

2.4 ลองพิสูจน์ด้วยตาเอง

คำถาม f(t) ไม่ใช่เรื่องเปรียบเปรย มันคือคำสั่งที่คุณยิงเองได้เดี๋ยวนี้:

$ npx hyperframes snapshot
  5 snapshots saved to snapshots/
   snapshots\frame-00-at-0s.png
   snapshots\frame-01-at-2.5s.png
   snapshots\frame-02-at-5s.png
   snapshots\frame-03-at-7.5s.png
   snapshots\frame-04-at-9.7s.png

นี่คือการถาม f(t) ห้าครั้งที่ t = 0, 2.5, 5, 7.5, 9.7 ได้ภาพนิ่งห้าใบโดยไม่ต้องเรนเดอร์วิดีโอทั้งไฟล์ รันซ้ำกี่รอบก็ได้ภาพชุดเดิมเป๊ะ — หลักฐานว่าไม่มีการอัด มีแต่การถาม

LEVEL 1 · พื้นฐานที่ใช้ทุกวัน

กายวิภาคของ composition#

บทนี้จะพาอ่าน composition ออกทั้งไฟล์ แล้วเริ่มสร้างการ์ดนัดประชุมทีมที่เห็นในบทที่ 1 องค์ประกอบมีน้อยกว่าที่คิด: กล่องหลักหนึ่งใบ กับลูก ๆ ที่ประกาศเวลาของตัวเอง

3.1 root — กล่องที่นิยามทั้งเรื่อง

ทุก composition มี element รากหนึ่งตัวใน body ที่ถือข้อมูลนิยามของวิดีโอทั้งหมด:

<div
  id="root"
  data-composition-id="main"
  data-start="0"
  data-duration="10"
  data-width="1920"
  data-height="1080"
></div>
  • data-composition-id — ชื่อประจำตัว ใช้เป็นกุญแจตามหา timeline (บทที่ 5)
  • data-duration — ความยาววิดีโอเป็นวินาที ตัวนี้คือคำตอบของคำถาม "เรนเดอร์กี่เฟรม" ไม่ใช่ความยาวของ animation
  • data-width / data-height — ขนาดเฟรมเป็นพิกเซล และ root ต้องมี CSS กำหนดขนาดจริงตามนี้ด้วย

กติกาข้อสุดท้ายสำคัญกว่าที่เห็น: ถ้า root ไม่ถูกกำหนดขนาดเป็นพิกเซลจริง ลูกที่ใช้ height: 100% จะยุบเหลือศูนย์แล้วเนื้อหาทั้งหมดไปกองที่มุมซ้ายบน — เป็นบั๊กเงียบที่เครื่องตรวจอัตโนมัติไม่ฟ้อง ต้องดู snapshot ด้วยตาเท่านั้นถึงเห็น

3.2 clip — ลูกที่ประกาศเวลาของตัวเอง

เนื้อหาทุกก้อนบนจอคือ clip: element ที่ประกาศ "ฉันอยู่บนจอช่วงไหน" ด้วย attribute สามตัว นี่คือ clip จริงจากการ์ดนัดประชุม:

<div id="headline" class="clip" data-start="0.5" data-duration="9.5" data-track-index="2">
  <div class="column">
    <div id="team-chip">ทีมการตลาด</div>
    <div id="headline-text">ประชุมทีมรายสัปดาห์</div>
  </div>
</div>

data-start = โผล่วินาทีไหน · data-duration = อยู่นานเท่าไหร่ · data-track-index = อยู่รางไหน (บทที่ 4) ส่วน class="clip" เป็นธรรมเนียมให้เครื่องมือหาเจอ การซ่อน-โชว์ clip ตามเวลาเป็นหน้าที่ของ framework ล้วน ๆ — โค้ดคุณห้ามไปยุ่งกับ visibility ของมัน

3.3 กติกาที่เครื่องไม่ตรวจให้

ระหว่างเขียนเล่มนี้ ผมทดลองถอด data-start ออกจาก root แล้วรันทั้ง lint และ checkผ่านเงียบทั้งคู่ (v0.8.8) ทั้งที่ contract ระบุว่าต้องมี บทเรียนอยู่ตรงนี้: เครื่องตรวจของ HyperFrames เก่งมากในเรื่องที่มันเห็น แต่กติกาบางข้อยังไม่มีใครตรวจแทนคุณ เขียนให้ครบตาม contract ตั้งแต่แรก อย่ารอให้เครื่องเตือน

ตัดต่อบน DOM — clips & tracks#

ในโปรแกรมตัดต่อ คุณลากคลิปวางบนราง ใน HyperFrames รางก็มีเหมือนกัน แต่มันคือตัวเลขหนึ่งตัวบน attribute และความเข้าใจผิดที่ต้องรื้อก่อนคือ: ราง (track) เป็นเรื่องของเวลา ไม่ใช่เรื่องของชั้นภาพ

4.1 ราง = เลนของเวลา

data-track-index มีหน้าที่เดียว: บอกว่า clip ไหนแชร์เลนเวลากัน กติกาคือ สอง clip บนรางเดียวกันห้ามซ้อนช่วงเวลากัน ส่วนใครบังหน้าใครบนจอเป็นเรื่องของ CSS z-index ล้วน ๆ — clip บนราง 5 ไม่ได้ "อยู่เหนือ" ราง 1

การ์ดนัดประชุมใช้ 6 ราง แยกตามบทบาท:

0s2.5s5s7.5s10sราง 1#bg · พื้นกระดาษ (0–10)ราง 2#headline · หัวเรื่อง (0.5–10)ราง 3#details · แถวรายละเอียด (2.5–10)ราง 4#outro · การ์ดปิด (6.5–10)ราง 5#broll · วิดีโอ (3–6.5)ราง 10#chimeเสียงเปิด (0.5–1.7)รางเดียวกัน = ห้ามซ้อนช่วงเวลา · ชั้นภาพหน้า-หลังไม่เกี่ยวกับเลขราง
FIG 4.1 ผังรางจริงของการ์ดนัดประชุม — data-track-index คือเลนของเวลา: clip สองตัวบนรางเดียวกันห้ามซ้อนช่วงเวลากัน ส่วนใครบังใครบนจอเป็นเรื่องของ z-index ล้วน ๆ · ธรรมเนียม: เสียงแยกไปรางเลขสูง (ราง 10) ให้อ่านผังง่าย

4.2 ซ้อนรางแล้วเกิดอะไร — ทดลองจริง

ผมย้ายการ์ดปิดไปราง 3 ให้ซ้อนกับแถวรายละเอียด (6.5–10 ทับ 2.5–10) แล้วรันทุกด่าน: lint เงียบ check ผ่าน และเฟรมที่ถ่ายออกมาดูปกติทุกอย่าง — ทั้งสอง clip โชว์พร้อมกันเหมือนไม่มีอะไรเกิดขึ้น

นั่นแหละคือจุดที่ต้องกลัว contract เรียกผลของการซ้อนรางว่า undefined — ไม่ได้แปลว่า "พัง" แต่แปลว่า ไม่มีสัญญาอะไรทั้งนั้น วันนี้ดูปกติ รุ่นหน้าอาจไม่ และไม่มีเครื่องตรวจตัวไหนช่วยคุณ (ยืนยันกับ v0.8.8) เจอ clip ใหม่ที่ช่วงเวลาทับของเดิม ให้หารางว่างหรือขยับเวลาอย่างตั้งใจ

4.3 ตัดต่อจริง: เลื่อน หั่น เรียงใหม่

การตัดต่อทั้งหมดคือการแก้ตัวเลขบน attribute: เลื่อนคลิป = แก้ data-start · ยืด-หด = แก้ data-duration · หั่นวิดีโอหนึ่งไฟล์เป็นหลายท่อน = สร้าง video หลาย element ชี้ไฟล์เดียวกัน แล้วใช้ data-media-start เลือกจุดเริ่มในไฟล์ต้นทางของแต่ละท่อน

ที่เก๋กว่านั้นคือ data-start รับชื่อ clip อื่นแทนตัวเลขได้: data-start="intro" แปลว่า "เริ่มเมื่อ intro จบ" และ data-start="intro - 0.5" สร้างช่วงเกยสำหรับ crossfade (ต้องอยู่คนละราง — ซ้อนรางเดียวกันผิดกติกาข้อ 4.1 เสมอ)

Timeline เดียวที่หยุดนิ่ง#

บทที่ 2 ทิ้งไว้ว่า timeline ต้อง "สร้างจบแล้วหยุดนิ่งรอ" บทนี้ลงมือจริงด้วย GSAP และไขว่าทำไมท่าเขียนบางท่าปลอดภัยกว่าอีกท่าทั้งที่ผลลัพธ์บนตาเหมือนกัน

5.1 สัญญาสามข้อ

ทุก composition ลงทะเบียน timeline เดียว แบบนี้ (โค้ดจริงจากการ์ด):

window.__timelines = window.__timelines || {};
const tl = gsap.timeline({ paused: true });

tl.fromTo(
  "#headline-text",
  { autoAlpha: 0, y: 40 },
  { autoAlpha: 1, y: 0, duration: 0.8, ease: "power3.out" },
  0.6
);

window.__timelines["main"] = tl;

สัญญา: (ก) paused: true — คนหมุนเวลาคือเครื่องเรนเดอร์ (ข) สร้างแบบ synchronous ตอนโหลดเพจ ห้ามไปอยู่ใน setTimeout / Promise / event handler เพราะเครื่องอาจถามเฟรมก่อนโค้ดพวกนั้นวิ่ง (ค) กุญแจ "main" ต้องตรงกับ data-composition-id ของ root เป๊ะ — มันคือช่องทางเดียวที่เครื่องใช้ตามหา timeline

ตัวเลขท้ายบรรทัด (0.6) คือตำแหน่งบนแกนเวลาของเรื่อง — คุณกำลังวางแผนอนาคต ไม่ใช่สั่งเล่น

data-composition-id="main"window.__timelines["main"]ต้องตรงกันเครื่องเรนเดอร์seek(4.0)0s10sสร้าง synchronous ตอนโหลด · paused: true · ไม่มีใครเรียก play() — มีแต่ถูก seek
FIG 5.1 timeline สร้างจบตั้งแต่เพจโหลดแล้วหยุดนิ่ง (paused: true) — เข็มไม่เดินเอง เครื่องเรนเดอร์เป็นคนหมุนไปยัง t ที่ต้องการ · กุญแจ window.__timelines ต้องตรงกับ data-composition-id เป๊ะ เพราะเป็นช่องทางเดียวที่เครื่องใช้ตามหา timeline

5.2 ทำไมต้อง fromTo — กลไกที่มองไม่เห็นด้วยตา

gsap.to() บอกแค่ปลายทาง แล้วให้ GSAP "จำสภาพตอนเริ่ม" เอาเอง ปัญหาคือมันจำตอนที่ถูกถามถึงครั้งแรก — และเครื่องเรนเดอร์ถามเฟรมไม่เรียงลำดับ (หลาย worker แบ่งกันถาม — บทที่ 11) สภาพตอนเริ่มที่ถูกจำจึงขึ้นกับลำดับการถาม ผิดกฎ f(t) ทันที

gsap.fromTo() ประกาศทั้งสองปลายเอง ไม่ต้องจำอะไร ถามลำดับไหนก็ได้คำตอบเดิม ผมทดลองเพิ่ม CSS transform: translateY(40px) ให้ element เดียวกับที่มี fromTo แล้วถ่ายเฟรมก่อนคิวเข้า: element หายไปเลย — fromTo ยึดสภาพเริ่มของมัน (ซ่อนไว้) ตั้งแต่เพจโหลด ค่า CSS ไม่มีผลอะไร นี่คือเหตุผลที่กติกาบอกให้ตั้งค่าเริ่มใน tween ไม่ใช่ใน CSS: ให้ความจริงมีที่อยู่ที่เดียว

5.3 ลืมลงทะเบียนแล้วใครจับ — ทดลองจริง

ผมคอมเมนต์บรรทัด window.__timelines["main"] = tl; ทิ้ง: lint เงียบสนิท แต่ check ล้มด้วยสามข้อจาก motion sidecar — ไฟล์ index.motion.json ที่ผมประกาศเจตนาไว้ข้าง composition:

✗ motion_appears_late: never reaches visible opacity but should be visible by 1.6s
  Fix: The renderer seeks a paused timeline; a forward-only reveal can be skipped.
✗ motion_out_of_order: #headline-text should appear before #outro-card-line ...
✗ motion_frozen: nothing moves within #root between 2.5s and 6.5s (4s static)

ไม่มี timeline = ไม่มีอะไรขยับ และเครื่องรู้เพราะผมบอกไว้ก่อนว่าอะไรควรขยับเมื่อไหร่ sidecar หน้าตาแบบนี้:

{
  "assertions": [
    { "kind": "appearsBy", "selector": "#headline-text", "bySec": 1.6 },
    { "kind": "before", "a": "#headline-text", "b": "#outro-card-line" },
    { "kind": "keepsMoving", "withinSelector": "#root" }
  ]
}

ประกาศเจตนาให้เครื่องอ่านได้ แล้วเครื่องจะเฝ้าแทนคุณ — นี่คือหัวใจของการทำวิดีโอแบบวิศวกร

สี่คำสั่งที่คือ 90% ของชีวิตจริง#

วงจรทำงานจริงวนอยู่กับสี่คำสั่ง: check → snapshot → preview → render (มี lint เป็นตัวเช็กเร็วระหว่างพิมพ์ — check รันมันซ้ำให้อยู่แล้ว ไม่ต้องเรียกเบิ้ล) เข้าใจวิธีอ่านรายงานของพวกมัน = ทำงานได้ทั้งวัน ทั้งตอนเขียนเองและตอนตรวจงานที่ AI เขียน

6.1 check — ด่านเดียวที่รันครบห้าหมวด

$ npx hyperframes check
Lint      ◇ 0 errors, 0 warnings
Runtime   ◇ 0 errors, 0 warnings
Layout    ◇ 0 issues across 9 sample(s)
Motion    ◇ 0 errors, 0 warnings
Contrast  ◇ 32/32 text checks pass WCAG AA
◇  Check passed

มันบูตเบราว์เซอร์หนึ่งรอบ แล้วกวาด seek ตามตาราง (ค่าเริ่มต้น 9 จุด) รันครบ: โค้ด error / เลย์เอาต์ (ตัวหนังสือล้นกรอบ ทับกัน) / เจตนา motion (บทที่ 5) / คอนทราสต์ WCAG ทุก finding มาพร้อม selector + เวลา + ข้อเสนอแก้ — อ่านจบแก้ได้เลยโดยไม่ต้องเปิดรูป

ความฉลาดที่ควรรู้: ความรุนแรงขึ้นกับความคงอยู่ ปัญหาที่เห็นแค่จุด seek เดียว (เช่นจังหวะกำลังเฟดเข้า) ถูกลดเป็น info ไม่กั้นทาง ที่ค้างหลายจุดถึงนับเป็น error และถ้าทั้งเรื่องไม่มีอะไรขยับเลย มันปฏิเสธทั้งวิดีโอ (sweep_static) — เฟรมนิ่งทำให้คำตัดสิน "ผ่าน" ทุกข้อเชื่อไม่ได้ เส้นใต้ที่ลากยาวทั้งเรื่องในการ์ดของเราเกิดมาเพื่อข้อนี้

6.2 snapshot — ตาเปล่าเห็น f(t)

npx hyperframes snapshot ถ่าย 5 เฟรมหลัก หรือ --at 2.5,7 เลือกเวลาเอง ได้ contact sheet ให้กวาดตาด้วย ใช้บ่อยที่สุดตอนไล่บั๊กเลย์เอาต์ที่เครื่องมองไม่เห็น (บทที่ 3–4 มีตัวอย่างจริงทั้งคู่)

6.3 preview — Studio สำหรับตามนุษย์

$ npx hyperframes preview --background
Studio    http://localhost:3002/?view=storyboard#project/handbook-lab

เปิด Studio ในเบราว์เซอร์: ลาก playhead ดู timeline จริง แก้ไฟล์แล้ว reload ให้อัตโนมัติ นี่คือหน้าจอที่คุณใช้รีวิวก่อนอนุมัติเรนเดอร์ — และเป็นจุดส่งงานให้คนอื่นดูด้วย

6.4 render — จ่ายเงินเมื่อพร้อม

npx hyperframes render --quality draft ไว้วนงานเร็ว (การ์ด 10 วิ ใช้ 30–60 วินาทีบนเครื่องที่เขียนเล่มนี้) ส่งจริงค่อย --quality high เสร็จแล้วเช็กไฟล์จริงเสมอ: มีอยู่ ไม่ว่าง ความยาวถูก (ffprobe หนึ่งบรรทัดจบ)

LEVEL 2 · ระดับกลาง

สื่อจริง: วิดีโอ เสียง และฟอนต์ไทย#

ได้เวลาไขปริศนาข้อสองจากบทที่ 2: ทำไมเพจถูกห้ามเล่น <video> / <audio> เอง

7.1 framework ยึดสิทธิ์เล่นสื่อ — และหลักฐานคาตา

ถ้าเพจกด .play() เอง เวลาของสื่อจะไหลตามโลกจริง — เฟรมที่ได้จากการถาม "วินาที 4.5" จะขึ้นกับว่าถามตอนไหน ไม่ใช่ขึ้นกับ 4.5 ดังนั้น framework จึง seek สื่อให้เองทุกเฟรม: เวลาในไฟล์สื่อ = เวลาของเรื่อง ลบ data-start ของ clip

ผมพิสูจน์เรื่องนี้ด้วยวิดีโอทดสอบที่มี timestamp ฝังในภาพ: วางไว้ที่ data-start="3" แล้วถ่ายเฟรมของเรื่องที่วินาที 4.5 — ในภาพเห็น timestamp ของไฟล์ต้นทางชี้ 00:00:01.500 พอดีเป๊ะ (4.5 − 3.0) ไม่มีการเล่น มีแต่การเปิดไปหน้าที่ถูกถาม

7.2 กติกาการวางสื่อ

<video id="broll" class="clip" src="assets/broll.mp4"
  data-start="3" data-duration="3.5" data-track-index="5" muted playsinline></video>

<audio id="chime" src="assets/chime.m4a"
  data-start="0.5" data-track-index="10" data-volume="0.8"></audio>
  • <video> ต้อง muted + playsinline เสมอ — เสียงเป็นหน้าที่ของ <audio> แยกต่างหาก แม้ใช้ไฟล์เดียวกัน
  • <audio> ไม่ใส่ data-duration = ใช้ความยาวจริงของไฟล์
  • data-volume คือระดับเสียงพื้นฐาน อยากเฟดให้ tween volume บน timeline
  • ห้ามเรียก .play() / .pause() / .currentTime เองเด็ดขาด — คุณเพิ่งอ่านเหตุผลไปใน 7.1

เรนเดอร์แล้วเสียงถูกรวม (mux) ลงไฟล์จริง (log ขึ้น hasAudio: true และ ffprobe เห็น stream aac) — ทั้งหมดนี้ทำงานที่ความลึกไหนของ DOM ก็ได้ รวมถึงใน sub-composition (บทที่ 8)

7.3 ฟอนต์ไทย — ด่านที่คนไทยเจอก่อนใคร

พิมพ์ชื่อฟอนต์เฉย ๆ (font-family: "Sarabun") โดยไม่มี @font-face ชี้ไฟล์ = lint แดงทันที:

✗ font_family_without_font_face: Font families used without @font-face declaration:
  sarabun, noto serif thai. ... Text will fall back to a generic font,
  producing incorrect typography in the video.

ทางแก้คือดาวน์โหลด .woff2 มาไว้ในโปรเจกต์แล้วประกาศ @font-face ชี้ไฟล์ local และมีของแปลกหนึ่งจุดที่ควรรู้ (v0.8.8): ประกาศไว้ในไฟล์ CSS แยกแล้ว link เข้ามา — lint เข้าใจ แต่ตัวตรวจตอนรัน (StaticGuard) มองไม่เห็นและเตือนหลอกทั้งที่ผลจริงถูกต้อง (ผมยันจากเฟรมใน MP4 แล้ว) ย้าย @font-face มา inline ใน style ของเพจแล้วเสียงเตือนหาย — แนะนำ inline ให้จบเรื่อง

สุดท้าย กติกาไทยแท้ ๆ หนึ่งข้อ: line-height ของข้อความไทยบนวิดีโอต้อง ≥ 1.3 ไม่งั้นสระบน-วรรณยุกต์ชนบรรทัดบนจนเครื่องตรวจเลย์เอาต์ฟ้อง

Sub-composition — ประกอบเรื่องจากชิ้นส่วน#

การ์ดใบเดียวอยู่ไฟล์เดียวได้สบาย แต่พองานโต — หลายฉาก หรืออยากใช้การ์ดปิดเดิมซ้ำในคลิปอื่นของทีม — คุณต้องแตกไฟล์ และการแตกไฟล์ใน HyperFrames มีกลไกขนส่งเฉพาะตัวที่ต้องเข้าใจก่อน ไม่งั้นเจอบั๊กที่ "ทุกด่านผ่านแต่ render พัง"

8.1 template คือตู้คอนเทนเนอร์ — ของนอกตู้ถูกทิ้งท่าเรือ

ตอน mount ไฟล์ลูก runtime ทำแค่นี้: fetch ไฟล์ → หา templateโคลนเฉพาะของข้างใน ไปวางในเรื่อง ทุกอย่างนอก template — รวมทั้ง head ทั้งหัว — ถูกทิ้ง

ผลคือกฎเหล็กข้อแรก: style และ script ของไฟล์ลูกต้องอยู่ใน template วางไว้ใน head ตามสัญชาตญาณ HTML ปกติ = สไตล์ทั้งชุดหายเงียบ อาการคือตัวหนังสือจิ๋วไม่ติดสไตล์กองมุมซ้ายบน — ด่าน static ทุกตัวผ่านหมดเพราะแต่ละไฟล์ถูกต้องในตัวเอง พังเฉพาะตอนประกอบ

compositions/outro.html<head> …ถูกทิ้ง<template>stylemarkupscriptของที่ render ต้องใช้ อยู่ในนี้ทั้งหมดโคลนเฉพาะในตู้index.htmlช่องเสียบ (host slot)data-composition-src=…เวลาในไฟล์ลูก = เวลาท้องถิ่นของ slotid บนช่องเสียบ=id บน root ไฟล์ลูก=กุญแจ __timelines
FIG 8.1 runtime โคลนเฉพาะของใน template — ทุกอย่างนอกนั้นรวมทั้ง head ถูกทิ้งตั้งแต่ท่าเรือ (สไตล์ที่เผลอวางใน head จึงหายเงียบทั้งชุด) · และชื่อ composition ต้องตรงกันสามจุด: ช่องเสียบ = root ของไฟล์ลูก = กุญแจ timeline

8.2 ของจริง: การ์ดปิดของเรา

ผมแตกการ์ดปิดออกเป็น compositions/outro.html โครงตามนี้ (ตัดให้สั้น):

<body>
  <template>
    <style>
      #root { position: absolute; inset: 0; }
      #outro-card-line { position: absolute; left: 200px; top: 920px; ... }
    </style>

    <div id="root" data-composition-id="outro-card" data-width="1920" data-height="1080">
      <div id="outro-card-line">เตรียมอัปเดตงานของตัวเอง คนละ 1 นาที แล้วเจอกันครับ</div>
    </div>

    <script>
      const outroTl = gsap.timeline({ paused: true });
      outroTl.fromTo("#outro-card-line", { autoAlpha: 0, y: 16 },
        { autoAlpha: 1, y: 0, duration: 0.8 }, 0.1);
      window.__timelines["outro-card"] = outroTl;
    </script>
  </template>
</body>

ฝั่งเรื่องหลัก clip ตัวเดิมกลายเป็น "ช่องเสียบ":

<div id="outro" data-composition-id="outro-card"
  data-composition-src="compositions/outro.html"
  data-start="6.5" data-duration="3.5" data-track-index="4"
  data-width="1920" data-height="1080"></div>

สังเกตสามอย่าง: (ก) id ต้องตรงกันสามจุด — บนช่องเสียบ บน root ของไฟล์ลูก และกุญแจ __timelines — เพราะชื่อนี้คือทางเดียวที่เครื่องใช้จับคู่ timeline อย่าตั้งชื่อช่องเสียบเป็น outro-mount เด็ดขาด (ข) เวลาใน timeline ลูกเป็นเวลาท้องถิ่น: 0.1 ของมันคือ 6.6 ของเรื่อง (ค) สไตล์ root ของไฟล์ลูกต้องเขียนผ่าน #root — ตอนประกอบ CSS ทุกกฎถูก scope ด้วย composition-id เป็น descendant selector คลาสที่แปะบน root เองจึงไม่มีวัน match แล้วกฎทั้งชุดหายเงียบ

8.3 ด่านตรวจของบทนี้: snapshot กลางฉาก

บั๊ก mount ทั้งตระกูลนี้ผ่าน static check ได้หมด ด่านเดียวที่จับคือถ่ายเฟรมกลางช่วงของ slot แล้วดูด้วยตา — ผมรัน snapshot --at 7.5 แล้วเห็นการ์ดปิดขึ้นครบถูกตำแหน่ง ฟอนต์มาเต็ม นั่นคือเสียงเขียวจริงของบทนี้ (check ก็ยังผ่านเต็มทุกหมวด รวม motion sidecar ที่ assert ทะลุเข้าไปหา element ในไฟล์ลูกได้ด้วย)

Motion ที่ดูเป็นงานอาชีพ#

ของที่แยกงานมือใหม่กับงานโปรไม่ใช่จำนวนเอฟเฟกต์ แต่คือวินัยไม่กี่ข้อที่ใช้ซ้ำทั้งเรื่อง บทนี้สรุปวินัยที่การ์ดของเราใช้จริง แล้วปิดด้วยเครื่องมือที่ช่วยให้ไม่ต้องประดิษฐ์ท่าเอง

9.1 วินัยที่การ์ดใบนี้ใช้

  • เข้านุ่ม ตั้งใจ: ทุกทางเข้าใช้ ease ตระกูล out (power2.out, power3.out) — พุ่งมาแล้วเบรกนิ่ม ตาคนอ่านว่า "ตั้งใจมา" ไม่ใช่ "หล่นมา" ระยะทางสั้น (16–40px) และ 0.5–0.8 วินาทีก็พอ
  • ทีละจังหวะ: ของหลายชิ้นเข้าพร้อมกัน = ตาไม่รู้จะดูอะไร แถวรายละเอียดสามแถวใช้ stagger: 0.25 — โครงเดียวกัน เหลื่อมเวลากัน สายตาถูกจูงทีละบรรทัด
  • อย่าปล่อยเฟรมตาย: จอนิ่งสนิทหลายวินาทีดูเหมือนวิดีโอค้าง (และ check ปฏิเสธจริงตาม sweep_static — บทที่ 6) การ์ดเราแก้ด้วยเส้นใต้เส้นเดียวที่ลากช้า ๆ ทั้งเรื่อง: ease: "none" ยาว 8.8 วินาที เคลื่อนไหวพอให้มีชีวิต เบาพอไม่แย่งซีน
  • จังหวะไทยสองข้อ (จ่ายราคามาแล้วจากโปรเจกต์จริง): หั่นประโยคไทยใส่ span หลายตัวเพื่อ animate ทีละคำ — ถ้า markup มีช่องว่างระหว่าง span จะเกิดวรรคปลอมกลางคำบนจอ ("สิบปี ที่...") ที่เครื่องตรวจไม่มีวันเห็น ต้องเขียน span ชิดกันและดู contact sheet เอง · ข้อความเข้าบนพื้นสี ใช้ทางเข้าแบบ binary (tl.set โผล่ทั้งก้อน) แทนการเฟด — ตัวตรวจ contrast ชอบจับเฟรมกลางเฟดที่สีจางพอดี แก้ทีเดียวได้ทั้งความถูกและจังหวะที่เฉียบกว่า

9.2 หาท่าจากคลังก่อนประดิษฐ์เอง

HyperFrames มีคลังท่าสำเร็จ ค้นด้วยภาษาอังกฤษ (คลังทำ index เป็นอังกฤษ — ถามไทยได้ผลว่างเปล่า) และถามด้วยผลลัพธ์ที่อยากได้ ไม่ใช่ชื่อกลไก:

$ npx hyperframes catalog --query "gentle continuous motion to keep a static card alive"
echo-trail        component   A moving element renders with N ghosted copies...
telemetry-hud     component   Quiet mono debug-HUD readouts frame a slotted subject...
chart-story       component   One chart builds from data in reading order...
...
162 items. Run "hyperframes add <name>" to install.

เจอที่ใช่ก็ติดตั้งเข้าโปรเจกต์: npx hyperframes add whip-pan-cut ได้ไฟล์มาที่ compositions/components/ พร้อมก๊อปลงเรื่อง ส่วนรอบที่ผมค้นจริงข้างบน — ผลลัพธ์ล้วนเป็นท่าโชว์ ขณะที่โจทย์ผมคือเส้นใต้เส้นเดียว หนึ่งบรรทัด tween เขียนเองถูกกว่า: ค้นก่อนเสมอ แต่กล้าตอบว่า "ไม่มีอะไรเรียบง่ายเท่าเขียนเอง"

LEVEL 3 · ระดับสูง

จากโจทย์ถึงสตอรีบอร์ด — ทำงานกับ AI#

งานวิดีโอจริงไม่ได้เริ่มที่โค้ด มันเริ่มที่เจตนา และเมื่อคนลงมือคือ AI ชั้นเจตนายิ่งต้องเขียนเป็นลายลักษณ์ — เพราะทุกอย่างที่ไม่ได้เขียน agent จะเดาแทน

10.1 BRIEF.md — สัญญาเจตนา

BRIEF คือไฟล์เดียวที่ตอบว่า ทำไปทำไม ให้ใครดู ยาวเท่าไหร่ ห้ามอะไร ตัวอย่างจริงจากคลิปที่ผม ship แล้ว (คลิปแนะนำตัวของ tayakorn.dev):

message: "สิบปีที่ไม่มีชั้นไหนหายไป — มีแต่ทับถมขึ้น"
destination: vertical-social
aspect: 1080x1920
length: 40s
audience: "ผู้อ่านสายเรียนลึกชาวไทย + คนที่กำลังประเมินตัวตนของทยากร"

และส่วนที่มีค่าที่สุดคือ Notes — ข้อห้ามที่ตัดสินใจแล้ว: "ห้ามสีเย็นทุกเฉด · ห้าม bounce/elastic · ตัวเลขทุกตัวต้องตรวจได้จริง ณ วันเรนเดอร์" กติกาพวกนี้เขียนครั้งเดียว คุมงานทุกเฟรมที่ agent เขียนต่อจากนั้น

10.2 STORYBOARD.md — แผนรายเฟรมที่เครื่องอ่านออก

ระดับถัดลงมาคือสตอรีบอร์ด: ไฟล์ markdown หนึ่งไฟล์ frontmatter คุมภาพรวม (format / message / arc / audience) แล้วหนึ่งเฟรมต่อหนึ่งหัว ## Frame N — ชื่อ พร้อม metadata เป็น bullet: status (outline → built → animated) · duration · src ชี้ไฟล์ฉากเมื่อสร้างแล้ว

มันไม่ใช่แค่เอกสารคน — เครื่อง parse มันจริง: ผมเขียนแผนสามเฟรมของการ์ดเวอร์ชันถัดไป แล้วถาม Studio ผ่าน API ได้ manifest กลับมาครบ (HTTP 200): สามเฟรม เฟรมสุดท้าย status: built, src: compositions/outro.html, srcExists: true — สถานะแผนกับของจริงบนดิสก์เชื่อมถึงกัน และ Studio เปิดหน้า contact sheet ของแผนนี้ให้ดูอัตโนมัติ (?view=storyboard)

10.3 บทบาทของคุณเมื่อ agent เป็นคนพิมพ์

วงจรที่ใช้ได้จริง: คุณเขียน BRIEF (เจตนา + ข้อห้าม) → agent ร่างสตอรีบอร์ด → คุณรีวิวที่ชั้นแผน (แก้ตรงนี้ถูกกว่าแก้ตอนโค้ดเสร็จหลายเท่า) → agent สร้างทีละฉาก → คุณตรวจรับด้วยเครื่องมือบทที่ 6: อ่านรายงาน check กวาด contact sheet และรีวิวใน Studio ก่อนอนุมัติ render — ทักษะทั้งเล่มนี้แหละคือทักษะตรวจรับ

กลไกภายใน: เครื่องถ่ายทีละเฟรม#

บทนี้เปิดฝากล่องเครื่องเรนเดอร์ แล้วปริศนาทุกข้อที่เล่มนี้ปลูกไว้จะถูกไขพร้อมกันด้วยเหตุผลเดียว

11.1 ไปป์ไลน์จริงจาก log

เรนเดอร์การ์ด 10 วินาทีหนึ่งรอบ log เล่าไปป์ไลน์ครบ (ตัดมาเฉพาะสาระ):

phase: capture   totalFrames: 300  workerCount: 4  captureMode: beginframe
phase: encode    (FFmpeg เย็บเฟรมเป็นวิดีโอ)
phase: assemble  hasAudio: true  (ผสมเสียง ปิดไฟล์)

จุดที่ควรหยุดอ่านคือ workerCount: 4 — เครื่องเปิดเบราว์เซอร์หลายตัวแบ่งกันถามคนละช่วงเฟรมพร้อมกัน เฟรม 200 อาจถูกถ่ายก่อนเฟรม 50 ไม่มีการ "เล่นจากต้นจนจบ" อยู่ในระบบเลยแม้แต่ขั้นตอนเดียว

เว็บเพจtimeline หยุดนิ่งตอบ f(t) อย่างเดียวworker 1 · เฟรม 1–75worker 2 · เฟรม 76–150worker 3 · เฟรม 151–225worker 4 · เฟรม 226–300ถามพร้อมกัน — ไม่เรียงลำดับcapture · 300 เฟรมencode · FFmpegassemble · +เสียง.mp4อะไรที่ตอบ t เดิมไม่เหมือนเดิม (random · นาฬิกา · network · input) พังตั้งแต่ชั้นนี้
FIG 11.1 ของจริงจาก log การเรนเดอร์: workerCount: 4 — สี่เบราว์เซอร์แบ่งกันถามคนละช่วงเฟรมพร้อมกัน เฟรม 200 จึงอาจถูกถ่ายก่อนเฟรม 50 · จากนั้นสามเฟสตามลำดับ captureencodeassemble (ผสมเสียง) · ไม่มี “เล่นจากต้นจนจบ” อยู่ในระบบแม้แต่ขั้นตอนเดียว — รากของกฎ determinism ทุกข้อ

11.2 ปริศนาทั้งเล่ม ไขด้วยประโยคเดียว

ทุกกฎของ HyperFrames คือผลของข้อเท็จจริงที่ว่า เฟรมถูกถามไม่เรียงลำดับและถามซ้ำได้:

  • ห้าม Math.random() / นาฬิกาจริง / fetch ตอนเรนเดอร์ — ค่าพวกนี้ตอบไม่เหมือนเดิมต่อคำถามเดิม (ปลูกไว้บทที่ 2)
  • ห้ามเพจเล่นสื่อเอง — เวลาไหลจริงผูกคำตอบกับ "ตอนไหนที่ถาม" (ไขแล้วบทที่ 7)
  • ห้าม animation วนไม่รู้จบ (repeat: -1) — ตำแหน่งในลูปนิยามจากเวลาไม่ได้ถ้าไม่รู้จำนวนรอบ ใช้จำนวนจำกัดที่คำนวณจากความยาวจริง (ปลูกไว้บทที่ 2)
  • timeline ต้อง paused และ fromTo ชนะ to — การ "จำสภาพตอนเริ่ม" ขึ้นกับลำดับการถาม (ไขแล้วบทที่ 5)
  • แม้แต่ hover / scroll ก็ต้องห้าม — เครื่องเรนเดอร์ไม่มีมือ ไม่มี input event ใด ๆ

11.3 พิสูจน์เชิงประจักษ์ — hash ไม่โกหก

ผมเติม Math.random() หนึ่งบรรทัดให้เส้นใต้เอียงแบบสุ่ม แล้วถ่ายเฟรมที่ t=5 สองรอบ จากนั้นถอดออกแล้วถ่ายซ้ำอีกสองรอบ เทียบ hash ระดับพิกเซล:

มี Math.random   run1: 98820fd7...   run2: 5490ad2c...   ← คนละภาพ
โค้ดสะอาด        run1: cdaa3183...   run2: cdaa3183...   ← เหมือนกันทุก byte

โค้ดสะอาดตอบ f(t) เดิมเป๊ะทุกครั้งจริง ๆ ไม่ใช่วาทกรรม — และหนึ่งบรรทัดสุ่มทำลายมันทันที ลองเองได้ในหนึ่งนาที: snapshot --at 5 สองรอบแล้วเทียบไฟล์

กฎทุกข้อที่ดู "จุกจิก" ของ framework นี้ คือราคาของสัญญาเดียว: ถามเฟรมไหน เมื่อไหร่ กี่รอบ ก็ได้ภาพเดิม

เรนเดอร์สเกลจริง & ส่งมอบ#

บทที่ 1 สัญญาไว้ว่า "คลิปโครงเดียวกันร้อยชื่อร้อยเวอร์ชัน" ได้เวลาจ่ายคืน — และนี่คือจุดที่วิดีโอแบบโค้ดทิ้งห่างโปรแกรมตัดต่อขาดลอย

12.1 เปิดช่องรับตัวแปร

ประกาศตัวแปรบน html (ชนิด + ค่า default) แล้วผูกเข้ากับ element ด้วย data-var-text:

<html data-composition-variables='[
  {"id":"team","type":"string","label":"ชื่อทีม","default":"ทีมการตลาด"},
  {"id":"room","type":"string","label":"สถานที่","default":"ห้องประชุม 2 ชั้น 5"}
]'>
...
<div id="team-chip" data-var-text="team">ทีมการตลาด</div>

ข้อความในไฟล์กลายเป็นค่า fallback ตัวแปรทุกตัวยังโผล่เป็น CSS custom property (var(--team)) ให้ฝั่งสไตล์ใช้ด้วย ระวังจุดสับสนเดียวของเรื่องนี้: ตอนประกาศเป็น array ของ schema แต่ตอนส่งค่าเป็น object ธรรมดา ({"team": "ทีมบัญชี"})

12.2 rows.json → ฝูงวิดีโอ

{
  "rows": [
    { "name": "marketing", "team": "ทีมการตลาด", "room": "ห้องประชุม 2 ชั้น 5" },
    { "name": "accounting", "team": "ทีมบัญชี", "room": "ห้องประชุม 1 ชั้น 3" },
    { "name": "it", "team": "ทีมไอที", "room": "ออนไลน์ (Teams)" }
  ]
}
$ npx hyperframes render --batch rows.json --output "renders/{name}.mp4" --quality draft
◇  renders\it.mp4   744.4 KB · 10.0s video · rendered in 30.0s
Batch complete: 3 completed.
Manifest: renders\manifest.json

สามทีม สามไฟล์ หนึ่งคำสั่ง — ผมเปิดเฟรมจาก accounting.mp4 ดู: ชิปขึ้น "ทีมบัญชี" ห้องขึ้น "ห้องประชุม 1 ชั้น 3" ครบถ้วน ในโปรแกรมตัดต่อ งานนี้คือเปิดโปรเจกต์แก้มือสามรอบ ที่นี่คือเพิ่มบรรทัดใน JSON แถมได้ manifest.json สรุปผลทุกไฟล์ไว้ตรวจย้อน

12.3 นิสัยส่งมอบ

วนงานด้วย --quality draft (เร็วกว่า high หลายเท่า) ส่งจริงค่อย --quality high แล้วปิดจ๊อบด้วยการยันไฟล์เสมอ — สามคำถาม: ไฟล์มีจริง ขนาดไม่ใช่ศูนย์ ความยาวตรงตั้งใจ:

$ ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1 renders/accounting.mp4
duration=10.000000

ส่งท้าย — สิ่งที่จะอยู่นานกว่าเวอร์ชัน#

เล่มนี้ทดสอบกับ hyperframes v0.8.8 ที่ยังเดินเร็ว — ชื่อคำสั่งอาจขยับ ด่านตรวจจะหนาขึ้น (กติกาบางข้อที่วันนี้เครื่องยังไม่จับ พรุ่งนี้อาจจับ) แต่สังเกตไหมว่าเนื้อแท้ของทุกบทกลับไม่ได้ผูกกับเวอร์ชันเลย: เฟรมคือฟังก์ชันของเวลา · เวลาถูกประกาศ ไม่ใช่ถูกสั่ง · เครื่องตรวจเห็นไม่ครบ ตาและ contract คือด่านสุดท้าย · เจตนาที่เขียนเป็นลายลักษณ์คุมงานได้ไกลกว่าคำสั่งปากเปล่า

หลักพวกนี้ไม่ใช่ของ HyperFrames ด้วยซ้ำ — มันคือหลักของการทำวิดีโอแบบวิศวกร และตอนนี้มันอยู่ในมือคุณครบทั้งวง: เขียน composition จากศูนย์ อ่านรายงานเครื่องออก ไขบั๊กจากกลไกไม่ใช่จากการเดา และเรนเดอร์งานจริงส่งมอบได้ทั้งใบเดียวและทั้งฝูง

อ่านต่อ: Harness Engineering#

บทที่ 10 เปิดประตูไว้แล้ว: งานวิดีโอสมัยนี้คือการกำกับ agent — เขียนเจตนา วางข้อห้าม ตรวจรับผลงาน ถ้าอยากเข้าใจกลไกฝั่งนั้นให้ลึกเท่าที่เล่มนี้พาลงลึกฝั่งวิดีโอ ต่อด้วย Harness Engineering Handbook: Agent = Model + Harness ผ่ากลไก agent loop, tools, memory จนถึงผ่า Claude Code ทั้งตัว

ถ้าเนื้อหานี้มีประโยชน์ —เลี้ยงกาแฟสักแก้ว