DESIGN.md · amontapdum.org

Nagi Glass 凪硝子

สเปกดีไซน์ของทั้งโดเมน — สี ตัวอักษร ระยะ และชิ้นส่วนที่ใช้จริง ทุกค่าตรงกับ CSS ที่รันอยู่ และ export ไปใช้กับ agent ได้

v1 · alpha 15 สี 6 ระดับตัวอักษร 4 รัศมี โหมดสว่าง / มืด
ดาวน์โหลด DESIGN.md
การตั้งค่าการแสดงผล

amontapdum.org is one portal that hosts several self-run systems: KHLANG (shop back office and POS), JERNEY (student activity registration), MUSUBI (link hub with click stats) and the shared account app. The design language is Nagi Glass: quiet Japanese calm expressed through a single soft motion curve. Native disclosure controls on the design document animate open and closed without changing their native keyboard semantics.

The window metaphor matters: every page is wrapped in a rounded frame with an even inset on all four sides, so the whole domain reads as one application window, not a set of loose pages.

The mood references rain, still water, hydrangea and evening lantern light. Japanese atmosphere comes from spacing, restraint and light — never from sakura, torii or decorative kanji.

Color palette

สีทั้งหมด · ค่า HEX และคอนทราสต์ที่วัดจากของจริง

#042249
primary ปุ่มหลัก แท็บที่เลือก โฟกัส 15.8:1 บน surface · AAA
#FFFFFF
on-primary ตัวอักษรบนพื้น primary 15.8:1 บน primary · AAA
#2E7D9E
secondary ลิงก์เงียบ · เน้นรอง 4.6:1 บน surface · AA
#7D8FD9
tertiary ไฮเดรนเยีย — ไล่เฉด/ภาพประกอบ 3.1:1 บน surface · AA-large
#E9EEF5
neutral พื้นหน้า · พื้นช่องกรอก พื้นผิว / เส้น — ไม่ใช้กับข้อความ
#FFFFFF
surface แผงทึบ (เมนู, โมดัล) พื้นผิว / เส้น — ไม่ใช้กับข้อความ
#0A0E2D
ink ตัวอักษรหลัก 18.9:1 บน surface · AAA
#5D687D
muted คำอธิบายรอง · เมตาดาตา 5.6:1 บน surface · AA
#E3E8F0
line เส้นคั่นบาง พื้นผิว / เส้น — ไม่ใช้กับข้อความ
#FFFFFF
desk พื้นโต๊ะรอบกรอบหน้าต่าง พื้นผิว / เส้น — ไม่ใช้กับข้อความ
#FAFBFF
glass พื้นการ์ดโปร่งขุ่น · ใช้ที่ 64% + blur พื้นผิว / เส้น — ไม่ใช้กับข้อความ
#6D3AC6
brand-jerney สีแบรนด์ JERNEY 6.8:1 บน surface · AA
#1B7A4B
success — 5.3:1 บน surface · AA
#9A5B00
warning — 5.4:1 บน surface · AA
#B3261E
danger — 6.5:1 บน surface · AA
  • Primary (#042249): deep night navy. The single interaction color for the hub and KHLANG —

active tab fills, primary buttons, focus rings. 15.8:1 on white.

  • On-primary (#FFFFFF): text and icons placed on a primary fill. It stays white even when the

device asks for a dark scheme, because the fill stays navy.

  • Secondary (#2E7D9E): still-water teal for quiet links and secondary emphasis.
  • Tertiary (#7D8FD9): hydrangea blue-purple used for accents, gradients and illustration.
  • Neutral (#E9EEF5): the page wash — cool paper that keeps glass cards readable.
  • Surface (#FFFFFF): opaque panel color for menus and modals. Panels must be opaque in front

of text; only cards may be translucent.

  • Ink (#0A0E2D): body text. Muted (#5D687D): captions, metadata, secondary labels.
  • Line (#E3E8F0): hairline dividers.
  • Desk (#FFFFFF): the surface around the window frame. White in light mode, #03060C in dark.
  • Success (#1B7A4B), Warning (#9A5B00), Danger (#B3261E): semantic fills for confirm, caution and

destructive actions. All three clear AA on white (5.3, 5.4 and 6.5:1) and carry white text. They keep these light-content values in both device schemes.

  • Glass (#FAFBFF): the base of every card. Cards are drawn at ~64% alpha over a tinted wash and

blurred, so the value reads as a cool frosted white, never a flat panel.

  • Brand JERNEY (#6D3AC6): JERNEY keeps its violet identity on the light surface. A lighter

#B79BF0 remains reserved for text placed on a dark photograph or overlay, never for a dark page.

Measured contrast (WCAG AA needs 4.5:1 for text)

Pair Ratio Verdict
ink #0A0E2D on surface #FFFFFF 18.9:1 AAA
primary #042249 on #FFFFFF 15.8:1 AAA
on-primary #FFFFFF on primary #042249 15.8:1 AAA
muted #5D687D on #FFFFFF 5.6:1 AA
secondary #2E7D9E on #FFFFFF 4.6:1 AA
brand-jerney #6D3AC6 on #FFFFFF 6.8:1 AA

Verify these with lint; the CLI checks every component's textColor against its backgroundColor and warns below 4.5:1.

Dark mode

Content is light in every scheme. There is no dark content palette: pages, cards, dialogs, menus, tables and form controls keep the values above whether the device asks for light or dark. color-scheme: light is declared on :root so the user agent renders scrollbars, form widgets and autofill light as well.

Only the window reacts to the device theme — the desk around the frame and its hairline rim:

Role Light scheme Dark scheme
desk (--ng-desk) #FFFFFF #03060C
rim (--ng-rim) rgba(10,14,45,.16) rgba(255,255,255,.18)

The dark media query therefore contains exactly those two tokens and nothing else. Adding a content rule inside it is a defect: an app that painted itself dark in the dark scheme (KHLANG, JERNEY, MUSUBI, ACCOUNT) has been returned to the light values.

Typography

ตัวอักษร 6 ระดับ · Prompt สำหรับหัวข้อ (ไทย + ละติน) · Noto Sans Thai Looped สำหรับเนื้อหา · JetBrains Mono สำหรับโค้ด

display Prompt · 2.5rem · w600 · lh 1.4
静かに、強く、光るรวมระบบงานที่ใช้งานจริงไว้ในที่เดียวThe quick brown fox0123456789 · ๑๒๓๔๕๖๗๘๙
h1 Prompt · 2rem · w600 · lh 1.35
静かに、強く、光るรวมระบบงานที่ใช้งานจริงไว้ในที่เดียวThe quick brown fox0123456789 · ๑๒๓๔๕๖๗๘๙
h2 Prompt · 1.375rem · w600 · lh 1.45
静かに、強く、光るรวมระบบงานที่ใช้งานจริงไว้ในที่เดียวThe quick brown fox0123456789 · ๑๒๓๔๕๖๗๘๙
body-md Noto Sans Thai Looped · 1rem · w400 · lh 1.75
静かに、強く、光るรวมระบบงานที่ใช้งานจริงไว้ในที่เดียวThe quick brown fox0123456789 · ๑๒๓๔๕๖๗๘๙
label-sm Noto Sans Thai Looped · 0.8125rem · w500 · lh 1.5
静かに、強く、光るรวมระบบงานที่ใช้งานจริงไว้ในที่เดียวThe quick brown fox0123456789 · ๑๒๓๔๕๖๗๘๙
code JetBrains Mono · 0.875rem · w400 · lh 1.65
静かに、強く、光るรวมระบบงานที่ใช้งานจริงไว้ในที่เดียวThe quick brown fox0123456789 · ๑๒๓๔๕๖๗๘๙

Layout · spacing & radius

ระยะห่าง 7 ขั้น และรัศมีมุม 4 ระดับ

xs4px
sm8px
md12px
lg16px
xl20px
2xl24px
3xl32px
sm12px
md16px
lg28px
pill999px

A 4px base scale, used in this order: 4 · 8 · 12 · 16 · 20 · 24 · 32. The window frame uses a symmetric inset (7px desktop, 4px at ≤880px) — the same value on all four sides, never a different value to dodge a scrollbar.

Content columns stay narrow enough to keep line length comfortable; cards breathe rather than pack. Negative space is the primary luxury signal.

  • sm 12px — inputs on small screens, small tiles.
  • md 16px — window frame, medium panels.
  • lg 28px — cards and modals.
  • pill 999px — every button, tab, chip and text field. Capsules are the identity.

Radii shrink with the element: a frame inset of 7px cannot carry a 26px radius without looking like a pill, so the frame radius is 16px.

Components

ชิ้นส่วนที่ใช้จริง พร้อม markup ตัวอย่าง

nav-tab-active
amontapdum.org ภาพรวม KHLANG JERNEY
button · primary / secondary / danger
ปุ่มที่กดไม่ได้ใช้ opacity 45%
chip · neutral / status
สต็อก POS รายงาน
พร้อมใช้งาน ใกล้เต็ม ล้มเหลว
input-capsule · 48px
โฟกัสแล้วขอบเปลี่ยนเป็นสี primary
card-glass · 28px
คลัง KHLANG

ระบบหลังร้านสำหรับร้านค้า จัดการสินค้าและสต็อก บันทึกการขาย ออกใบเสร็จ

เปิดใช้งาน →
  • window-frame — fixed rounded overlay around every page, symmetric inset (7px desktop, 4px at

≤880px), desk ring masking scrolled content; never a border on <body>.

  • nav-shell — floating capsule bar holding the wordmark and link pills; horizontally snug,

collapsing to one menu button at ≤880px.

  • nav-tab-active — filled brand-primary capsule with on-primary text in both device schemes.
  • button-primary — the single high-emphasis action per view. JERNEY swaps in its own violet.
  • button-secondary — white surface, 1px line border, ink label: the "look closer" action that

sits next to a primary. Never give it the primary fill — two filled buttons read as two equal choices.

  • button-danger — danger fill, white label, used only for destructive actions (delete, revoke,

wipe). Pair it with a confirmation; never place it first in a row.

  • chip-success / chip-warning — status pills; the dot or fill carries the meaning, the label stays

short ("พร้อมใช้งาน", "ใกล้เต็ม"). Red is reserved for failure and destructive intent, amber for attention, green for healthy — never reuse these three as decoration.

  • card-glass — translucent card, one shadow, 28px radius, 24px padding.
  • chip — neutral capsule for tags and status; neutral means "no judgement", semantic status uses

the success/warning/danger tokens instead.

  • input-capsule — 48px tall pill field with 18px horizontal padding; background always set

explicitly and color-scheme: light keeps native fields consistent in both device schemes.

  • link-quiet — teal text link, no underline until hover.
  • divider-line — hairline separator in line, paired with a muted caption.

Component previews sit directly on the section card — one layer, never a card inside a card. Groups are separated by a small uppercase label with a hairline under it, not by another bordered box. A control nested two cards deep loses its edge and the page starts to look like a mockup of itself.

The /designmd page keeps the main Download DESIGN.md action prominent, with copy and the visual/source switch beside it. Content and previews are locked to light; the dark-preview control is disabled. Only the outer window desk/rim follows the device scheme. Keep a single clear home navigation path, mark the current page with aria-current="page", and collapse technical metadata and agent-use details on narrow screens. Its navigation is for moving between pages only; do not add authentication or permission behavior to this document view.

Native disclosures

The design document's details controls animate both expansion and collapse using measured panel height and opacity. Keep native details/summary semantics so Enter and Space toggle them; preserve focus on the summary and keep closed content out of keyboard navigation. Use a visible +/− state, consistent hover, pressed, and focus-visible feedback, and allow panel height to follow content (never clamp or use a fixed maximum). Rapid toggles must cancel/finish the previous transition without stale callbacks. Re-measure segmented thumbs after disclosure layout settles. Under prefers-reduced-motion: reduce, remove animation while retaining instant native state changes. Copy feedback must reset; disabled demo controls must be semantically disabled; dialogs must restore focus to their trigger on close.

Motion

อนิเมชั่นชุดเดียวกับ musubi · เข้าและออกครบคู่

popup · enter / exit
300msenter
220msexit
240msbackdrop
กดปุ่มหรือคลิก/แตะพื้นหลังเพื่อดูขาออก
stagger · เข้าทีละชิ้น
  • การ์ดใบแรก+0ms
  • การ์ดใบที่สอง+60ms
  • การ์ดใบที่สาม+120ms
  • การ์ดใบที่สี่+180ms
แชร์ลิงก์musubi · ตัวอย่างอนิเมชั่น
ทุกองค์ประกอบไต่ขึ้น 8px แล้วค่อย ๆ ชัด — ปุ่มด้านล่างไล่เข้ามาทีละ 40ms ตอนปิด เนื้อหายุบลง 4px ก่อนกล่องจะจางหาย ไม่ตัดจบเฉย ๆ

Motion is lifted whole from musubi: one enter curve, one exit curve, and every popup animates both ways. Nothing appears by cutting in, and nothing disappears by vanishing — the close is what makes the open feel deliberate.

Token Value Use
--ng-ease-out cubic-bezier(.16,1,.3,1) everything entering
--ng-ease-in cubic-bezier(.4,0,.7,1) everything leaving
--ng-dur 300ms popup / dialog enter
--ng-dur-scrim 240ms backdrop fade in
--ng-dur-fast 180ms inner rows leaving
--ng-dur-slow 440ms the QR block / hero scale pop
--ng-exit 220ms popup leave

Enter: translateY(14px) scale(.965) → none, opacity 0 → 1, .30s on ease-out. Exit: none → translateY(10px) scale(.97), opacity → 0, .22s on ease-in. Inner content rises 8px on enter (.28s ease, staggered 0 / 60 / 130ms) and drops 4px on exit (.18s ease), so the panel and its contents move as one object instead of two.

Lists and grids: rise .36s on ease-out, staggered --d at 38–60ms per item, capped near 260ms. Past ten items the stagger stops adding delay — a long list should not take two seconds to appear.

Closing is a state, not an instant. Add .closing (or .ng-closing), let the exit animation run, then call dialog.close() on animationend with a setTimeout fallback. Calling close() first removes ::backdrop immediately and the dialog blinks out with no exit at all.

Usage. Markup carries the classes, the shared script carries the timing:

<button data-ng-open="#share">แชร์</button>
<dialog class="ng-dialog" id="share" data-ng-dialog>
  <div class="ng-dialog-head">…</div>
  <div class="ng-dialog-body">…</div>
  <div class="ng-dialog-foot ng-seq"><button>ยกเลิก</button><button class="primary">คัดลอก</button></div>
</dialog>

NagiGlass.dialog.open(el) / .close(el) handle showModal, the .ng-closing state, the animationend wait and the cancel (Esc) event. data-ng-open / data-ng-close bind themselves.

Reduced motion: every animation above is disabled under prefers-reduced-motion: reduce; the classes and the JS helper both check it. Never gate content behind an animation that a user turned off.

Elevation & depth

ความลึกมาจากความโปร่งใส + เงาชั้นเดียว

Depth comes from translucency plus one soft shadow — never from stacked borders or bevels.

  • Card: 0 0 0 1px rgba(10,14,45,.05), 0 12px 34px rgba(86,96,160,.13) on a 64% white fill with

backdrop-filter: blur(22px) saturate(1.45).

  • Lifted (popovers, submenus): 0 10px 26px rgba(10,14,45,.12).
  • Window frame: a 1px rgba(10,14,45,.16) hairline plus a soft inset shadow. On a white desk

the rim must stay a faint ink hairline — a white ring disappears entirely.

Anti-neumorphism rule: no inset shadows, no dual light/dark bevels. A surface is either glass or paper; it never pretends to be carved.

Do's & Don'ts

กฎที่ยึด เพื่อไม่ให้ดีไซน์เพี้ยนระหว่างทาง

ควรทำ Do

  • Keep content tokens light and change only outer desk/rim tokens with the device scheme.
  • Keep panels that sit in front of text fully opaque.
  • Measure the rendered result (getComputedStyle, getBoundingClientRect) before calling a layout correct.

ไม่ควรทำ Don't

  • Hardcode foregrounds on accent fills; use on-accent so brand and semantic pairings stay readable.
  • Add dark content overrides to brand classes; the only device-dependent colours are the outer desk/rim.
  • Add sakura, torii, red suns or decorative kanji to signal "Japanese".
  • Animate more than one property family at a time; one curve, one distance.
  • Inline arbitrary brand hex values into a component demo; demos use var(--primary) and related shared tokens.
  • Nest cards. One card per level: the section is the card, everything inside it is content. If a group needs separation, use a label and a hairline — not a second border.

Application UI verification

กติกา KHLANG / JERNEY · ผลตรวจจริงและขอบเขตที่ยังไม่ทดสอบ

KHLANG: geometry and interaction

  • Mobile bottom sheets retain 16px left/right gutters and a zero external bottom gap. Internal padding accommodates safe-area-inset-bottom. Rounded top corners follow the 28px surface token; the lower edge is docked, not a floating rounded card.
  • Centered settings dialogs and desktop modals retain their own placement. Do not force all overlays into one alignment.
  • All modal overlays escape animated ancestors through a body Teleport. The scrim blurs by 8px and the content panel is opaque white. The outer frame remains above overlays and never captures pointer events.
  • Product category rails reserve at least 56px and use flex-shrink:0; long product lists scroll independently without squeezing the rail, search field or heading.
  • Page-entry motion uses backwards fill, not persistent transform fill. After entry, the parent transform must be none, and a fixed POS action bar must measure bottom gap zero at 360, 390, 768 and 1280px widths.
  • Mobile table labels use the Thai body font even if their barcode or numeric value is monospace. Apply the body font directly to the label pseudo-element; inheriting from the data cell can render Thai as missing glyphs.
  • Async modal openers must preserve their return-focus target before loading disables the trigger. Escape/cancel restores focus after the modal closes and the loading state settles.

KHLANG audit coverage

The source router inventory contains 22 page route patterns, including login, shop pages, administrator pages, parameterized shop administrator pages and the not-found page. Public-production observations include 42 authenticated/not-found page-and-viewport rows at 390 and 1280px, plus four anonymous login/theme rows. The owner role was reviewed separately from the platform administrator role: a platform administrator with no shop memberships is not proof that owner pages have been reviewed with shop data.

The completed visual-layout sweep found no document horizontal overflow or body/text theme switching in those measured rows. Additional safe interaction checks opened/cancelled product, supplier, purchase-order, shop and stock-issue forms. During this round, fixed POS action-bar positioning, purchase-order opener focus restoration, and Thai mobile table-label fonts were corrected and rechecked on public production.

The receipt was rendered with synthetic component props in a local harness, with print CSS and event emission checked. This is not a completed production sale or proof of printer integration. No real sale, payment, purchase-order commit, inventory mutation, permission change, print job or export was submitted. Record-dependent tracking/receive/cancel workflows and every possible data state are not certified by this read-only UI review. A route visit, source check or empty state must never be labelled a fully tested workflow.

JERNEY: geometry and interaction

  • QR, certificate and confirmation dialogs use the shared AppModal lifecycle. The panel is opaque white, radius 28px; backdrop blur is 8px and modal z-index is 100, below the persistent frame at 200.
  • Enter panel motion is 300ms, leave panel motion 220ms, and backdrop lifecycle 240ms. Keep the Teleport outside the Transition and return focus only after leave finishes.
  • Escape must be tested from the focused element so the real bubbling path reaches the modal keyboard handler. Dispatching a keyboard event directly on window does not exercise a handler on the modal ancestor.
  • Event creation/editing and organization membership editors are inline forms, not popups. Do not report a missing modal as a defect for these controls. Never click submit while auditing an opener.

JERNEY audit coverage

Public-production read-only audit: 2026-10-20 UTC. The source router contains 15 route patterns, including the legacy ticket/feedback aliases. Fourteen patterns were visited at 390 and 1280px (28 page-and-viewport rows), with anonymous staff login separately checked in both device schemes. Measured document overflow was zero and body foreground/background stayed light in those rows. Admin, organizer dashboard and staff check-in were actually authenticated, not counted from a login redirect.

The public event page, calendar list/grid controls, empty personal ticket/history pages, administrator event editor, organization membership editor, QR and certificate dialogs were inspected without submitting writes. QR and certificate panels were white, within the viewport, with Escape closing and opener focus restored. The QR panel was tested by real touch at 390 x 500: scrollTop changed from 0 to 200 while the underlying page stayed at scrollY 0. The previously missing backdrop blur was added and the modal layer moved beneath the persistent frame. Panel enter/leave was measured as 300/220ms.

Coverage limits: certificate-detail, valid-ticket and feedback-success layouts require an owned valid registration/check-in. Only invalid-identifier and permission/error states were exercised on those parameterized routes; this is not success-state verification. No registration, check-in, certificate issue/upload, account/organization change, event publication/deletion, email, native share or file download was submitted. Real camera hardware and offline ticket recovery are not certified by this round. Empty dashboard/guest datasets do not certify populated tables or charts. These limits must remain visible beside the audit result.

Verification evidence and completion rule

The visual audit and contract tests are separate evidence. KHLANG web contract tests: 12 pass, 0 fail; JERNEY web contract tests: 11 pass, 0 fail. Both web typechecks and production builds completed. These are UI regression tests, not a claim that every API/business acceptance suite ran. Targeted deploys uploaded hashed assets before the HTML shell and verified uploaded content by read-back hashes.

A completed UI review means the stated read-only matrix has been recorded, issues found in that matrix corrected and retested, and unexecuted states named. It must never be shortened to "every workflow passed". Keep personal data, session cookies, bearer tokens, private ticket URLs and reviewer identities out of this public document.

ต้นฉบับ DESIGN.md

ไฟล์ต้นทางที่ agent และเครื่องมืออ่านได้

---
version: alpha-rollout-2026-10-20
name: amontapdum.org — Nagi Glass
description: Japanese-calm liquid glass for a self-hosted work portal. One design language across KHLANG, JERNEY, MUSUBI and the account app; content is light in every scheme, only the window frame follows the device theme.
colors:
  primary: "#042249"
  on-primary: "#FFFFFF"
  secondary: "#2E7D9E"
  tertiary: "#7D8FD9"
  neutral: "#E9EEF5"
  surface: "#FFFFFF"
  ink: "#0A0E2D"
  muted: "#5D687D"
  line: "#E3E8F0"
  desk: "#FFFFFF"
  glass: "#FAFBFF"
  brand-jerney: "#6D3AC6"
  success: "#1B7A4B"
  warning: "#9A5B00"
  danger: "#B3261E"
typography:
  display:
    fontFamily: Prompt
    fontSize: 2.5rem
    fontWeight: 600
    lineHeight: 1.4
    letterSpacing: 0em
  h1:
    fontFamily: Prompt
    fontSize: 2rem
    fontWeight: 600
    lineHeight: 1.35
  h2:
    fontFamily: Prompt
    fontSize: 1.375rem
    fontWeight: 600
    lineHeight: 1.45
  body-md:
    fontFamily: Noto Sans Thai Looped
    fontSize: 1rem
    fontWeight: 400
    lineHeight: 1.75
  label-sm:
    fontFamily: Noto Sans Thai Looped
    fontSize: 0.8125rem
    fontWeight: 500
    lineHeight: 1.5
  code:
    fontFamily: JetBrains Mono
    fontSize: 0.875rem
    fontWeight: 400
    lineHeight: 1.65
rounded:
  sm: 12px
  md: 16px
  lg: 28px
  pill: 999px
spacing:
  xs: 4px
  sm: 8px
  md: 12px
  lg: 16px
  xl: 20px
  2xl: 24px
  3xl: 32px
components:
  window-frame:
    backgroundColor: "{colors.desk}"
    rounded: "{rounded.md}"
    padding: 7px
  nav-shell:
    backgroundColor: "{colors.surface}"
    textColor: "{colors.ink}"
    rounded: "{rounded.pill}"
    padding: {spacing.sm}
  nav-tab-active:
    backgroundColor: "{colors.primary}"
    textColor: "{colors.on-primary}"
    rounded: "{rounded.pill}"
    padding: {spacing.sm}
  button-primary:
    backgroundColor: "{colors.primary}"
    textColor: "{colors.on-primary}"
    rounded: "{rounded.pill}"
    padding: {spacing.lg}
  button-primary-jerney:
    backgroundColor: "{colors.brand-jerney}"
    textColor: "{colors.on-primary}"
    rounded: "{rounded.pill}"
    padding: {spacing.lg}
  card-glass:
    backgroundColor: "{colors.glass}"
    textColor: "{colors.ink}"
    rounded: "{rounded.lg}"
    padding: {spacing.2xl}
  chip:
    backgroundColor: "{colors.neutral}"
    textColor: "{colors.ink}"
    rounded: "{rounded.pill}"
    padding: {spacing.sm}
  input-capsule:
    backgroundColor: "{colors.neutral}"
    textColor: "{colors.ink}"
    rounded: "{rounded.pill}"
    height: 48px
  link-quiet:
    textColor: "{colors.secondary}"
    typography: "{typography.label-sm}"
  divider-line:
    backgroundColor: "{colors.line}"
    height: 1px
  button-secondary:
    backgroundColor: "{colors.surface}"
    textColor: "{colors.ink}"
    rounded: "{rounded.pill}"
    padding: {spacing.lg}
  button-danger:
    backgroundColor: "{colors.danger}"
    textColor: "{colors.on-primary}"
    rounded: "{rounded.pill}"
    padding: {spacing.lg}
  chip-success:
    backgroundColor: "{colors.success}"
    textColor: "{colors.on-primary}"
    rounded: "{rounded.pill}"
    padding: {spacing.sm}
  chip-warning:
    backgroundColor: "{colors.warning}"
    textColor: "{colors.on-primary}"
    rounded: "{rounded.pill}"
    padding: {spacing.sm}
  caption-muted:
    textColor: "{colors.muted}"
    typography: "{typography.label-sm}"
---

## Overview

**amontapdum.org** is one portal that hosts several self-run systems: KHLANG (shop back office
and POS), JERNEY (student activity registration), MUSUBI (link hub with click stats) and the
shared account app. The design language is **Nagi Glass**: quiet Japanese calm expressed through
a single soft motion curve. Native disclosure controls on the design document animate open and closed without changing their native keyboard semantics.

The window metaphor matters: every page is wrapped in a rounded frame with an even inset on all
four sides, so the whole domain reads as **one application window**, not a set of loose pages.

The mood references rain, still water, hydrangea and evening lantern light. Japanese atmosphere
comes from **spacing, restraint and light** — never from sakura, torii or decorative kanji.

## Colors

- **Primary (#042249):** deep night navy. The single interaction color for the hub and KHLANG —
  active tab fills, primary buttons, focus rings. 15.8:1 on white.
- **On-primary (#FFFFFF):** text and icons placed on a primary fill. It stays white even when the
  device asks for a dark scheme, because the fill stays navy.
- **Secondary (#2E7D9E):** still-water teal for quiet links and secondary emphasis.
- **Tertiary (#7D8FD9):** hydrangea blue-purple used for accents, gradients and illustration.
- **Neutral (#E9EEF5):** the page wash — cool paper that keeps glass cards readable.
- **Surface (#FFFFFF):** opaque panel color for menus and modals. Panels must be opaque in front
  of text; only cards may be translucent.
- **Ink (#0A0E2D):** body text. **Muted (#5D687D):** captions, metadata, secondary labels.
- **Line (#E3E8F0):** hairline dividers.
- **Desk (#FFFFFF):** the surface around the window frame. White in light mode, `#03060C` in dark.
- **Success (#1B7A4B), Warning (#9A5B00), Danger (#B3261E):** semantic fills for confirm, caution and
  destructive actions. All three clear AA on white (5.3, 5.4 and 6.5:1) and carry white text.
  They keep these light-content values in both device schemes.
- **Glass (#FAFBFF):** the base of every card. Cards are drawn at ~64% alpha over a tinted wash and
  blurred, so the value reads as a cool frosted white, never a flat panel.
- **Brand JERNEY (#6D3AC6):** JERNEY keeps its violet identity on the light surface. A lighter
  `#B79BF0` remains reserved for text placed on a dark photograph or overlay, never for a dark page.

### Measured contrast (WCAG AA needs 4.5:1 for text)

| Pair | Ratio | Verdict |
| --- | --- | --- |
| ink `#0A0E2D` on surface `#FFFFFF` | 18.9:1 | AAA |
| primary `#042249` on `#FFFFFF` | 15.8:1 | AAA |
| on-primary `#FFFFFF` on primary `#042249` | 15.8:1 | AAA |
| muted `#5D687D` on `#FFFFFF` | 5.6:1 | AA |
| secondary `#2E7D9E` on `#FFFFFF` | 4.6:1 | AA |
| brand-jerney `#6D3AC6` on `#FFFFFF` | 6.8:1 | AA |

Verify these with `lint`; the CLI checks every component's `textColor` against its
`backgroundColor` and warns below 4.5:1.

### Dark mode

**Content is light in every scheme.** There is no dark content palette: pages, cards, dialogs,
menus, tables and form controls keep the values above whether the device asks for light or dark.
`color-scheme: light` is declared on `:root` so the user agent renders scrollbars, form widgets
and autofill light as well.

Only the **window** reacts to the device theme — the desk around the frame and its hairline rim:

| Role | Light scheme | Dark scheme |
| --- | --- | --- |
| desk (`--ng-desk`) | `#FFFFFF` | `#03060C` |
| rim (`--ng-rim`) | `rgba(10,14,45,.16)` | `rgba(255,255,255,.18)` |

The dark media query therefore contains exactly those two tokens and nothing else. Adding a
content rule inside it is a defect: an app that painted itself dark in the dark scheme (KHLANG,
JERNEY, MUSUBI, ACCOUNT) has been returned to the light values.

## Typography

Six scales, three families, one rhythm. Headlines are **Prompt** for Thai and Latin alike;
running text is **Noto Sans Thai Looped**; anything machine-readable is **JetBrains Mono**.

| Scale | Family | Size | Weight | Line height |
| --- | --- | --- | --- | --- |
| display | Prompt | 40px | 600 | 1.40 |
| h1 | Prompt | 32px | 600 | 1.35 |
| h2 | Prompt | 22px | 600 | 1.45 |
| body-md | Noto Sans Thai Looped | 16px | 400 | 1.75 |
| label-sm | Noto Sans Thai Looped | 13px | 500 | 1.50 |
| code | JetBrains Mono | 14px | 400 | 1.65 |

Thai headings sit at 600: Prompt's Thai strokes start closing up above that weight, and asking for
800 would make the browser fake-bold a face that stops at 700. Heading line-height stays at ≥1.35
(1.35–1.45) and tracking at `0` — negative tracking pulls Thai tone marks into the line above and
makes the vowels touch.

Never set Thai text below 13px; the tone marks collide and the vowels fill in.

## Type stack — three families, one voice

- **Prompt — headings, Thai and Latin.** One family covers both scripts, so a headline never mixes
  two designs: Latin words and Thai words share the same skeleton, weight and rhythm. Weight 600 —
  heavy enough to lead, light enough that Thai tone marks stay clear.
- **Noto Sans Thai Looped — body.** Looped Thai letterforms match how Thai is read in print; it is
  the only family used for running text, inputs and captions. Weights 400–600.
- **JetBrains Mono — code, hex, numbers.** Mono numerals keep columns of stats and swatches from
  jittering, and its curved terminals sit next to Prompt without fighting it. Weight 400–500.

Prompt carries Thai and Latin glyphs itself, so `--font-head` lists Prompt first and Noto Sans Thai
Looped only as a fallback for a failed font request. Nunito and Mitr are retired from headings
(their files stay on disk so cached pages keep working) — never list them in a new heading stack,
and never split a headline across two families again.

Numbers inside prose stay in the body family; numbers inside tables, stats, hex chips and code use
`JetBrains Mono` with `font-variant-numeric: tabular-nums`.


## Layout

A 4px base scale, used in this order: 4 · 8 · 12 · 16 · 20 · 24 · 32.
The window frame uses a symmetric inset (`7px` desktop, `4px` at ≤880px) — the same value on
all four sides, never a different value to dodge a scrollbar.

Content columns stay narrow enough to keep line length comfortable; cards breathe rather than
pack. Negative space is the primary luxury signal.

## Motion

Motion is lifted whole from musubi: one enter curve, one exit curve, and every popup animates both
ways. Nothing appears by cutting in, and nothing disappears by vanishing — the close is what makes the
open feel deliberate.

| Token | Value | Use |
| --- | --- | --- |
| `--ng-ease-out` | `cubic-bezier(.16,1,.3,1)` | everything entering |
| `--ng-ease-in` | `cubic-bezier(.4,0,.7,1)` | everything leaving |
| `--ng-dur` | `300ms` | popup / dialog enter |
| `--ng-dur-scrim` | `240ms` | backdrop fade in |
| `--ng-dur-fast` | `180ms` | inner rows leaving |
| `--ng-dur-slow` | `440ms` | the QR block / hero scale pop |
| `--ng-exit` | `220ms` | popup leave |

**Enter:** `translateY(14px) scale(.965)` → `none`, opacity 0 → 1, `.30s` on `ease-out`.
**Exit:** `none` → `translateY(10px) scale(.97)`, opacity → 0, `.22s` on `ease-in`.
Inner content rises 8px on enter (`.28s ease`, staggered `0 / 60 / 130ms`) and drops 4px on exit
(`.18s ease`), so the panel and its contents move as one object instead of two.

**Lists and grids:** `rise .36s` on `ease-out`, staggered `--d` at 38–60ms per item, capped near
260ms. Past ten items the stagger stops adding delay — a long list should not take two seconds to appear.

**Closing is a state, not an instant.** Add `.closing` (or `.ng-closing`), let the exit animation run,
then call `dialog.close()` on `animationend` with a `setTimeout` fallback. Calling `close()` first
removes `::backdrop` immediately and the dialog blinks out with no exit at all.

**Usage.** Markup carries the classes, the shared script carries the timing:

```html
<button data-ng-open="#share">แชร์</button>
<dialog class="ng-dialog" id="share" data-ng-dialog>
  <div class="ng-dialog-head">…</div>
  <div class="ng-dialog-body">…</div>
  <div class="ng-dialog-foot ng-seq"><button>ยกเลิก</button><button class="primary">คัดลอก</button></div>
</dialog>
```

`NagiGlass.dialog.open(el)` / `.close(el)` handle `showModal`, the `.ng-closing` state, the
`animationend` wait and the `cancel` (Esc) event. `data-ng-open` / `data-ng-close` bind themselves.

**Reduced motion:** every animation above is disabled under `prefers-reduced-motion: reduce`; the
classes and the JS helper both check it. Never gate content behind an animation that a user turned off.

## Elevation & Depth

Depth comes from translucency plus one soft shadow — never from stacked borders or bevels.

- **Card:** `0 0 0 1px rgba(10,14,45,.05), 0 12px 34px rgba(86,96,160,.13)` on a 64% white fill with
  `backdrop-filter: blur(22px) saturate(1.45)`.
- **Lifted (popovers, submenus):** `0 10px 26px rgba(10,14,45,.12)`.
- **Window frame:** a 1px `rgba(10,14,45,.16)` hairline plus a soft inset shadow. On a white desk
  the rim must stay a faint ink hairline — a white ring disappears entirely.

Anti-neumorphism rule: no inset shadows, no dual light/dark bevels. A surface is either glass or
paper; it never pretends to be carved.

## Glass surfaces — the card recipe

Cards are the signature of this system: light, cool, slightly milky. The wash behind them is what
makes them readable, so both layers ship together.

**Page wash** (behind everything):

```css
background:
  radial-gradient(820px 480px at 6% -10%, rgba(200,184,255,.55), transparent 62%),
  radial-gradient(720px 460px at 96% 2%,  rgba(158,203,255,.50), transparent 60%),
  radial-gradient(900px 520px at 50% 108%, rgba(158,203,255,.50), transparent 65%),
  #F2F5F9;
background-attachment: fixed;
```

**Card**:

```css
background: rgba(255,255,255,.64);
backdrop-filter: blur(22px) saturate(1.45);
border: 1px solid rgba(255,255,255,.78);
border-radius: 28px;
box-shadow: 0 0 0 1px rgba(10,14,45,.05), 0 12px 34px rgba(86,96,160,.13);
```

**Rules**

- The card stays at 60–70% alpha. Below ~55% the text starts to fight the wash; above ~75% it stops
  reading as glass and becomes a plain panel.
- Text on glass uses `ink`; the composited card must stay lighter than `#F4F5FA` so `ink` keeps
  ~17:1. Never put `muted` on glass for small text unless it is ≥13px and ≥5:1.
- Colour chips, code blocks and modals sit **opaque** (`surface`): a frosted surface behind a colour
  sample lies about the colour.
- Device dark mode does not alter this light card recipe; only the window desk/rim changes.
- If `backdrop-filter` is unsupported, fall back to `92%` opaque `surface` — never leave a raw
  transparent card.

## Window frame

The frame is not a border on `<body>` — a body border scrolls away with the document and the page
stops reading as one window. It is a **fixed overlay** drawn twice, in this order:

```css
/* layer 1 — opaque desk ring: masks anything that scrolls past the edge */
.ng-frame::before { box-shadow: 0 0 0 999px var(--ng-desk); }

/* layer 2 — the visible edge, painted on top */
.ng-frame::after  { border: 1px solid rgba(10,14,45,.16); border-radius: 16px; }
```

`position: fixed` + `height: 100dvh` keeps all four sides on screen while mobile browsers collapse
or expand their URL bar — `100vh` alone leaves the bottom edge below the visible viewport.

The ring must be opaque: it is what hides content that has scrolled past the rounded corner. Draw it
on the *lower* pseudo-element and the border on the upper one, otherwise the ring paints over the
hairline and the frame vanishes. `pointer-events: none` keeps it out of the way of taps.

### KHLANG app-local frame and navigation sheet

When a Vue app cannot consume the shared navigation script, use an app-local fixed frame overlay
with the same inset/radius/desk tokens and render it above every page layer. Keep all four corners
and rim visible while scrolling and while menus are open. The app's mobile navigation is an opaque
surface sheet with a 45–60% scrim, 300ms enter (`cubic-bezier(.16,1,.3,1)`), 220ms leave
(`cubic-bezier(.4,0,.7,1)`), and 240ms scrim fade. Keep the closing DOM mounted until its leave
transition ends; Escape and backdrop dismiss; trap focus, restore it to the trigger, and lock body
scroll. Reduced-motion removes transitions. Do not inject the shared nav script into an app that
already owns its own header/menu. Search controls with a leading icon must reserve icon width plus
at least 8px before editable text; audit effective padding after global form rules.

## Shapes

- `sm` 12px — inputs on small screens, small tiles.
- `md` 16px — window frame, medium panels.
- `lg` 28px — cards and modals.
- `pill` 999px — every button, tab, chip and text field. Capsules are the identity.

Radii shrink with the element: a frame inset of 7px cannot carry a 26px radius without looking
like a pill, so the frame radius is 16px.

## Components

- **window-frame** — fixed rounded overlay around every page, symmetric inset (7px desktop, 4px at
  ≤880px), desk ring masking scrolled content; never a border on `<body>`.
- **nav-shell** — floating capsule bar holding the wordmark and link pills; horizontally snug,
  collapsing to one menu button at ≤880px.
- **nav-tab-active** — filled brand-primary capsule with on-primary text in both device schemes.
- **button-primary** — the single high-emphasis action per view. JERNEY swaps in its own violet.
- **button-secondary** — white surface, 1px `line` border, `ink` label: the "look closer" action that
  sits next to a primary. Never give it the primary fill — two filled buttons read as two equal choices.
- **button-danger** — `danger` fill, white label, used only for destructive actions (delete, revoke,
  wipe). Pair it with a confirmation; never place it first in a row.
- **chip-success / chip-warning** — status pills; the dot or fill carries the meaning, the label stays
  short ("พร้อมใช้งาน", "ใกล้เต็ม"). Red is reserved for failure and destructive intent, amber for
  attention, green for healthy — never reuse these three as decoration.
- **card-glass** — translucent card, one shadow, 28px radius, 24px padding.
- **chip** — neutral capsule for tags and status; neutral means "no judgement", semantic status uses
  the success/warning/danger tokens instead.
- **input-capsule** — 48px tall pill field with 18px horizontal padding; background always set
  explicitly and `color-scheme: light` keeps native fields consistent in both device schemes.
- **link-quiet** — teal text link, no underline until hover.
- **divider-line** — hairline separator in `line`, paired with a muted caption.

Component previews sit **directly on the section card** — one layer, never a card inside a card. Groups
are separated by a small uppercase label with a hairline under it, not by another bordered box. A
control nested two cards deep loses its edge and the page starts to look like a mockup of itself.

The `/designmd` page keeps the main **Download DESIGN.md** action prominent, with copy and the
visual/source switch beside it. Content and previews are locked to light; the dark-preview control
is disabled. Only the outer window desk/rim follows the device scheme.
Keep a single clear home navigation path, mark the current page with `aria-current="page"`, and
collapse technical metadata and agent-use details on narrow screens. Its navigation is for moving
between pages only; do not add authentication or permission behavior to this document view.



### Native disclosures

The design document's `details` controls animate both expansion and collapse using measured panel height and opacity. Keep native `details`/`summary` semantics so Enter and Space toggle them; preserve focus on the summary and keep closed content out of keyboard navigation. Use a visible `+`/`−` state, consistent hover, pressed, and focus-visible feedback, and allow panel height to follow content (never clamp or use a fixed maximum). Rapid toggles must cancel/finish the previous transition without stale callbacks. Re-measure segmented thumbs after disclosure layout settles. Under `prefers-reduced-motion: reduce`, remove animation while retaining instant native state changes. Copy feedback must reset; disabled demo controls must be semantically disabled; dialogs must restore focus to their trigger on close.


## Do's and Don'ts

- **Do** keep content tokens light and change only outer desk/rim tokens with the device scheme.
- **Do** keep panels that sit in front of text fully opaque.
- **Do** measure the rendered result (`getComputedStyle`, `getBoundingClientRect`) before calling
  a layout correct.
- **Don't** hardcode foregrounds on accent fills; use on-accent so brand and semantic pairings stay readable.
- **Don't** add dark content overrides to brand classes; the only device-dependent colours are the outer desk/rim.
- **Don't** add sakura, torii, red suns or decorative kanji to signal "Japanese".
- **Don't** animate more than one property family at a time; one curve, one distance.
- **Don't** inline arbitrary brand hex values into a component demo; demos use `var(--primary)` and related shared tokens.
- **Don't** nest cards. One card per level: the section is the card, everything inside it is content.
  If a group needs separation, use a label and a hairline — not a second border.


## Disclosure animation and system theme behavior

- Animated `<details>` disclosures must intercept the summary click before native activation so the browser does not hide the panel before its closing animation. Keep `details.open` true while a Web Animations API animation collapses the panel from its current height and opacity to zero; set `open=false` only after the animation finishes.
- Track the intended state independently from `details.open`, cancel/replace an in-flight animation on rapid activation, and honor reduced motion with an immediate state change. Keep the summary's `aria-expanded` synchronized, mark a closing panel inert, restore focus to the summary if focus was inside the panel, and recalculate segmented-control placement after opening and on resize.
- System color-scheme changes update only outer desk/rim tokens. The document root and preview content remain light; dark preview selection is disabled.

## Application UI verification contract

### KHLANG: geometry and interaction

- Mobile bottom sheets retain 16px left/right gutters and a zero external bottom gap. Internal padding accommodates `safe-area-inset-bottom`. Rounded top corners follow the 28px surface token; the lower edge is docked, not a floating rounded card.
- Centered settings dialogs and desktop modals retain their own placement. Do not force all overlays into one alignment.
- All modal overlays escape animated ancestors through a body Teleport. The scrim blurs by 8px and the content panel is opaque white. The outer frame remains above overlays and never captures pointer events.
- Product category rails reserve at least 56px and use `flex-shrink:0`; long product lists scroll independently without squeezing the rail, search field or heading.
- Page-entry motion uses backwards fill, not persistent transform fill. After entry, the parent transform must be `none`, and a fixed POS action bar must measure bottom gap zero at 360, 390, 768 and 1280px widths.
- Mobile table labels use the Thai body font even if their barcode or numeric value is monospace. Apply the body font directly to the label pseudo-element; inheriting from the data cell can render Thai as missing glyphs.
- Async modal openers must preserve their return-focus target before loading disables the trigger. Escape/cancel restores focus after the modal closes and the loading state settles.

### KHLANG audit coverage

The source router inventory contains 22 page route patterns, including login, shop pages, administrator pages, parameterized shop administrator pages and the not-found page. Public-production observations include 42 authenticated/not-found page-and-viewport rows at 390 and 1280px, plus four anonymous login/theme rows. The owner role was reviewed separately from the platform administrator role: a platform administrator with no shop memberships is not proof that owner pages have been reviewed with shop data.

The completed visual-layout sweep found no document horizontal overflow or body/text theme switching in those measured rows. Additional safe interaction checks opened/cancelled product, supplier, purchase-order, shop and stock-issue forms. During this round, fixed POS action-bar positioning, purchase-order opener focus restoration, and Thai mobile table-label fonts were corrected and rechecked on public production.

The receipt was rendered with synthetic component props in a local harness, with print CSS and event emission checked. This is **not** a completed production sale or proof of printer integration. No real sale, payment, purchase-order commit, inventory mutation, permission change, print job or export was submitted. Record-dependent tracking/receive/cancel workflows and every possible data state are not certified by this read-only UI review. A route visit, source check or empty state must never be labelled a fully tested workflow.


### JERNEY: geometry and interaction

- QR, certificate and confirmation dialogs use the shared AppModal lifecycle. The panel is opaque white, radius 28px; backdrop blur is 8px and modal z-index is 100, below the persistent frame at 200.
- Enter panel motion is 300ms, leave panel motion 220ms, and backdrop lifecycle 240ms. Keep the Teleport outside the Transition and return focus only after leave finishes.
- Escape must be tested from the focused element so the real bubbling path reaches the modal keyboard handler. Dispatching a keyboard event directly on window does not exercise a handler on the modal ancestor.
- Event creation/editing and organization membership editors are inline forms, not popups. Do not report a missing modal as a defect for these controls. Never click submit while auditing an opener.

### JERNEY audit coverage

Public-production read-only audit: 2026-10-20 UTC. The source router contains 15 route patterns, including the legacy ticket/feedback aliases. Fourteen patterns were visited at 390 and 1280px (28 page-and-viewport rows), with anonymous staff login separately checked in both device schemes. Measured document overflow was zero and body foreground/background stayed light in those rows. Admin, organizer dashboard and staff check-in were actually authenticated, not counted from a login redirect.

The public event page, calendar list/grid controls, empty personal ticket/history pages, administrator event editor, organization membership editor, QR and certificate dialogs were inspected without submitting writes. QR and certificate panels were white, within the viewport, with Escape closing and opener focus restored. The QR panel was tested by real touch at 390 x 500: scrollTop changed from 0 to 200 while the underlying page stayed at scrollY 0. The previously missing backdrop blur was added and the modal layer moved beneath the persistent frame. Panel enter/leave was measured as 300/220ms.

**Coverage limits:** certificate-detail, valid-ticket and feedback-success layouts require an owned valid registration/check-in. Only invalid-identifier and permission/error states were exercised on those parameterized routes; this is not success-state verification. No registration, check-in, certificate issue/upload, account/organization change, event publication/deletion, email, native share or file download was submitted. Real camera hardware and offline ticket recovery are not certified by this round. Empty dashboard/guest datasets do not certify populated tables or charts. These limits must remain visible beside the audit result.

### Verification evidence and completion rule

The visual audit and contract tests are separate evidence. KHLANG web contract tests: 12 pass, 0 fail; JERNEY web contract tests: 11 pass, 0 fail. Both web typechecks and production builds completed. These are UI regression tests, not a claim that every API/business acceptance suite ran. Targeted deploys uploaded hashed assets before the HTML shell and verified uploaded content by read-back hashes.

A completed UI review means the stated read-only matrix has been recorded, issues found in that matrix corrected and retested, and unexecuted states named. It must never be shortened to "every workflow passed". Keep personal data, session cookies, bearer tokens, private ticket URLs and reviewer identities out of this public document.

สร้างจาก DESIGN.md ด้วย build-designmd.py · อัปเดต 2026-10-26