วิธีอ่านเอกสาร
เอกสารนี้รวม Design Spec, Component Inventory, Page Inventory และ Reference Audit เป็นฉบับเดียว ทุกค่าติดป้ายบอกที่มา เพื่อแยกสิ่งที่วัดจริงออกจากข้อเสนอ
ข้อมูลมาจากการตรวจเว็บ reference เมื่อ 2026-10-12 ที่ 3 ความกว้าง (1440, 820, 390) ด้วย REA 6.4.0 บน Chrome 155 headless ไม่ได้แตะ source code ของแอปใดเลย
Note ชื่อแบรนด์ สี ภาพ และข้อความของ reference เป็นของเจ้าของเว็บ เอกสารนี้ใช้เรียนรู้รูปแบบ UX เท่านั้น งานที่สร้างต้องใช้ชื่อ สี โลโก้ ภาพ และ copy ของตัวเอง
| ป้าย |
ความหมาย |
| O |
Observed: วัดจาก DOM / computed style ของ reference |
| I |
Inferred: สรุปจากโครงสร้างหรือ class |
| R |
Recommended: ข้อเสนอของเราสำหรับงานใหม่ ไม่ใช่ค่าของ reference |
| U |
Unverified: ยังไม่ได้ยืนยัน ต้องตรวจด้วยมือ (ดูส่วนท้ายเอกสาร) |
| Add |
ข้อกำหนดที่ reference ไม่มีหรือไม่ครบ ต้องเพิ่มในงานใหม่ |
- ชื่อ component เป็นชื่อที่เราตั้ง ไม่ใช่ชื่อใน source ของ reference
- ค่าสีของ reference ในเอกสารนี้เป็นตัวอย่างโครงสร้าง token เท่านั้น ให้แทนที่ด้วย palette ของแบรนด์ใหม่ โดยคง role ของแต่ละ token ไว้
ภาพรวมสั้น
สิ่งที่ควรจำถ้าอ่านแค่ส่วนนี้
| หัวข้อ |
สรุป |
| Visual language |
ขอบ 2px + hard shadow + ปุ่ม pill + card มุม 16px |
| Shell |
sidebar 288px (>= 1024px) หรือ header + tab bar + bottom sheet (< 1024px) |
| Token |
semantic token + 3 ธีม x light/dark ผ่าน data-theme และ .dark |
| Component |
37 ชิ้นใน 5 ชั้น แกนที่ใช้ซ้ำคือ Button, Card (3 variant), NavLink, Badge |
| ลอกได้ |
โครง shell, bottom sheet, tab bar, ลำดับ block ของ hero และหน้าคอร์ส |
| ห้ามลอก / ต้องแก้ |
contrast ปุ่มส้ม 2.8:1, ไม่มี skip link, tap 24-28px, grid 205px |
| ประเภทเว็บ |
ขายคอร์สสอน AI แบบนัดสด 1:1 + คลังบทความ/คลิป/ebook + ระบบสนับสนุนเงิน |
| Stack ที่เห็นจาก bundle |
Next.js (Turbopack), React, Tailwind v4, host บน Vercel |
| หน้า |
25 route สาธารณะ จัดเป็น template 8 แบบ (T1-T8) |
| ความครบของการตรวจ |
หน้าแรก, list/detail, auth, 404 ครบ 3 viewport. หน้าอื่นตรวจระดับ list |
| ความเชื่อมั่น |
token, breakpoint, ขนาด: สูง. focus, keyboard, loading/error: ยังไม่ตรวจ |
ลำดับเอกสาร
| ส่วน |
เนื้อหา |
ใช้ตอน |
| โครงหน้าจอ |
shell, breakpoint, ตำแหน่งจริงของหน้าแรก |
วางโครงหน้า |
| หน้าและ template |
route 25 หน้า, template T1-T8, งาน backend |
วางแผนหน้าและข้อมูล |
| Design token |
สี ธีม ตัวอักษร ระยะ ขอบ เงา |
ตั้งค่า CSS variable |
| Component |
5 ชั้น 37 ชิ้น พร้อมขนาด variant state |
สร้างทีละชิ้น |
| Interaction และ state |
hover, active, loading, empty, error, motion |
ออกแบบพฤติกรรม |
| Accessibility |
contrast ที่คำนวณ และข้อกำหนด WCAG 2.2 AA |
ตรวจก่อนส่งงาน |
| Implement |
stack ที่แนะนำ |
เริ่มลงมือ |
| ข้อมูลทางเทคนิคและข้อจำกัด |
วิธีตรวจ, bundle, สมมติฐาน, สิ่งที่ยังไม่ยืนยัน |
ประเมินความน่าเชื่อถือ |
โครงหน้าจอ
Shell สองแบบ
Shell เปลี่ยนที่ 1024px ฝั่ง desktop ใช้ sidebar ฝั่ง mobile ใช้ header บนกับ tab bar ล่าง
| ช่วงกว้าง |
โครง |
| lg (>= 1024px) |
aside ซ้าย 288px sticky สูง 100vh เลื่อนในตัวเอง + main ที่เหลือ |
| < 1024px |
header sticky สูง 58px + main + tab bar ลอยล่าง + bottom sheet |
Desktop (>= 1024px) Mobile (< 1024px)
+----------+----------------------+ +------------------------+
| Sidebar | | | MobileHeader (58px) |
| 288px | main | +------------------------+
| sticky | | | |
| scroll | | | main |
| ในตัว | | | |
| Footer | | | [ BottomTabBar 72px ] |
+----------+----------------------+ +------------------------+
+ BottomSheet (เปิดจาก "เมนู")
Breakpoint และ responsive
Breakpoint ที่วัดจริงด้วย viewport หลายความกว้าง (O) ตรงกับ breakpoint ของ Tailwind
| Viewport |
Sidebar |
Header บน |
Tab bar ล่าง |
Grid คอร์ส |
| 1024 |
แสดง |
ซ่อน |
ซ่อน |
3 คอลัมน์ (การ์ดกว้าง 205px) |
| 1023 |
ซ่อน |
แสดง |
แสดง |
2 คอลัมน์ |
| 768 |
ซ่อน |
แสดง |
แสดง |
2 คอลัมน์ |
| 640 |
ซ่อน |
แสดง |
แสดง |
2 คอลัมน์ |
| 639 |
ซ่อน |
แสดง |
แสดง |
1 คอลัมน์ |
Media / container query ทั้งหมดที่พบใน CSS: min-width 40rem, 48rem, 64rem, 80rem, 96rem, max-width: 600px, container query 42rem และ 80rem, hover: hover, hover: none and pointer: coarse, prefers-reduced-motion (3 รูปแบบ), print
ค่าของเนื้อหาในแต่ละช่วง
| ส่วน |
< 640 |
640 - 1023 |
>= 1024 |
| Grid คอร์ส |
1 คอลัมน์ |
2 |
3 |
| Hero |
คอลัมน์เดียว, H1 30px |
คอลัมน์เดียว, H1 48px |
2 คอลัมน์ (ข้อความ + price card) |
| Stat tiles |
3 คอลัมน์, ตัวเลข 20px |
3 |
3, กว้างสูงสุด 672px |
| Page padding |
16px |
24px |
32px |
Warning ที่ viewport 1024px แสดง 3 คอลัมน์ทั้งที่ sidebar กิน 288px การ์ดเหลือ 205px กว้างเกินไปสำหรับข้อความไทย งานใหม่ควรเปลี่ยนเป็น 3 คอลัมน์เมื่อ >= 1280px (container query ของเนื้อหา)
ลำดับสายตาของหน้าแรก
ลำดับจากบนลงล่าง (O): hero 2 คอลัมน์ (headline + stat, price card) -> แถบสนับสนุนพื้นรอง -> การ์ดความน่าเชื่อถือ 3 ใบ -> หัวข้อ "คอร์ส" พร้อมลิงก์ดูทั้งหมด -> grid คอร์ส 9 ใบ
| ระดับ |
เทคนิคที่ใช้ |
| 1 (เด่นสุด) |
H1 หนา 900 ขนาดใหญ่ + คำสำคัญสีหลัก, price card ลอยด้วย hard shadow |
| 2 |
CTA แบบ pill 2 แบบ (secondary ขาวขอบดำ, primary สีหลัก) |
| 3 |
stat tile กรอบเรียบ (ไม่มีเงา) ต่างจาก card ที่มีเงา |
| 4 |
การ์ดคอร์ส: ภาพ 96px, ชื่อ 18px, คำอธิบายตัด 3 บรรทัด, ราคา + ลิงก์ |
| พื้นหลัง |
dot-grid, วงกลม/สี่เหลี่ยมมนตกแต่ง (aria-hidden), แถบเฉียงเหนือแถบสนับสนุน |
ตำแหน่งจริงของหน้าแรก desktop 1440px
| ส่วน |
x, y, กว้าง x สูง (O) |
main |
x 288, กว้าง 1137 (เหลือ scrollbar 15px) |
| Hero section |
y 0, 1137 x 648, ขอบล่าง 2px |
| Hero H1 |
x 320, y 127, 673 x 152 (2 บรรทัด) |
| Price card |
x 1033, y 85, 360 x 339 |
| แถบสนับสนุน |
y 648, 1137 x 85 |
| ส่วนการ์ดความน่าเชื่อถือ |
y 733, สูง 304, การ์ด 344 x 192 |
| Grid คอร์ส |
x 320, กว้าง 1073, การ์ด 344 x 291, ภาพปก 340 x 96 |
หน้าและ template
รายการ route จาก sitemap, เมนู และการเปิดทดสอบ 25 หน้าสาธารณะ จัดเป็น template 8 แบบ พร้อมระบุว่าอะไรต้องใช้ backend และอะไรทำฝั่ง frontend ได้
ขอบเขตและความมั่นใจ
| รายการ |
ค่า |
| วิธีได้ route |
sitemap.xml, ลิงก์ใน DOM, string ใน bundle, เปิดจริงด้วย GET |
| หน้าที่เปิดสำเร็จ (HTTP 200) |
25 (หน้าแรก + 24 หน้า) |
| หน้าที่ถ่าย screenshot 3 viewport |
หน้าแรก, courses (list+detail), tips, guides, login, register, community, 404 |
| หน้าที่ดูแค่ HTML ฝั่ง server (curl) |
ที่เหลือ: จัดกลุ่ม template จากจำนวน heading และลิงก์ ไม่ได้ดูภาพ |
Path ที่ robots.txt ห้าม (ไม่ได้เปิดดู) |
admin, api, account, schedule, my-courses, enrollments, learn, teach ฯลฯ |
Note template ของหน้าที่ดูแค่ HTML เป็น I (สรุปจากโครงสร้าง) ให้เปิดดูจริงก่อนใช้เป็นแบบ
Route ทั้งหมด
คอลัมน์ Template อ้างรหัส T1-T8 จากหัวข้อ Template ที่ใช้ซ้ำ
เมนู "เรียน"
| Route |
H1 (ย่อ) |
Template |
Auth |
ข้อมูลจาก |
/courses |
เรียนสดตัวต่อตัว เลือกวันเวลาเอง |
T2 catalog |
ไม่ต้อง |
ฐานข้อมูลคอร์ส |
/courses/[slug] |
ชื่อคอร์ส |
T3 course detail |
ดูได้ จองต้อง login |
คอร์ส + ตารางว่าง |
/group-courses |
คอร์สสอนแบบกลุ่ม |
T5 marketing |
ไม่ต้อง |
เนื้อหา static |
/workshop |
AI ใช้จริง เปลี่ยนงานให้เป็นเรื่องง่าย |
T5 marketing |
ไม่ต้อง |
เนื้อหา static |
เมนู "อ่านและดู"
| Route |
H1 (ย่อ) |
Template |
Auth |
ข้อมูลจาก |
/tips |
เคล็ดลับที่ dev ใช้จริง |
T2 catalog (แบ่งหน้า) |
ไม่ต้อง |
บทความ |
/tips/[slug] |
ชื่อบทความ |
T2b article (U) |
ไม่ต้อง |
บทความ |
/guides |
สอนใช้เครื่องมือ AI ตั้งแต่ยังไม่เคยแตะ |
T2 catalog จัดกลุ่มเป็น series |
ไม่ต้อง |
บทความ |
/guides/[slug] |
ชื่อคู่มือ |
T2b article (U) |
ไม่ต้อง |
บทความ |
/watch |
เปิดคลิปไว้ข้าง ๆ แล้วทำตามไปพร้อมกัน |
T2 catalog วิดีโอ |
ไม่ต้อง |
รายการวิดีโอ |
/shorts |
เรื่องเดียวจบใน 1 นาที |
T2 catalog วิดีโอสั้น |
ไม่ต้อง |
รายการวิดีโอ |
/ebooks |
จ่ายครั้งเดียว ได้ของที่ยังโตต่อไปเรื่อย ๆ |
T2 catalog สินค้า |
ซื้อต้อง login (I) |
สินค้า |
/feed |
ทุกโพสต์บนเพจ เก็บไว้ที่นี่แบบเต็ม |
T2 catalog โพสต์ |
ไม่ต้อง |
โพสต์ |
/stories |
คลังคลิปเรื่องเล่า |
T2 catalog วิดีโอ |
ไม่ต้อง |
วิดีโอ |
เมนู "ลงมือ"
| Route |
H1 (ย่อ) |
Template |
Auth |
ข้อมูลจาก |
/projects |
โปรเจคพร้อมโค้ด |
T2 catalog (การ์ด 7 ใบ) |
ไม่ต้อง |
รายการโปรเจค |
/prompt-lab |
คลิป AI ไวรัล พร้อมพรอมต์ |
T2 catalog |
ไม่ต้อง |
รายการ |
/one-prompt |
หัวข้อภาษาอังกฤษแบบ landing |
T5 marketing + ตัวอย่าง |
ไม่ต้อง |
รายการ |
/playground |
เดโมลองเล่น |
T2 catalog เดโม 14 รายการ |
ไม่ต้อง |
รายการเดโม |
/behind-the-pixels |
หลังฉากภาพ AI |
T2 gallery (ไม่มี heading ย่อย) |
ไม่ต้อง |
ภาพ (ไฟล์ HTML 589KB) |
เมนู "อื่น ๆ" และหน้าระบบ
| Route |
H1 (ย่อ) |
Template |
Auth |
ข้อมูลจาก |
/community |
ถาม-ตอบ |
T7 community list |
อ่านได้ โพสต์ต้อง login |
โพสต์ + ไลก์ |
/community/[id] |
หัวข้อกระทู้ |
T7b thread (U) |
อ่านได้ |
โพสต์ (UUID) |
/shop |
ของที่ dev คัดมาแนะนำ |
T2 catalog สินค้าแนะนำ |
ไม่ต้อง |
รายการสินค้า |
/supporters |
ผู้สนับสนุน |
T5 marketing + รายชื่อ |
ไม่ต้อง |
รายชื่อผู้สนับสนุน |
/promise |
จ่ายแล้วได้อะไรกลับไป |
T6 prose (7 หัวข้อ) |
ไม่ต้อง |
เนื้อหา static |
/terms |
ข้อกำหนดการใช้งาน |
T6 prose |
ไม่ต้อง |
เนื้อหา static |
/privacy |
นโยบายความเป็นส่วนตัว |
T6 prose |
ไม่ต้อง |
เนื้อหา static |
/login |
เข้าสู่ระบบ |
T4 auth |
ไม่ต้อง |
- |
/register |
สมัครสมาชิก |
T4 auth |
ไม่ต้อง |
- |
| path ที่ไม่มี |
404 |
T8 not found |
- |
- |
Query และ hash ที่พบ
| รูปแบบ |
ใช้ทำอะไร |
/login?next=%2Fschedule |
กลับหน้าเดิมหลัง login (ค่าที่เห็นคือ /schedule) |
/register?next=... |
เหมือนกัน |
/community?course=[slug] |
เปิดบอร์ดถาม-ตอบกรองตามคอร์ส (ลิงก์จากหน้าคอร์ส) |
/courses/[slug]#enroll |
เลื่อนไปการ์ดจอง |
/supporters#support |
เลื่อนไปฟอร์มสนับสนุน |
/#courses |
เลื่อนไปส่วนคอร์สบนหน้าแรก |
/tips?page=N |
แบ่งหน้า (พบลิงก์ /tips?page=2 ใน HTML) |
ลำดับชั้นของหน้า
/ (home)
|-- เรียน
| |-- /courses --> /courses/[slug] --> (จอง, ต้อง login) --> /schedule
| |-- /group-courses
| `-- /workshop
|-- อ่านและดู
| |-- /tips --> /tips/[slug]
| |-- /guides --> /guides/[slug]
| |-- /watch, /shorts, /stories
| |-- /ebooks
| `-- /feed
|-- ลงมือ
| |-- /projects, /prompt-lab, /one-prompt
| |-- /playground
| `-- /behind-the-pixels
|-- อื่น ๆ
| |-- /community --> /community/[id]
| |-- /shop
| `-- /supporters
|-- ข้อมูลและกฎ: /promise, /terms, /privacy
`-- บัญชี: /login, /register
Template ที่ใช้ซ้ำ
| ID |
ชื่อ |
หน้าที่ใช้ |
โครงหลัก |
| T1 |
Home / landing |
/ |
hero 2 คอลัมน์, แถบสนับสนุน, การ์ดความน่าเชื่อถือ, grid คอร์ส |
| T2 |
Catalog |
courses, tips, guides, watch, shorts, feed, shop ฯลฯ (12 หน้า) |
H1 + lead, grid การ์ด, แบ่งหน้า, บล็อกสินค้า, บล็อกสนับสนุน |
| T3 |
Course detail |
/courses/[slug] |
ลิงก์ย้อนกลับ, hero + price, วิดีโอ, การ์ดจอง, เนื้อหา, FAQ |
| T4 |
Auth |
/login, /register |
คอลัมน์กลาง, ฟอร์ม, ปุ่ม OAuth, ลิงก์สลับหน้า |
| T5 |
Marketing |
/group-courses, /workshop, /one-prompt, /supporters |
หัวข้อใหญ่, section สลับ, CTA |
| T6 |
Prose / legal |
/promise, /terms, /privacy |
H1 + H2 หลายหัวข้อ ข้อความยาว |
| T7 |
Community |
/community |
H1, รายการกระทู้, ปุ่มถูกใจ, ลิงก์ login/สมัคร |
| T8 |
Not found |
path ที่ไม่มี |
ค่า default ของ framework (ข้อความอังกฤษ) ใน shell |
โครง T1 Home (O)
| ลำดับ |
Block |
หมายเหตุ |
| 1 |
Hero (ซ้าย: badge, eyebrow, H1, lead, 2 CTA, 3 stat; ขวา: price card) |
สูง 648px desktop |
| 2 |
แถบสนับสนุนพื้นรอง + แถบเฉียงด้านบน |
2 ปุ่มเล็ก |
| 3 |
การ์ดความน่าเชื่อถือ 3 ใบ (ไอคอน, หัวข้อ, ข้อความ) |
2 ใน 3 ใบเป็นลิงก์ |
| 4 |
หัวข้อ section + ลิงก์ดูทั้งหมด |
- |
| 5 |
Grid การ์ดคอร์ส 9 ใบ |
1/2/3 คอลัมน์ |
| 6 |
Footer (mobile) หรือท้าย sidebar (desktop) |
ลิงก์นโยบาย + เวอร์ชัน |
โครง T2 Catalog (O, ตัวอย่าง /tips)
| ลำดับ |
Block |
หมายเหตุ |
| 1 |
H1 + lead + badge หมวดที่ active |
- |
| 2 |
Grid การ์ดบทความ 24 ใบ/หน้า (3 คอลัมน์ desktop) |
ภาพปก, tag, ชื่อ, สรุป, เวลาอ่าน, จำนวนอ่าน |
| 3 |
แถบแบ่งหน้า: ก่อนหน้า, "หน้า 1 จาก 2", ถัดไป |
ปุ่มก่อนหน้าดูจางในหน้าแรก (U ว่า disabled จริง) |
| 4 |
บล็อกสินค้าแนะนำ 3 ใบ |
ภาพสินค้า, ราคา, ปุ่มไปร้านค้า |
| 5 |
บล็อกสนับสนุน: จำนวนเงิน, ชื่อ, QR, ลิงก์ |
ซ้ำกับ /courses, /guides |
โครง T3 Course detail (O, ตัวอย่างคอร์สหนึ่ง)
| ลำดับ |
Block |
| 1 |
ลิงก์ย้อนกลับ + ปุ่มแชร์ |
| 2 |
Badge, H1, lead, ราคา, CTA เลื่อนไปจอง; ขวา: การ์ดภาพ + ข้อดี 3 ข้อ |
| 3 |
วิดีโอตัวอย่าง (ภาพ thumbnail + ปุ่มเล่น) + ไทล์จุดขาย 3 ใบ |
| 4 |
Chip สรุป (ราคา, รูปแบบ, ระยะเวลา, ระดับ) |
| 5 |
การ์ดจอง: แพ็กเกจ, หัวข้อ (combobox), ราคา, วัน, เวลา, โค้ดส่วนลด, ปุ่มยืนยัน |
| 6 |
การ์ดรับประกัน + ลิงก์ไปหน้าเงื่อนไข |
| 7 |
ผลลัพธ์หลังเรียน (2 คอลัมน์), รายละเอียด, สิ่งที่จะได้เรียน 6 ข้อ |
| 8 |
เหมาะกับใคร / ในคอร์สมีอะไร (2 คอลัมน์), ผู้สอน, FAQ |
| 9 |
รายการหัวข้อ 30 ข้อ (2 คอลัมน์) + ลิงก์ไปถาม-ตอบของคอร์ส |
โครง T4 Auth
| Block |
ค่า (O) |
| คอลัมน์ |
กลาง กว้างราว 704px (x 512-1216 ที่ 1440px) |
| H1 |
ชิดซ้ายในคอลัมน์ |
| ฟอร์ม |
card ขอบหนา, label ตัวหนา 14px, input สูง 48px |
| ปุ่มหลัก |
เต็มความกว้าง pill สีหลัก |
| ตัวคั่น |
เส้นพร้อมคำว่า "หรือ" |
| OAuth |
ปุ่มขาว pill พร้อมโลโก้ Google |
| ลิงก์สลับ |
ท้ายฟอร์ม พก next |
| field |
login: อีเมล + รหัสผ่าน. สมัคร: ชื่อ + อีเมล + รหัสผ่าน |
ต้องใช้ backend หรือ frontend ล้วน
| ความสามารถ |
ฝั่ง |
เหตุผล |
| Shell, เมนู, sheet, สลับธีม, dark mode |
Frontend |
เก็บ localStorage |
| Cookie consent |
Frontend (+ backend ถ้าต้องบันทึกหลักฐาน) |
consent-storage ในเบราว์เซอร์ |
| แสดงหน้า catalog / บทความ |
Frontend + ไฟล์/CMS |
อ่านอย่างเดียว render ฝั่ง server ได้ |
| ค้นหา |
Backend |
พบ /api/search, ต้อง index เนื้อหา |
| สมัคร / เข้าสู่ระบบ / OAuth Google |
Backend |
พบ /api/auth |
| จองคาบเรียน (วัน/เวลา/โค้ด) |
Backend |
ต้องเช็คเวลาว่าง, โค้ดส่วนลด, สร้างรายการจอง |
| ชำระเงิน |
Backend + ผู้ให้บริการ |
U: ไม่เห็น flow ชำระเงินเพราะต้อง login |
| โพสต์ / ไลก์ใน community |
Backend |
ต้องมีบัญชีและตรวจสิทธิ์ |
| ระบบสนับสนุนเงิน, รายชื่อผู้สนับสนุน |
Backend |
บันทึกยอด และอนุมัติรายชื่อ |
| ตัวนับผู้เรียน / จำนวนอ่าน |
Backend |
ตัวเลขเปลี่ยนตามเวลา |
หน้าหลัง login (/schedule, /my-courses, /learn) |
Backend |
ต้องมีบัญชี ห้ามสำรวจตาม robots |
| Share, ลิงก์ภายนอก (ร้านค้า) |
Frontend |
แค่ลิงก์ |
หน้าที่ต้องเปิดดูเพิ่มก่อนใช้เป็นแบบ
| หน้า |
เหตุผล |
วิธีตรวจ |
/tips/[slug], /guides/[slug] |
ยังไม่รู้ layout บทความ (TOC, โค้ดบล็อก, ปุ่มแชร์) |
เปิดจริง 1 หน้า |
/community/[id] |
ยังไม่รู้ layout กระทู้และกล่องตอบ |
เปิดจริง 1 หน้า |
/watch, /shorts, /stories |
ไม่รู้ player / modal วิดีโอ |
เปิดและกดเล่น |
/ebooks |
ไม่รู้ flow ซื้อ |
ดูหน้า ไม่กดซื้อ |
| ค้นหา |
ไม่ได้ทดสอบผลลัพธ์ / ว่าง / error |
พิมพ์ 2 ตัวอักษรและจับ network |
Design token
โครง token แบบ semantic
Reference ใช้ CSS variable บน html[data-theme] และ class .dark ซ้อนกัน 3 ธีม x 2 โหมด
| Token |
Role |
ตัวอย่าง O (Bold, light) |
--background |
พื้นหน้าเว็บ |
#fefce8 |
--foreground |
ข้อความหลัก |
#1c1917 |
--card |
พื้น card / dialog / input |
#ffffff |
--muted |
ข้อความรอง |
#78716c |
--muted-surface |
พื้นแถบ / chip รอง |
#fff7ed |
--border |
เส้นขอบ 2px ทุกจุด |
#15161c |
--ring |
focus ring |
#fb923c |
--brand-50..900 |
สเกลสีหลัก 10 ขั้น |
500 = #f97316, 700 = #c2410c |
--accent-100..800 |
สีเน้นรอง |
500 = #f43f5e |
--on-brand |
ตัวอักษรบนสีหลัก |
#ffffff |
--success/warning/error/info และ -surface |
สถานะ |
success #15803d บน #dcfce7 |
--code-surface / --code-foreground |
บล็อกโค้ด |
#0d1117 / #e6edf3 |
ค่าของแต่ละธีม
| ธีม |
Brand 500 |
Background light |
Background dark |
Border dark |
| Bold |
#f97316 |
#fefce8 |
#0f0a05 |
#78350f |
| Ocean |
#0891b2 |
#f3fbfd |
#06171c |
#204551 |
| Grape |
#7c3aed |
#faf6ff |
#120b1f |
#3a2c5c |
- ใน dark mode สเกล
brand-50..300 กลายเป็นสีมืดและ brand-700..900 กลายเป็นสีสว่าง (สลับด้านของสเกล) เพื่อให้ class เดิมใช้ได้ทั้งสองโหมด ตรงนี้เป็นวิธีที่ควรเลียนแบบ
- พบกฎ
html[data-theme] อีกชุดที่บังคับเป็นสีกระดาษ (--doc-paper #fff, --doc-ink #16181d) ข้อสรุป (I): เป็น stylesheet สำหรับ print เพราะพบ @media print ใน CSS
ตัวอักษร
Font (O): Noto Sans Thai (body) และ Exo 2 600/700 (wordmark, label กลุ่ม)
| Role |
ขนาด / line-height |
น้ำหนัก |
Tracking |
| Hero H1 (sm ขึ้นไป) |
48px / 60px |
900 |
-1.2px |
| Hero H1 (mobile) |
30px / 37.5px |
900 |
-0.75px |
| Eyebrow |
16px (mobile 14px) |
800, uppercase |
0.1em |
| Section H2 |
30px / 36px |
900 |
-0.75px |
| Card title H3 |
18px / 24.75px |
800 |
ปกติ |
| Lead |
18px / 29.25px |
400 |
ปกติ |
| Body |
16px / 24px |
400 |
ปกติ |
| Small / nav / card desc |
14px / 20px |
400-600 |
ปกติ |
| Caption / stat label |
12px / 16.5px |
400-700 |
ปกติ |
| Nav group label |
10.88px / 16.3px |
700, uppercase |
0.025em |
| Stat number |
24px (mobile 20px) |
900 |
ปกติ |
ราคาและตัวเลข stat ใช้น้ำหนัก 900 กับสีหลักหรือสี foreground
ระยะ ขอบ มุม เงา
ฐาน spacing 4px (--spacing: .25rem) ทุกค่าด้านล่างเป็น O
| รายการ |
ค่า |
| Page padding แนวนอน |
16px (mobile), 24px (sm), 32px (lg) |
| Section padding แนวตั้ง |
56px |
| Hero padding แนวตั้ง |
56px (mobile), 80px (sm ขึ้นไป) |
| Gap grid การ์ด |
20px |
| Gap stat tile |
12px |
| Padding card หลัก |
24px (price card), 20px (เนื้อหาใน course card) |
| Border ทั่วไป |
2px solid --border |
| Radius card |
16px |
| Radius input / ลิงก์ในเมนู |
12px |
| Radius ปุ่ม / badge / chip |
9999px (pill) |
| Radius tab bar ล่าง |
26px |
| Radius bottom sheet (บน) |
24px |
| Shadow |
ค่า (O) |
| Card |
4px 4px 0 0 สี --border (hard shadow ไม่เบลอ) |
| Card hover |
6px 6px 0 0 และ translate(-2px,-2px) |
| ปุ่ม |
3px 3px 0 0 สี --border |
| Tab bar ล่าง |
Tailwind shadow-lg |
| Bottom sheet |
0 25px 50px -12px rgba(0,0,0,.25) |
| Header บน (mobile) |
ไม่มีเงา ใช้ border-bottom: 2px และ backdrop-filter: blur(8px) |
Recommendation Hard shadow + ขอบหนา คือ "ลายเซ็น" ของ reference ถ้าต้องการให้คนไม่เห็นว่าเลียนแบบ ให้เปลี่ยนความหนาขอบหรือระยะเงา และใช้ palette ใหม่
Component
37 component ใน 5 ชั้น แต่ละชิ้นมีขนาดที่วัดได้ state ที่เห็น และข้อกำหนดที่ต้องเพิ่ม ค่าสีอ้างเป็นชื่อ token จากส่วน Design token
| ชั้น |
Component |
| Shell |
AppShell (รวม Footer), Sidebar, NavGroup, NavLink, MobileHeader, BottomTabBar |
| Overlay |
BottomSheet, SearchDialog, CookieBanner, Backdrop |
| ปุ่มและฟอร์ม |
Button, IconButton, ThemeSwitcher, Input, Combobox, Chip x2, OAuth, Divider |
| Card |
Card, CourseCard, ArticleCard, ProductCard, StatTile, PriceCard, TrustCard |
| เนื้อหา |
Hero, SectionHeader, Sponsor x2, Accordion, Checklist, Badge, Video, Pagination |
ชั้น Shell
AppShell
| รายการ |
ข้อกำหนด |
| หน้าที่ |
วาง Sidebar หรือ MobileHeader + BottomTabBar รอบ main |
| ขนาด (O) |
desktop: sidebar 288px + main. mobile: main เต็มจอ + padding ล่างเผื่อ tab bar |
| จุดเปลี่ยน (O) |
1024px |
| Footer desktop (O) |
ท้าย sidebar: 4 ลิงก์ 12px + เวอร์ชัน |
| Footer mobile (O) |
footer ท้ายหน้า ลิงก์เดียวกัน padding ล่าง 128px เผื่อ tab bar |
| Add |
skip link เป็น element แรก, main มี id ให้ลิงก์ข้าม |
Sidebar
| รายการ |
ข้อกำหนด |
| ขนาด (O) |
กว้าง 288px, padding 20px 12px, ขอบขวา 2px, sticky top 0, สูง 100vh, เลื่อนในตัว |
| โครง (O) |
logo, ปุ่มเข้า/สมัคร, ค้นหา, 4 NavGroup, ธีม, ลิงก์นโยบาย, เวอร์ชัน |
| Logo (O) |
40x40px + wordmark |
| ปุ่มเข้าสู่ระบบ / สมัคร (O) |
เต็มความกว้าง 239x44px, pill, ปุ่มสมัครใช้สีหลัก |
| ช่องค้นหา (O) |
ปุ่มหน้าตาเหมือน input 239x50px, radius 12px, badge shortcut ขวา |
| กลุ่มเมนู (O) |
4 กลุ่ม (เรียน, อ่านและดู, ลงมือ, อื่น ๆ) รวม 21 ลิงก์ |
| แถวธีม (O) |
4 ปุ่ม pill 55x24px (3 ธีม + dark mode) ใช้ aria-pressed |
| ท้าย sidebar (O) |
ลิงก์ 4 ตัว ขนาด 12px และ string เวอร์ชัน/commit |
| State |
scroll ภายใน ไม่มีการพับ/ย่อ (O) |
| A11y (Add) |
aria-label ที่ nav, scrollbar ที่มี focus ได้ |
Sidebar สูงกว่า viewport 900px (แถวธีมอยู่ที่ y=1095) จึงต้องเลื่อนภายใน aside
NavGroup และ NavLink
| รายการ |
ข้อกำหนด |
| NavGroup label (O) |
10.88px, 700, uppercase, tracking 0.025em, สี --muted, padding 0 12px 4px |
| NavLink ขนาด (O) |
สูง 36px (sidebar) หรือ 40px (sheet), padding 8px 12px, radius 12px |
| NavLink เนื้อหา (O) |
ไอคอน (ราว 20px ไม่ได้วัด) + ข้อความ 14px/600, gap 12px |
| Default (O) |
สี --muted, พื้นโปร่ง |
| Hover (O) |
พื้น --muted-surface, ข้อความ --foreground, 150ms |
| Active (O/I) |
พื้นสีหลัก + ข้อความขาว (เห็นในภาพ), aria-current |
| Focus (Add) |
ring --ring 2px |
MobileHeader
| รายการ |
ข้อกำหนด |
| ขนาด (O) |
สูง 58px, padding 10px 16px, sticky top-0, z-index 20 |
| พื้น (O) |
--background 90% + blur 8px, ขอบล่าง 2px |
| เนื้อหา (O) |
logo + wordmark ซ้าย, ปุ่มค้นหากับปุ่มบัญชีขวา (ไอคอน) |
| A11y (O) |
ปุ่ม aria-label ค้นหา / เข้าสู่ระบบ, aria-haspopup=dialog |
BottomTabBar
| รายการ |
ข้อกำหนด |
| ตำแหน่ง (O) |
fixed, กลางจอ, ห่างล่าง max(12px, safe-area-inset-bottom) |
| ขนาด (O) |
กว้าง calc(100% - 24px) สูงสุด 460px, สูง 72px, radius 26px, ขอบ 2px, --card |
| รายการ (O) |
5 ช่อง (คอร์ส, บทความ, วิดีโอ, ถาม-ตอบ, เมนู) ช่องละ ~68px ไอคอน + ป้าย 10-11px |
| ช่อง "เมนู" (O) |
button เปิด BottomSheet, aria-label "เมนูเนื้อหาทั้งหมด" |
| ช่องกลาง (I) |
เด่นกว่าช่องอื่น (วงกลม) U: ไม่รู้ว่าเด่นถาวรหรือตาม active |
| ซ่อน (O) |
ตั้งแต่ 1024px |
| Add |
aria-current="page" ที่ช่อง active, ไม่ให้ cookie banner บัง |
ชั้น Overlay
BottomSheet
| รายการ |
ข้อกำหนด |
| ตำแหน่ง (O) |
ชิดล่าง absolute inset-x-0 bottom-0 กว้างเต็ม, สูงสุด 85vh, เลื่อนในตัว |
| รูปทรง (O) |
radius บน 24px, ขอบบน 2px, padding 12px 16px 24px, handle เล็กตรงกลางบน |
| ตัวอย่างที่มี (O) |
เมนูเนื้อหา (21 ลิงก์ แถวละ 40px), บัญชี (login, สมัคร, ธีม, ลิงก์นโยบาย) |
| ARIA (O) |
role=dialog, aria-modal=true, aria-label |
| Backdrop (O) |
ปุ่มเต็มจอ aria-label="ปิดเมนู", ดำโปร่ง 40% |
| Motion (I) |
เลื่อนขึ้นจากล่าง ซ่อนด้วย transform (sheet ยังอยู่ใน DOM) |
| Add |
focus trap, Esc ปิด, คืน focus ไปปุ่มที่เปิด, inert พื้นหลัง |
SearchDialog
| รายการ |
ข้อกำหนด |
| Trigger desktop (O) |
ปุ่มหน้าตา input 239x50px + badge shortcut Cmd+K (U: ยังไม่ได้ทดสอบการกด) |
| Trigger mobile (O) |
ไอคอนใน header |
| Desktop (O) |
กล่องกลางจอ ราว 575px, y 90px, backdrop ดำโปร่ง, input + badge Esc |
| Mobile (O) |
แผงกว้าง 358px ห่างขอบ 16px, ชิดบน |
| ข้อความช่วย (O) |
บอกให้พิมพ์ >= 2 ตัวอักษร และบอกขอบเขตที่ค้นได้ |
| ปิด (O) |
Esc ปิดได้ (desktop) |
| Backend |
ต้องมี endpoint ค้นหา (พบ /api/search) |
| Add |
เลือกผลลัพธ์ด้วยลูกศร, สถานะ loading / ไม่พบ / error, role=combobox |
CookieBanner
| รายการ |
ข้อกำหนด |
| ตำแหน่ง (O) |
fixed ล่างสุดเต็มกว้าง, พื้น --card, ขอบบนหนา, ทับ tab bar จนกว่าจะตอบ |
| เนื้อหา (O) |
ข้อความ 14px 4 บรรทัด (desktop) + ลิงก์นโยบาย, 3 ปุ่ม |
| ปุ่ม (O) |
ไม่ยินยอม (secondary เล็ก), ยอมรับ (primary เล็ก), ปิด (ไอคอน aria-label) |
| ปุ่มขนาด (O) |
สูง 28px (เล็กกว่า 44px) |
| ความจำ (O) |
consent-storage {consentedVersion, analytics} |
| ARIA (O) |
role=region |
| Add |
ไม่ทับ tab bar, ปุ่มสูง >= 44px บนจอสัมผัส, ปุ่มปิด = ไม่ยินยอม |
ชั้นปุ่มและฟอร์ม
Button
| Variant |
ค่าที่วัด (O) |
| primary |
พื้น --brand-500, ตัวอักษร --on-brand, ขอบ 2px --border, pill |
| secondary |
พื้น --card, ตัวอักษร --foreground, ขอบ 2px, pill |
| ghost |
พื้นโปร่ง, ตัวอักษร --muted, ขอบโปร่ง 2px |
| ขนาดปกติ |
สูง 44px, padding 10px 20px, 16px/700, gap 8px |
| ขนาดเล็ก |
สูง 28px (ปุ่มในแถบสนับสนุนและ cookie) |
| เงา |
3px 3px 0 0 สี --border (ghost ไม่มีเงา) |
| Transition |
transform + box-shadow 100ms |
| กว้างเต็ม |
ปุ่มใน sidebar, price card, ฟอร์ม login |
| State |
ข้อกำหนด |
| Hover (U) |
ไม่ได้วัด ใช้ translate -1/-1px และเงา 4px (R) |
| Active (U) |
ค่าที่เลื่อนไม่ได้วัด ใช้ translate +1/+1px และเงา 1px (R) |
| Focus (U) |
ring 2px --ring offset 2px (Add) |
| Disabled (I) |
จางลง ปุ่มจองก่อนกรอกครบ (เห็นในภาพ) Add: aria-disabled + เหตุผลใกล้ปุ่ม |
| Loading (Add) |
spinner + aria-busy, กันกดซ้ำ |
| Contrast (Add) |
primary ขาวบนส้ม #f97316 ได้ 2.8:1 ไม่ผ่าน AA ต้องเปลี่ยนสีพื้น |
IconButton
| รายการ |
ข้อกำหนด |
| ใช้ที่ (O) |
ค้นหา, บัญชี, ปิด, ถูกใจ |
| A11y (O) |
มี aria-label ทุกตัวที่ตรวจ (ค้นหา, ปิด, ถูกใจ, เล่นวิดีโอ) |
| ขนาดแตะ (Add) |
>= 44x44px (ปุ่มปิดใน cookie สูง 28px) |
ThemeSwitcher
| รายการ |
ข้อกำหนด |
| โครง (O) |
แถวเดียว 4 ปุ่ม pill 55x24px: 3 ธีม (ไอคอน) + dark mode |
| ARIA (O) |
aria-pressed, title บอกชื่อ (Bold, Ocean, Grape, โหมดมืด) |
| สถานะเลือก (O) |
พื้นสีหลัก, ตัวอักษรขาว |
| พฤติกรรม (O) |
เปลี่ยนทันทีไม่ reload |
| การเก็บ (O) |
theme-storage {template, dark} ใน localStorage |
| ที่อยู่ (O) |
desktop: ท้าย sidebar. mobile: ใน sheet บัญชี |
| Add |
ปุ่มมี aria-label (ไม่พึ่ง title), เคารพ prefers-color-scheme ครั้งแรก |
Field / Input
| รายการ |
ข้อกำหนด (O) |
| Label |
14px ตัวหนา อยู่เหนือ input |
| Input |
สูง 48px, ขอบ 2px --border, radius 12px, พื้น --card |
| ชนิดที่พบ |
email, password, text (ชื่อ), text (โค้ดส่วนลด) |
| Add |
label ผูก input, autocomplete, error ใต้ field พร้อม aria-describedby |
| Add |
ปุ่มแสดงรหัสผ่าน |
Combobox (เลือกหัวข้อ)
| รายการ |
ข้อกำหนด |
| ARIA (O) |
role=combobox, aria-haspopup, aria-expanded |
| ตัวเลือกแรก (O) |
ให้ผู้สอนช่วยเลือก (ค่า default) |
| Add |
ลูกศร/Enter/Esc, aria-activedescendant, ค้นในรายการ 30 ข้อได้ |
TimeSlotChip และ AmountChip
| รายการ |
ข้อกำหนด |
| TimeSlot (O) |
chip เวลาเริ่ม 09:00 ถึง 21:00 เว้นทีละ 2 ชั่วโมง, ขอบ 2px, pill |
| AmountChip (O) |
10, 20, 50, 100, 500, 1,000, อื่นๆ; ขนาดสูง ~24px |
| State (I) |
เลือกแล้วพื้นสีหลัก |
| Add |
role=radiogroup + aria-checked, ขนาดแตะ >= 44px |
OAuthButton และ Divider
| รายการ |
ข้อกำหนด (O) |
| OAuthButton |
secondary pill เต็มกว้าง สูง 48px, โลโก้ Google 20px + ข้อความ |
| Divider |
เส้น 2px 2 ฝั่ง คำว่า "หรือ" 12px สี --muted กลางเส้น |
| งานใหม่ |
ใช้ปุ่มตามแนวทางแบรนด์ของผู้ให้บริการ OAuth ที่เลือก |
ชั้น Card
Card (พื้นฐาน)
| Variant |
ค่าที่วัด (O) |
card |
พื้น --card, ขอบ 2px --border, radius 16px, เงา 4px 4px 0 |
card-flat |
เหมือน card แต่ไม่มีเงา (ใช้กับ stat tile) |
lift |
เพิ่ม hover: translate(-2px,-2px) + เงา 6px, transition 120ms |
| padding |
p-6 24px หรือ p-0 เมื่อมีภาพชิดขอบ |
CourseCard
| รายการ |
ข้อกำหนด (O) |
| ขนาด |
desktop 344x291px (ใน grid 3 คอลัมน์ 1073px) |
| ภาพปก |
สูง 96px, object-fit: cover, ต้นฉบับ 475x250, loading=lazy |
| Overlay บนภาพ |
badge ซ้ายบน (รูปแบบเรียน), chip ขวาบน (ไอคอนตา + ตัวเลข) |
| เนื้อหา |
padding 20px: H3 18px/800, คำอธิบาย 14px ตัด 3 บรรทัด |
| ท้ายการ์ด |
เส้นคั่น + ราคา 16px ตัวหนา ซ้าย, "ดูรายละเอียด" + ลูกศร ขวา |
| ทั้งใบ |
เป็นลิงก์ <a> เดียว |
| State |
hover = lift. Add: focus ring รอบการ์ด, skeleton ขณะโหลด |
ArticleCard
| รายการ |
ข้อกำหนด |
| โครง (O) |
ภาพปก, tag, ชื่อ, สรุป 3 บรรทัด, เวลาอ่าน + จำนวนอ่าน, ลิงก์อ่านต่อ |
| Grid (O) |
3 คอลัมน์ desktop, 24 ใบ/หน้า |
| ความสูงการ์ด (I) |
ใกล้เคียงกันด้วย flex-col + flex-1 ที่คำอธิบาย |
| บางใบไม่มีภาพ (O) |
แสดงเป็นการ์ดตัวอักษรล้วน (แถวล่างของหน้า tips) |
ProductCard
| รายการ |
ข้อกำหนด |
| โครง (O) |
พื้นที่ภาพ, ชื่อสินค้า, คำอธิบายสั้น, ราคา, ลิงก์ไปร้านค้า |
| สังเกต (O) |
พื้นที่ภาพว่างในภาพที่ตรวจ (ภาพยังไม่โหลดหรือไม่มีภาพ) |
| Add |
ระบุว่าเป็นลิงก์ affiliate/ภายนอก (rel, ข้อความกำกับ) |
StatTile
| รายการ |
ข้อกำหนด (O) |
| ชนิด |
card-flat, padding 16px 12px |
| เนื้อหา |
ตัวเลข 24px/900 (mobile 20px) + ป้าย 12px สี --muted |
| วาง |
grid 3 คอลัมน์ gap 12px, กว้างรวมสูงสุด 672px |
| ข้อมูล |
ตัวเลขมาจากระบบ (ผู้เรียน, หัวข้อ, บรรทัดโค้ด) ต้องมี backend |
PriceCard
| รายการ |
ข้อกำหนด (O) |
| ขนาด |
360x339px, card p-6 |
| ลำดับ |
badge, ป้าย "เริ่มต้น", ราคาใหญ่, หน่วย, checklist 3 ข้อ, ปุ่ม primary |
| หน้าแรก |
อยู่ขวาของ hero ที่ >= 1024px, ลงมาอยู่ใต้ข้อความบนมือถือ |
TrustCard
| รายการ |
ข้อกำหนด (O) |
| ขนาด |
344x192px |
| โครง |
ไทล์ไอคอน 48px (สีต่างกัน 3 ใบ), H3 18px/800, ข้อความ 14px |
| ลิงก์ |
2 ใน 3 ใบเป็นลิงก์ (ไปหน้าเงื่อนไขและหน้าโปรเจค) |
| หมายเหตุ |
ใช้ H3 โดยไม่มี H2 คั่น (ข้ามระดับ heading) |
ชั้นเนื้อหา
Hero
| รายการ |
ข้อกำหนด (O) |
| โครง desktop |
2 คอลัมน์: ซ้าย badge, eyebrow, H1, lead, CTA x2, stat x3; ขวา price card |
| โครง mobile |
คอลัมน์เดียวเรียงตามลำดับเดิม |
| ขนาด |
สูง 648px (desktop), padding แนวตั้ง 80px (sm) / 56px |
| H1 |
48px/60px/900 (mobile 30px), คำเน้นสี --brand-500 |
| พื้นหลัง |
dot-grid + รูปทรงตกแต่ง aria-hidden + pointer-events: none |
| Add |
ปรับสีคำเน้นให้ contrast >= 3:1 (ตัวใหญ่) |
SectionHeader
| รายการ |
ข้อกำหนด (O) |
| H2 |
30px/36px/900, tracking -0.75px |
| ลิงก์ขวา |
"ดูทั้งหมด" + ลูกศร 14px สีหลัก |
| ระยะ |
ห่าง grid ด้านล่าง 20px |
SponsorBar และ SponsorBlock
| รายการ |
ข้อกำหนด |
| SponsorBar (O) |
เต็มกว้าง สูง 85px, พื้น --muted-surface, ไอคอนหัวใจ, ข้อความ, ปุ่มเล็ก 2 ปุ่ม |
| แถบเฉียง (O) |
ลายเฉียงคั่นเหนือแถบ ความสูงราว 20px |
| SponsorBlock (O) |
หัวข้อ + AmountChip + ปุ่มสนับสนุนแบบมีชื่อ + QR + ลิงก์ ขอชื่อ/ดูรายชื่อ |
| Backend |
ต้องมี: บันทึกยอด, ชื่อ, ตรวจรายการโอน |
| ข้อควรระวัง |
งานใหม่ต้องมีเส้นแบ่งชัดเจนว่าเป็นการสนับสนุน ไม่ใช่การสั่งซื้อ |
Accordion
| รายการ |
ข้อกำหนด |
| ใช้ที่ (O) |
FAQ ในหน้าคอร์ส (ปุ่ม toggle พร้อม aria-expanded) animation: U |
| รายการ "สิ่งที่จะได้เรียน" (I) |
การ์ดมีเลขลำดับ 1-6 อาจขยายได้ U |
| Add |
aria-controls, Enter/Space สลับ, ไอคอนหมุน, เคารพ reduced motion |
Checklist
| รายการ |
ข้อกำหนด (O) |
| รายการ |
ไอคอนวงกลมติ๊กสีเขียว (--success) + ข้อความ 14px ตัวหนา |
| ใช้ที่ |
price card, ผลลัพธ์หลังเรียน (2 คอลัมน์ในการ์ด) |
| Add |
ไอคอน aria-hidden, ใช้ <ul> |
Badge
| รายการ |
ข้อกำหนด (O) |
| ขนาด |
12px/700, สูง ~20px, pill, ไอคอนนำหน้า 12px |
| Variant |
พื้น --card ตัวอักษรเข้ม (บนภาพ), พื้น --muted-surface ตัวอักษรรอง (ในการ์ด) |
| ใช้ที่ |
"รูปแบบเรียนสด" บนการ์ดและ hero, tag บทความ |
VideoThumbnail
| รายการ |
ข้อกำหนด |
| ภาพ (O) |
กว้างเต็มคอลัมน์ มีปุ่มเล่นกลางภาพ aria-label="เล่นวิดีโอ: ชื่อ" |
| ปุ่มประกอบ (O) |
"ดูบน YouTube" ลิงก์ภายนอก |
| Add |
โหลด player เมื่อกดเท่านั้น (lazy), ปุ่มเล่น focus ได้ |
Pagination
| รายการ |
ข้อกำหนด |
| โครง (O) |
ปุ่มก่อนหน้า, ข้อความ "หน้า N จาก M", ปุ่มถัดไป |
| ลิงก์ (O) |
/tips?page=2 (เป็นลิงก์จริง ไม่ใช่ state ฝั่ง client) |
| Add |
nav aria-label, aria-current="page", ปุ่มก่อนหน้า aria-disabled ในหน้าแรก |
DecorativeLayer
| รายการ |
ข้อกำหนด (O) |
| ชนิด |
วงกลมเส้น, สี่เหลี่ยมมนหมุน, จุด dot-grid, แถบเฉียง |
| ข้อกำหนด |
aria-hidden, pointer-events: none, อยู่ใน hero เท่านั้น |
| Motion |
animation-name: none ที่ตรวจ ไม่มี animation |
Component ที่ reference ไม่มีหรือไม่ครบ
งานใหม่ต้องเพิ่มทั้งหมดนี้ (Add)
| Component |
เหตุผล |
| SkipLink |
ไม่พบ |
| Toast / Alert |
พบ aria-live 1 จุด แต่ไม่เห็น UI ของ toast |
| Skeleton |
ไม่ได้เห็นสถานะ loading |
| EmptyState |
ไม่ได้เห็น (ค้นหาไม่พบ, ยังไม่มีโพสต์) |
| ErrorState / ErrorBoundary |
หน้า 404 เป็นค่า default ของ framework |
| Tooltip |
ปุ่มธีมใช้ title เท่านั้น |
| Modal ยืนยัน |
ไม่ได้เห็น (อาจอยู่หลัง login) |
Interaction และ state
Interaction ที่วัดหรือสังเกตได้
รายละเอียดของ search, theme, cookie, accordion, combobox และ chip เวลาอยู่ในหัวข้อ component ของแต่ละชิ้นแล้ว ตารางนี้เก็บเฉพาะที่ข้ามหลาย component
| Interaction |
ข้อกำหนด |
ที่มา |
| Hover ลิงก์เมนู |
พื้น --muted-surface + ข้อความเข้มขึ้น, 150ms |
O (class) |
| Hover ลิงก์เล็ก |
ข้อความเข้มขึ้น + ขีดเส้นใต้ |
O (class) |
| Active (กดค้าง) |
ปุ่มใช้ transition ของ transform/shadow 100ms |
O (transition), ค่า U |
| เมนู mobile |
แตะ "เมนู" เปิด sheet จากล่าง |
O |
| ลิงก์สมัคร / เข้าสู่ระบบ |
พก ?next= กลับหน้าเดิม |
O |
พฤติกรรมที่ทดสอบจริง
ผลจากการทดสอบด้วย CDP (hover, เปิด/ปิด dialog, สลับธีม) พร้อมหลักฐานที่ใช้ยืนยัน
| พฤติกรรม |
ผลที่เห็น |
หลักฐาน |
| Hover การ์ดคอร์ส |
translate(-2px,-2px) และเงา 4px เป็น 6px ใน 0.12 วินาที |
computed style ก่อน/หลัง |
| เปิดค้นหา (desktop) |
overlay กลางหน้า, input, badge Esc, ข้อความให้พิมพ์ 2 ตัวอักษรขึ้นไป |
screenshot i-desk-search.png |
| กด Esc (desktop) |
overlay ค้นหาหาย |
เช็ค dialog ที่มองเห็นหลังกด |
| ปุ่ม ไม่ยินยอม |
banner หาย, เก็บ analytics:false ใน consent-storage |
อ่าน localStorage |
| สลับ dark mode |
html.dark, พื้นหลัง #0f0a05, เก็บ dark:true |
computed style + storage |
| สลับธีม Ocean/Grape |
data-theme เปลี่ยน, สีพื้นเปลี่ยนทันที |
computed style |
| Mobile: ปุ่ม เมนู |
เปิด bottom sheet เมนู 21 ลิงก์ แถวละ 40px |
screenshot j-mob-menu-open.png |
| Cookie banner บน mobile |
ทับ tab bar ล่างจนกว่าจะตอบ |
screenshot i-mob-home-viewport.png |
สถานะที่ต้องออกแบบ
Reference ตรวจสถานะเหล่านี้ไม่ครบ งานใหม่ต้องมีทุกข้อ (R)
| สถานะ |
ข้อกำหนด |
| Loading |
skeleton ของการ์ดขนาดเท่าจริง (ภาพ 96px + 3 บรรทัด) กัน layout shift |
| Empty |
ข้อความ + ปุ่มกลับ (ค้นหาไม่พบ, ยังไม่มีโพสต์) |
| Error |
กล่องสี --error-surface ขอบ --error, ปุ่ม ลองใหม่, ไม่เด้ง toast อย่างเดียว |
| Focus |
ring 2px สี --ring ห่างจากขอบ 2px ใช้ :focus-visible |
| Disabled |
ปุ่มจองก่อนเลือกครบ: จาง + aria-disabled + บอกเหตุผลใกล้ปุ่ม |
| Expanded |
accordion/combobox ต้องหมุนไอคอนและตั้ง aria-expanded |
| Offline / 404 |
404 ของ reference เป็นอังกฤษ default ควรทำให้ภาษาและสไตล์เดียวกับเว็บ |
Motion
| ที่มา |
ข้อกำหนด |
| O |
duration 100-150ms (--default-transition-duration: .15s) |
| O |
easing cubic-bezier(.4,0,.2,1) |
| O |
CSS มีกฎ prefers-reduced-motion ครบทั้ง reduce และ no-preference |
| R |
ปิด transform/translate ทุกตัวเมื่อ prefers-reduced-motion: reduce |
| R |
ห้ามใช้ animation ที่วนซ้ำ ยกเว้น loading |
Accessibility
เป้าหมายคือ WCAG 2.2 AA ตาราง contrast คำนวณเองจากค่า token ที่ O ยังไม่ได้ยืนยันด้วย Lighthouse
Contrast ของ reference
| คู่สี |
อัตราส่วน |
ผล AA |
| ข้อความหลัก บนพื้น (Bold light) |
16.9 : 1 |
ผ่าน |
| ข้อความ muted บนพื้น |
4.64 : 1 |
ผ่านเฉียด |
ข้อความ muted บน muted-surface |
4.52 : 1 |
ผ่านเฉียด |
ตัวอักษรขาวบนปุ่ม brand-500 (ส้ม) |
2.80 : 1 |
ไม่ผ่าน (ตัวหนา 14-16px) |
ตัวอักษรขาวบน brand-700 |
5.18 : 1 |
ผ่าน |
Eyebrow brand-700 บนพื้น |
5.01 : 1 |
ผ่าน |
| คำเน้นสีส้ม 48px/900 บนพื้น |
2.71 : 1 |
ไม่ผ่าน (เกณฑ์ตัวใหญ่ 3 : 1) |
ธีม Ocean: ขาวบน brand-500 |
3.68 : 1 |
ไม่ผ่าน (ตัวเล็ก) |
ธีม Grape: ขาวบน brand-500 |
5.70 : 1 |
ผ่าน |
| Dark Bold: muted บนพื้น |
7.81 : 1 |
ผ่าน |
Warning ปุ่มหลักสีส้มของ reference มี contrast ต่ำกว่า AA งานใหม่ต้องเลือกสีหลักที่ให้ตัวอักษรบนปุ่มได้ >= 4.5 : 1 (เช่น ใช้ขั้น 700 ของสเกล หรือตัวอักษรสีเข้มบนสีสว่าง)
ข้อกำหนดตามหมวด
| หมวด |
ข้อกำหนด |
สถานะใน reference |
| Landmark |
header, nav ใส่ aria-label, main 1 ตัว |
มี landmark ครบ แต่ nav ไม่มี label |
| Skip link |
ลิงก์ "ข้ามไปเนื้อหา" เป็น focus แรก |
ไม่พบ |
| Heading |
ลำดับ H1 -> H2 -> H3 ไม่ข้ามระดับ |
หน้าแรกมี H3 ก่อน H2 (ข้ามระดับ) |
| Dialog |
aria-modal, focus เข้า dialog, trap, Esc ปิด, คืน focus |
มี role/aria-modal, ส่วนอื่น U |
| ปุ่มไอคอน |
ต้องมี aria-label |
ปุ่ม ค้นหา/ปิด/ถูกใจ มี, ปุ่มธีมใช้ title + aria-pressed |
| ภาพ |
alt ที่สื่อความหมาย, ภาพตกแต่ง alt="" |
โลโก้ alt ว่าง, ภาพปกคอร์สมี alt |
| ขนาดแตะ |
>= 44x44px (WCAG 2.2 AA ขั้นต่ำ 24x24px) |
ปุ่มใน cookie banner และแถบสนับสนุนสูง 28px, chip จำนวนเงิน 24px |
| ภาษา |
lang="th" |
มี |
| Zoom / reflow |
ไม่มี scroll แนวนอนที่ 320-1440px |
ไม่พบ overflow ที่ 390, 820, 1440 |
| Focus |
ring มองเห็นชัดทุกองค์ประกอบ |
U |
| Live region |
ข้อความ toast/สถานะใช้ aria-live |
พบ aria-live 1 ตัว |
| สีอย่างเดียว |
สถานะไม่สื่อด้วยสีอย่างเดียว |
เมนูที่เลือกใช้พื้นสี + aria-current |
แนวทาง implement
ข้อเสนอทั้งหมดเป็น R
| เรื่อง |
ข้อเสนอ |
| CSS |
Tailwind v4 + CSS variable semantic token ตามตารางใน Design token |
| ธีม |
data-theme + .dark บน html ตั้งก่อน paint (inline script) กัน flash |
| Responsive เนื้อหา |
ใช้ container query กับ grid เพื่อแก้ปัญหา 205px |
| Font |
variable font ที่รองรับไทย + font-display: swap + size-adjust กัน layout shift |
| ภาพ |
next/image หรือเทียบเท่า, ภาพปกสัดส่วนคงที่ (340x96 ~ 3.5:1), lazy |
| ไอคอน |
ชุดไอคอน stroke เดียวกันทั้งเว็บ (reference ใช้ลายเส้น 24px) |
ข้อมูลทางเทคนิคและข้อจำกัดของการตรวจ
ส่วนนี้บอกว่าค่าข้างบนได้มาอย่างไร เชื่อได้แค่ไหน และอะไรยังต้องตรวจเอง
วิธีตรวจ
REA เชื่อมกับ Chrome ผ่าน CDP (loopback) แล้วเก็บหลักฐานแบบ passive ส่วน computed style ดึงด้วย script CDP แยกอีกชุดเพราะ REA ไม่เก็บ CSS ราย element
| ขั้น |
เครื่องมือ |
ได้อะไร |
| 1 |
curl + robots.txt + sitemap.xml |
status, header, กฎ crawl, รายการ route |
| 2 |
rea inspect-web-page |
DOM 996 node, accessibility tree 589 node, 45 script, 27 resource |
| 3 |
rea analyze-web-bundle |
parse 45 script ครบ 0 error, route string, fingerprint framework |
| 4 |
rea capture-web-screenshot |
viewport PNG พร้อม sha256 (1418x802) |
| 5 |
CDP script (Runtime.evaluate) |
computed style, CSS variable, media query, rect ของ element |
| 6 |
CDP Input events |
hover, เปิด/ปิด dialog, ปิด cookie banner, สลับธีม/dark mode |
Chrome ที่ใช้เป็น profile แยกในโฟลเดอร์ชั่วคราวของเครื่อง ไม่ใช่ profile หลักของผู้ใช้ ไม่มีการ login, ไม่ส่งฟอร์ม, ไม่ยิง request เกิน GET หน้าสาธารณะ 24 route (เว้น 0.5 วินาที) และไม่แตะ path ที่ robots.txt ห้าม
ข้อมูลจาก DOM และ bundle
ส่วนบนวัดจาก DOM/CSS จริง ส่วนล่างมาจาก static analysis ของ script 45 ไฟล์ (parse ผ่านทั้งหมด) ไม่ได้รัน JavaScript
| รายการ (O) |
ค่าที่วัดได้ |
<html> |
lang="th", data-theme="bold", class dark เมื่อเปิด dark mode |
| Landmark |
aside (desktop), header (mobile), nav x2, main, footer (mobile) |
| Dialog |
role=dialog, aria-modal=true 2 ตัว (เมนูเนื้อหา, บัญชี) + 1 ตัวค้นหา |
| Storage |
theme-storage และ consent-storage เก็บเป็น JSON {state, version} |
| Meta |
og:* และ twitter:* ครบ, og:image 1942x809 |
| Framework |
Next.js (ความมั่นใจสูง), React (กลาง) จาก detector ของ REA |
| Route string ใน bundle |
9 path เมนู เช่น /tips, /courses, /projects, /group-courses |
| API path ที่เห็นเป็น string |
/api/auth, /api/search (regex บน script ที่ดาวน์โหลด) |
| Image pipeline |
/_next/image ส่ง webp, loading=lazy ที่การ์ด |
| Hosting |
header Server: Vercel, X-Powered-By: Next.js |
| Security header |
HSTS preload, X-Frame-Options: SAMEORIGIN, CSP แบบจำกัด |
ข้อสันนิษฐาน
สมเหตุสมผลจากหลักฐาน แต่ไม่มีการยืนยันตรง ๆ (I)
| ข้อสันนิษฐาน |
เหตุผล |
| ใช้ Tailwind v4 |
CSS variable --spacing, --text-* และ class h-18, max-w-115 |
| State ธีม/consent อาจใช้ Zustand persist |
รูป JSON {state, version} ตรงกับ persist middleware, bundle มีคำ zustand 1 จุด |
| ไอคอนมาจาก Lucide |
มี string lucide จำนวนมากใน bundle |
| หน้าเนื้อหา (tips, guides) render ฝั่ง server จาก CMS/ไฟล์ |
HTML ตอบกลับมีเนื้อหาครบ ขนาด 200-330KB ต่อหน้า |
| การจองคอร์สต้อง login |
ลิงก์สมัคร/เข้าสู่ระบบพก next=%2Fschedule |
| ระบบสนับสนุนเงินใช้ QR โอนเงิน |
หน้า tips/courses แสดง QR และปุ่มจำนวนเงิน 10-1,000 |
ค้นหาเรียก /api/search |
พบ path เป็น string ใน bundle ยังไม่ได้จับ request จริง |
สิ่งที่ยังไม่ยืนยัน
ต้องตรวจเองด้วยมือ (U) รายการหน้าที่ต้องเปิดดูเพิ่มอยู่ในส่วนหน้าและ template
| รายการ |
ทำไมยังไม่รู้ |
วิธีตรวจ |
| Focus ring ของ keyboard |
focus() ใน script ได้ outline ของ browser ไม่ใช่ :focus-visible จริง |
กด Tab ใน browser จริง |
| Esc ปิด bottom sheet บน mobile |
sheet อยู่ใน DOM ตลอด ซ่อนด้วย transform จึงเช็คไม่ได้ |
ทดสอบบนจอสัมผัสหรือ DevTools |
| Focus trap ใน dialog |
ไม่ได้ส่ง Tab ผ่าน dialog |
กด Tab วนใน dialog |
| ปุ่มลัด Cmd/Ctrl+K |
หน้าจอแสดง badge แต่ไม่ได้ส่ง key combo |
กดจริงใน browser |
| Loading / empty / error ของค้นหาและจอง |
ไม่มี request ให้ trigger โดยไม่ login / ไม่ยิง API |
ใช้ network throttle ใน DevTools |
| Active state ของเมนู sidebar |
เห็นจาก screenshot (พื้นส้ม) ไม่ได้อ่าน computed style |
getComputedStyle บนลิงก์ที่ aria-current |
| Active state ของปุ่ม (กดค้าง) |
ไม่ได้ตรวจ :active เลย |
กดค้างแล้วอ่าน transform/shadow |
| Animation ของ hero, accordion, sheet |
animation-name ของ hero ได้ none ส่วนอื่นไม่ได้วัด |
เปิด DevTools Animations |
| ช่องกลางของ tab bar ("วิดีโอ") |
ไม่รู้ว่าเด่นถาวรหรือเพราะเป็น state active |
เปิดหน้าอื่นแล้วดูว่าเด่นตามหรือไม่ |
| Contrast ratio จริง |
ค่าในส่วน Accessibility คำนวณเอง |
ยืนยันด้วย Lighthouse |
หน้าหลัง login (/schedule, /account) |
robots.txt ห้าม และต้องมีบัญชี |
ไม่ตรวจ ตามกติกาของงาน |
Request จริงของ /api/* |
REA ไม่เก็บ body และเปิด session สั้น |
จับ HAR ด้วย rea observe-web-session |
ข้อจำกัดและหลักฐาน
- Full-page screenshot ของ CDP วาง element แบบ
fixed และ sticky ผิดตำแหน่ง (cookie banner, sidebar) รูปที่ใช้เทียบ layout จึงเป็นรูปขนาด viewport
- ตรวจใน headless Chrome 155 เท่านั้น ไม่ได้ทดสอบ Safari หรือ Firefox
- ไม่ได้ตรวจ
hover ของทุก element
- ตัวเลขสถิติหน้าแรก (จำนวนผู้เรียน ฯลฯ) เป็นข้อมูล ณ เวลาที่ตรวจ ไม่ใช่ค่าคงที่ของ UI
rea doctor รายงาน Hopper/Ghidra/IDA ไม่พร้อม ไม่กระทบงานนี้เพราะเป็นเครื่องมือวิเคราะห์ native binary
หลักฐานที่เก็บไว้ในเครื่อง
| ไฟล์ |
เนื้อหา |
<evidence>\inspect.json |
Evidence จาก rea inspect-web-page |
<evidence>\bundle.json |
Evidence จาก rea analyze-web-bundle |
<evidence>\probe-*.json |
computed style และโครงหน้า 9 route x 3 viewport |
<evidence>\shot-*.png, i-*.png, j-*.png, k-*.png |
screenshot ทุกสถานะที่อ้างถึง |
<evidence>\r.json, q-desktop.json |
ค่า typography, breakpoint, ธีม |
คำถามที่ต้องตัดสินใจก่อนสร้าง
| ข้อ |
คำถาม |
| 1 |
งานใหม่ต้องรองรับกี่ธีม (reference มี 3 ธีม x 2 โหมด = 6 ชุด) |
| 2 |
ภาษาที่รองรับ: ไทยอย่างเดียว หรือหลายภาษา (กระทบ font และ layout) |
Summary
| ข้อ |
สรุป |
| Visual language |
ขอบ 2px + hard shadow + ปุ่ม pill + card มุม 16px |
| Shell |
sidebar 288px (>= 1024) หรือ header + tab bar + sheet (< 1024) |
| Token |
semantic token + 3 ธีม x light/dark ผ่าน data-theme และ .dark |
| Component |
37 ชิ้นใน 5 ชั้น แกนคือ Button, Card (3 variant), NavLink, Badge |
| สิ่งที่ลอกได้ |
โครง shell, bottom sheet, tab bar, ลำดับ block ของ hero และหน้าคอร์ส |
| สิ่งที่ต้องแก้ |
contrast ปุ่ม, tap target 24-28px, focus/keyboard, state ที่หายไป |
| Template |
8 แบบ (T1-T8) โดย T2 catalog ใช้ซ้ำมากที่สุด (12 หน้า) |
| ส่วนที่ซ้ำทุกหน้า |
shell, cookie banner, บล็อกสนับสนุน, บล็อกสินค้าแนะนำ |
| ส่วนที่ต้อง backend |
auth, จอง, ค้นหา, community, สนับสนุน, ตัวนับ |
| ต้องตรวจมือต่อ |
focus ring, ปุ่ม active, keyboard ใน dialog, animation, player, หน้าบทความ |
| ข้อควรระวัง |
ห้ามใช้ชื่อ สี ภาพ ข้อความของ reference ตรง ๆ |