@hotelfriendag/design-tokens 0.3.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,576 +1,474 @@
1
1
  # HotelFriend Design System
2
2
 
3
- Cross-project design foundation: tokens, generated outputs for every stack (CSS / SCSS / TS / Tailwind v3 + v4 / shadcn), the `.hf-*` component layer, and AI-tool rules.
3
+ Cross-project design foundation: tokens, generated outputs for every stack (CSS / SCSS / TS / Tailwind v3 + v4 / shadcn), the `.hf-*` component layer, and AI-tool rules. Distributed as the public npm package **`@hotelfriendag/design-tokens`**.
4
4
 
5
- > **Extracted on 2026-05-25** from [`hotelfriend/backend-hf`](https://bitbucket.org/hotelfriend/backend-hf) `docs/portable-design/` to make this the single source of truth that other HotelFriend projects consume.
5
+ > **Source repo:** `HotelFriendAG/design-system` (private). Extracted on 2026-05-25 from [`hotelfriend/backend-hf`](https://bitbucket.org/hotelfriend/backend-hf) `docs/portable-design/` to be the single source of truth other HotelFriend projects consume.
6
6
  >
7
- > **Status:** RFC-0001 Phase 1 complete (semantic three-tier model + collision-safe prefixing + drift CI). Versioned package distribution (`@hotelfriendag/design-tokens` via GitHub Packages) is the next step see `RFC-0001-cross-project-design-system.md` §9 for the full status checklist.
7
+ > **Status:** RFC-0001 Phase 1 complete (semantic three-tier model + collision-safe `hf-` prefix + drift CI). Published on npmjs.com via Trusted Publishing. See [`ROADMAP.md`](ROADMAP.md) for remaining items and [`CHANGELOG.md`](CHANGELOG.md) for phase history.
8
8
 
9
- ## File hierarchy (read in this order)
10
-
11
- ```
12
- portable-design/
13
-
14
- ├── components.html ← PRIMARY · canonical visual reference (open in browser)
15
- ├── UI_DESIGN.md ← NARRATIVE · rationale, anatomy, decisions (AI reads this first)
16
- ├── tokens.figma.json ← TOKENS · atomic Tokens Studio export → feeds Figma + generators
17
-
18
- ├── pre-built/ ← GENERATED · drop one into your stack's build pipeline
19
- │ ├── tailwind.css · Tailwind v4 @theme block (recommended)
20
- │ ├── tailwind.preset.js · Tailwind v3 preset (legacy)
21
- │ ├── tokens.css · vanilla CSS custom properties
22
- │ ├── _tokens.scss · SCSS variables
23
- │ ├── tokens.ts · TypeScript const
24
- │ ├── shadcn-tokens.css · shadcn/ui contract
25
- │ └── components.css · .hf-* component primitives (generated from src/components.css)
26
-
27
- ├── src/ ← hand-edited sources for the generator (currently: components.css only)
28
-
29
- ├── states-canonical.json ← curated interactive states for new components (use this)
30
- ├── states.json ← raw portal extraction (160 KB — prefer states-canonical.json)
31
- ├── generate-tokens.cjs ← Node script (no deps) — turns tokens into Tailwind/CSS/SCSS/TS/shadcn
32
-
33
- ├── ai-rules/ ← drop ONE into the new project's root
34
- │ ├── CLAUDE.md · for Claude Code (autodetected)
35
- │ ├── cursorrules.template · rename to .cursorrules
36
- │ ├── github-copilot-instructions.md · put at .github/copilot-instructions.md
37
- │ └── system-prompt-compact.md · compact prompt for ChatGPT/v0/Lovable
38
-
39
- ├── portal-audit.html ← ARCHIVAL · legacy portal audit snapshot (NOT for new code)
40
- └── README.md ← you are here (quickstart)
41
- ```
42
-
43
- ### Precedence rule — when files disagree
44
-
45
- 1. **`components.html`** — canonical for **visual decisions** (colors, sizes, anatomy)
46
- 2. **`tokens.figma.json`** — canonical for **token values**. Regenerate `pre-built/*` after editing
47
- 3. **`UI_DESIGN.md`** — canonical for **WHY decisions were made** (history, trade-offs, portal drift notes)
48
- 4. **`pre-built/*`** — **generated**, never hand-edit. Re-run `generate-tokens.cjs` after JSON changes
49
- 5. **`portal-audit.html`** — **archival only**. Shows what the legacy portal currently looks like — useful for migration tracking, NOT for new product UI
50
-
51
- When `components.html` and `UI_DESIGN.md` disagree, **components.html wins** and `UI_DESIGN.md` is stale.
52
-
53
- ## 60-second quickstart
9
+ ## Install
54
10
 
55
11
  ```bash
56
- # 1. Copy this whole folder into the new project (anywhere; we suggest docs/)
57
- cp -r /path/to/portable-design ../new-project/docs/
58
-
59
- # 2. Wire the CSS into your build (pick ONE)
60
- # Tailwind v4: @import the pre-built tailwind.css
61
- # Vanilla CSS: link tokens.css + components.css
62
- # SCSS: @import _tokens.scss
63
-
64
- # 3. Wire AI to follow the system (pick ONE)
65
- cp docs/portable-design/ai-rules/CLAUDE.md ../../CLAUDE.md # Claude Code
66
- cp docs/portable-design/ai-rules/cursorrules.template ../../.cursorrules
67
- mkdir -p ../../.github && cp docs/portable-design/ai-rules/github-copilot-instructions.md ../../.github/copilot-instructions.md
12
+ pnpm add @hotelfriendag/design-tokens
68
13
  ```
69
14
 
70
- ## ⚠️ Existing-project integration
15
+ Public package — no `.npmrc`, no auth, no CI secrets. Works from any project, any CI.
16
+
17
+ ## Wire it up (pick your stack)
71
18
 
72
- > **Status:** Solved in Phase 1A (RFC-0001 §4.2). All emitted tokens carry the `hf-` prefix INSIDE the category segment (`--color-hf-*`, `--text-hf-*`, `--radius-hf-*`, `--spacing-hf-*`, `--font-hf-*`, `--shadow-hf-*`). None of them can collide with Tailwind v4 defaults. The full `@import` is now safe in existing projects.
19
+ The package exposes each generated file via a subpath export, e.g. `@hotelfriendag/design-tokens/tailwind.css`, `/components.css`, `/status.css`, `/_tokens.scss`, `/tokens.css`, `/tokens.ts`, `/shadcn-tokens.css`.
73
20
 
74
- **Recommended setup for any project (greenfield or existing):**
21
+ ### Tailwind v4 (recommended e.g. `ui-hf`)
75
22
 
76
23
  ```css
77
- /* app/globals.css */
24
+ /* your main CSS entry, where @import "tailwindcss" lives */
78
25
  @import "tailwindcss";
79
- @import "./docs/portable-design/pre-built/tailwind.css"; /* @theme — adds --color-hf-*, --text-hf-*, etc. */
80
- @import "./docs/portable-design/pre-built/components.css"; /* .hf-* primitives */
81
26
 
82
- /* Optional: exclude the bundle's own demo HTML from your Tailwind scan
83
- so legacy classes / bg-[#hex] from the showcase don't leak into your bundle. */
84
- @source not "./docs/portable-design/components.html";
85
- @source not "./docs/portable-design/portal-audit.html";
27
+ @import "@hotelfriendag/design-tokens/tailwind.css"; /* @theme tokens, hf- prefix */
28
+ @import "@hotelfriendag/design-tokens/components.css"; /* .hf-* primitives */
29
+ @import "@hotelfriendag/design-tokens/status.css"; /* .status-{domain}-{state} */
86
30
  ```
87
31
 
88
- What this gives you:
32
+ Put the DS imports **after** any project-local `@theme {}` block so DS values win `--color-hf-*` collisions. Import `tailwind.css` only — `tailwind.additive.css` is byte-identical (the `hf-` prefix made the additive filter unnecessary), importing both duplicates every declaration.
89
33
 
90
- - `bg-hf-accent` (= `#24AFE8` — brand) — Tailwind's `bg-blue-500` keeps its default
91
- - `text-hf-base` (= 14px body) — Tailwind's `text-base` keeps its default 16px
92
- - `rounded-hf-sm` (= 6px) — Tailwind's `rounded-sm` keeps its default 2px
93
- - `shadow-hf-modal` (= portal modal shadow)
94
- - … etc. Your existing utility usage is **untouched**.
95
-
96
- **For projects whose integration scripts ask for an "additive" target by name**, `--target=tailwind-v4-additive` is an explicit alias of `--target=tailwind-v4`. Identical output — the prefix made the additive filter unnecessary.
97
-
98
- ```bash
99
- node generate-tokens.cjs --target=tailwind-v4-additive > pre-built/tailwind.additive.css
100
- # (same bytes as --target=tailwind-v4)
34
+ ```html
35
+ <button class="bg-hf-accent hover:bg-hf-accent-hover text-white h-10 px-5 rounded-hf-sm text-hf-base font-semibold">Save</button>
36
+ <span class="hf-pill status-booking-confirmed">Confirmed</span>
101
37
  ```
102
38
 
103
- ## Stack-specific snippets
39
+ ### SCSS (Yii / Laravel / WP — e.g. `backend-hf`)
104
40
 
105
- ### React + Tailwind v4 (recommended, greenfield)
41
+ The package ships `pre-built/_tokens.scss` with 135 compile-time variables — `$colorAccent`, `$colorFg`, `$colorBorder`, `$radiusSm`, `$shadowModal`, … Semantic tokens are resolved to concrete values (e.g. `$colorAccent: #24AFE8;`), so no runtime cascade is needed. `node_modules` must be on your Sass load path.
106
42
 
107
- ```css
108
- /* app/globals.css */
109
- @import "tailwindcss";
110
- @import "./docs/portable-design/pre-built/tailwind.css"; /* @theme tokens */
111
- @import "./docs/portable-design/pre-built/components.css"; /* .hf-* primitives */
112
- ```
43
+ ```scss
44
+ // Dart Sass (sass-embedded / dart-sass) — node_modules on loadPaths, then:
45
+ @use '@hotelfriendag/design-tokens/pre-built/tokens' as ds;
113
46
 
114
- ```jsx
115
- <button className="bg-hf-primary hover:bg-hf-primary-hover text-white h-10 px-5 rounded-hf text-hf-md font-semibold">
116
- Save
117
- </button>
118
- <span className="hf-pill status-booking-confirmed">Confirmed</span>
119
- <div className="hf-modal max-w-[500px]">
120
- <div className="hf-modal__header">
121
- <h2 className="hf-modal__title">Edit Guest</h2>
122
- <button className="hf-modal__close">✕</button>
123
- </div>
124
- <div className="hf-modal__body">…</div>
125
- <div className="hf-modal__footer"><button>Cancel</button><button>Save</button></div>
126
- </div>
47
+ .my-btn {
48
+ background: ds.$colorAccent; // #24AFE8
49
+ color: #fff;
50
+ border-radius: ds.$radiusSm; // 6px
51
+ box-shadow: ds.$shadowModal;
52
+ }
127
53
  ```
128
54
 
129
- ### React + Tailwind v3 (legacy)
55
+ ```scss
56
+ // webpack sass-loader — bare path resolves node_modules (older sass-loader: prefix with ~)
57
+ @import '@hotelfriendag/design-tokens/pre-built/tokens';
130
58
 
131
- ```js
132
- // tailwind.config.js
133
- const hfPreset = require('./docs/portable-design/pre-built/tailwind.preset.js');
134
- module.exports = {
135
- presets: [hfPreset],
136
- content: ['./app/**/*.{ts,tsx}', './components/**/*.{ts,tsx}'],
137
- };
59
+ .my-btn { background: $colorAccent; border-radius: $radiusSm; }
138
60
  ```
139
61
 
140
- ```html
141
- <link rel="stylesheet" href="docs/portable-design/pre-built/components.css">
142
- ```
62
+ For the `.hf-*` component primitives, also pull the generated CSS once (it references `var(--color-hf-*)` — emit `tokens.css` into the page too so the custom properties exist at runtime):
143
63
 
144
- ### Next.js + shadcn/ui
64
+ ```scss
65
+ @use '@hotelfriendag/design-tokens/pre-built/tokens'; // SCSS variables
66
+ ```
67
+ ```css
68
+ @import "@hotelfriendag/design-tokens/tokens.css"; /* :root custom properties */
69
+ @import "@hotelfriendag/design-tokens/components.css"; /* .hf-* primitives */
70
+ @import "@hotelfriendag/design-tokens/status.css"; /* status pills */
71
+ ```
145
72
 
146
- Append `pre-built/shadcn-tokens.css` to your `app/globals.css`. shadcn components automatically pick up `--primary`, `--background`, `--ring`, etc.
73
+ > Why both? SCSS `$variables` are compile-time (good for your own rules); the `.hf-*` component CSS resolves `var(--color-hf-*)` at runtime, so the page needs `tokens.css` loaded for those custom properties to exist.
147
74
 
148
- ### Vue 3 / Nuxt
75
+ ### Vue 3 / Nuxt (vanilla CSS variables)
149
76
 
150
77
  ```ts
151
78
  // nuxt.config.ts
152
79
  export default defineNuxtConfig({
153
80
  css: [
154
- '~/docs/portable-design/pre-built/tokens.css',
155
- '~/docs/portable-design/pre-built/components.css',
81
+ '@hotelfriendag/design-tokens/tokens.css', // :root custom properties
82
+ '@hotelfriendag/design-tokens/components.css', // .hf-* primitives
83
+ '@hotelfriendag/design-tokens/status.css',
156
84
  ],
157
85
  });
158
86
  ```
159
87
 
160
- Use `var(--color-hf-accent)`, `var(--font-size-hf-base)`, or `.hf-modal` / `.hf-pill .status-booking-confirmed` in any template.
88
+ Then use `var(--color-hf-accent)`, `var(--font-size-hf-base)`, or `.hf-modal` / `.hf-pill .status-booking-confirmed` in any template.
161
89
 
162
- ### SCSS-based (Yii / Laravel / WP)
90
+ ### TypeScript / CSS-in-JS
163
91
 
164
- ```scss
165
- // _app.scss
166
- @import 'docs/portable-design/pre-built/tokens';
167
- @import 'docs/portable-design/pre-built/components.css';
92
+ ```ts
93
+ import { tokens } from '@hotelfriendag/design-tokens'; // compiled tokens.js + .d.ts
168
94
 
169
- .my-btn {
170
- background: $colorPrimaryDefault;
171
- height: $sizeBtnDefault;
172
- border-radius: $borderRadiusSm;
173
- }
95
+ const Button = styled.button`
96
+ background: ${tokens.color.accent.default}; // #24AFE8 (semantic + primitive share tokens.color.*)
97
+ border-radius: ${tokens.radius.sm}; // 6px
98
+ `;
174
99
  ```
175
100
 
176
- ### TS / CSS-in-JS
101
+ ### Next.js + shadcn/ui
177
102
 
178
- ```ts
179
- import { tokens } from './docs/portable-design/pre-built/tokens';
103
+ Append `shadcn-tokens.css` to `app/globals.css` (after `@import "tailwindcss"`). shadcn components pick up `--primary`, `--background`, `--ring`, etc.
180
104
 
181
- const Button = styled.button`
182
- background: ${tokens.color.primary.default};
183
- height: ${tokens.size.btnDefault};
184
- border-radius: ${tokens.borderRadius.sm};
185
- `;
105
+ ```css
106
+ @import "@hotelfriendag/design-tokens/shadcn-tokens.css";
186
107
  ```
187
108
 
188
109
  ### Vanilla / static HTML
189
110
 
190
111
  ```html
191
- <link rel="stylesheet" href="docs/portable-design/pre-built/tokens.css">
192
- <link rel="stylesheet" href="docs/portable-design/pre-built/components.css">
112
+ <link rel="stylesheet" href="node_modules/@hotelfriendag/design-tokens/pre-built/tokens.css">
113
+ <link rel="stylesheet" href="node_modules/@hotelfriendag/design-tokens/pre-built/components.css">
114
+ <link rel="stylesheet" href="node_modules/@hotelfriendag/design-tokens/pre-built/status.css">
193
115
 
194
116
  <span class="hf-pill status-booking-confirmed">Confirmed</span>
195
- <button class="hf-modal__close">✕</button>
196
117
  ```
197
118
 
119
+ ### No-npm fallback
120
+
121
+ If a consumer can't reach npmjs.com, the repo also works as a **git submodule** (relative `@import` paths). See the appendix in [`INTEGRATION-ui-hf.md`](INTEGRATION-ui-hf.md).
122
+
123
+ ## What you get
124
+
125
+ - `bg-hf-accent` = `#24AFE8` (brand) — Tailwind's `bg-blue-500` keeps its default
126
+ - `text-hf-base` = 14px — Tailwind's `text-base` keeps its default 16px
127
+ - `rounded-hf-sm` = 6px — Tailwind's `rounded-sm` keeps its default 2px
128
+ - `shadow-hf-modal`, `text-hf-fg`, `border-hf-border`, `bg-hf-bg-surface`, … — full semantic set
129
+ - Your existing utility usage is **untouched** (collision-safe prefix).
130
+
131
+ App code should reference **semantic** tokens (`accent`, `fg`, `bg-*`, `border`, `status-*`) — primitives (`hf-blue-500`, `hf-gray-300`) are an implementation detail the theming layer can swap.
132
+
198
133
  ## Component primitives shipped in `components.css`
199
134
 
200
135
  | Class | Anatomy | See |
201
136
  |---|---|---|
137
+ | `.hf-btn` + `--primary` / `--danger` / `--outline-primary` / `--outline-default` / `--cancel` (sizes `--sm` / `--icon`) | Buttons — 40px h, 6px radius, token-driven chrome | components.html#buttons |
138
+ | `.hf-input` / `.hf-textarea` / `.hf-select` (+ `.hf-select-wrap`) | Form controls — 39px h, accent focus ring, token caret | components.html#inputs |
139
+ | `.hf-form-field` + `__label` / `__hint` / `__error` (`--required`, `--error`) | Field wrapper — label + control + hint/error | components.html#inputs |
140
+ | `.hf-switch` | Toggle — styled checkbox, 40×22 track, accent when on | components.html#inputs |
141
+ | `.hf-card` + `__header/__title/__description/__body/__footer` (`--flat`) | Elevated card — 12px radius, card shadow | components.html#cards |
142
+ | `.hf-drawer` + `__backdrop/__panel/__header/__body/__footer` (`--left`) | Side-panel — backdrop + sliding panel | components.html#layout |
202
143
  | `.hf-pill` + `.status-{domain}-{state}` | Status badge — 6px radius, 15% bg + 100% text + 1px border | components.html#status |
203
144
  | `.hf-tab` / `.hf-tab--sm` / `.hf-pill-tabs` | Underline + segmented tabs | components.html#tabs |
204
- | `.hf-pagination` + `__item` / `__ellipsis` | Subtle gray active (NOT primary!) — 34×34, 8px radius | components.html#pagination |
145
+ | `.hf-pagination` + `__item` / `__ellipsis` | Subtle gray active (NOT accent!) — 34×34, 8px radius | components.html#pagination |
205
146
  | `.hf-modal` + `__header/__title/__body/__footer/__close` | 6px radius, footer no top border | components.html#modal |
206
147
  | `.hf-alert` + `--success/--info/--warn/--error` | White bg + 3px top accent bar + 26×26 squared icon | components.html#alerts |
207
148
  | `.hf-alert--tinted` / `--banner` / `--compact` | Modifiers — bg tint / full-width strip / compact-in-card | components.html#alerts |
208
- | `.hf-toast` | Floating notification (bottom-right) — 9px radius | components.html#alerts |
209
- | `.hf-check` / `.hf-radio` | Custom checkbox+radio — 18×18, filled primary on check | components.html#inputs |
210
- | `.hf-dropdown-menu` + `__item / __header / __shortcut / __icon / __divider` | 9px radius dropdown with portal-shadow `0 1px 10px rgba(0,0,0,.1)` | components.html#dropdown |
211
- | `.skeleton` + `.hf-spin` | Loading state primitives (shimmer + rotation keyframes) | components.html#empty |
149
+ | `.hf-toast` | Floating notification — 9px radius | components.html#alerts |
150
+ | `.hf-check` / `.hf-radio` | Custom checkbox+radio — 18×18, filled accent on check | components.html#inputs |
151
+ | `.hf-dropdown-menu` + `__header / __item / __item-icon / __shortcut / __divider` (+ `__item--danger`) | 9px radius dropdown, portal shadow (legacy flat `.hf-dropdown-{header\|item\|divider}` kept as deprecated aliases) | components.html#dropdown |
152
+ | `.skeleton` + `.hf-spin` | Loading-state primitives (shimmer + rotation keyframes) | components.html#empty |
212
153
 
213
- ## How the AI uses this
154
+ ## Usage examples
214
155
 
215
- After you drop one of the `ai-rules/*` files at the new project's root:
156
+ Copy-paste markup for the main `.hf-*` components — framework-agnostic (works in HTML, Vue, JSX; swap `class`/`className`). Icons are illustrative; wire your own (Lucide, etc.).
216
157
 
217
- 1. **Claude Code / Cursor / Copilot** automatically prepend it to every chat. The agent learns:
218
- - canonical palette (primary `#24AFE8`, status colors, neutrals)
219
- - typography (Roboto, sizes 11/13/14/15/16/18/20/22/26/30, weights 400/500/600)
220
- - spacing scale (multiples of 4)
221
- - radius scale (6/8/9/12/99)
222
- - shadow set (default/subtle/wrapper/card/modal/outline/hover)
223
- - component library (`.hf-modal`, `.hf-alert`, `.hf-pill`, `.hf-tab`, `.hf-pagination`, `.hf-check`, `.hf-dropdown-menu`, etc.)
224
- - hard rules ("never hard-code hex", "always provide hover/focus/disabled", "no mixed icon sets")
158
+ ### Status pill
225
159
 
226
- 2. **Visual reference for new projects**: open `components.html` in a browser — canonical reference with all `.hf-*` patterns rendered.
227
-
228
- 3. **For chat-based AI** (ChatGPT, v0, Lovable): paste `ai-rules/system-prompt-compact.md` or `UI_DESIGN.md` §9 JSON token reference as a system prompt before asking for UI work.
229
-
230
- ## Refresh the bindings
160
+ ```html
161
+ <span class="hf-pill status-booking-confirmed">Confirmed</span>
162
+ <span class="hf-pill status-order-waiting">Waiting</span>
163
+ <span class="hf-pill status-room-item-cleaning-dirty">Dirty</span>
164
+ ```
231
165
 
232
- When `tokens.figma.json` changes, regenerate the bindings:
166
+ ### Tabs
233
167
 
234
- ```bash
235
- cd docs/portable-design
236
- node generate-tokens.cjs --target=tailwind-v4 > pre-built/tailwind.css # v4 (recommended)
237
- node generate-tokens.cjs --target=tailwind > pre-built/tailwind.preset.js # v3 (legacy)
238
- node generate-tokens.cjs --target=css > pre-built/tokens.css
239
- node generate-tokens.cjs --target=scss > pre-built/_tokens.scss
240
- node generate-tokens.cjs --target=ts > pre-built/tokens.ts
241
- node generate-tokens.cjs --target=shadcn > pre-built/shadcn-tokens.css
168
+ ```html
169
+ <div class="flex border-b border-hf-border">
170
+ <button class="hf-tab is-active">Overview</button>
171
+ <button class="hf-tab">Bookings <span class="hf-tab__count">12</span></button>
172
+ <button class="hf-tab">Guests</button>
173
+ <button class="hf-tab" disabled>Reports</button>
174
+ </div>
175
+ <!-- compact sub-filter variant: add .hf-tab--sm -->
242
176
  ```
243
177
 
244
- > `pre-built/components.css` is **generated** from `src/components.css` by `generate-tokens.cjs --target=components-css` (`npm run build`). Edit the source file under `src/`, never the output under `pre-built/`. CI runs `git diff --exit-code -- pre-built/` so any hand-edit to the output is caught as drift.
245
-
246
- Or wire token regeneration into `package.json`:
178
+ ### Pagination
247
179
 
248
- ```json
249
- {
250
- "scripts": {
251
- "tokens:build:v4": "node docs/portable-design/generate-tokens.cjs --target=tailwind-v4 > tailwind.css",
252
- "tokens:build:v3": "node docs/portable-design/generate-tokens.cjs --target=tailwind > tailwind.preset.js",
253
- "prebuild": "npm run tokens:build:v3 && npm run tokens:build:v4"
254
- }
255
- }
180
+ ```html
181
+ <div class="hf-pagination">
182
+ <button class="hf-pagination__item" disabled aria-label="Previous">‹</button>
183
+ <button class="hf-pagination__item is-active">1</button>
184
+ <button class="hf-pagination__item">2</button>
185
+ <button class="hf-pagination__item">3</button>
186
+ <span class="hf-pagination__ellipsis">…</span>
187
+ <button class="hf-pagination__item">12</button>
188
+ <button class="hf-pagination__item" aria-label="Next">›</button>
189
+ </div>
256
190
  ```
257
191
 
258
- ## Validation checklist
192
+ ### Modal
259
193
 
260
- After porting to a new project, verify:
194
+ ```html
195
+ <div class="hf-modal max-w-[500px] mx-auto">
196
+ <div class="hf-modal__header">
197
+ <h2 class="hf-modal__title">Edit Guest</h2>
198
+ <button class="hf-modal__close" aria-label="Close">✕</button>
199
+ </div>
200
+ <div class="hf-modal__body">…</div>
201
+ <div class="hf-modal__footer">
202
+ <button class="hf-btn hf-btn--cancel">Cancel</button>
203
+ <button class="hf-btn hf-btn--primary">Save</button>
204
+ </div>
205
+ </div>
206
+ <!-- footer with top border: add .hf-modal--with-footer-border on .hf-modal -->
207
+ ```
261
208
 
262
- - [ ] `cat CLAUDE.md` (or `.cursorrules`) shows the design-system rules.
263
- - [ ] `node docs/portable-design/generate-tokens.cjs --target=css` runs cleanly and produces stable output.
264
- - [ ] AI agent answers "what's the primary color?" with `#24AFE8`.
265
- - [ ] First button generated by AI matches `.btn-primary` (40px / 15px / 600 / `#24AFE8` / 6px radius).
266
- - [ ] Status badges resolve via `--status-{domain}-{state}-color` tokens.
267
- - [ ] `.hf-modal`, `.hf-alert`, `.hf-pagination` render correctly when `pre-built/components.css` is loaded.
209
+ ### Alert
268
210
 
269
- ## What's NOT portable
211
+ ```html
212
+ <div class="hf-alert hf-alert--success">
213
+ <div class="hf-alert__icon"><!-- icon svg --></div>
214
+ <div class="hf-alert__body">
215
+ <div class="hf-alert__title">Saved successfully</div>
216
+ <p class="hf-alert__text">Booking #1284 has been updated.</p>
217
+ </div>
218
+ <button class="hf-alert__close" aria-label="Dismiss">✕</button>
219
+ </div>
220
+ <!-- variants: --success | --info | --warn | --error · modifiers: --tinted | --banner | --compact -->
221
+ ```
270
222
 
271
- These files in the parent `docs/` are **specific to the HotelFriend portal** (legacy audit / drift detection) and should NOT be copied:
223
+ ### Toast
272
224
 
273
- - `docs/migration-plan.md` — legacy SCSS cleanup queue (Yii portal only)
274
- - `docs/design-tokens-audit.md` — drift report
275
- - `docs/ui-elements-catalog.*` observed components in the portal
276
- - `docs/icon-audit.*` FA→Lucide migration map
277
- - `docs/component-anatomy.json` — measurements of legacy modals
278
- - `scratch/` — Playwright walkers that crawl the portal
225
+ ```html
226
+ <div class="hf-toast">
227
+ <span class="hf-toast__icon"><!-- icon svg --></span>
228
+ <span class="hf-toast__text">Booking <strong>#2841</strong> confirmed</span>
229
+ <button class="hf-toast__close" aria-label="Dismiss">✕</button>
230
+ </div>
231
+ ```
279
232
 
280
- They live in the source repo for housekeeping; a new project doesn't need them.
233
+ ### Dropdown menu
281
234
 
282
- The `portal-audit.html` inside this bundle is the only legacy artifact that ships with the portable folder — keep it for historical reference and migration tracking. New code should never copy patterns from `portal-audit.html`.
235
+ ```html
236
+ <div class="hf-dropdown-menu w-52">
237
+ <div class="hf-dropdown-menu__header">Actions</div>
238
+ <div class="hf-dropdown-menu__item">View details</div>
239
+ <div class="hf-dropdown-menu__item">Edit <span class="hf-dropdown-menu__shortcut">⌘E</span></div>
240
+ <div class="hf-dropdown-menu__item is-disabled">Refresh</div>
241
+ <div class="hf-dropdown-menu__divider"></div>
242
+ <div class="hf-dropdown-menu__item hf-dropdown-menu__item--danger">Delete</div>
243
+ </div>
244
+ <!-- pre-0.4 flat names (.hf-dropdown-header / -item / -divider) still work as deprecated aliases -->
245
+ ```
283
246
 
284
- ---
247
+ ### Checkbox & radio
285
248
 
286
- ## Drift detection & CI integration
249
+ ```html
250
+ <input type="checkbox" class="hf-check" checked />
251
+ <input type="checkbox" class="hf-check" disabled />
252
+ <input type="radio" name="grp" class="hf-radio" checked />
253
+ ```
287
254
 
288
- ### Pre-commit hook (recommended for any project that touches this bundle)
255
+ ### Buttons
289
256
 
290
- ```bash
291
- # With husky (recommended)
292
- pnpm add -D husky
293
- pnpm husky init
294
- cp docs/portable-design/scripts/pre-commit.sh .husky/pre-commit
295
- chmod +x .husky/pre-commit
296
-
297
- # Without husky (raw git hook)
298
- cp docs/portable-design/scripts/pre-commit.sh .git/hooks/pre-commit
299
- chmod +x .git/hooks/pre-commit
257
+ ```html
258
+ <button class="hf-btn hf-btn--primary">Save</button>
259
+ <button class="hf-btn hf-btn--outline-primary">Preview</button>
260
+ <button class="hf-btn hf-btn--outline-default">Settings</button>
261
+ <button class="hf-btn hf-btn--cancel">Cancel</button>
262
+ <button class="hf-btn hf-btn--danger">Delete</button>
263
+ <button class="hf-btn hf-btn--primary hf-btn--sm">Small</button>
264
+ <button class="hf-btn hf-btn--icon hf-btn--outline-default" aria-label="Edit">✎</button>
300
265
  ```
301
266
 
302
- The hook runs `validate-tokens.cjs` when any tokens / pre-built / docs / components.html file is staged. Fails the commit if there's drift. Silent on success.
303
-
304
- ### Stylelint config (recommended for consumer projects)
267
+ ### Form controls & field
305
268
 
306
- A shareable Stylelint config that forbids raw hex / named colors / literal `box-shadow` declarations:
269
+ ```html
270
+ <div class="hf-form-field">
271
+ <label class="hf-form-field__label hf-form-field__label--required">Email</label>
272
+ <input type="email" class="hf-input" placeholder="guest@example.com" />
273
+ <span class="hf-form-field__hint">We'll never share it.</span>
274
+ </div>
307
275
 
308
- ```js
309
- // .stylelintrc.cjs
310
- module.exports = {
311
- extends: [
312
- 'stylelint-config-standard',
313
- './node_modules/@hotelfriendag/design-tokens/pre-built/stylelint-design-system.cjs'
314
- // — or, if pulled in via copy-folder:
315
- // './docs/portable-design/pre-built/stylelint-design-system.cjs'
316
- ],
317
- overrides: [
318
- // Generated files may contain hex (fallbacks, primitives) — opt-out
319
- { files: ['**/pre-built/*.css'], rules: { 'color-no-hex': null } },
320
- ],
321
- };
322
- ```
276
+ <div class="hf-form-field hf-form-field--error">
277
+ <label class="hf-form-field__label">Room</label>
278
+ <div class="hf-select-wrap">
279
+ <select class="hf-select">
280
+ <option>Deluxe</option>
281
+ <option>Suite</option>
282
+ </select>
283
+ </div>
284
+ <span class="hf-form-field__error">Please choose a room.</span>
285
+ </div>
323
286
 
324
- ### Standalone validator (one-shot CI check)
287
+ <textarea class="hf-textarea" placeholder="Notes…"></textarea>
325
288
 
326
- ```bash
327
- cd docs/portable-design
328
- node scripts/validate-tokens.cjs
329
- # ✓ validate-tokens: all checks passed
330
- # 168 CSS variables defined, all var() refs resolve, no bare hex.
289
+ <!-- toggle: a styled checkbox -->
290
+ <input type="checkbox" class="hf-switch" checked />
331
291
  ```
332
292
 
333
- Use this in CI alongside lint/test. Exits non-zero on:
334
- 1. `var(--*)` reference that doesn't resolve to a defined CSS variable
335
- 2. Bare hex literal in `components.css` / `status.css` (outside comments + `var()` fallbacks)
336
- 3. Token reference in doc code-blocks that doesn't exist in `pre-built/*`
337
-
338
- ## NPM package (npmjs.com — public)
293
+ ### Card
339
294
 
340
- The bundle ships as [`@hotelfriendag/design-tokens`](https://www.npmjs.com/package/@hotelfriendag/design-tokens) on the public npmjs.com registry — no `.npmrc`, no tokens, no CI secrets:
341
-
342
- ```bash
343
- # In any consumer project:
344
- pnpm add @hotelfriendag/design-tokens
295
+ ```html
296
+ <section class="hf-card">
297
+ <header class="hf-card__header">
298
+ <div>
299
+ <h3 class="hf-card__title">Channel settings</h3>
300
+ <p class="hf-card__description">Enable the sales channels for this property.</p>
301
+ </div>
302
+ <button class="hf-btn hf-btn--outline-default hf-btn--sm">Edit</button>
303
+ </header>
304
+ <div class="hf-card__body">…</div>
305
+ <footer class="hf-card__footer">
306
+ <button class="hf-btn hf-btn--cancel">Cancel</button>
307
+ <button class="hf-btn hf-btn--primary">Save</button>
308
+ </footer>
309
+ </section>
310
+ <!-- borderless/flat variant: add .hf-card--flat -->
345
311
  ```
346
312
 
347
- Then import per stack:
313
+ ### Drawer (side-panel)
348
314
 
349
- ```css
350
- /* CSS / Tailwind v4 */
351
- @import "@hotelfriendag/design-tokens/tailwind.css";
352
- @import "@hotelfriendag/design-tokens/components.css";
353
- @import "@hotelfriendag/design-tokens/status.css";
315
+ ```html
316
+ <div class="hf-drawer">
317
+ <div class="hf-drawer__backdrop"></div>
318
+ <aside class="hf-drawer__panel">
319
+ <header class="hf-drawer__header">
320
+ <h2 class="hf-drawer__title">Reservation #2841</h2>
321
+ <button class="hf-drawer__close" aria-label="Close">✕</button>
322
+ </header>
323
+ <div class="hf-drawer__body">…</div>
324
+ <footer class="hf-drawer__footer">
325
+ <button class="hf-btn hf-btn--cancel">Close</button>
326
+ <button class="hf-btn hf-btn--primary">Check in</button>
327
+ </footer>
328
+ </aside>
329
+ </div>
330
+ <!-- slide from the left: add .hf-drawer--left on .hf-drawer -->
354
331
  ```
355
332
 
356
- ```ts
357
- // TypeScript / JavaScript (default export — uses compiled tokens.js + tokens.d.ts)
358
- import { tokens } from '@hotelfriendag/design-tokens';
333
+ ## Cheat-sheet — common tokens & utilities
359
334
 
360
- // Or the TS source directly (Vite / Vue / Next handle this natively):
361
- import { tokens } from '@hotelfriendag/design-tokens/tokens.ts';
362
- ```
335
+ Tailwind v4 derives utilities from the `@theme` tokens (`bg-hf-*`, `text-hf-*`, `rounded-hf-*`, `shadow-hf-*`). Vanilla CSS uses the `var(--color-hf-*)` form; SCSS uses `$colorAccent`, `$radiusSm`, See [`UI_DESIGN.md`](UI_DESIGN.md) §9 for the full token list.
363
336
 
364
- ```scss
365
- // SCSS
366
- @import '@hotelfriendag/design-tokens/_tokens';
367
- ```
337
+ **Brand & text color**
368
338
 
369
- ### Publishing (maintainer)
339
+ | Utility | Token | Value |
340
+ |---|---|---|
341
+ | `bg-hf-accent` / `text-hf-accent` | `--color-hf-accent` | `#24AFE8` (brand) |
342
+ | `hover:bg-hf-accent-hover` | `--color-hf-accent-hover` | `#149AD1` |
343
+ | `bg-hf-accent-subtle` / `-subtler` | `--color-hf-accent-subtle` | light tints |
344
+ | `text-hf-fg` | `--color-hf-fg` | body `#2B2B2B` |
345
+ | `text-hf-fg-muted` / `-subtle` / `-faint` | `--color-hf-fg-*` | secondary → placeholder |
346
+ | `text-hf-on-accent` | `--color-hf-on-accent` | `#FFFFFF` (text on accent) |
370
347
 
371
- Releases are tag-triggered via `.github/workflows/release.yml` — push a `v*` tag
372
- and the workflow builds, validates, then `npm publish`-es to npmjs.com via
373
- **Trusted Publishing (OIDC)**. No `NPM_TOKEN` secret, no PATs: GitHub Actions
374
- mints a short-lived OIDC token that npm exchanges for publish credentials.
348
+ **Surfaces & borders**
375
349
 
376
- `--provenance` is NOT used: npmjs rejects sigstore provenance from private
377
- source repositories, and this repo stays private (only the built artifact is
378
- public — see RFC-0001 for the rationale). The trade is a missing attestation
379
- badge on the npm page in exchange for keeping the source code private.
350
+ | Utility | Token |
351
+ |---|---|
352
+ | `bg-hf-bg-surface` | card / modal / popover (white) |
353
+ | `bg-hf-bg-page` / `-section` / `-muted` | page section muted grays |
354
+ | `border-hf-border` / `-subtle` / `-strong` | default → faint → hover border |
380
355
 
381
- ```bash
382
- # From the repo root, on main:
383
- npm version patch # or minor / major → updates package.json, creates v* tag, commits
384
- git push --follow-tags # workflow runs on the pushed tag and publishes
385
- ```
356
+ **Scales**
386
357
 
387
- The `prepublishOnly` script re-runs the generator + validator so the published package is always self-consistent. If you need to publish manually from a local machine (hotfix), `npm publish` works the same way after `npm version <bump>` and `npm login` — but Trusted Publishing is the canonical path.
358
+ - **Text:** `text-hf-xs` 11 · `text-hf-sm` 13 · `text-hf-base` 14 · `text-hf-md` 15 · `text-hf-lg` 16 · `text-hf-xl` 18
359
+ - **Radius:** `rounded-hf-sm` 6 · `-md` 8 · `-lg` 9 · `-xl` 12 · `-pill` 99
360
+ - **Shadow:** `shadow-hf-subtle` · `shadow-hf-card` · `shadow-hf-modal` · `shadow-hf-hover`
361
+ - **Status:** `text-hf-status-{success|warning|error|info|cancel}` + `bg-hf-status-{…}-bg` — or just use `.hf-pill .status-{domain}-{state}`
388
362
 
389
- The Trusted Publisher binding on npmjs (Package → Settings → Trusted publishing) is configured for:
363
+ ## Visual reference (live)
390
364
 
391
- | Field | Value |
392
- |---|---|
393
- | Organization | `HotelFriendAG` |
394
- | Repository | `design-system` |
395
- | Workflow filename | `release.yml` |
365
+ `components.html` is the canonical rendered showcase of every `.hf-*` component. The package is public on npm, so **unpkg serves it rendered in the browser** — no hosting needed:
396
366
 
397
- Changing the workflow filename, repo name, or org requires updating the npmjs binding before the next release will succeed.
367
+ **→ https://unpkg.com/@hotelfriendag/design-tokens/components.html**
398
368
 
399
- > Historical: versions `0.2.x` were briefly published to GitHub Packages while the registry decision was pending. `0.3.0+` is published exclusively to npmjs.com; consumers should install from there. `0.3.0` itself was bootstrapped by a one-time manual publish from a maintainer's machine using a short-lived Granular Access Token (since Trusted Publishing requires the package to already exist on npm). The token was revoked immediately after; all subsequent versions use OIDC.
369
+ Always serves the latest; pin a version with `…/design-tokens@0.4.0/components.html`. Or open the copy in your own project:
400
370
 
401
- ## Changelog
371
+ ```bash
372
+ open node_modules/@hotelfriendag/design-tokens/components.html # macOS
373
+ xdg-open node_modules/@hotelfriendag/design-tokens/components.html # Linux
374
+ start node_modules\@hotelfriendag\design-tokens\components.html # Windows
375
+ ```
402
376
 
403
- ### Phase 3Distribution + governance scaffolding (2026-05-21)
377
+ > Use the **unpkg** link, not jsDelivr jsDelivr serves `.html` as `text/plain` (shows source); unpkg serves it as `text/html` (renders).
404
378
 
405
- Closes the remaining RFC-0001 governance items. The bundle is now SHIPPABLE as a versioned npm package with drift-detection enforcement.
379
+ ## AI rules
406
380
 
407
- - **Phase 3A** `components.html` Phase 2D reconciliation closed. Embedded `@layer components` stays standalone (Tailwind v4 browser CDN doesn't support nested `@import`). The @theme block already mirrors `pre-built/tailwind.css` (canonical hf-prefixed) + legacy aliases for demo continuity.
408
- - **Phase 3B** — `pre-built/stylelint-design-system.cjs` ships as a shareable Stylelint config. Forbids raw hex, named CSS colors, literal `box-shadow` declarations, and arbitrary-value Tailwind hex. Consumer projects extend it in their `.stylelintrc.cjs`.
409
- - **Phase 3C** — `scripts/pre-commit.sh` ships as a Git pre-commit hook scaffold (husky-compatible). Runs `validate-tokens.cjs` when bundle files are staged. Skips silently when no bundle changes are present.
410
- - **Phase 3D** — `package.json` defines the npm package skeleton:
411
- - Exports map for every generated file + ai-rules + JSON sources
412
- - `scripts.build` chains all generator targets (8 in total)
413
- - `scripts.validate` runs the drift detector
414
- - `scripts.prepublishOnly` re-builds + validates before publish
415
- - `publishConfig` points at GitHub Packages registry (private/restricted access)
381
+ The package ships agent rules under `ai-rules/`. Drop one into the consumer's root so Claude / Cursor / Copilot follow the same UI rules:
416
382
 
417
- ### Phase 2 Component-tier + status externalization + verification (2026-05-21)
383
+ - `ai-rules/CLAUDE.md` consumer root `CLAUDE.md` (Claude Code, autodetected)
384
+ - `ai-rules/cursorrules.template` → `.cursorrules`
385
+ - `ai-rules/github-copilot-instructions.md` → `.github/copilot-instructions.md`
386
+ - `ai-rules/system-prompt-compact.md` → compact prompt for ChatGPT / v0 / Lovable
418
387
 
419
- Closes the RFC-0001 §5 + RFC-0002 §Q7 refinement TODOs that Phase 1B left behind. Three sub-phases all shipped.
388
+ If the consumer already has a `CLAUDE.md`, reference the package rules from it (e.g. point at `node_modules/@hotelfriendag/design-tokens/ai-rules/CLAUDE.md`) instead of overwriting. The agent learns the canonical palette, typography, spacing/radius/shadow scales, the `.hf-*` library, and the hard rules ("never hard-code hex", "always provide hover/focus/disabled").
420
389
 
421
- **Phase 2A Status mapping externalized (`status-map.json`)**
390
+ ## Keeping a consumer up to date
422
391
 
423
- The domain→semantic-status mapping (which `.status-booking-confirmed` resolves to → semantic `info`, etc.) lived as a hardcoded block in `components.css`. That re-introduced the drift RFC-0001 §5b warned about. Now:
392
+ ```bash
393
+ pnpm update @hotelfriendag/design-tokens # pull the latest published version
394
+ ```
424
395
 
425
- - **New file:** `status-map.json` JSON dictionary `{domain.state}` `semantic-status-name`
426
- - **New target:** `node generate-tokens.cjs --target=status-css > pre-built/status.css` emits the `.status-{domain}-{state}` CSS rules with `var(--color-hf-status-*)` references
427
- - **`pre-built/components.css`** — the `.status-*` block REMOVED; now you `@import` both files
428
- - Adding a new status is JSON-only — no CSS edit
396
+ A convenient consumer script: `"design-system:update": "pnpm update @hotelfriendag/design-tokens && pnpm ls @hotelfriendag/design-tokens"`. Commit the bumped lockfile with `chore: bump design-system to <version>`.
429
397
 
430
- **Phase 2B 4 component-tier tokens promoted**
398
+ ## Drift detection & CI (for consumers)
431
399
 
432
- Bare hex values that didn't fit a generic semantic slot but were used by exactly one component:
400
+ **Stylelint config** forbids raw hex / named colors / literal `box-shadow`:
433
401
 
434
- | Token | Value | Used by |
435
- |---|---|---|
436
- | `--color-hf-tab-fg-inactive` | `var(--color-hf-gray-700)` (#485B78) | `.hf-tab` default, `.hf-pill-tab` |
437
- | `--color-hf-tab-count-fg` | `var(--color-hf-gray-700)` | `.hf-tab__count` (inactive tab) |
438
- | `--color-hf-pagination-fg-inactive` | `var(--color-hf-gray-700)` | `.hf-pagination__item` default |
439
- | `--color-hf-pagination-fg-active` | `var(--color-hf-gray-950)` (#252F4A) | `.hf-pagination__item.is-active` |
440
- | `--color-hf-input-border` | `#DBDFE9` (raw — between gray-200 and gray-300) | `.hf-check`, `.hf-radio` |
441
- | `--color-hf-menu-bg-hover` | `#F5F5F5` (raw — portal `$table__hover`) | `.hf-dropdown-item:hover/:focus-visible` |
402
+ ```js
403
+ // .stylelintrc.cjs
404
+ module.exports = {
405
+ extends: [
406
+ 'stylelint-config-standard',
407
+ './node_modules/@hotelfriendag/design-tokens/pre-built/stylelint-design-system.cjs',
408
+ ],
409
+ overrides: [
410
+ { files: ['**/pre-built/*.css'], rules: { 'color-no-hex': null } },
411
+ ],
412
+ };
413
+ ```
442
414
 
443
- Naming convention: `{component}.{slot}` under `semantic.color` in `tokens.figma.json`. Generator emits `--color-hf-{component}-{slot}` which gives Tailwind utilities `bg-hf-tab-fg-inactive` etc.
415
+ **Pre-commit hook** `scripts/pre-commit.sh` runs the token validator when bundle files are staged (husky-compatible).
444
416
 
445
- **Phase 2C — Verifier (`scripts/validate-tokens.cjs`)**
417
+ ---
446
418
 
447
- Drift-detection script. Three checks:
448
- 1. Every `var(--*)` in `pre-built/*.css` resolves to a defined CSS variable
449
- 2. No bare hex literals in `components.css` / `status.css` (outside comments + `var()` fallbacks; `#fff`/`#000` allowed)
450
- 3. Every token reference in doc code-blocks exists in `pre-built/*` (anti-RFC-0001 §2.2 drift)
419
+ ## Maintainer reference
451
420
 
452
- Exits 1 on any failure — suitable for pre-commit hook or CI step.
421
+ ### Repo layout & precedence
453
422
 
454
- ```bash
455
- node scripts/validate-tokens.cjs
456
- # validate-tokens: all checks passed
457
- # 168 CSS variables defined, all var() refs resolve, no bare hex.
423
+ ```
424
+ design-system/
425
+ ├── components.html PRIMARY · canonical visual reference (open in browser)
426
+ ├── UI_DESIGN.md NARRATIVE · rationale, anatomy, decisions
427
+ ├── tokens.figma.json TOKENS · Tokens Studio export — single source of truth
428
+ ├── status-map.json domain→semantic status mapping (feeds status.css)
429
+ ├── src/components.css SOURCE for .hf-* rules (edit here)
430
+ ├── generate-tokens.cjs Node generator (no deps)
431
+ ├── pre-built/ GENERATED — never hand-edit (CI drift gate)
432
+ └── ai-rules/ agent rule files
458
433
  ```
459
434
 
460
- **Phase 2D `components.html` reconciled with Phase 1B+2 tokens**
435
+ Precedence when files disagree:
436
+ 1. **`components.html`** — visual decisions (colors, sizes, anatomy)
437
+ 2. **`tokens.figma.json`** — token values (regenerate `pre-built/*` after edits)
438
+ 3. **`UI_DESIGN.md`** — WHY decisions were made
439
+ 4. **`pre-built/*`** — generated, never hand-edit
440
+ 5. **`portal-audit.html`** — archival only (legacy portal snapshot)
461
441
 
462
- The embedded `@theme {}` block now mirrors `pre-built/tailwind.css` (canonical hf-prefixed three-tier model) + adds legacy aliases for the demo's existing class refs. Result: BOTH `bg-primary` AND `bg-hf-accent` resolve to `#24AFE8` in the demo. The demo continues to render without a 500+ class rewrite, and the canonical `hf-` naming is documented in the @theme source.
442
+ ### Regenerating outputs
463
443
 
464
- **What was tracked as Phase 2 TODO but stays open for Phase 3 / future:**
444
+ `tokens.figma.json` is the single source of truth; `src/components.css` is the source for the `.hf-*` layer. After editing either:
465
445
 
466
- - ✅ ~~Fully *generate* `pre-built/components.css` from sources~~ — done 2026-05-27 (ROADMAP #4): source moved to `src/components.css`, `--target=components-css` added to the generator, drift gate covers regeneration.
467
- - Rewrite `components.html` demo HTML to use the new prefixed class refs (`bg-hf-accent` everywhere) and drop legacy aliases entirely
468
- - Add ESLint/Stylelint plugin (RFC-0001 §4.6 forbid raw hex)
469
- - Publish as `@hotelfriendag/design-tokens` npm package (RFC-0001 §4.4)
446
+ ```bash
447
+ npm run build # regenerates all pre-built/* (tokens, tailwind v3+v4, scss, ts/js/dts, shadcn, status.css, components.css)
448
+ npm run validate # drift detector: every var() resolves, no bare hex
449
+ ```
470
450
 
471
- ### Phase 1B RFC-0002 semantic tier (2026-05-21)
451
+ CI runs `git diff --exit-code -- pre-built/` so outputs must be reproducible from source. Edit `src/`, never `pre-built/`.
472
452
 
473
- Introduces the three-tier model: **primitive → semantic → (Phase 2) component**. Per RFC-0002. App code should now reference SEMANTIC tokens (`var(--color-hf-accent)`, `var(--color-hf-fg)`, etc.) — primitives (`hf-blue-500`, `hf-gray-300`) are an implementation detail. Themes swap the semantic layer without touching primitives.
453
+ ### Publishing
474
454
 
475
- **Breaking changes (consumers must update):**
455
+ Releases are tag-triggered via `.github/workflows/release.yml` — push a `v*` tag and the workflow builds, validates, then `npm publish`-es to npmjs.com via **Trusted Publishing (OIDC)**. No `NPM_TOKEN` secret, no PATs: GitHub Actions mints a short-lived OIDC token that npm exchanges for publish credentials.
476
456
 
477
- | Phase 1A name | Phase 1B replacement |
478
- |---|---|
479
- | `--color-hf-primary*` | `--color-hf-accent` / `-hover` / `-subtle` / `-subtler` |
480
- | `--color-hf-text-primary` | `--color-hf-fg` |
481
- | `--color-hf-text-secondary` | `--color-hf-fg-muted` |
482
- | `--color-hf-text-useful` | `--color-hf-fg-subtle` |
483
- | `--color-hf-text-placeholder` | `--color-hf-fg-faint` |
484
- | `--color-hf-neutral-light-grey` | `--color-hf-bg-page` |
485
- | `--color-hf-neutral-bg-accent` | `--color-hf-bg-section` |
486
- | `--color-hf-neutral-very-light` | `--color-hf-bg-muted` |
487
- | `--color-hf-neutral-border` | `--color-hf-border` |
488
- | `--color-hf-neutral-light-blue-gray` | `--color-hf-border-subtle` |
489
- | `--color-hf-neutral-border-hover` | `--color-hf-border-strong` |
490
- | `--color-hf-status-success` etc. | unchanged ✓ (already canonical) |
491
- | `--color-hf-badge-{domain}-{state}-*` | **removed** — see status-map note below |
492
-
493
- **New: SEMANTIC status colors** (replace per-domain `badge.*` tokens):
457
+ `--provenance` is intentionally NOT used: npmjs rejects sigstore provenance from private source repositories, and this repo stays private (only the built artifact is public).
494
458
 
459
+ ```bash
460
+ npm version patch # or minor / major → bumps package.json, creates v* tag, commits
461
+ git push --follow-tags # workflow runs on the tag and publishes
495
462
  ```
496
- --color-hf-status-success / -bg / -strong
497
- --color-hf-status-warning / -bg / -strong
498
- --color-hf-status-error / -bg / -strong / -alt
499
- --color-hf-status-info / -bg
500
- --color-hf-status-cancel / -bg
501
- --color-hf-status-coral / -bg (portal-specific)
502
- --color-hf-status-violet / -bg (portal-specific)
503
- --color-hf-status-olive / -bg (portal-specific)
504
- --color-hf-status-orange-deep / -bg (portal-specific)
505
- ```
506
463
 
507
- Domain→semantic mapping for CSS classes (e.g. `.status-booking-confirmed`uses `--color-hf-status-info`) currently lives **hardcoded in `pre-built/components.css`** with `TODO Phase 2` markers. RFC-0002 §Q7 refinement requires extraction to `status-map.json` consumed by the generator — tracked as Phase 2.
508
-
509
- **New: tier-aware generator** (`generate-tokens.cjs`):
510
- - Recognises `primitive.*` and `semantic.*` top-level groups in `tokens.figma.json`
511
- - Resolves Tokens Studio `{path.to.token}` references — CSS targets emit `var()` chain (so theming works at runtime), TS/SCSS/v3-preset inline the resolved value
512
- - All existing `--prefix=` / `--target=` flags unchanged
513
-
514
- **Files affected:**
515
- - `tokens.figma.json` — restructured: `primitive.color.{family}.{step}` + `semantic.color.{slot}.{variant}`
516
- - `generate-tokens.cjs` — alias resolution + tier path normalisation
517
- - `pre-built/*` — all regenerated; tokens.css now has primitives + semantic aliases
518
- - `pre-built/components.css` — fully rewritten; binds chrome to semantic, hardcodes status-map (TODO externalize)
519
- - `components.html` — **NOT touched** (still self-contained with its own pre-Phase 1B @theme). Phase 2 will reconcile.
520
-
521
- ### Phase 1A — RFC-0001 collision-safe prefixing (2026-05-21)
522
-
523
- Solves the biggest RFC-0001 critique (§2.1, §4.2) — namespace collisions with Tailwind v4 defaults that silently resized utilities used thousands of times in `ui-hf`.
524
-
525
- **Breaking change for `ui-hf` and any other consumer:** all token names now carry the `hf-` prefix INSIDE the category segment.
526
-
527
- - `--color-primary` → `--color-hf-primary` (Tailwind utility: `bg-hf-primary`)
528
- - `--text-base` → `--text-hf-base` (utility: `text-hf-base` = 14px; Tailwind's default `text-base` keeps 16px — no more silent override)
529
- - `--radius-sm` → `--radius-hf-sm` (utility: `rounded-hf-sm` = 6px; default `rounded-sm` stays 2px)
530
- - `--spacing-7` → `--spacing-hf-7` (utility: `p-hf-7` = 30px; default `p-7` stays 28px)
531
- - `--font-sans` → `--font-hf-sans` (utility: `font-hf-sans` = Roboto)
532
- - `--shadow-modal` → `--shadow-hf-modal` (utility: `shadow-hf-modal`)
533
- - All `--color-badge-{domain}-{state}-*` → `--color-hf-badge-{domain}-{state}-*`
534
-
535
- **Why the prefix goes INSIDE the category** (e.g. `--color-hf-primary`, not `--hf-color-primary`):
536
- Tailwind v4 only generates utility classes from CSS variables that start with its known utility-prefixes (`--color-*`, `--text-*`, `--spacing-*`, `--radius-*`, `--shadow-*`, `--font-*`, etc.). A variable like `--hf-color-primary` would NOT generate any utility. The current convention preserves the utility-class derivation while keeping the namespace conflict-free.
537
-
538
- **Files touched:**
539
- - `tokens.figma.json` — unchanged (the prefix is applied at generation time, not in source)
540
- - `generate-tokens.cjs` — new `--prefix=` argument; PREFIX defaults to `hf-`. Override with `--prefix=` (empty) for legacy unprefixed output
541
- - `pre-built/*.css/scss/ts/js` — all regenerated with `--hf-` prefix in CSS targets (Tailwind v3 / SCSS / TS / shadcn unchanged for now)
542
- - `pre-built/components.css` — all `var(--*)` refs updated to new prefixed names
543
- - Docs — all `var(--color-primary)` etc. updated to `var(--color-hf-primary)` form
544
-
545
- **What's NOT changed yet (Phase 1B):**
546
- - `components.html` `@theme {}` block still uses unprefixed names (it's a self-contained demo — no collision risk in standalone use). Phase 1B will reconcile when the semantic tier lands.
547
- - Tailwind v3 preset, SCSS, TS outputs stay unprefixed (legacy paths — less risk of collision in those ecosystems).
548
-
549
- ### Phase 0 — RFC-0001 quick wins (2026-05-21)
550
-
551
- Addresses critique from `RFC-0001-cross-project-design-system.md` §2 + §8 — small fixes that don't require architectural decisions:
552
-
553
- - **ESM compatibility** — renamed `generate-tokens.js` → `generate-tokens.cjs` so the script runs under Node in `"type":"module"` projects (Vite/Vue 3/Next).
554
- - **Token name fix** — `color.primary.default` JSON key no longer emits `--color-primary-default` (awkward `bg-primary-default` utility). Generator now strips trailing `default`/`DEFAULT` so emit is `--color-primary` → utility `bg-primary` works. Same logic applies to any future `.default` sub-keys.
555
- - **Shadow token namespace** — JSON group `boxShadow` → `shadow` so emit is `--shadow-modal` (was `--box-shadow-modal`). Matches what `components.html` and docs already reference.
556
- - **`components.css` — RFC §5 conformance:**
557
- - **No more hardcoded hex** (RFC §5a) — all values reference `var(--color-*)` / `var(--font-*)` / `var(--border-radius-*)` with hex fallbacks. The 4 remaining bare hex (`#fff`, `#252F4A`, `#DBDFE9`, `#F5F5F5`) are commented as Phase 1 TODOs for the semantic tier.
558
- - **One namespace** (RFC §5c) — dropped the parallel `:root { --status-* }` declarations; consumes canonical `--color-badge-*` tokens directly.
559
- - **Class names match token paths** (RFC §5d) — breaking renames:
560
- - `.status-clean-*` → `.status-room-item-cleaning-*`
561
- - `.status-fd-*` → `.status-front-desk-*`
562
- - `.status-order-action` → `.status-order-action-required`
563
- - **Doc/output snapshot** — added a smoke test verifying every `var(--*)` referenced in docs exists in `pre-built/tokens.css` or `pre-built/components.css`. Currently 0 missing.
564
- - **Existing-project integration warning** — new "⚠️ Existing-project integration" section above documents the Tailwind v4 collision issue + `@source not` exclusion until Phase 1 lands prefixed tokens.
565
-
566
- ### Still open (deferred to Phase 1+, per RFC roadmap)
567
-
568
- - Token namespace prefix (`--hf-*`) + `--target=tailwind-v4-additive` (RFC §4.2)
569
- - Three-tier token architecture: primitive → semantic → component (RFC §4.1)
570
- - `text-text-primary` awkward utility — fix when semantic tier introduces `--hf-color-fg` (RFC §8.3)
571
- - ✅ ~~`pre-built/components.css` is hand-mirrored from `components.html` rather than generated (RFC §5b)~~ — resolved 2026-05-27 (ROADMAP #4): now generated from `src/components.css` by `--target=components-css`.
572
- - Two competing visual SSOTs (`components.html` vs `UI_DESIGN.md`) — Phase 2 will pick one
464
+ The Trusted Publisher binding on npmjs (Package Settings Trusted publishing) is configured for org `HotelFriendAG`, repo `design-system`, workflow `release.yml`. Changing any of those requires updating the binding before the next release.
465
+
466
+ `prepublishOnly` re-runs the generator + validator so the published package is always self-consistent.
573
467
 
574
468
  ---
575
469
 
576
- Built from the HotelFriend Design System v0.1.0 — see `UI_DESIGN.md` §9 for the JSON token reference and `components.html` for the canonical visual reference.
470
+ - **Changelog / phase history:** [`CHANGELOG.md`](CHANGELOG.md)
471
+ - **Remaining work:** [`ROADMAP.md`](ROADMAP.md)
472
+ - **First-consumer playbook:** [`INTEGRATION-ui-hf.md`](INTEGRATION-ui-hf.md)
473
+ - **Architecture rationale:** [`RFC-0001`](RFC-0001-cross-project-design-system.md) · [`RFC-0002`](RFC-0002-semantic-tier-naming.md)
474
+ - **Visual reference:** `components.html` · **Token narrative:** `UI_DESIGN.md`