Command Palette

Search for a command to run...

Internal referencePrinciples · 2026-04-24

TNRR Design Principles

เอกสารอธิบาย ทำไม ของทุกการตัดสินใจออกแบบใน TNRR AI Portal. ใช้คู่กับ /design-system (visual catalog) — หน้านี้เล่าหลักการ, หน้านั้นโชว์ของจริง. ทุกหัวข้ออ้างอิงไฟล์ + บรรทัดใน repo — ถ้าโค้ดเปลี่ยนต้องอัปเดตที่นี่ด้วย.

ปรัชญา

3 เสาหลัก: Trust · Discovery · AI Transparency

เลือกแนวทางตาม context ของผู้ใช้ — นักวิจัย/นักศึกษา/ผู้กำหนดนโยบายไทย ที่มาหา 'ข้อมูลงานวิจัยที่เชื่อถือได้' ไม่ใช่ consumer app

Trust — ดูน่าเชื่อถือก่อนดูสนุก

ข้อมูลงานวิจัยของประเทศไม่เหมาะกับ playful design. การเลือก Ocean Blue เป็นสีหลัก + typography แบบสะอาด + spacing ที่ไม่อัด = ผู้อ่านรู้สึกว่ากำลังอยู่ในแหล่งข้อมูลทางการ

app/globals.css :10-21components/layout/navbar.tsx
Discovery — facet/filter/semantic color ช่วยคัดกรอง

ผู้ใช้ไม่ได้รู้จัก dataset มาก่อน. Badge สีสัน semantic (topic/career/type/keyword) + แผนที่ + clustering + knowledge graph เป็นเครื่องมือให้ค่อยๆ narrow จากความคลุมเครือไปหางานที่ต้องการ

components/ui/badge.tsx :24-34app/clustering, /map, /analytics
AI Transparency — ผู้ใช้เห็นว่า AI 'กำลังคิด' แค่ไหน

AI ในงานวิจัยต้องไม่ขายความแน่นอนเกินจริง. Streaming cursor บอกว่ากำลังตอบสด; Research Meter 3-สี แยก หลักฐาน 'สนับสนุน/กล่าวถึง/ขัดแย้ง' ให้ผู้ใช้ตัดสินใจเองแทนที่จะเชื่อ AI ทั้งก้อน

components/ai/research-meter.tsxapp/globals.css :303-312 (cursor)
Demo-as-truth: repo นี้ไม่มี DB จริง ไม่มี LLM จริง — fixtures + mock SSE ต้องดูสมจริงราวกับ production. หลักคิดคือ ถ้า demo รู้สึก flaky ทีมจะไม่เชื่อว่า production จะทำงานได้. ดังนั้น searchResearch() มี 3-tier fallback ไม่คืน empty เพื่อให้ demo ไม่หน้าขาว.
Color

ระบบสี — 4 palette แยกบทบาท + 3 semantic สำหรับ Research

แต่ละ palette มี 'หน้าที่' ไม่ทับกัน. ไม่ใช้สีเดียวทั้งไซต์เพราะจะแยกไม่ออกว่าอะไรคืองานของแพลตฟอร์ม vs AI vs action

Ocean Blue
Primary — trust / authority
#1E5C8A

ใช้กับ: brand logo, navbar, primary button, link สำคัญ, chart series ที่ 1. สีน้ำเงินลึกสื่อถึงทางการและหน่วยงานรัฐ (วช.)

globals.css :10-21
Teal (AI)
AI accent — สด / เทคโนโลยี
#0EA5A0

ใช้กับ: glass-ai panel, streaming cursor, focus ring, Badge variant='ai', AI button gradient. Teal-500 เป็น --ring เพื่อแยก focus ring ออกจากสีหลัก — ให้ motion ของ AI มี identity ชัด

globals.css :23-33, 178, 221components/ai/ai-panel.tsx
Coral
Accent — human warmth / CTA รอง
#E8734A

ใช้กับ: Badge variant='career', chart-3, highlight เชิงคน/มนุษยศาสตร์. ตัดกับ ocean blue โดยไม่ทำให้รู้สึกเตือนภัย (ถ้าใช้แดงแทนจะดุเกิน)

globals.css :36-42
Amber
Soft warning / mentioning
#F5B342

ใช้กับ: Research Meter 'กล่าวถึง', warning state, chart-4. เลือก amber แทนเหลืองจัดเพราะอ่านง่ายกว่าในทั้ง light และ dark

globals.css :45-48
3 สี semantic สำหรับ Research Meter
สนับสนุน
green = evidence เข้าทาง
กล่าวถึง
amber = mention แต่ไม่ชี้ไปทางไหน
ขัดแย้ง
red = evidence สวนทาง — ไม่ใช่ข้อผิดพลาด

ข้อสำคัญ: สีแดงใน Research Meter ไม่ได้ หมายความว่า AI ผิด — มันแสดงว่ามีหลักฐานขัดแย้งในคลัง. นี่คือ transparency ที่ตั้งใจให้เห็น ไม่ใช่บั๊ก. ดู components/ai/research-meter.tsx:26-35

Semantic tokens ซ้อนทับ palette

ทุกหน้าเขียน class จาก semantic (bg-background, text-foreground, ring-ring) — ไม่เรียก primary-700 ตรง ยกเว้นเมื่อต้องการสีที่ ไม่ flip ใน dark mode (เช่น footer ที่ตั้งใจให้พื้นหลังเข้มเสมอ). ดู mapping ที่ app/globals.css:158-227

อย่าผสม palette ข้ามบทบาท. ปุ่ม CTA หลักใช้ variant="primary" (ocean), ปุ่มที่เกี่ยวกับ AI ใช้ variant="ai" (teal gradient). การสลับจะทำให้ผู้ใช้สับสนว่า action ไหนเป็นของแพลตฟอร์ม action ไหนเป็นของ AI
Typography

Thai-first pairing + สเกล 5 ระดับ

กลุ่มผู้อ่านหลักเป็นคนไทย — ต้องอ่านภาษาไทยสบายก่อน ภาษาอังกฤษเป็นส่วนประกอบ

IBM Plex Sans Thai เป็นลำดับแรกใน :lang(th)

Plex Thai มี proportional weight + ligature ที่อ่านยาวๆ ได้สบาย, รองรับวรรณยุกต์ครบ, และคู่กับ Inter ได้ดีเพราะ x-height ใกล้กัน. Fallback เป็น Noto Sans Thai สำหรับเครื่องที่โหลด Plex ไม่ทัน

globals.css :80-83, 247app/layout.tsx (font loading)
Display / Heading / Body / Caption / Overline

5 ระดับพอสำหรับ long-form documentation + card list. Display สำหรับ hero หรือ page title เท่านั้น, heading สำหรับ section, body 15px เป็น default (ไม่ใช้ 16px เพราะทำให้หน้า data-heavy ดูแน่น), caption 12px สำหรับ metadata, overline 11px สำหรับ kicker

globals.css :316-326
Display — 36px / bold / -0.02em
Heading LG — 22px / semibold
Heading MD — 18px / semibold
Body MD — 15px line-height 26px ใช้สำหรับ paragraph ส่วนใหญ่ของระบบ
Body SM — 13px สำหรับ metadata, list compact, secondary info
CAPTION — 12px / 0.01em tracking
OVERLINE — 11px / 0.08em uppercase
อย่าใช้ letter-spacing ลบกับ heading ภาษาไทย

CSS negative tracking ที่ทำให้ตัวอักษร Latin ดูแน่นขึ้น จะทำให้สระ/วรรณยุกต์ไทยซ้อนกันอ่านไม่ออก. ระบบตั้ง -0.01em ถึง -0.02em เฉพาะ h1/h2 แต่ Plex Thai ออกแบบมารองรับระดับนี้ — ถ้าจะต่ำกว่านี้ต้องเช็คภาษาไทยก่อน

globals.css :248-251
Layout

1440px container + 4px rhythm + lg-only gutter shift

Layout เดียวใช้ซ้ำทั้งไซต์ — ลด cognitive load ของผู้ใช้และของทีม

max-w-[1440px] mx-auto ทุกหน้า

1440 เป็นความกว้างของ laptop แถวหน้า (MacBook 14/16, งานวิจัย desktop ส่วนใหญ่) และไม่ยืดบน 4K. เกิน 1440 paragraph จะกว้างเกิน readability (>90ch). ทุก page, navbar, footer ใช้ค่านี้เท่ากัน

components/layout/navbar.tsx :91components/layout/footer.tsx :7
Gutter: px-4 lg:px-6 (16 / 24)

16px ที่ mobile + tablet พอให้นิ้วโป้งจับขอบได้, 24px ที่ lg (≥1024) ให้หายใจ. ไม่ใช้ md breakpoint เพราะ tablet แคบยังอยากได้พื้นที่คอนเทนต์มากกว่ารอบ

CLAUDE.md Responsive section
Rhythm 4px (gap-1 / 2 / 3 / 4 / 6 / 8)

Tailwind default ก็ 4px อยู่แล้ว — ไม่เพิ่ม custom step. ภายใน card ใช้ gap-2/3, ระหว่าง section ใช้ space-y-14 (56px)

ตัวอย่าง container pattern
<div className="mx-auto max-w-[1440px] px-4 lg:px-6 py-8 lg:py-12">
  {/* content */}
</div>
Components

shadcn-on-Base-UI + CVA variants + data-slot targeting

ไม่ได้เลือก shadcn เพราะ trend — เลือกเพราะรวม 3 อย่างที่ต้องการ: a11y จาก Base UI, variant system จาก CVA, และ flexibility ที่เจ้าของ codebase เป็น source

ใช้ @base-ui/react เป็น primitive

Base UI ให้ keyboard nav, ARIA role, focus trap ฟรี. เราไม่ต้อง implement เอง ไม่ต้องเพิ่ม react-aria. Button/Input/Menu ทั้งหมด wrap Base UI เสมอ

components/ui/button.tsx :1components/ui/input.tsx :2
TNRR custom variants: ai · ai-soft · primary · accent + sizes tnrr-sm / tnrr-lg

shadcn default มีแค่ default / secondary / outline / ghost / link / destructive ซึ่งยังขาด 'AI' และ 'primary navy' ที่เป็น identity ของไซต์. Custom ผ่าน CVA เพิ่มใน components/ui/button.tsx โดยไม่ fork Base UI

components/ui/button.tsx :22-48
Card ใช้ data-slot + @container

CardHeader / CardContent / CardFooter แต่ละอันมี data-slot ของตัวเอง — CSS เขียน selector แบบ semantic ได้ (เช่น group-has-[data-slot='card-action'] เพื่อรู้ว่ามี action button อยู่). Container query (@container/card-header) ให้ header responsive โดยไม่ต้อง media query

components/ui/card.tsx :14-17
เมื่อไหร่สร้าง variant ใหม่? เมื่อ pattern เดียวปรากฏ ≥3 ที่และต่างจาก variant ที่มี. ถ้าใช้ที่เดียวให้เขียน className ตรงๆ ไม่ต้องเพิ่ม variant — เพราะ variant ที่ไม่มีคนใช้ซ้ำจะกลายเป็น dead code
AI ★

4 pattern ที่แยก TNRR ออกจาก dashboard ทั่วไป

ทุก pattern นี้ตั้งใจสื่อ 'AI ทำงานแบบไหน' ให้ผู้ใช้ไม่ต้องเดา — เพราะ AI ในงานวิจัยต้อง justifiable

1. Streaming cursor
บอกว่าข้อความกำลังถูกพิมพ์สด ไม่ใช่ loader

ข้อความ AI ที่ยังไม่จบจะมี teal cursor กะพริบต่อท้าย (class .streaming-cursor). แยกให้ผู้ใช้รู้ว่านี่คือข้อความสด ถ้าปิดตอนนี้ข้อมูลไม่หาย — ต่างจาก spinner ที่บอกแค่ว่า 'รอ'

<div className={cn(!done && "streaming-cursor")}>{text}</div>
app/globals.css :303-312components/search/ai-summary-strip.tsx :60
2. Research Meter — 3-segment staggered bar
ไม่ใช่ confidence % เดียว แต่เป็น breakdown 3 ด้าน

Bar แบ่ง success / amber / error = สนับสนุน / กล่าวถึง / ขัดแย้ง. แต่ละส่วนมี transition-delay คนละ 150ms — ลำดับ cascade 0/150/300ms ทำให้ผู้ใช้อ่านค่าแต่ละด้านได้ทัน ไม่ใช่เห็นพร้อมกันแล้วข้ามไป. Detail สำคัญ: สีแดง = หลักฐานสวนทาง ไม่ใช่บั๊ก

<div className="h-full bg-success-500 transition-[width] duration-700 ease-out"
     style={{ width: `${s}%`, transitionDelay: "0ms" }} />
<div className="h-full bg-amber-400"   style={{ transitionDelay: "150ms" }} />
<div className="h-full bg-error-600"   style={{ transitionDelay: "300ms" }} />
components/ai/research-meter.tsx :24-36
3. Glass-AI panel
แยก 'output ของ AI' ออกจาก 'ข้อมูลดิบ' ด้วย visual layer

Class .glass-ai ให้ card พื้นหลัง teal-tinted + backdrop-blur + teal-200 border. ทุก AI content (summary strip, chatbot, DocChat) wrap ด้วย AiPanel — ผู้ใช้เห็นปุ๊บรู้ว่านี่คือสิ่งที่ AI สร้าง ไม่ใช่ metadata ของเอกสารต้นฉบับ

.glass-ai {
  background: var(--card);
  border: 1px solid var(--color-teal-200);
  backdrop-filter: blur(12px);
}
.glass-ai::before { background: var(--gradient-ai); }
app/globals.css :276-290components/ai/ai-panel.tsx
4. Mock SSE philosophy — re-producible 'liveness'
Fixtures + deterministic helpers = demo รู้สึกสด แต่ตอบเหมือนเดิมทุกครั้ง

lib/mock-ai.ts มี detectTopic (8 keyword pattern), detectIntent (7 chat intent), และ computeMeter ที่ deterministic ตาม paperId+columnType. lib/sse-server.ts wrap streaming ให้ส่งทีละคำพร้อม jitter เล็กน้อย — ได้ feeling LLM ไม่ต้องพึ่ง API จริง. ทุก route ใน app/api/ ต้อง runtime='edge' เพราะ Netlify regular function cap 10s จะตัด AI flow ของเรา (8-12s)

export const runtime = "edge";
export async function POST() {
  return sseStream(async (send) => {
    await streamTokens(send, answer);  // word-by-word with jitter
    send("done", {});
  });
}
lib/mock-ai.tslib/sse-server.tslib/mock-docchat.tslib/mock-columns.ts
อย่าเพิ่ม conditional ใน route handler. ถ้าคำถามใหม่ไม่ match intent — ให้ขยาย pattern map ใน lib/mock-ai.ts / lib/mock-docchat.ts. การเพิ่ม if-else ใน handler ทำให้ตรวจสอบ deterministic ยาก และ diverge จาก visual catalog ของ /workspace ที่ pre-seed ผ่าน mock-columns
Responsive

Desktop-first + additive — ไม่ใช่ mobile-first

กลุ่มผู้ใช้หลัก (นักวิจัย) ใช้ laptop/desktop. Mobile support เพิ่มเข้ามาโดยไม่ลดอะไรของ desktop

Hamburger + Sheet drawer ≥ lg collapse

Navbar links เต็ม ≥1024px. เมื่อ <lg แสดงปุ่ม hamburger เปิด Sheet drawer (280-320px) — ให้ผู้ใช้ยังเข้าได้ทุกหน้าโดยไม่ hide feature

components/layout/navbar.tsx :95-142
FAB สำหรับ DocChat และ action หลัก

บนมือถือ sidebar hide หมด — action หลัก (DocChat, New Workspace) ย้ายเป็น FAB ตำแหน่งคงที่: lg:hidden fixed bottom-5 right-5 z-30. ซึ่งเป็นมือถืออยู่แล้วที่เอื้อมถึง

components/research/docchat-fab.tsx :16
Sidebar / filter panel hide <lg หรือ <md

Sidebar ปกติ hide <md หรือ <lg แล้วเปิดผ่าน Sheet. หลัก: เปิดเป็น overlay เสมอ ไม่ดัน content. ผู้ใช้มือถือเลื่อนหาผลการค้นหาก่อน — filter เป็นขั้นถัดไป

ห้ามยุบ desktop feature เพื่อทำ mobile. เช่น ถ้า detail drawer มี 3 tab บน desktop — บน mobile ต้องยังมี 3 tab (อาจ scroll). การตัดเหลือ 1 tab เพราะ 'จอเล็ก' คือทำให้ผู้ใช้ mobile เป็นพลเมืองชั้นสอง
Motion

Vocabulary แบบเรียบ — motion เป็น garnish ไม่ใช่ payload

การเคลื่อนไหวไม่ควรเป็นสาเหตุที่ทำให้ feature ทำงานได้. ทุกอย่างต้อง degrade อย่างสง่างามเมื่อ prefers-reduced-motion

Hover-lift
translateY(-0.5) + shadow-cardh
ใช้กับ: Card, Button ที่ interactive

Lift เบาๆ ชวนคลิกโดยไม่ bounce (ไม่ใช้ scale). ให้ feel premium เหมาะกับ tone ทางการ

components/research/research-card.tsx :47
Staggered cascade
150ms delay step
ใช้กับ: Research Meter, list reveal

ตามองทีละส่วน — ไม่ปล่อยเผยพร้อมกันแล้วข้ามไป. 150ms เป็น perceptual sweet spot

components/ai/research-meter.tsx :24-36
Shimmer skeleton
1.8s ease-in-out infinite
ใช้กับ: Loading state ที่ข้อมูลกำลัง fetch

shimmer สื่อ 'กำลังโหลด' โดยไม่ต้อง spinner กลาง. เร็วพอให้ไม่รำคาญ ช้าพอให้ไม่ hyper

app/globals.css :292-301
Fade-up entrance
600ms cubic-bezier(0.16, 1, 0.3, 1)
ใช้กับ: Section เข้ามาจาก scroll หรือ route change

Curve exponential out — เริ่มไว จบนุ่ม. 600ms พอให้รู้สึกว่ามี transition ไม่หั่นกระด้าง

app/globals.css :113-116, 314
Respect prefers-reduced-motion

Media query ใน globals.css ตัด animation-duration และ transition-duration เหลือ 0.01ms เมื่อ OS ตั้งค่า reduce motion — feature ต้องยังทำงานเต็ม ไม่หาย. อย่าเขียน animation ที่ 'ต้องเห็นเพื่อเข้าใจ'

app/globals.css :259-264
A11y

Focus · ARIA ภาษาไทย · keyboard ฟรีจาก Base UI

เข้าถึงได้ไม่ใช่ตัวเลือก — เป็น baseline. ทุก interactive ต้องใช้คีย์บอร์ดได้และต้องมี visible focus

Focus ring teal 2px offset 2px ทั่วทั้งไซต์

Global rule *:focus-visible ใช้ --ring = teal-500 (light) / teal-400 (dark) — ทุกปุ่ม ทุก input ได้ focus indicator เหมือนกัน. ไม่ override โดยไม่จำเป็น

app/globals.css :252-256
ARIA label เป็นภาษาไทยเสมอสำหรับ icon-only button

Screen reader ของผู้ใช้ไทยต้องอ่านออกเป็นภาษาไทย. ตัวอย่าง: aria-label='เปิดเมนู', 'ย่อ', 'ขยาย', 'สร้างใหม่'. ห้าม aria-label ว่างหรือใช้ภาษาอังกฤษถ้า UI เป็นไทย

components/layout/navbar.tsx :97components/search/ai-summary-strip.tsx :49-52
aria-invalid built-in ใน input CVA

Input / Textarea มี aria-invalid:border-destructive aria-invalid:ring-destructive/20 — แค่ set aria-invalid={true} สถานะ error visible อัตโนมัติ ไม่ต้องเขียน class เพิ่ม

components/ui/input.tsx :11-12
Base UI ให้ keyboard nav ฟรี

Menu, Select, Tabs, Dialog ของ @base-ui/react รองรับ arrow keys, escape, focus trap อยู่แล้ว. ถ้าเราเขียน custom component ต้องทดสอบ tab / shift-tab / esc ให้ครบก่อน ship

Test เร็วๆ: เปิดหน้าแล้วกด Tab จากบนสุด — ถ้าเจอ element ที่ focus ไปแต่ไม่มี ring visible แสดงว่าโดน override มาจากที่ไหนสักแห่ง. Fix ที่ต้นเหตุ อย่าใช้ outline-none เพื่อ 'ให้ดูสะอาด'
Theming

next-themes class-based + token 2 ชั้น

Light เป็น default (ตาม use case ทางการ), dark เป็น opt-in. ทั้งสองต้องทดสอบทุกหน้า

Token 2 ชั้น: scale คงที่ + semantic swap

ชั้นแรก (primary-50..950, teal-50..900, ink-0..950) คงที่ทุก theme. ชั้นสอง (background, foreground, muted, ring, chart-1..5) swap ตาม :root vs .dark. เวลาเขียน component ใช้ semantic เสมอ — ยกเว้นเมื่อต้องการสีเฉพาะ theme ตั้งใจ (เช่น footer dark navy ทั้ง light/dark)

app/globals.css :158-227
Dark accent ย้ายมาใช้ teal-800 เป็น background ของ AI area

ใน light mode teal-50 (อ่อนสุด) เป็นพื้น AI, ใน dark ใช้ teal-800 (เข้ม) + foreground teal-200. Swap นี้คงความ 'เป็นพื้นที่ของ AI' แต่ไม่ทำให้พื้นเจิดจ้าเกินในธีมมืด

app/globals.css :215-216
Gotcha — Recharts ไม่อ่าน CSS variable. Chart library ใช้ JS color (SVG fill, canvas). ต้อง useTheme().resolvedTheme แล้วส่งสี explicit เป็น prop. Default Tooltip ยังเป็นสีขาวใน dark mode — ต้อง override contentStyle. ดู pattern ที่ TrendChart และ AuthorNetwork
::selection สี teal-200 ทั้ง theme

คนเลือกข้อความเพื่อ copy — ให้สีเดียวกันในทั้ง 2 theme เพื่อ muscle memory

app/globals.css :257-258
Content ★

Content & Voice — Thai-first, action-oriented, AI-as-assistant

เนื้อหาและทุกคำพูดในระบบต้องสื่อสารภายใต้ tone เดียวกัน — ทางการพอให้น่าเชื่อถือ, ชัดพอให้ทำงานต่อได้

Formal Thai ไม่ใช่คำพูด

ผู้ใช้เป็นนักวิจัย/นักศึกษาบัณฑิต/ข้าราชการ. ใช้ 'ค้นหางานวิจัย' ไม่ใช่ 'หาอะไรดี'; 'เพิ่มใน Workspace' ไม่ใช่ 'ใส่ไว้เดี๋ยว'. แต่ไม่ต้อง stiff — หลีกเลี่ยงคำโบราณ เช่น 'โปรดกระทำ'

CTA ใช้คำกริยาตรงตัว + objective ชัด

'สร้างรายงาน', 'เพิ่มใน Workspace', 'ค้นหา', 'ดาวน์โหลด PDF'. ไม่ใช้ 'ไปกันเลย', 'เริ่มต้น' เพราะผู้ใช้ต้องรู้ว่า <em>อะไร</em> จะเกิดขึ้น

Empty state ชวน action ไม่ใช่บอก 'ไม่พบ'

searchResearch() มี 3-tier fallback ตั้งใจคืนผลเสมอ — โดยดีไซน์เพื่อหลีกเลี่ยง empty state ใน demo. ถ้าหลีกเลี่ยงไม่ได้จริงๆ ให้เขียน 'ลองคำค้น...' + แนะนำ 2-3 ตัวอย่าง ไม่ใช่ '—' หรือ 'No results'

lib/fixtures/research.ts searchResearch
AI messages พูดในฐานะ 'ผู้ช่วย' ไม่ใช่ system

แทนที่ 'Error: request failed' ให้เขียน 'ขออภัย ดึงข้อมูลไม่ได้ กรุณาลองใหม่'. แทนที่ 'Loading' ให้ใช้ 'กำลังสรุป...', 'กำลังค้นหา...'. Tone นี้ map กับ streaming cursor — ผู้ใช้รู้สึกว่า 'คน' กำลังช่วยอยู่

components/search/ai-summary-strip.tsx
ศัพท์เทคนิคอังกฤษที่ไม่มีคำไทยตรง — คงไว้

Workspace, Knowledge Graph, Clustering, Dashboard, SSE. การแปลฝืนเช่น 'กราฟความรู้' ทำให้ค้นยาก และศัพท์เหล่านี้เป็นภาษากลางในงานวิจัย. แต่สำหรับคำที่มีคำไทยดี (Search → 'ค้นหา', Report → 'รายงาน') ให้ใช้ไทย

วันที่ / ปี พ.ศ. หรือ ค.ศ.

UI ทั่วไปใช้ พ.ศ. (เช่น footer '© พ.ศ. 2569'). แต่ metadata งานวิจัยหลายชิ้นเป็น ค.ศ. (ตาม DOI / ORCID). กติกา: สำหรับ UI chrome ใช้ พ.ศ., สำหรับข้อมูลจากคลังวิจัยคงตามต้นฉบับ

components/layout/footer.tsx :56
เทคนิคเขียน microcopy: อ่านเสียงดัง. ถ้าตัวเองไม่พูดแบบนี้ในที่ประชุมวิชาการ — เขียนใหม่. ถ้าต้องใช้คำว่า 'โปรด' มากกว่า 1 ครั้งในหน้าเดียว — tone อาจจะ stiff เกินไป

เอกสารนี้ sync ด้วยมือกับโค้ดจริง — ถ้าแก้ app/globals.css, components/ui/*, lib/mock-ai.ts ควรเช็คว่า section ที่เกี่ยวข้องในหน้านี้ยังตรง. สำหรับ token values หรือ component playground ให้ดูที่ /design-system.