# Wild Way Design Tokens

Design system สำหรับหน้าจอ kiosk แนวตั้งของสวนสัตว์ขอนแก่น แยกจาก `index.html` เพื่อใช้เป็น source of truth ร่วมกันระหว่างงานออกแบบและ frontend

## Files

- `design-tokens.css` — CSS Custom Properties ที่หน้า prototype โหลดใช้งานจริง
- `design-tokens.json` — โครงสร้าง token แบบ W3C Design Tokens Community Group สำหรับนำเข้า pipeline หรือแปลงไปใช้กับ Figma/Style Dictionary
- `index.html` — โหลด `design-tokens.css` หลัง component styles เพื่อให้ token ชุดนี้เป็นค่าหลัก

## Token architecture

ระบบแบ่งเป็น 3 ชั้น:

1. **Primitive** — ค่าดิบ เช่น `green.600 = #2F8D22`
2. **Semantic** — ความหมายในการใช้งาน เช่น `action.primary = green.600`
3. **Component** — ข้อตกลงของ component เช่น `nav.center.size = 58px`

ควรเรียก semantic token ใน component ก่อน primitive token เพื่อให้เปลี่ยนธีมและตรวจ contrast ได้ง่าย

---

## 1. Color tokens

### Brand green

| Token | Value | Usage |
|---|---:|---|
| `green.25` | `#F8FAF7` | กรอบอุปกรณ์/พื้นหลังสว่างมาก |
| `green.50` | `#F5FAF3` | ปุ่มรอง/พื้นหลัง action เบา |
| `green.75` | `#F6F8F4` | พื้นหลัง icon และ control |
| `green.100` | `#EDF7E9` | success surface, selected subtle |
| `green.150` | `#EAF3E7` | service information |
| `green.200` | `#DBE6DF` | proof chip และ neutral green |
| `green.250` | `#CFE5D8` | page canvas |
| `green.300` | `#B9E28E` | active navigation ring |
| `green.400` | `#79BE32` | highlight/lime accent |
| `green.500` | `#7BC344` | CTA บนพื้นเขียวเข้ม |
| `green.600` | `#2F8D22` | brand/action primary |
| `green.700` | `#25721C` | action text/hover |
| `green.800` | `#15522D` | brand strong, headings |
| `green.900` | `#0F4C27` | dark translucent hero actions |

### Neutral

| Token | Value | Usage |
|---|---:|---|
| `neutral.25` | `#F7F9F6` | input background |
| `neutral.50` | `#F5F6F2` | app background |
| `neutral.100` | `#F0F3EE` | amenity/background neutral |
| `neutral.150` | `#E4E9DF` | subtle dividers |
| `neutral.200` | `#DCE4D9` | control borders |
| `neutral.300` | `#CDDDC9` | secondary action borders |
| `neutral.400` | `#9AA49C` | inactive navigation |
| `neutral.500` | `#748176` | secondary text |
| `neutral.600` | `#59685E` | supporting text |
| `neutral.700` | `#24352A` | primary text |

### Supporting and status colors

| Token | Value | Usage |
|---|---:|---|
| `orange.50` | `#FFF0E9` | paid/warning surface |
| `orange.500` | `#EE744F` | time highlight |
| `orange.600` | `#E97B3F` | map activity, route card |
| `orange.700` | `#B24D30` | warning text |
| `purple.50` | `#F3EDF9` | accessibility surface |
| `purple.500` | `#7653A6` | accessibility hotspot |
| `purple.700` | `#705A83` | accessibility copy |
| `teal.500` | `#147B82` | map service hotspot |
| `red.500` | `#E2533C` | current location |
| `red.600` | `#D9573E` | urgent/help action |
| `amber.50` | `#FFF5DC` | prototype/demo notice |
| `amber.700` | `#76634D` | notice text |

### Semantic mapping

| Semantic token | Resolves to | Purpose |
|---|---|---|
| `color.canvas` | `green.250` | พื้นหลัง portfolio |
| `color.appSurface` | `neutral.50` | พื้นหลักใน kiosk |
| `color.surface` | `white` | card/modal/navigation |
| `color.surfaceSubtle` | `green.75` | control background |
| `color.surfaceSuccess` | `green.100` | selected/success |
| `color.surfaceWarning` | `amber.50` | disclaimer |
| `color.surfaceAccessible` | `purple.50` | accessibility notice |
| `color.textPrimary` | `neutral.700` | ข้อความหลัก |
| `color.textSecondary` | `neutral.500` | คำอธิบาย |
| `color.textTertiary` | `neutral.400` | inactive/metadata |
| `color.textInverse` | `white` | ข้อความบนพื้นเข้ม |
| `color.textBrand` | `green.800` | title/brand |
| `color.brandPrimary` | `green.600` | brand หลัก |
| `color.brandStrong` | `green.800` | brand เข้ม |
| `color.actionPrimary` | `green.600` | primary CTA |
| `color.actionHover` | `green.700` | hover/pressed |
| `color.actionSelected` | `green.800` | active center navigation |
| `color.borderSubtle` | `neutral.150` | divider |
| `color.borderControl` | `neutral.200` | input/filter border |
| `color.mapAnimal` | `green.600` | animal pin |
| `color.mapActivity` | `orange.600` | activity pin |
| `color.mapService` | `teal.500` | service pin |
| `color.mapAccessible` | `purple.500` | accessible pin |
| `color.mapCurrentLocation` | `red.500` | current position |

### Legacy dark primitives

สี `night`, `panel`, `mint` และ `sand` เป็นฐานจาก concept ก่อนหน้าและยังเก็บไว้เพื่อรองรับ selector ชั้นแรกใน `index.html` แต่ UI ปัจจุบันใช้ semantic green/white layer เป็นหลัก ห้ามใช้ legacy token ใน component ใหม่

---

## 2. Typography tokens

### Font families

| Role | Stack | Usage |
|---|---|---|
| Sans / Thai UI | `IBM Plex Sans Thai`, `Noto Sans Thai`, system sans | เนื้อหาและ control ทั้งหมด |
| Display / Data | `Space Grotesk`, `IBM Plex Sans Thai`, system sans | รหัสจอง เวลา ตัวเลข uppercase label และ portfolio headings |
| Icons | `Material Symbols Rounded` | icon glyph |

### Font weights

| Token | Value | Usage |
|---|---:|---|
| `regular` | 400 | body text |
| `medium` | 500 | labels / secondary emphasis |
| `semibold` | 600 | eyebrow / small heading |
| `bold` | 700 | CTA, title, code, active navigation |

### Type scale

| Token | Size | Typical usage |
|---|---:|---|
| `2xs` | 8px | kiosk metadata, nav label, badge |
| `xs` | 9px | helper, card supporting text |
| `sm` | 10px | control, body compact |
| `md` | 11px | body and app label |
| `base` | 12px | card title, button |
| `lg` | 13px | section label, summary |
| `xl` | 15px | map sheet title |
| `2xl` | 18px | home heading/icon |
| `3xl` | 20px | large icon/action |
| `4xl` | 23px | modal title |
| `5xl` | 24px | screen title |
| `6xl` | 25px | success title |
| `display-sm` | 36px | small portfolio display |
| `display-md` | 52px | responsive portfolio display |
| `display-lg` | 88px | desktop portfolio hero |

### Line height and tracking

- Tight `1.0` — large English display
- Heading `1.25` — screen and card headings
- Body `1.5` — compact kiosk text
- Relaxed `1.65` — supporting copy
- Reading `1.75–1.8` — portfolio narrative
- Tracking ranges from `-0.07em` for display headings to `0.18em` for uppercase eyebrow labels

---

## 3. Spacing tokens

ฐานหลักคือ 4px แต่มีค่า optical 5, 6, 7, 9, 10, 11, 13, 14 และ 15px สำหรับพื้นที่ขนาดเล็กของ kiosk

| Scale | Value | Scale | Value | Scale | Value |
|---|---:|---|---:|---|---:|
| `0` | 0 | `0.5` | 2px | `1` | 4px |
| `1.25` | 5px | `1.5` | 6px | `1.75` | 7px |
| `2` | 8px | `2.25` | 9px | `2.5` | 10px |
| `2.75` | 11px | `3` | 12px | `3.25` | 13px |
| `3.5` | 14px | `3.75` | 15px | `4` | 16px |
| `4.5` | 18px | `5` | 20px | `5.5` | 22px |
| `6` | 24px | `7` | 28px | `8` | 32px |
| `9` | 36px | `10` | 40px | `11` | 44px |
| `12` | 48px | `14` | 56px | `16` | 64px |
| `18` | 72px | `21` | 84px | `22` | 88px |

Guideline:

- ภายใน control: 8–12px
- ภายใน card: 12–16px
- ระหว่าง card: 10–14px
- ระหว่าง section: 24–32px ใน kiosk, 62–88px ใน portfolio
- Touch target ต่ำสุด: 44px

---

## 4. Radius and border tokens

| Token | Value | Usage |
|---|---:|---|
| `radius-xs` | 7px | compact CTA |
| `radius-sm` | 8px | badge/time chip |
| `radius-control` | 9px | mini button |
| `radius-md` | 10px | stepper/note |
| `radius-field` | 11px | input/QR action |
| `radius-button` | 12px | filter/option |
| `radius-card-sm` | 13px | secondary button |
| `radius-card` | 14px | CTA/booking code |
| `radius-card-lg` | 16px | standard card |
| `radius-panel` | 18px | QR pass |
| `radius-map` | 19px | full map |
| `radius-modal` | 20px | QR modal code |
| `radius-screen` | 25px | kiosk screen |
| `radius-device` | 34px | device shell |
| `radius-pill` | 999px | pill/chip |

Border widths:

- 1px — divider และ control border
- 2px — current location/key emphasis
- 3px — map pin white edge และ active ring
- 5px — center navigation white keyline

---

## 5. Elevation tokens

| Token | Value | Usage |
|---|---|---|
| `shadow-device` | `0 32px 80px rgba(41,73,49,.24)` + 1px keyline | อุปกรณ์ kiosk |
| `shadow-card` | `0 7px 20px rgba(46,74,53,.07)` | card มาตรฐาน |
| `shadow-card-soft` | `0 7px 20px rgba(46,74,53,.06)` | card แบบเบา |
| `shadow-action` | `0 9px 22px rgba(47,141,34,.20)` | primary CTA |
| `shadow-success` | `0 14px 30px rgba(47,141,34,.24)` | success icon |
| `shadow-map` | `0 12px 30px rgba(31,72,40,.16)` | full map |
| `shadow-map-pin` | `0 4px 12px rgba(23,72,34,.34)` | hotspot |
| `shadow-sheet` | `0 12px 26px rgba(26,61,33,.22)` | map detail sheet |
| `shadow-modal` | `0 16px 38px rgba(33,75,42,.12)` | QR modal |
| `shadow-nav-center` | `0 7px 18px rgba(47,141,34,.30)` | center nav action |

Backdrop blur ใช้ 10, 12 และ 14px ตามลำดับความเด่น

---

## 6. Motion tokens

| Token | Value | Usage |
|---|---:|---|
| `duration-fast` | 180ms | hotspot/filter feedback |
| `duration-base` | 200ms | switch/control |
| `duration-slow` | 220ms | screen transition/map sheet |
| `easing-standard` | CSS `ease` | state change ทั่วไป |
| `easing-emphasized` | `cubic-bezier(.2,.8,.2,1)` | emphasized movement |

Animation ต้องเคลื่อนสั้นไม่เกิน 5px เพื่อไม่ทำให้ผู้ใช้หน้าจอ kiosk สับสน

---

## 7. Layout and size tokens

| Token | Value | Notes |
|---|---:|---|
| `page-max-width` | 1260px | portfolio canvas |
| `device-width` | 444px | kiosk mockup outer width |
| `device-height` | 754px | kiosk interactive screen |
| `device-padding` | 9px | frame inset |
| `bottom-nav-height` | 72px | persistent navigation |
| `bottom-nav-columns` | 5 | equal-width navigation items |
| `touch-target-min` | 44px | minimum accessible target |
| `nav-center-size` | 58px | raised Animal Finder action |
| `nav-center-offset` | -25px | vertical lift from nav baseline |
| `map-pin-size` | 36px | hotspot target visual |
| `map-height-route` | 330px | route preview |
| `map-height-full` | 545px | full zoo map |
| `qr-size-inline` | 146px | QR inside pass |
| `qr-size-modal` | 260px | fullscreen QR |

Breakpoints:

- Mobile portfolio: 520px
- Single-column/tablet portfolio: 900px
- Kiosk app remains fixed-ratio and scrolls internally

---

## 8. Z-index tokens

| Layer | Value | Content |
|---|---:|---|
| Base | 0 | screen content |
| Pin | 2 | map hotspots/current location |
| Pin active | 4 | selected hotspot |
| Sheet | 5 | map detail sheet |
| Navigation | 20 | persistent bottom nav |
| Modal | 30 | QR fullscreen |
| Critical modal | 31 | help/assistance sheet |

Bottom navigation ต้องอยู่เหนือ map sheet เพื่อให้เปลี่ยนเมนูได้ตลอดเวลา

---

## 9. Component tokens

### Primary button

- Height: 52px
- Radius: 14px
- Background: `action.primary`
- Foreground: `text.inverse`
- Weight: 700
- Shadow: `shadow.action`
- Disabled: `#66816A` / text `#D5DFD2`

### Card

- Background: `surface`
- Radius: 16px
- Padding: 14px default
- Shadow: `shadow.cardSoft`
- Divider: 1px `border.subtle`

### Form field

- Minimum height: 44px
- Radius: 11px
- Background: `neutral.25`
- Border: `neutral.200`
- Focus: brand border + 3px translucent green ring

### Bottom navigation

- Height: 72px
- Columns: 5
- Surface: 96% white + 14px blur
- Standard icon: 21px
- Label: 8px
- Active color: `brand.primary`
- Center action: 58px circle, -25px offset, 27px icon, 5px white keyline
- Center selected: `brand.strong` + 3px green-300 ring

### Map hotspot

- Visual size: 36px
- Icon: 18px
- Border: 3px white
- Shape: map-pin radius `50% 50% 50% 0`
- Rotation: -45° container / +45° icon
- Category color is mandatory and must be paired with icon/label, not color alone

### QR pass

- Card radius: 18px
- Inline QR: 146px
- Fullscreen QR: 260px
- QR dark color: `brand.strong`
- Always show human-readable booking/reference code

### Modal

- Layer: 30–31
- Background: 98% white
- Close target: 44px circle
- Primary modal radius: 20px

---

## 10. Accessibility rules

- Interactive touch targets should be at least 44×44px; compact 34–42px controls are allowed only when surrounded by sufficient spacing and not used as the sole critical action.
- Do not communicate status with color alone; pair color with icon, text, or position.
- Primary text uses `neutral.700` on white; secondary text uses `neutral.500` and should not be reduced below 9px in production kiosk hardware without distance testing.
- Focus uses both border change and a translucent ring.
- Modal layers must trap attention visually and expose an explicit 44px close action.
- Accessibility hotspots use purple plus explicit labels/icons.
- Motion stays within 180–220ms and short travel distances.

---

## 11. Usage examples

### CSS

```css
.example-card {
  padding: var(--card-padding);
  color: var(--color-text-primary);
  background: var(--card-bg);
  border-radius: var(--card-radius);
  box-shadow: var(--card-shadow);
}

.example-primary-action {
  min-height: var(--button-primary-height);
  color: var(--button-primary-fg);
  background: var(--button-primary-bg);
  border-radius: var(--button-primary-radius);
}
```

### JavaScript

```js
const tokens = await fetch('./design-tokens.json').then(response => response.json());
const primaryGreen = tokens.color.primitive.green['600'].$value;
```

### Figma mapping

- Primitive tokens → Figma Variables collection `Primitives`
- Semantic tokens → collection `Semantic / Light`
- Component tokens → collection `Components / Kiosk`
- Modes ที่ควรเพิ่มในอนาคต: `Light`, `High Contrast`, `Dark Operations`

---

## 12. Governance

1. เพิ่ม raw value ใหม่ได้เฉพาะใน primitive layer
2. Component ใหม่ต้องใช้ semantic หรือ component token
3. ห้ามใส่ hex ใหม่โดยตรงใน component ก่อนตรวจว่ามี token เดิมหรือไม่
4. เปลี่ยนชื่อ token แบบ breaking change ต้องเพิ่ม version major
5. ตรวจ contrast และ touch target ทุกครั้งที่เปลี่ยน semantic color หรือ size
6. `design-tokens.css` เป็น runtime source; `design-tokens.json` เป็น interchange source และทั้งสองไฟล์ต้องอัปเดตพร้อมกัน

## Known cleanup debt

`index.html` ยังมีค่า one-off ใน selector เก่าบางส่วน เช่นสีภาพซ้อน โปร่งใสเฉพาะฉาก และตำแหน่ง hotspot แต่ primitive/semantic/component token ที่เป็นระบบหลักถูกแยกออกแล้ว รอบ refactor ถัดไปควรแทน hard-coded component values ด้วยตัวแปรจาก `design-tokens.css` โดยไม่เปลี่ยน visual output
