eCourt-note/app/globals.css
Your Name c5c6b65bd2 genesis+worker: design system Genesis ทั้งชุด + patch pdf.worker แก้ fake-space ไทย + ปิด review design/responsive/quality รอบ 2026-09-24 — (1) Genesis: ฟอนต์ General Sans self-host + DM Sans + Sarabun, AppNav 56px backdrop-blur แทน SessionBar, DotGrid หน้า login, palette/รัศมี/เงา/focus ตาม docs/DESIGN.md ครบทุกหน้า (2) postinstall scripts/patch-pdf-worker.mjs ถอด if (scaledDim) รอบ prevTransform ให้อัปเดตทุก glyph — กัน addFakeSpaces แทรก space กลางคำไทยใน PDF ที่ justify ด้วย letter-spacing + test กัน regression (tests/scripts/ + fixture thai_letterspacing.pdf, invariant #6) (3) ฟอร์มอัปโหลด + ช่องกลุ่ม typeId ทางเลือก — normDocumentGroup ที่ utils/external-file.ts เป็น single source ร่วมกับ N8 (4) token fixes: focus ring ปุ่ม pin + แถว bookmark ตามหน้า, ปุ่ม action ใน popup/composer/dialog เป็น rounded-md ทั้งคู่, selection preview เป็น tint ของ primary แทน blue-500 ดิบ, default สี shape/highlight รวมที่ utils/annotation-display, loading animate-pulse + จดข้อยกเว้น (เงา pin, font weights ของ viewer) ลง DESIGN.md (5) responsive: Export PDF max-lg:order-first ให้เห็นบน iPad, แถวบน header แถวเดียวเลื่อนได้บนจอแคบ, re-fit ความกว้างตอน toggle sidebar เฉพาะเมื่อยังไม่ซูมเอง, pin popover flip ขึ้นบนเมื่อใกล้ท้ายหน้า, toast hint ตอนสลับ tool กลางเอกสาร, ป้าย "ชนิด · ผู้ทำ" ของรายการผู้อื่นสำหรับจอสัมผัส (6) คุณภาพ: @layer ครอบ @media กันกฎ touch รั่ยไป desktop (Tailwind v3 ยก @layer ออกจาก media ทิ้ง wrapper — ยืนยันด้วย build จริง + วัดบนจอ), กฎ M5 ครอบ contenteditable กัน Safari zoom ตอนแก้ไข TEXTBOX บน iPad, COMMENT ใช้ box มาตราฐานเดียวตาม pin px จริง (render/scroll target/move-clamp/bookmark), ตัดหัวการ์ดซ้ำกับ h2 หน้ารายการ — พร้อมไฟล์ untracked ที่ postinstall/โค้ดอ้างถึงครบชุด (app/fonts/, AppNav, DotGrid, tests/) กัน fresh clone fail ที่ npm install (คำขอผู้ใช้ 2026-09-24)
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-24 11:46:02 +07:00

423 lines
24 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@tailwind base;
@tailwind components;
@tailwind utilities;
/* ─── Genesis utilities (docs/DESIGN.md 2026-09-23) ────────────────────────
focus indicator = outline น้ำเงินทึบ 2px + offset 2px ของ design system — แยกเป็น
class ร่วมเพื่อใช้ซ้ำทุกปุ่ม/ลิงก์; input ใช้ .genesis-input (ขอบน้ำเงิน + outline ตอน
:focus ตาม spec Inputs) — ครอบด้วย @layer components ให้ Tailwind utilities ทับได้
(UX review M3 2026-09-23: halo alpha 0.12 ของ spec เดิม blend บนขาวแล้ว ~1.2:1
ต่ำกว่าเกณฑ์ 3:1 ของ WCAG 1.4.11 — alpha 0.25–0.35 ก็ยังไม่ผ่าน จึงเปลี่ยนเป็น
outline ทึบเป็นตัวชี้วัด + offset คั่นไม่ให้จมกับพื้นปุ่ม primary) */
@layer components {
.focus-ring {
@apply focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary;
}
.genesis-input {
@apply rounded-md border border-line bg-white px-3.5 py-2 text-sm text-ink placeholder:text-ink-faint focus:border-primary focus:outline focus:outline-2 focus:outline-offset-1 focus:outline-primary;
}
}
/* ─── TextLayer ของ pdfjs v4 ─────────────────────────────────────────────────
คัดมาจาก node_modules/pdfjs-dist/web/pdf_viewer.css (ส่วนที่จำเป็น)
ใช้กับ pdfjs.TextLayer ที่ render ลง div.textLayer ใน PDFViewer */
.textLayer {
position: absolute;
text-align: initial;
inset: 0;
overflow: clip;
opacity: 1;
line-height: 1;
text-size-adjust: none;
forced-color-adjust: none;
transform-origin: 0 0;
caret-color: CanvasText;
/* ห้ามใส่ z-index ที่นี่ — บล็อกนี้อยู่หลัง @tailwind utilities ใน cascade
จะ override z-[2] ของ Tailwind จน textLayer ไปอยู่ใต้ canvas (invariant #3) */
}
.textLayer :is(span, br) {
color: transparent;
position: absolute;
white-space: pre;
cursor: text;
transform-origin: 0% 0%;
}
.textLayer > :not(.markedContent),
.textLayer .markedContent span:not(.markedContent) {
z-index: 1;
}
.textLayer span.markedContent {
top: 0;
height: 0;
}
.textLayer span[role="img"] {
user-select: none;
cursor: default;
}
.textLayer ::selection {
background: rgba(0 0 255 / 0.25);
background: color-mix(in srgb, AccentColor, transparent 75%);
}
.textLayer br::selection {
background: transparent;
}
.textLayer .endOfContent {
display: block;
position: absolute;
inset: 100% 0 0;
z-index: 0;
cursor: default;
user-select: none;
}
.textLayer.selecting .endOfContent {
top: 0;
}
/* canvas วัด font metrics ที่ pdfjs.TextLayer สร้างแล้ว append ท้าย document.body
— คัดมาจาก pdf_viewer.css ของ pdfjs-dist เช่นกัน (ตอนแรกคัดมาแค่ส่วน .textLayer
กฎนี้จึงตกหล่น ทำให้ canvas ว่าง alpha:false แสดงเป็นก้อนดำ 300×150 ที่ล่างซ้าย
ของ document และเพิ่ม scrollHeight เกินจริง 150px) */
.hiddenCanvasElement {
position: absolute;
top: 0;
left: 0;
width: 0;
height: 0;
background-size: 100% 100%;
background-color: transparent;
}
/* ─── UX review 2026-09-09 ข้อ 4: ปุ่ม header เล็กกว่ามาตรฐาน touch 44×44 ─────
ขยายเฉพาะอุปกรณ์จอสัมผัส — ครอบปุ่ม toggle/page nav/zoom/tool group
(รวมปุ่ม dropdown ของ tool)/undo-redo/bookmark/export + ปุ่ม summary (Link) */
@media (pointer: coarse) {
header .touch-44 > button,
header .touch-44 > div > button,
header .touch-44 > div > div > button,
header .touch-44 > div > a {
min-height: 2.75rem;
min-width: 2.75rem;
/* min 44px ทำให้ปุ่ม icon เดี่ยวกว้างเกิน content — ต้อง center icon
ไม่งั้นชิดซ้าย+ลอยตาม baseline (ปุ่ม dropdown มี flex อยู่แล้ว ไม่กระทบ) */
display: inline-flex;
align-items: center;
justify-content: center;
}
/* UX review 2026-09-14 L2: ปุ่มใน dropdown panel (สี/ความหนา/preset) — swatch 16px
เล็กเกินนิ้ว แต่ 44px ทำ panel บวม จึงใช้ 36px / C1: เมนู dropdown + popup bookmark
ย้ายไป createPortal ที่ document.body แล้ว (ไม่มี .z-50 เหลือใน header) จึง target
ผ่าน [data-tool-menu] ตรง ๆ / ไม่ใส่ display/justify — แถวเมนูเป็น text-left อยู่แล้ว */
[data-tool-menu] button:not(.combo-step),
[data-tool-menu] label {
min-height: 2.25rem;
min-width: 2.25rem;
}
/* NumberCombo (ความหนา/ความทึบ/ขนาด): ▲▼ ซ้อนแนวตั้ง 2 ปุ่ม — ถ้าโดน min 36px
คอลัมน์จะสูง 72px และ input (items-stretch) ถูกดึงบวมตามทั้งแถว — ปุ่ม ▲▼
จึงหลุดจาก rule ด้านบน (combo-step) แล้วตั้งความสูงแถว input เองที่ 36px แทน
(:first-child — เว้น dropdown ของ preset ที่เป็นลูก div ตัวที่สอง ไม่ให้ถูก
บังคับความสูงจนตัดรายการ): แตะ input = เปิด preset dropdown (เปิดขึ้นบน — L2:
แถวท้ายเมนูเปิดลงจะล้นเขต scroll ของเมนู) คือ touch path หลัก ส่วน ▲▼
(ครึ่งแถว) เป็น fine-tune สำรอง — ขนาดใกล้ desktop ไม่บวม */
[data-tool-menu] .number-combo > div:first-child {
height: 2.25rem;
}
[data-tool-menu] .combo-step {
min-width: 1.25rem;
}
/* M3: ตัวควบคุมบนหน้ากระดาษเล็กกว่ามาตรฐาน touch มาก (ไม่ได้อยู่ใน header จึงหลุด
จาก rule ด้านบน) — ของที่มองเห็นคงขนาดเดิม ขยายพื้นที่รับ touch แทน:
handle/strip ของกรอบ selection เป็น div ขนาดจาก inline style (10px/8px) แก้
width/height ตรง ๆ ไม่ได้ จึงใช้ ::after แผ่รับ pointer รอบ ๆ (handle 10→hit 28px,
strip 8→hit 16px — มุม render ทีหลังใน DOM จึงชนะบริเวณซ้อนกับ strip = ต้องการ) */
.sel-handle::after {
content: "";
position: absolute;
inset: -9px;
}
.sel-strip::after {
content: "";
position: absolute;
inset: -4px;
}
/* ปุ่ม bookmark/ลบในแผง selection (20×20) + swatch สีในแผง (16×16) —
sel-panel-btn ใส่ centering แบบ .coarse-36 กันผู้ใช้ class ไม่ได้เอา flex
มาเอง (ไอคอนลอยชิดบนในกล่อง 36px); ปุ่มที่ใช้อยู่มี flex แล้ว ผลลัพธ์ไม่เปลี่ยน */
.sel-panel-btn {
min-height: 2.25rem;
min-width: 2.25rem;
display: inline-flex;
align-items: center;
justify-content: center;
}
.sel-panel .color-swatches button,
.sel-panel .color-swatches label {
min-height: 2rem;
min-width: 2rem;
}
/* M6: ปุ่ม icon ใน sidebar (16/14px ติดกัน — จิ้มพลาดไปปุ่มข้าง ๆ ง่าย โดยเฉพาะ
"ลบ" ชิด "ประวัติ") → อย่างน้อย 32px แบบไม่ต้องขยายตัว icon ที่มองเห็น */
/* UX audit 2026-09-16: ขยายเป็นปุ่มทุกตัวใน sidebar — รวมปุ่มข้อความเล็ก
(ยกเลิก/บันทึก/ล้างทั้งหมด/ViewSwitch/แท็บ) ที่เดิมหลุดจาก rule; แยกเป็น 2 rule
เพราะ specificity ของ selector นี้ชนะ Tailwind class เดี่ยว — ใส่ display/
align-items ใน rule กว้างจะขโมย items-start ของปุ่มแถวรายการ (ไอคอนลอยกลาง
ข้อความ 2 บรรทัด) ตัวจัด center จึงคงไว้เฉพาะ button.shrink-0 (ไอคอนเดี่ยว)
ส่วนแถวรายการเป็นปุ่มเต็มความกว้างสูงพออยู่แล้ว ไม่กระทบ */
aside[data-sidebar] button {
min-height: 2rem;
}
aside[data-sidebar] button.shrink-0 {
min-height: 2rem;
min-width: 2rem;
display: inline-flex;
align-items: center;
justify-content: center;
}
/* ─── ยกเว้น chevron หุบ/กางของต้นไม้ bookmark (bug report 2026-09-22) ──────
คอลัมน์นี้เป็น "ตาราง" ของต้นไม้ ต้องกว้างเท่าไอคอน 14px เท่ากับ spacer
(w-3.5) ของแถวลูกเสมอ — ถ้าปล่อย min-width 32px ดันไอคอน bookmark ของ
หัวข้อแม่ไปชนแนวพอดีกับของหัวข้อลูก (indent 16px/ระดับโดนกิน) ลำดับ
ชั้นมองไม่เห็น และ root มีลูก (chevron) กับไม่มีลูก (spacer) ก็เพี้ยน
ไม่ตรงแนวกันเอง — จึงคงขนาดจริง 14px แล้วขยาย hit area ด้วย ::after
แบบเดียวกับ sel-handle (จิ้มสบาย 32px เท่าเดิม โดยไม่จอง layout) */
aside[data-sidebar] button.bm-toggle {
min-height: 0;
min-width: 0;
display: inline-flex;
align-items: center;
justify-content: center;
position: relative;
}
aside[data-sidebar] button.bm-toggle::after {
content: "";
position: absolute;
/* ขยาย hit รอบไอคอน 14px เป็น 32px (±9px) โดยไม่กว้างขึ้นจริง */
inset: -0.5625rem;
}
/* M6: ตัด double-tap zoom ของ browser เมื่อแตะปุ่มรัว ๆ (ยัง pinch ได้ตามปกติ —
handle ของกรอบ selection เป็น div + touch-none อยู่แล้ว ไม่โดน rule นี้) */
button,
label {
touch-action: manipulation;
}
/* M6: กัน long-press บน iOS เปิด magnifier/callout ขอคัดลอก — scope เฉพาะ layer
ของ TEXTBOX/STAMP เท่านั้น (textLayer ต้อง select/copy ได้ตามปกติ) */
.no-callout,
.no-callout * {
-webkit-touch-callout: none;
}
/* M6: ปุ่มเล็กใน comment composer/popover/pin (~24px) → 36px ผ่าน class แทนการ
แก้ขนาดจริง (desktop คงเดิม) */
.coarse-36 {
min-height: 2.25rem;
min-width: 2.25rem;
display: inline-flex;
align-items: center;
justify-content: center;
}
/* M6: ปุ่มหน้าสรุป (30px) → 44px ตามมาตรฐาน touch */
main.summary-page a {
min-height: 2.75rem;
min-width: 2.75rem;
display: inline-flex;
align-items: center;
}
/* design review 2026-09-17 H1: ปุ่มหุบ/กางของ CollapsibleBlock เป็น <button>
ไม่ใช่ <a> จึงหลุดจากกฎข้างบน (สูงจริง ~29px) — ปุ่มเป็น flex w-full
text-left อยู่แล้ว ใส่แค่ min-height (ไม่ต้อง display/justify แบบกฎ <a>) */
main.summary-page button {
min-height: 2.75rem;
}
/* UX review M5 2026-09-23: ช่องกรอกทุกชนิด ≥16px บนจอสัมผัส — iOS Safari ซูมทั้ง
หน้าทุกครั้งที่แตะโฟกัสช่องที่ตัวอักษรเล็กกว่า 16px (ทีมตั้งกติกาเดียวกันไว้
หน้า login แล้ว — ขยายครบทุกช่องของแอปเฉพาะอุปกรณ์จอสัมผัส; !important จำเป็น
เพราะช่องส่วนใหญ่กำหนดขนาดด้วย text-xs/text-[11px] ที่ specificity สูงกว่า)
responsive audit 2026-09-24: ครอบ contentEditable ของ TEXTBOX editor ด้วย —
fontSize default 2.5% ≈ 15px ทำ Safari ซูม viewport ตอนแตะเข้าโหมดแก้ไข
(ตัวอักษรขยายเป็น 16px เฉพาะช่วงพิมพ์ ต่างจากผลลัพธ์ ~7% — trade-off มาตรฐาน
ของ editor บน iOS เพราะ maximum-scale กัน focus-zoom ไม่ได้) */
input,
textarea,
select,
[contenteditable]:not([contenteditable="false"]) {
font-size: 1rem !important;
}
}
/* UX review M7 2026-09-23: ปุ่มหลักของหน้ารายการ/กลุ่ม (Annotate h-8, ลิงก์กลุ่ม,
ออกจากระบบ) สูง 32px ต่ำกว่ามาตรฐาน touch 44px ที่ viewer/หน้าสรุปได้แล้ว —
หน้าที่ผู้ใช้ iPad แตะบ่อยที่สุดก่อนเปิดเอกสารต้องได้เป้าเท่ากัน (ครอบ a/button
ของ <main class="listing-page"> + header ของ AppNav; select/summary เป็น
native control/มี flex อยู่แล้ว ใส่แค่ min-height)
ครอบด้วย @layer components ให้ Tailwind utilities (flex/items-*) ทับได้ —
กฎ unlayered ชนะทุก layer จนบังคับ align-items:center ทับ items-start ของ
แถวลิงก์ในการ์ดกลุ่ม กลายเป็นไอคอนลอยกลางแถว 2–3 บรรทัด (code review 2026-09-23)
+ justify-content:center คืนค่า default ของปุ่ม (text-align:center ของ UA
หมดผลเมื่อเป็น flex) ไม่งั้นปุ่ม w-full เช่น "เปิดเอกสาร"/อัปโหลด ข้อความชิดซ้าย
(คำขอผู้ใช้ 2026-09-23)
responsive audit 2026-09-24: @layer ต้องครอบ @media จากด้านนอกเท่านั้น — ซ้อนกลับกัน
(@media { @layer {…} }) แล้ว Tailwind v3 ยกเนื้อหา @layer ออกไปวางที่ components
section โดยทิ้ง media wrapper กฎจึงหลุดไป apply บน desktop ด้วย (ยืนยันด้วยการ
build จริง: ปุ่มหน้ารายการได้ min-height 44px + flex บนจอ desktop) */
@layer components {
@media (pointer: coarse) {
main.listing-page a,
main.listing-page button {
min-height: 2.75rem;
display: inline-flex;
align-items: center;
justify-content: center;
}
main.listing-page select,
main.listing-page summary {
min-height: 2.75rem;
}
header.appnav a,
header.appnav button {
min-height: 2.75rem;
display: inline-flex;
align-items: center;
justify-content: center;
}
}
}
/* ─── Print (หน้าสรุป) — design review 2026-09-17 L4 ───────────────────────
หน้าสรุปมีโอกาสถูกสั่งพิมพ์แนบสำนวนสูง — browser ตัด background ตอนพิมพ์
โดย default ทำให้แถบหัวน้ำเงินเหลือ text ขาวบนกระดาษขาว อ่านไม่ออกทั้งแถบ:
แปลงเป็นกรอบเส้นน้ำเงิน + ตัวอักษรน้ำเงินแทน (อ่านได้บนเครื่องพิมพ์ขาวดำ
ประหยัดหมึก) และไม่ผ่าหน้ากระดาษกลาง section ของหน้าเอกสาร (หน้าที่ยาวเกิน
กระดาษเดียว browser เลื่อนทั้งก้อนขึ้นหน้าถัดไปเอง — break-inside เป็นคำ
แนะนำ ไม่ใช่กฎเหล็ก) / แถบ action กับรายการตามหน้ามือถือซ่อนด้วย print:hidden ใน JSX */
@media print {
main.summary-page header {
background: #fff;
border: 1.5pt solid #0033a1;
}
main.summary-page header h1,
main.summary-page header p {
color: #0033a1;
}
main.summary-page section {
break-inside: avoid-page;
}
}
/* UX review 2026-09-14 H2 → typography audit 2026-09-16 (T1 + Option B) →
Genesis 2026-09-23: DM Sans (next/font ตัวแปร Latin) นำหน้า stack ให้ตัว
Latin/ตัวเลขได้บุคลิก body ของ design system ก่อน จากนั้น webfont Sarabun
(next/font — มี adjusted fallback ของ next/font ในตัว) รับ glyph ไทยโดยเฉพาะ
แสดงผลเหมือนกันทุกอุปกรณ์; ที่เหลือเป็น fallback ตอนฟอนต์ยังไม่ถึง
(display:swap)/โหลดไม่ได้ — ไทยได้ Leelawadee UI (Windows) / Thonburi (iPad)
/ Noto Sans Thai (Android/Linux) / heading ใช้คลาส font-display ที่นำ
General Sans มาก่อน Sarabun ด้วยหลักการเดียวกัน */
body {
font-family: var(--font-dm-sans, "DM Sans"), var(--font-sarabun, "Sarabun"),
-apple-system, "Segoe UI", "Helvetica Neue", Arial, "Leelawadee UI",
"Thonburi", "Noto Sans Thai", sans-serif;
}
/* M6: touch hygiene — ตัดแถบเทากระพริบของ iOS ทุกครั้งที่แตะปุ่ม และกัน
pull-to-refresh ของ Android ตอนอยู่ ๆ สุดขอบบน (reload = ล้าง undo
history ฝั่ง client ที่ไม่เก็บใน DB) */
html {
-webkit-tap-highlight-color: transparent;
overscroll-behavior-y: contain;
}
/* 2026-09-22: <main> ของ viewer เป็น scroll container เดียวของหน้า (แนวตั้ง +
แนวนอน — ซูมจนหน้ากว้างเกินจอแล้วปัดเลื่อนดูส่วนที่เกินได้) — จำกัด gesture
สัมผัสให้เป็นการแพนของกรอบนี้เท่านั้น ไม่ปล่อยให้ browser pinch-zoom/double-tap
zoom ทั้งหน้าเว็บ (อาการซูมแล้ว header/sidebar โต้ตามไปด้วย); ปุ่มซูม ± เป็น
ทางเลือก single-pointer ตาม WCAG 2.5.1 — ต้องอยู่ก่อน .pdf-custom-select
เพื่อให้โหมดมาร์กข้อความ (touch-action:none) ยังชนะตอนเปิดใช้ */
.pdf-scroll-main {
touch-action: pan-x pan-y;
}
/* custom selection ของ tool highlight บนจอสัมผัส (hooks/useCustomSelection) —
ปิด native selection/callout ของ OS และยึด gesture ลากเป็นของเราทั้งหมด
(touch-action:none กัน browser ยึดไปเป็น scroll → pointercancel)
hook ใส่/ถอด class นี้บน <main> ตาม enabled — ปิดแล้ว native selection
กลับมาทันที (tool อื่น long-press copy ของ OS ใช้ได้ตามปกติ) */
.pdf-custom-select {
user-select: none;
-webkit-user-select: none;
-webkit-touch-callout: none;
touch-action: none;
}
/* ช่องกรอกข้อมูลในหน้า (reply ของ comment pin, textbox) ที่อยู่ใต้ <main> ตอน
tool highlight — ต้องเลือก/คัดลอกข้อความในช่องและเลื่อนข้อความภายในช่อง (reply
ยาว) ได้ตามปกติ ไม่โดน touch-action:none ของ class ด้านบนลากไปด้วย (การ scroll
ในช่องจบที่ตัวช่องเองซึ่งเป็น scroll container — pointerdown ของ custom
selection ก็เริ่มบน .textLayer เท่านั้น ไม่ชนกัน) */
.pdf-custom-select input,
.pdf-custom-select textarea,
.pdf-custom-select [contenteditable="true"] {
user-select: text;
-webkit-user-select: text;
-webkit-touch-callout: default;
touch-action: auto;
}
/* เส้นคั่นบางระหว่างแถวตั้งค่า (PanelRow) ที่ต่อกันในเมนู tool/แผง selection —
ระยะ/เส้นเท่ากับแทรก <div class="my-1.5 border-t border-line"> ไว้ระหว่างแถว
ทุกจุด (แถวแรกของกลุ่มไม่มีเส้น — ไม่ซ้ำกับ divider ที่เมนูใส่ไว้เองแล้ว)
(#E8E8EC = token line — เดิม #f1f5f9 เป็น slate-100 หลงเหลือก่อน Genesis) */
.panel-row + .panel-row {
margin-top: 0.375rem;
padding-top: 0.625rem;
border-top: 1px solid #e8e8ec;
}
/* M6: safe-area — defensive (ค่าเป็น 0 จนกว่าจะใส่ viewport-fit=cover ใน meta
ของ layout — ยังไม่ใส่ตามขอบเขตงานนี้ ใส่ไว้ก่อนเพื่อไม่ต้องแก้ CSS ซ้ำ) */
header {
padding-top: env(safe-area-inset-top);
}
.comment-composer {
padding-bottom: env(safe-area-inset-bottom);
}
/* UX audit 2026-09-16 L7: เคารพ prefers-reduced-motion — ตัด transition ของ
sidebar, animation กระพริบของปุ่ม export และ smooth scroll ทุกจุด
(scrollToBelowHeader ฝั่ง JS เช็ค media query เดียวกันตอนเลือก behavior)
UX review m7 2026-09-23: เพิ่ม blanket rule ครอบ transition/animation ที่เหลือ
(hover-lift ของการ์ด, scale ของสวอตช์สี ฯลฯ) — ยก motion ทั้งหน้าให้จบทันที */
@media (prefers-reduced-motion: reduce) {
aside[data-sidebar] {
transition: none;
}
.animate-pulse {
animation: none;
}
*,
::before,
::after {
transition-duration: 0.01ms !important;
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
scroll-behavior: auto !important;
}
}