# ASWINOLOGY° Components: guide for AI agents

Read this before you write UI for **ASWINOLOGY°** (aswinology.com) or **Lurnelle** (learn.aswinology.com).
Docs: https://components.aswinology.com · Tokens: https://components.aswinology.com/ds/v1/tokens.json

## What this is

One design system, two brands. Components read semantic tokens only, so the same markup renders as either brand.

| | ASWINOLOGY° | Lurnelle |
|---|---|---|
| Role | Parent brand, personal site, portfolio | Learning product "by ASWINOLOGY°" |
| Feel | Soft Swiss: warm paper, graphite ink, one lime degree mark | Bright mist, glass and clay, slate + periwinkle, sun/mint/peach highlights |
| Display / body | Archivo (variable width) | Inter Tight / Geist |
| Thai | Anuphan | Noto Sans Thai |
| Mono | Geist Mono | Geist Mono |
| Primary button | Lime fill, ink text, ink arrow disc | Slate fill, white text, clay shadow |
| Signature | Degree mark `°`, project Poster | Loop stroke `ℓ`, Course card, mesh background |

## Setup

Plain HTML or any framework:

```html
<link rel="stylesheet" href="https://components.aswinology.com/ds/v1/ds.css">
<html lang="th" data-brand="aswinology">  <!-- or "lurnelle" -->
```

`ds.css` = `tokens.css` + `base.css` + `components.css`. Load fonts yourself (Google Fonts names above) or with `next/font` using the variables `--f-archivo`, `--f-anuphan`, `--f-inter-tight`, `--f-geist`, `--f-noto-thai`, `--f-geist-mono`.

React / Next.js: copy `ui/` from the repo (`DESIGN-SYSTEM/ui`) and import the three stylesheets once in the root layout.

```tsx
import "@/ui/styles/tokens.css";
import "@/ui/styles/base.css";
import "@/ui/styles/components.css";
import { Button, Field, Input, ToastProvider } from "@/ui";
```

## Brand and theme

- Brand: `data-brand="aswinology" | "lurnelle"` on `<html>` or on any subtree. Default is ASWINOLOGY°.
- Theme: `data-theme="light" | "dark" | "inverse"` on `<html>` or any subtree. Without it, the OS preference wins. `inverse` flips to the opposite of its surroundings (one highlighted card or band; `<Card tone="inverse">` does this for you).
- Colours are `light-dark()` pairs. Each `data-theme` subtree re-declares its brand tokens (via `@scope`), so nested themes need no extra CSS.
- Show brand-specific content with `<BrandOnly brand="lurnelle">` (CSS `display: contents` switch).

## Tokens (use these, never hex)

Semantic colour: `--bg --bg-alt --surface --surface-raised --surface-sunken --fg --fg-2 --muted --line --line-strong --accent --on-accent --accent-text --accent-soft --primary --on-primary --primary-hover --secondary --on-secondary --focus --focus-halo --success(-soft) --warning(-soft) --danger(-soft) --on-danger --info(-soft) --overlay --tint-1..4 --tint-accent`

Type: `--font-display --font-body --font-thai --font-mono --text-display --text-h1 --text-h2 --text-h3 --text-lead --text-body --text-small --text-meta --weight-display --weight-heading --tracking-display --tracking-heading --leading-body`

Shape, depth, motion: `--radius-xs|sm|md|lg|pill --shadow-sm|md|lg --ease-out --ease-in-out --ease-spring --dur-fast --dur-base --dur-slow`

Space: `--space-1..10` (4, 8, 12, 16, 24, 32, 48, 64, 96, 144 px). Z: `--z-sticky --z-header --z-overlay --z-dialog --z-toast --z-tooltip`.

## Components

Class prefix `ds-`, variants as `data-*` attributes. React names in brackets.

- Foundations: layout (`Container Stack Cluster Grid Separator`), icons (Phosphor), brand marks (`Wordmark Degree Loop BrandOnly BrandScope`)
- Actions: `Button` (variant primary|secondary|outline|ghost|danger|link, size sm|md|lg, `arrow`, `loading`, `iconOnly`, `as`), `ArrowLink`
- Inputs: `Field` (label, hint, error, wires ARIA), `Input`, `Textarea`, `Select` (native), `Checkbox`, `Radio` + `ChoiceGroup`, `Switch`, `Segmented`, `Slider`
- Feedback: `Alert`, `ToastProvider` + `useToast()`, `Badge`, `Tag`, `Progress`, `Skeleton`, `EmptyState`, `Dots`
- Overlays: `Dialog`, `Sheet` (native `<dialog>`), `Tooltip`, `Menu`
- Navigation: `Navbar`, `Tabs`, `Accordion` (native `<details>`), `Breadcrumb`, `Pagination`, `Stepper`
- Display: `Card` + parts, `Poster`, `CourseCard`, `Stat`, `TableWrap` + `.ds-table`, `Avatar`, `AvatarGroup`, `Kbd`

## Rules

1. Never hardcode colours, radii or shadows. Use tokens. A new need becomes a token in `tokens/tokens.mjs`.
2. One primary button per view. Secondary or outline for the rest. Danger only for irreversible actions.
3. Every input sits in a `Field` with a visible label. Never use placeholder as label.
4. Checkbox for values submitted later; Switch for settings that apply immediately.
5. Dialog and Sheet always have a `title`. Icon-only buttons always have `aria-label`.
6. Thai first: `lang="th"` on the root. Never set negative letter-spacing or italics on Thai text.
7. Accent (lime / periwinkle) is a fill, never body text on light backgrounds. Use `--accent-text` for coloured text.
8. Motion: transform and opacity only, `--ease-out` for entrances, honour `prefers-reduced-motion`. GSAP for timelines and scroll, CSS for hover.
9. Do not mix brands in one view, except the "by ASWINOLOGY°" endorsement on Lurnelle.
10. No em dashes in UI copy. No invented numbers, testimonials or prices.

## Copy voice

- ASWINOLOGY°: calm, precise, first person, short sentences. English or Thai.
- Lurnelle: warm, encouraging, plain Thai first, speaks to learners as "คุณ".
- Buttons: verb first, three words or fewer ("บันทึก", "เริ่มเรียน", "Save changes").
