@hotelfriendag/design-tokens 0.3.3 → 0.3.4

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,243 @@
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.
71
16
 
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.
17
+ ## Wire it up (pick your stack)
73
18
 
74
- **Recommended setup for any project (greenfield or existing):**
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`.
20
+
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:
89
-
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**.
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.
95
33
 
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
  |---|---|---|
202
137
  | `.hf-pill` + `.status-{domain}-{state}` | Status badge — 6px radius, 15% bg + 100% text + 1px border | components.html#status |
203
138
  | `.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 |
139
+ | `.hf-pagination` + `__item` / `__ellipsis` | Subtle gray active (NOT accent!) — 34×34, 8px radius | components.html#pagination |
205
140
  | `.hf-modal` + `__header/__title/__body/__footer/__close` | 6px radius, footer no top border | components.html#modal |
206
141
  | `.hf-alert` + `--success/--info/--warn/--error` | White bg + 3px top accent bar + 26×26 squared icon | components.html#alerts |
207
142
  | `.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 |
212
-
213
- ## How the AI uses this
214
-
215
- After you drop one of the `ai-rules/*` files at the new project's root:
216
-
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")
225
-
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
231
-
232
- When `tokens.figma.json` changes, regenerate the bindings:
233
-
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
242
- ```
143
+ | `.hf-toast` | Floating notification — 9px radius | components.html#alerts |
144
+ | `.hf-check` / `.hf-radio` | Custom checkbox+radio — 18×18, filled accent on check | components.html#inputs |
145
+ | `.hf-dropdown-menu` + `__item / __header / __shortcut / __icon / __divider` | 9px radius dropdown, portal shadow | components.html#dropdown |
146
+ | `.skeleton` + `.hf-spin` | Loading-state primitives (shimmer + rotation keyframes) | components.html#empty |
243
147
 
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.
148
+ ## AI rules
245
149
 
246
- Or wire token regeneration into `package.json`:
150
+ The package ships agent rules under `ai-rules/`. Drop one into the consumer's root so Claude / Cursor / Copilot follow the same UI rules:
247
151
 
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
- }
256
- ```
257
-
258
- ## Validation checklist
259
-
260
- After porting to a new project, verify:
261
-
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.
268
-
269
- ## What's NOT portable
270
-
271
- These files in the parent `docs/` are **specific to the HotelFriend portal** (legacy audit / drift detection) and should NOT be copied:
272
-
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
152
+ - `ai-rules/CLAUDE.md` → consumer root `CLAUDE.md` (Claude Code, autodetected)
153
+ - `ai-rules/cursorrules.template` → `.cursorrules`
154
+ - `ai-rules/github-copilot-instructions.md` → `.github/copilot-instructions.md`
155
+ - `ai-rules/system-prompt-compact.md` compact prompt for ChatGPT / v0 / Lovable
279
156
 
280
- They live in the source repo for housekeeping; a new project doesn't need them.
157
+ 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").
281
158
 
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`.
283
-
284
- ---
285
-
286
- ## Drift detection & CI integration
287
-
288
- ### Pre-commit hook (recommended for any project that touches this bundle)
159
+ ## Keeping a consumer up to date
289
160
 
290
161
  ```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
162
+ pnpm update @hotelfriendag/design-tokens # pull the latest published version
300
163
  ```
301
164
 
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.
165
+ 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>`.
303
166
 
304
- ### Stylelint config (recommended for consumer projects)
167
+ ## Drift detection & CI (for consumers)
305
168
 
306
- A shareable Stylelint config that forbids raw hex / named colors / literal `box-shadow` declarations:
169
+ **Stylelint config** forbids raw hex / named colors / literal `box-shadow`:
307
170
 
308
171
  ```js
309
172
  // .stylelintrc.cjs
310
173
  module.exports = {
311
174
  extends: [
312
175
  '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'
176
+ './node_modules/@hotelfriendag/design-tokens/pre-built/stylelint-design-system.cjs',
316
177
  ],
317
178
  overrides: [
318
- // Generated files may contain hex (fallbacks, primitives) — opt-out
319
179
  { files: ['**/pre-built/*.css'], rules: { 'color-no-hex': null } },
320
180
  ],
321
181
  };
322
182
  ```
323
183
 
324
- ### Standalone validator (one-shot CI check)
325
-
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.
331
- ```
184
+ **Pre-commit hook** — `scripts/pre-commit.sh` runs the token validator when bundle files are staged (husky-compatible).
332
185
 
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)
339
-
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
345
- ```
346
-
347
- Then import per stack:
186
+ ---
348
187
 
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";
354
- ```
188
+ ## Maintainer reference
355
189
 
356
- ```ts
357
- // TypeScript / JavaScript (default export — uses compiled tokens.js + tokens.d.ts)
358
- import { tokens } from '@hotelfriendag/design-tokens';
190
+ ### Repo layout & precedence
359
191
 
360
- // Or the TS source directly (Vite / Vue / Next handle this natively):
361
- import { tokens } from '@hotelfriendag/design-tokens/tokens.ts';
362
192
  ```
363
-
364
- ```scss
365
- // SCSS
366
- @import '@hotelfriendag/design-tokens/_tokens';
193
+ design-system/
194
+ ├── components.html PRIMARY · canonical visual reference (open in browser)
195
+ ├── UI_DESIGN.md NARRATIVE · rationale, anatomy, decisions
196
+ ├── tokens.figma.json TOKENS · Tokens Studio export — single source of truth
197
+ ├── status-map.json domain→semantic status mapping (feeds status.css)
198
+ ├── src/components.css SOURCE for .hf-* rules (edit here)
199
+ ├── generate-tokens.cjs Node generator (no deps)
200
+ ├── pre-built/ GENERATED — never hand-edit (CI drift gate)
201
+ └── ai-rules/ agent rule files
367
202
  ```
368
203
 
369
- ### Publishing (maintainer)
204
+ Precedence when files disagree:
205
+ 1. **`components.html`** — visual decisions (colors, sizes, anatomy)
206
+ 2. **`tokens.figma.json`** — token values (regenerate `pre-built/*` after edits)
207
+ 3. **`UI_DESIGN.md`** — WHY decisions were made
208
+ 4. **`pre-built/*`** — generated, never hand-edit
209
+ 5. **`portal-audit.html`** — archival only (legacy portal snapshot)
370
210
 
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.
211
+ ### Regenerating outputs
375
212
 
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.
213
+ `tokens.figma.json` is the single source of truth; `src/components.css` is the source for the `.hf-*` layer. After editing either:
380
214
 
381
215
  ```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
216
+ npm run build # regenerates all pre-built/* (tokens, tailwind v3+v4, scss, ts/js/dts, shadcn, status.css, components.css)
217
+ npm run validate # drift detector: every var() resolves, no bare hex
385
218
  ```
386
219
 
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.
388
-
389
- The Trusted Publisher binding on npmjs (Package → Settings → Trusted publishing) is configured for:
220
+ CI runs `git diff --exit-code -- pre-built/` so outputs must be reproducible from source. Edit `src/`, never `pre-built/`.
390
221
 
391
- | Field | Value |
392
- |---|---|
393
- | Organization | `HotelFriendAG` |
394
- | Repository | `design-system` |
395
- | Workflow filename | `release.yml` |
222
+ ### Publishing
396
223
 
397
- Changing the workflow filename, repo name, or org requires updating the npmjs binding before the next release will succeed.
224
+ 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.
398
225
 
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.
400
-
401
- ## Changelog
402
-
403
- ### Phase 3 — Distribution + governance scaffolding (2026-05-21)
404
-
405
- Closes the remaining RFC-0001 governance items. The bundle is now SHIPPABLE as a versioned npm package with drift-detection enforcement.
406
-
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)
416
-
417
- ### Phase 2 — Component-tier + status externalization + verification (2026-05-21)
418
-
419
- Closes the RFC-0001 §5 + RFC-0002 §Q7 refinement TODOs that Phase 1B left behind. Three sub-phases — all shipped.
420
-
421
- **Phase 2A — Status mapping externalized (`status-map.json`)**
422
-
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:
424
-
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
429
-
430
- **Phase 2B — 4 component-tier tokens promoted**
431
-
432
- Bare hex values that didn't fit a generic semantic slot but were used by exactly one component:
433
-
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` |
442
-
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.
444
-
445
- **Phase 2C — Verifier (`scripts/validate-tokens.cjs`)**
446
-
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)
451
-
452
- Exits 1 on any failure — suitable for pre-commit hook or CI step.
226
+ `--provenance` is intentionally NOT used: npmjs rejects sigstore provenance from private source repositories, and this repo stays private (only the built artifact is public).
453
227
 
454
228
  ```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.
229
+ npm version patch # or minor / major → bumps package.json, creates v* tag, commits
230
+ git push --follow-tags # workflow runs on the tag and publishes
458
231
  ```
459
232
 
460
- **Phase 2D `components.html` reconciled with Phase 1B+2 tokens**
461
-
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.
463
-
464
- **What was tracked as Phase 2 TODO but stays open for Phase 3 / future:**
465
-
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)
470
-
471
- ### Phase 1B — RFC-0002 semantic tier (2026-05-21)
472
-
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.
474
-
475
- **Breaking changes (consumers must update):**
476
-
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):
494
-
495
- ```
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
- ```
233
+ 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.
506
234
 
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
235
+ `prepublishOnly` re-runs the generator + validator so the published package is always self-consistent.
573
236
 
574
237
  ---
575
238
 
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.
239
+ - **Changelog / phase history:** [`CHANGELOG.md`](CHANGELOG.md)
240
+ - **Remaining work:** [`ROADMAP.md`](ROADMAP.md)
241
+ - **First-consumer playbook:** [`INTEGRATION-ui-hf.md`](INTEGRATION-ui-hf.md)
242
+ - **Architecture rationale:** [`RFC-0001`](RFC-0001-cross-project-design-system.md) · [`RFC-0002`](RFC-0002-semantic-tier-naming.md)
243
+ - **Visual reference:** `components.html` · **Token narrative:** `UI_DESIGN.md`
@@ -236,7 +236,11 @@ function emitScss() {
236
236
  lines.push('');
237
237
  for (const { path: p, token } of all) {
238
238
  if (typeof token.value !== 'string') continue;
239
- lines.push(`$${camel(p)}: ${token.value};${token.description ? ' // ' + token.description : ''}`);
239
+ // Resolve Tokens Studio refs ({primitive.color.blue.500}) to concrete values.
240
+ // SCSS variables are compile-time — there's no runtime var() chain — so semantic
241
+ // tokens must inline the primitive value, not emit the raw reference string.
242
+ const value = resolveValue(token, tokenIndex, 'inline');
243
+ lines.push(`$${camel(p)}: ${value};${token.description ? ' // ' + token.description : ''}`);
240
244
  }
241
245
  for (const { path: p, token } of all) {
242
246
  if (token.type !== 'boxShadow') continue;
@@ -259,7 +263,9 @@ function nestTokens() {
259
263
  cur[p[i]] = cur[p[i]] || {};
260
264
  cur = cur[p[i]];
261
265
  }
262
- let v = token.value;
266
+ // Resolve Tokens Studio refs to concrete values — TS/JS consumers want a usable
267
+ // value (#24AFE8), not the raw reference string {primitive.color.blue.500}.
268
+ let v = resolveValue(token, tokenIndex, 'inline');
263
269
  if (token.type === 'boxShadow' && typeof v === 'object' && !Array.isArray(v)) {
264
270
  v = `${v.x}px ${v.y}px ${v.blur}px ${v.spread}px ${v.color}`;
265
271
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotelfriendag/design-tokens",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
4
4
  "description": "HotelFriend Design System — portable bundle (tokens, components, AI rules). Three-tier model per RFC-0002.",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
@@ -31,49 +31,49 @@ $colorSlate500: #7F8FA4; // Canceled, deleted, OOS — cloud-gray
31
31
  $colorOlive700: #5B7A02; // Check-in (rare dark olive)
32
32
  $colorWhite: #FFFFFF;
33
33
  $colorBlack: #000000;
34
- $colorAccent: {primitive.color.blue.500}; // Brand primary — links, primary buttons, focus
35
- $colorAccentHover: {primitive.color.blue.600}; // Primary button hover
36
- $colorAccentSubtle: {primitive.color.blue.50}; // Accent tint — selected nav, active row
37
- $colorAccentSubtler: {primitive.color.blue.100}; // Even softer accent — reset-filters bg
38
- $colorOn-accent: {primitive.color.white}; // Text/icons ON accent bg (Radix-style foreground pair)
39
- $colorBgPage: {primitive.color.gray.25}; // Page background (outermost)
40
- $colorBgSection: {primitive.color.gray.50}; // Section bg, table header
41
- $colorBgMuted: {primitive.color.gray.100}; // Input bg / hover bg / muted surface
42
- $colorBgSurface: {primitive.color.white}; // Card / modal / popover surface
43
- $colorFg: {primitive.color.gray.900}; // Default text / heading color
44
- $colorFgMuted: {primitive.color.gray.800}; // Secondary text, descriptions
45
- $colorFgSubtle: {primitive.color.gray.550}; // Helper / hint text
46
- $colorFgFaint: {primitive.color.gray.400}; // Placeholder + disabled input text
47
- $colorBorder: {primitive.color.gray.300}; // Default 1px border (buttons, inputs, chips)
48
- $colorBorderSubtle: {primitive.color.gray.200}; // Faint dividers, light borders
49
- $colorBorderStrong: {primitive.color.gray.400}; // Hover/focus border
50
- $colorTabFg-inactive: {primitive.color.gray.700}; // Tab / nav-item text when NOT selected (.hf-tab, .hf-pill-tab default)
51
- $colorTabCount-fg: {primitive.color.gray.700}; // Count-badge text inside an inactive tab
52
- $colorPaginationFg-inactive: {primitive.color.gray.700}; // Pagination item text when not active
53
- $colorPaginationFg-active: {primitive.color.gray.950}; // Pagination item text on the subtle-gray ACTIVE bg
34
+ $colorAccent: #24AFE8; // Brand primary — links, primary buttons, focus
35
+ $colorAccentHover: #149AD1; // Primary button hover
36
+ $colorAccentSubtle: #E9F6FC; // Accent tint — selected nav, active row
37
+ $colorAccentSubtler: #EFF6FF; // Even softer accent — reset-filters bg
38
+ $colorOn-accent: #FFFFFF; // Text/icons ON accent bg (Radix-style foreground pair)
39
+ $colorBgPage: #FBFBFC; // Page background (outermost)
40
+ $colorBgSection: #F6F7FB; // Section bg, table header
41
+ $colorBgMuted: #F1F3F6; // Input bg / hover bg / muted surface
42
+ $colorBgSurface: #FFFFFF; // Card / modal / popover surface
43
+ $colorFg: #2B2B2B; // Default text / heading color
44
+ $colorFgMuted: #4B5675; // Secondary text, descriptions
45
+ $colorFgSubtle: #7E8EA6; // Helper / hint text
46
+ $colorFgFaint: #AEBCCF; // Placeholder + disabled input text
47
+ $colorBorder: #D1D6DD; // Default 1px border (buttons, inputs, chips)
48
+ $colorBorderSubtle: #E4E8EF; // Faint dividers, light borders
49
+ $colorBorderStrong: #AEBCCF; // Hover/focus border
50
+ $colorTabFg-inactive: #485B78; // Tab / nav-item text when NOT selected (.hf-tab, .hf-pill-tab default)
51
+ $colorTabCount-fg: #485B78; // Count-badge text inside an inactive tab
52
+ $colorPaginationFg-inactive: #485B78; // Pagination item text when not active
53
+ $colorPaginationFg-active: #252F4A; // Pagination item text on the subtle-gray ACTIVE bg
54
54
  $colorInputBorder: #DBDFE9; // Input + checkbox/radio border — between gray-200 (#E4E8EF) and gray-300 (#D1D6DD); portal-exact, doesn't fit a primitive step
55
55
  $colorMenuBg-hover: #F5F5F5; // Dropdown item hover bg — slightly warmer than bg-muted; portal $table__hover
56
- $colorStatusSuccess: {primitive.color.green.500}; // Success, due-in, clean
56
+ $colorStatusSuccess: #59B59D; // Success, due-in, clean
57
57
  $colorStatusSuccess-bg: rgba(89,181,157,0.15); // Success @15% — pill bg
58
- $colorStatusWarning: {primitive.color.amber.400}; // Warning, waiting, new
58
+ $colorStatusWarning: #FFBD5A; // Warning, waiting, new
59
59
  $colorStatusWarning-bg: rgba(255,189,90,0.15);
60
- $colorStatusWarning-strong: {primitive.color.amber.600}; // Frontdesk out-of-order — more saturated yellow
60
+ $colorStatusWarning-strong: #FFC900; // Frontdesk out-of-order — more saturated yellow
61
61
  $colorStatusWarning-strong-bg: rgba(255,201,0,0.15);
62
- $colorStatusError: {primitive.color.red.400}; // Error, no-show, dirty, action-required
62
+ $colorStatusError: #EA6565; // Error, no-show, dirty, action-required
63
63
  $colorStatusError-bg: rgba(234,101,101,0.15);
64
- $colorStatusError-strong: {primitive.color.red.700}; // btn-danger
65
- $colorStatusError-alt: {primitive.color.red.500}; // btn-cancel-red, deleted variant
66
- $colorStatusInfo: {semantic.color.accent.default}; // Info = accent
64
+ $colorStatusError-strong: #D9534F; // btn-danger
65
+ $colorStatusError-alt: #FB5A56; // btn-cancel-red, deleted variant
66
+ $colorStatusInfo: #24AFE8; // Info = accent
67
67
  $colorStatusInfo-bg: rgba(36,175,232,0.15);
68
- $colorStatusCancel: {primitive.color.slate.500}; // Cancellations, deleted, out-of-service
68
+ $colorStatusCancel: #7F8FA4; // Cancellations, deleted, out-of-service
69
69
  $colorStatusCancel-bg: rgba(127,143,164,0.15);
70
- $colorStatusCoral: {primitive.color.orange.500}; // Check-out, in-progress
70
+ $colorStatusCoral: #F87921; // Check-out, in-progress
71
71
  $colorStatusCoral-bg: rgba(248,121,33,0.15);
72
- $colorStatusViolet: {primitive.color.violet.500}; // Offer, inspected
72
+ $colorStatusViolet: #5761D8; // Offer, inspected
73
73
  $colorStatusViolet-bg: rgba(87,97,216,0.15);
74
- $colorStatusOlive: {primitive.color.olive.700}; // Check-in (rare deep olive)
74
+ $colorStatusOlive: #5B7A02; // Check-in (rare deep olive)
75
75
  $colorStatusOlive-bg: rgba(91,122,2,0.15);
76
- $colorStatusOrange-deep: {primitive.color.orange.700}; // Due-out brown
76
+ $colorStatusOrange-deep: #A55505; // Due-out brown
77
77
  $colorStatusOrange-deep-bg: rgba(165,85,5,0.15);
78
78
  $fontFamilyPrimary: Roboto;
79
79
  $fontWeightRegular: 400;
@@ -55,36 +55,36 @@ const tokens = {
55
55
  "white": "#FFFFFF",
56
56
  "black": "#000000",
57
57
  "accent": {
58
- "default": "{primitive.color.blue.500}",
59
- "hover": "{primitive.color.blue.600}",
60
- "subtle": "{primitive.color.blue.50}",
61
- "subtler": "{primitive.color.blue.100}"
58
+ "default": "#24AFE8",
59
+ "hover": "#149AD1",
60
+ "subtle": "#E9F6FC",
61
+ "subtler": "#EFF6FF"
62
62
  },
63
- "on-accent": "{primitive.color.white}",
63
+ "on-accent": "#FFFFFF",
64
64
  "bg": {
65
- "page": "{primitive.color.gray.25}",
66
- "section": "{primitive.color.gray.50}",
67
- "muted": "{primitive.color.gray.100}",
68
- "surface": "{primitive.color.white}"
65
+ "page": "#FBFBFC",
66
+ "section": "#F6F7FB",
67
+ "muted": "#F1F3F6",
68
+ "surface": "#FFFFFF"
69
69
  },
70
70
  "fg": {
71
- "default": "{primitive.color.gray.900}",
72
- "muted": "{primitive.color.gray.800}",
73
- "subtle": "{primitive.color.gray.550}",
74
- "faint": "{primitive.color.gray.400}"
71
+ "default": "#2B2B2B",
72
+ "muted": "#4B5675",
73
+ "subtle": "#7E8EA6",
74
+ "faint": "#AEBCCF"
75
75
  },
76
76
  "border": {
77
- "default": "{primitive.color.gray.300}",
78
- "subtle": "{primitive.color.gray.200}",
79
- "strong": "{primitive.color.gray.400}"
77
+ "default": "#D1D6DD",
78
+ "subtle": "#E4E8EF",
79
+ "strong": "#AEBCCF"
80
80
  },
81
81
  "tab": {
82
- "fg-inactive": "{primitive.color.gray.700}",
83
- "count-fg": "{primitive.color.gray.700}"
82
+ "fg-inactive": "#485B78",
83
+ "count-fg": "#485B78"
84
84
  },
85
85
  "pagination": {
86
- "fg-inactive": "{primitive.color.gray.700}",
87
- "fg-active": "{primitive.color.gray.950}"
86
+ "fg-inactive": "#485B78",
87
+ "fg-active": "#252F4A"
88
88
  },
89
89
  "input": {
90
90
  "border": "#DBDFE9"
@@ -93,27 +93,27 @@ const tokens = {
93
93
  "bg-hover": "#F5F5F5"
94
94
  },
95
95
  "status": {
96
- "success": "{primitive.color.green.500}",
96
+ "success": "#59B59D",
97
97
  "success-bg": "rgba(89,181,157,0.15)",
98
- "warning": "{primitive.color.amber.400}",
98
+ "warning": "#FFBD5A",
99
99
  "warning-bg": "rgba(255,189,90,0.15)",
100
- "warning-strong": "{primitive.color.amber.600}",
100
+ "warning-strong": "#FFC900",
101
101
  "warning-strong-bg": "rgba(255,201,0,0.15)",
102
- "error": "{primitive.color.red.400}",
102
+ "error": "#EA6565",
103
103
  "error-bg": "rgba(234,101,101,0.15)",
104
- "error-strong": "{primitive.color.red.700}",
105
- "error-alt": "{primitive.color.red.500}",
106
- "info": "{semantic.color.accent.default}",
104
+ "error-strong": "#D9534F",
105
+ "error-alt": "#FB5A56",
106
+ "info": "#24AFE8",
107
107
  "info-bg": "rgba(36,175,232,0.15)",
108
- "cancel": "{primitive.color.slate.500}",
108
+ "cancel": "#7F8FA4",
109
109
  "cancel-bg": "rgba(127,143,164,0.15)",
110
- "coral": "{primitive.color.orange.500}",
110
+ "coral": "#F87921",
111
111
  "coral-bg": "rgba(248,121,33,0.15)",
112
- "violet": "{primitive.color.violet.500}",
112
+ "violet": "#5761D8",
113
113
  "violet-bg": "rgba(87,97,216,0.15)",
114
- "olive": "{primitive.color.olive.700}",
114
+ "olive": "#5B7A02",
115
115
  "olive-bg": "rgba(91,122,2,0.15)",
116
- "orange-deep": "{primitive.color.orange.700}",
116
+ "orange-deep": "#A55505",
117
117
  "orange-deep-bg": "rgba(165,85,5,0.15)"
118
118
  }
119
119
  },
@@ -52,36 +52,36 @@ export const tokens = {
52
52
  "white": "#FFFFFF",
53
53
  "black": "#000000",
54
54
  "accent": {
55
- "default": "{primitive.color.blue.500}",
56
- "hover": "{primitive.color.blue.600}",
57
- "subtle": "{primitive.color.blue.50}",
58
- "subtler": "{primitive.color.blue.100}"
55
+ "default": "#24AFE8",
56
+ "hover": "#149AD1",
57
+ "subtle": "#E9F6FC",
58
+ "subtler": "#EFF6FF"
59
59
  },
60
- "on-accent": "{primitive.color.white}",
60
+ "on-accent": "#FFFFFF",
61
61
  "bg": {
62
- "page": "{primitive.color.gray.25}",
63
- "section": "{primitive.color.gray.50}",
64
- "muted": "{primitive.color.gray.100}",
65
- "surface": "{primitive.color.white}"
62
+ "page": "#FBFBFC",
63
+ "section": "#F6F7FB",
64
+ "muted": "#F1F3F6",
65
+ "surface": "#FFFFFF"
66
66
  },
67
67
  "fg": {
68
- "default": "{primitive.color.gray.900}",
69
- "muted": "{primitive.color.gray.800}",
70
- "subtle": "{primitive.color.gray.550}",
71
- "faint": "{primitive.color.gray.400}"
68
+ "default": "#2B2B2B",
69
+ "muted": "#4B5675",
70
+ "subtle": "#7E8EA6",
71
+ "faint": "#AEBCCF"
72
72
  },
73
73
  "border": {
74
- "default": "{primitive.color.gray.300}",
75
- "subtle": "{primitive.color.gray.200}",
76
- "strong": "{primitive.color.gray.400}"
74
+ "default": "#D1D6DD",
75
+ "subtle": "#E4E8EF",
76
+ "strong": "#AEBCCF"
77
77
  },
78
78
  "tab": {
79
- "fg-inactive": "{primitive.color.gray.700}",
80
- "count-fg": "{primitive.color.gray.700}"
79
+ "fg-inactive": "#485B78",
80
+ "count-fg": "#485B78"
81
81
  },
82
82
  "pagination": {
83
- "fg-inactive": "{primitive.color.gray.700}",
84
- "fg-active": "{primitive.color.gray.950}"
83
+ "fg-inactive": "#485B78",
84
+ "fg-active": "#252F4A"
85
85
  },
86
86
  "input": {
87
87
  "border": "#DBDFE9"
@@ -90,27 +90,27 @@ export const tokens = {
90
90
  "bg-hover": "#F5F5F5"
91
91
  },
92
92
  "status": {
93
- "success": "{primitive.color.green.500}",
93
+ "success": "#59B59D",
94
94
  "success-bg": "rgba(89,181,157,0.15)",
95
- "warning": "{primitive.color.amber.400}",
95
+ "warning": "#FFBD5A",
96
96
  "warning-bg": "rgba(255,189,90,0.15)",
97
- "warning-strong": "{primitive.color.amber.600}",
97
+ "warning-strong": "#FFC900",
98
98
  "warning-strong-bg": "rgba(255,201,0,0.15)",
99
- "error": "{primitive.color.red.400}",
99
+ "error": "#EA6565",
100
100
  "error-bg": "rgba(234,101,101,0.15)",
101
- "error-strong": "{primitive.color.red.700}",
102
- "error-alt": "{primitive.color.red.500}",
103
- "info": "{semantic.color.accent.default}",
101
+ "error-strong": "#D9534F",
102
+ "error-alt": "#FB5A56",
103
+ "info": "#24AFE8",
104
104
  "info-bg": "rgba(36,175,232,0.15)",
105
- "cancel": "{primitive.color.slate.500}",
105
+ "cancel": "#7F8FA4",
106
106
  "cancel-bg": "rgba(127,143,164,0.15)",
107
- "coral": "{primitive.color.orange.500}",
107
+ "coral": "#F87921",
108
108
  "coral-bg": "rgba(248,121,33,0.15)",
109
- "violet": "{primitive.color.violet.500}",
109
+ "violet": "#5761D8",
110
110
  "violet-bg": "rgba(87,97,216,0.15)",
111
- "olive": "{primitive.color.olive.700}",
111
+ "olive": "#5B7A02",
112
112
  "olive-bg": "rgba(91,122,2,0.15)",
113
- "orange-deep": "{primitive.color.orange.700}",
113
+ "orange-deep": "#A55505",
114
114
  "orange-deep-bg": "rgba(165,85,5,0.15)"
115
115
  }
116
116
  },