@hotelfriendag/design-tokens 0.3.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.
@@ -0,0 +1,138 @@
1
+ // HotelFriend design tokens — SCSS variables
2
+ // Generated from docs/tokens.figma.json — do NOT edit by hand.
3
+
4
+ $colorBlue50: #E9F6FC; // Light tint — active-state bg
5
+ $colorBlue100: #EFF6FF; // Faint tint — reset-filters bg
6
+ $colorBlue500: #24AFE8; // Canonical primary anchor (portal legacy SCSS still uses #26ADE4 — pending consolidation)
7
+ $colorBlue600: #149AD1; // Primary hover
8
+ $colorGray25: #FBFBFC; // Page background top
9
+ $colorGray50: #F6F7FB; // Section backgrounds
10
+ $colorGray100: #F1F3F6; // Inputs bg, hover-bg, header bars
11
+ $colorGray200: #E4E8EF; // Border-light, dividers
12
+ $colorGray300: #D1D6DD; // Default border
13
+ $colorGray400: #AEBCCF; // Placeholder + disabled text — portal $dark__Grey
14
+ $colorGray500: #99A1B7; // Placeholder legacy / soft helper
15
+ $colorGray550: #7E8EA6; // Helper text on light bg
16
+ $colorGray600: #50627E; // Cancel-button text, ink-muted
17
+ $colorGray700: #485B78; // Steel-blue (tab/nav inactive) — Phase 2 component-tier
18
+ $colorGray800: #4B5675; // Secondary text
19
+ $colorGray900: #2B2B2B; // Body text, headings
20
+ $colorGray950: #252F4A; // Deepest ink (pagination active text) — Phase 2 component-tier
21
+ $colorGreen500: #59B59D; // Success / mint
22
+ $colorAmber400: #FFBD5A; // Warning / buttermilk
23
+ $colorAmber600: #FFC900; // Strong yellow — frontdesk OOO
24
+ $colorRed400: #EA6565; // Error / rouge
25
+ $colorRed500: #FB5A56; // Cancel-red, deleted — between rouge and crimson
26
+ $colorRed700: #D9534F; // Deeper red — btn-danger
27
+ $colorOrange500: #F87921; // Coral / peach
28
+ $colorOrange700: #A55505; // Due-out brown
29
+ $colorViolet500: #5761D8; // Offer, inspected
30
+ $colorSlate500: #7F8FA4; // Canceled, deleted, OOS — cloud-gray
31
+ $colorOlive700: #5B7A02; // Check-in (rare dark olive)
32
+ $colorWhite: #FFFFFF;
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
54
+ $colorInputBorder: #DBDFE9; // Input + checkbox/radio border — between gray-200 (#E4E8EF) and gray-300 (#D1D6DD); portal-exact, doesn't fit a primitive step
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
57
+ $colorStatusSuccess-bg: rgba(89,181,157,0.15); // Success @15% — pill bg
58
+ $colorStatusWarning: {primitive.color.amber.400}; // Warning, waiting, new
59
+ $colorStatusWarning-bg: rgba(255,189,90,0.15);
60
+ $colorStatusWarning-strong: {primitive.color.amber.600}; // Frontdesk out-of-order — more saturated yellow
61
+ $colorStatusWarning-strong-bg: rgba(255,201,0,0.15);
62
+ $colorStatusError: {primitive.color.red.400}; // Error, no-show, dirty, action-required
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
67
+ $colorStatusInfo-bg: rgba(36,175,232,0.15);
68
+ $colorStatusCancel: {primitive.color.slate.500}; // Cancellations, deleted, out-of-service
69
+ $colorStatusCancel-bg: rgba(127,143,164,0.15);
70
+ $colorStatusCoral: {primitive.color.orange.500}; // Check-out, in-progress
71
+ $colorStatusCoral-bg: rgba(248,121,33,0.15);
72
+ $colorStatusViolet: {primitive.color.violet.500}; // Offer, inspected
73
+ $colorStatusViolet-bg: rgba(87,97,216,0.15);
74
+ $colorStatusOlive: {primitive.color.olive.700}; // Check-in (rare deep olive)
75
+ $colorStatusOlive-bg: rgba(91,122,2,0.15);
76
+ $colorStatusOrange-deep: {primitive.color.orange.700}; // Due-out brown
77
+ $colorStatusOrange-deep-bg: rgba(165,85,5,0.15);
78
+ $fontFamilyPrimary: Roboto;
79
+ $fontWeightRegular: 400;
80
+ $fontWeightMedium: 500;
81
+ $fontWeightSemibold: 600;
82
+ $fontSizeXs: 11px; // Tag labels, uppercase captions
83
+ $fontSizeSm: 13px; // Grid headers, chips, sm buttons
84
+ $fontSizeBase: 14px; // Body, labels (most common — 218 occurrences)
85
+ $fontSizeMd: 15px; // Primary buttons
86
+ $fontSizeLg: 16px; // Modal titles, large tabs
87
+ $fontSizeXl: 18px; // Info-block titles, modal title
88
+ $fontSize2xl: 20px; // Section heading
89
+ $fontSizeH2: 24px;
90
+ $fontSizePage: 26px; // Page title
91
+ $fontSizeHero: 30px; // Dashboard stat numbers
92
+ $lineHeightHeadings: 1.2;
93
+ $lineHeightBody: 1.5;
94
+ $lineHeightTight: 1;
95
+ $spacing0: 0px;
96
+ $spacing1: 4px;
97
+ $spacing2: 8px;
98
+ $spacing3: 12px;
99
+ $spacing4: 16px;
100
+ $spacing5: 20px; // Default section gap
101
+ $spacing6: 24px;
102
+ $spacing7: 30px;
103
+ $spacing8: 32px;
104
+ $radiusSm: 6px; // Buttons, inputs, status pills, modals (portal-exact)
105
+ $radiusMd: 8px; // Pagination items
106
+ $radiusLg: 9px; // Dropdowns, toasts, pill-tabs
107
+ $radiusXl: 12px; // Large elevated cards
108
+ $radiusPill: 99px; // Rounded pill (rare — most status pills are 6px square)
109
+ $opacityDisabled: 0.6; // Disabled state
110
+ $opacityTint: 0.15; // Status badge bg alpha
111
+ $sizeSidebarExpanded: 215px;
112
+ $sizeSidebarCollapsed: 80px;
113
+ $sizeHeaderHeight: 60px;
114
+ $sizeContentMax: 1440px;
115
+ $sizeBtnDefault: 40px; // Primary button height
116
+ $sizeBtnSm: 32px;
117
+ $sizeBtnSearch: 38px; // Inline filter/search submit height
118
+ $sizeInputDefault: 39px; // Form-control input height
119
+ $sizeTapTarget: 44px; // Mobile minimum tap target
120
+ $zIndexSidebar: 1000;
121
+ $zIndexDropdown: 1001;
122
+ $zIndexDatepicker: 3000;
123
+ $zIndexStickyTopbar: 4000;
124
+ $zIndexModalBackdrop: 10050;
125
+ $zIndexModalDialog: 10051;
126
+ $zIndexPreloader: 99999;
127
+ $zIndexTopmostOverlay: 1000001; // Above all
128
+ $motionFast: 0.1s;
129
+ $motion: 0.2s ease-in-out; // Canonical transition
130
+ $motionSlow: 0.3s; // Most common in legacy CSS — being normalized down to 0.2s
131
+ $motionBouncy: 0.4s cubic-bezier(0.88, -0.99, 0, 1.81);
132
+ $shadow: 0px 1px 8px 0px rgba(0,0,0,0.1);
133
+ $shadowSubtle: 0px 2px 4px 0px rgba(0,0,0,0.05);
134
+ $shadowWrapper: 0px 3px 4px 0px rgba(0,0,0,0.03);
135
+ $shadowCard: 0px 12px 24px 0px rgba(26,26,30,0.06);
136
+ $shadowModal: 0px 6px 18px 0px rgba(0,0,0,0.1);
137
+ $shadowOutline: 0px 1px 2px 0px rgba(72,91,120,0.18);
138
+ $shadowHover: 0px 2px 3px 0px rgba(0,0,0,0.06);
@@ -0,0 +1,515 @@
1
+ /*
2
+ * HotelFriend Design System — Component classes (.hf-*)
3
+ *
4
+ * Drop-in companion to `tokens.css` / `tailwind.css`. Defines the `.hf-*`
5
+ * component primitives used throughout `components.html`. Framework-agnostic
6
+ * — works with Tailwind v4, plain CSS, SCSS, Vue, React.
7
+ *
8
+ * Phase 1B (RFC-0002) status:
9
+ * ✅ All chrome bound to SEMANTIC tokens (var(--color-hf-fg), -bg-*, -border, -accent, -status-*).
10
+ * ✅ Primitives never referenced directly (except Phase-2 escape hatches — see TODOs below).
11
+ * ✅ One namespace — no forked --status-* declarations; all status colors come from
12
+ * `--color-hf-status-{role}` (semantic) which alias `--color-hf-{family}-{step}` (primitive).
13
+ * ✅ Class names equal token names — domain→semantic mapping NOW LIVES in `status-map.json`
14
+ * and is emitted to `pre-built/status.css` by `generate-tokens.cjs --target=status-css`.
15
+ * This file no longer contains the mapping — consumers should `@import` both files.
16
+ * ⚠ `pre-built/components.css` is still hand-mirrored from components.html (Phase 2D will generate).
17
+ *
18
+ * Phase 2 TODOs (tracked here):
19
+ * 1. ✅ Phase 2A done — status-map.json + emitStatusCss(). See `pre-built/status.css`.
20
+ * 2. ✅ Phase 2B done — 4 component-tier tokens introduced:
21
+ * --color-hf-tab-fg-inactive / -count-fg
22
+ * --color-hf-pagination-fg-inactive / -fg-active
23
+ * --color-hf-input-border (#DBDFE9 — was bare hex)
24
+ * --color-hf-menu-bg-hover (#F5F5F5 — was bare hex)
25
+ * 3. Phase 2C — generate this whole file from tokens.figma.json + status-map.json (TODO).
26
+ * 4. Phase 2D — reconcile components.html @theme block with Phase 1B token names (TODO).
27
+ *
28
+ * BREAKING CHANGES vs Phase 1A:
29
+ * - `--color-hf-primary*` → `--color-hf-accent[ -hover / -subtle / -subtler ]`
30
+ * - `--color-hf-text-*` → `--color-hf-fg[ -muted / -subtle / -faint ]`
31
+ * - `--color-hf-neutral-*` → `--color-hf-bg-*` / `--color-hf-border[ -subtle ]`
32
+ * - `--color-hf-badge-*` → removed; replaced by domain→semantic CSS map below
33
+ *
34
+ * Usage:
35
+ * <link rel="stylesheet" href="tokens.css"> ← variables first
36
+ * <link rel="stylesheet" href="components.css"> ← components after
37
+ * <link rel="stylesheet" href="status.css"> ← .status-{domain}-{state} rules (Phase 2A — generated from status-map.json)
38
+ *
39
+ * Or with Tailwind v4:
40
+ * @import "tailwindcss";
41
+ * @import "./tailwind.css";
42
+ * @import "./components.css";
43
+ * @import "./status.css";
44
+ *
45
+ * STATUS PILLS — the domain→semantic mapping lives in `status-map.json` and is
46
+ * emitted to `pre-built/status.css` by `generate-tokens.cjs --target=status-css`.
47
+ * To add a new status (e.g. `booking.partial-refund`), add it to status-map.json
48
+ * and re-run the generator — no CSS edit needed here.
49
+ */
50
+
51
+ /* ════════════════════════════════════════════════════════════════════════════
52
+ * PILL — .hf-pill (uses currentColor inherited from .status-* above)
53
+ * ════════════════════════════════════════════════════════════════════════════ */
54
+ .hf-pill {
55
+ display: inline-flex; align-items: center; justify-content: center;
56
+ padding: 6px 20px;
57
+ border-radius: var(--radius-hf-sm, 6px);
58
+ font-size: var(--font-size-hf-base, 14px);
59
+ font-weight: var(--font-weight-hf-medium, 500);
60
+ line-height: var(--line-height-hf-tight, 1);
61
+ white-space: nowrap;
62
+ border: 1px solid currentColor;
63
+ text-align: center;
64
+ user-select: none;
65
+ transition: opacity 200ms, background-color 200ms;
66
+ }
67
+ .hf-pill:hover { opacity: .9; }
68
+ .hf-pill.is-active {
69
+ background: transparent !important;
70
+ box-shadow: inset 0 0 0 1px currentColor;
71
+ }
72
+ .hf-pill--striped {
73
+ background-image: repeating-linear-gradient(
74
+ -45deg, currentColor 0, currentColor 1px, transparent 1px, transparent 6px
75
+ );
76
+ background-color: var(--color-hf-white, #fff) !important;
77
+ }
78
+ .hf-pill--dd {
79
+ padding-right: 32px;
80
+ cursor: pointer;
81
+ position: relative;
82
+ }
83
+ .hf-pill--dd::after {
84
+ content: '';
85
+ position: absolute; right: 12px; top: 50%;
86
+ width: 8px; height: 5px;
87
+ background-color: currentColor;
88
+ clip-path: polygon(0 0, 100% 0, 50% 100%);
89
+ transform: translateY(-50%);
90
+ }
91
+
92
+ /* ════════════════════════════════════════════════════════════════════════════
93
+ * TABS (.hf-tab) — large underline (page-nav) + small (sub-filter)
94
+ *
95
+ * ⚠ Phase 2 TODO: gray-700 used here for inactive tab color is portal-specific.
96
+ * Promote to component-tier token `--hf-tab-fg-inactive`.
97
+ * ════════════════════════════════════════════════════════════════════════════ */
98
+ .hf-tab {
99
+ padding: 22px 15px;
100
+ font-size: var(--font-size-hf-lg, 16px);
101
+ font-weight: var(--font-weight-hf-medium, 500);
102
+ color: var(--color-hf-tab-fg-inactive, #50627E);
103
+ cursor: pointer;
104
+ transition: color 200ms;
105
+ position: relative;
106
+ white-space: nowrap;
107
+ border: 0; background: 0;
108
+ line-height: var(--line-height-hf-tight, 1);
109
+ display: inline-flex; align-items: center; gap: 8px;
110
+ }
111
+ .hf-tab:hover { color: var(--color-hf-fg, #2B2B2B); }
112
+ .hf-tab.is-active { color: var(--color-hf-accent, #24AFE8); }
113
+ .hf-tab.is-active::after {
114
+ content: ''; position: absolute; left: 0; right: 0; bottom: -1px;
115
+ height: 3px; background: var(--color-hf-accent, #24AFE8);
116
+ }
117
+ .hf-tab:disabled { color: var(--color-hf-fg-faint, #AEBCCF); cursor: not-allowed; }
118
+ .hf-tab:disabled:hover { color: var(--color-hf-fg-faint, #AEBCCF); }
119
+
120
+ .hf-tab--sm {
121
+ padding: 10px 15px;
122
+ font-size: var(--font-size-hf-md, 15px);
123
+ }
124
+
125
+ .hf-tab__count {
126
+ display: inline-flex; align-items: center; justify-content: center;
127
+ min-width: 22px; height: 18px; padding: 0 6px;
128
+ background: rgba(72,91,120,.1);
129
+ color: var(--color-hf-tab-count-fg, #50627E);
130
+ border-radius: 9999px;
131
+ font-size: var(--font-size-hf-xs, 11px);
132
+ font-weight: var(--font-weight-hf-semibold, 600);
133
+ line-height: var(--line-height-hf-tight, 1);
134
+ }
135
+ .hf-tab.is-active .hf-tab__count {
136
+ background: var(--color-hf-accent-subtler, #EFF6FF);
137
+ color: var(--color-hf-accent, #24AFE8);
138
+ }
139
+
140
+ .hf-pill-tabs {
141
+ display: inline-flex; gap: 4px; padding: 4px;
142
+ background: var(--color-hf-bg-section, #F6F7FB);
143
+ border-radius: var(--radius-hf-lg, 9px);
144
+ }
145
+ .hf-pill-tab {
146
+ padding: 8px 14px;
147
+ font-size: var(--font-size-hf-base, 14px);
148
+ font-weight: var(--font-weight-hf-medium, 500);
149
+ color: var(--color-hf-tab-fg-inactive, #50627E);
150
+ background: transparent;
151
+ border: 0;
152
+ border-radius: var(--radius-hf-sm, 6px);
153
+ cursor: pointer;
154
+ transition: background 200ms, color 200ms;
155
+ white-space: nowrap;
156
+ }
157
+ .hf-pill-tab:hover { color: var(--color-hf-fg, #2B2B2B); }
158
+ .hf-pill-tab.is-active {
159
+ background: var(--color-hf-bg-surface, #fff);
160
+ color: var(--color-hf-accent, #24AFE8);
161
+ box-shadow: 0 1px 2px rgba(0,0,0,.05);
162
+ }
163
+
164
+ /* ════════════════════════════════════════════════════════════════════════════
165
+ * PAGINATION (.hf-pagination) — subtle gray active (NOT accent!)
166
+ *
167
+ * ⚠ Phase 2 TODO: gray-950 (#252F4A) used for active text is portal-specific.
168
+ * Promote to component-tier `--hf-pagination-fg-active`.
169
+ * ════════════════════════════════════════════════════════════════════════════ */
170
+ .hf-pagination {
171
+ display: inline-flex; align-items: center; gap: 4px;
172
+ padding: 0; list-style: none;
173
+ }
174
+ .hf-pagination__item {
175
+ display: inline-flex; align-items: center; justify-content: center;
176
+ min-width: 34px; height: 34px;
177
+ padding: 0 12px;
178
+ font-size: var(--font-size-hf-base, 14px);
179
+ font-weight: var(--font-weight-hf-regular, 400);
180
+ color: var(--color-hf-pagination-fg-inactive, #485B78);
181
+ background: transparent;
182
+ border: 1px solid transparent;
183
+ border-radius: var(--radius-hf-md, 8px);
184
+ cursor: pointer;
185
+ transition: background-color 200ms, color 200ms, border-color 200ms;
186
+ text-decoration: none;
187
+ }
188
+ .hf-pagination__item:hover {
189
+ background: var(--color-hf-bg-muted, #F1F3F6);
190
+ color: var(--color-hf-fg, #2B2B2B);
191
+ }
192
+ .hf-pagination__item.is-active {
193
+ background: var(--color-hf-border-subtle, #E4E8EF);
194
+ border-color: var(--color-hf-border-subtle, #E4E8EF);
195
+ color: var(--color-hf-pagination-fg-active, #252F4A);
196
+ cursor: default;
197
+ }
198
+ .hf-pagination__item.is-active:hover { background: var(--color-hf-border-subtle, #E4E8EF); }
199
+ .hf-pagination__item:disabled,
200
+ .hf-pagination__item.is-disabled {
201
+ color: var(--color-hf-fg-faint, #AEBCCF);
202
+ cursor: not-allowed;
203
+ pointer-events: none;
204
+ }
205
+ .hf-pagination__ellipsis {
206
+ display: inline-flex; align-items: center; justify-content: center;
207
+ min-width: 34px; height: 34px;
208
+ color: var(--color-hf-gray-700, #485B78);
209
+ cursor: default;
210
+ }
211
+
212
+ /* ════════════════════════════════════════════════════════════════════════════
213
+ * ALERT (.hf-alert)
214
+ * ════════════════════════════════════════════════════════════════════════════ */
215
+ .hf-alert {
216
+ position: relative;
217
+ display: flex; align-items: flex-start; gap: 16px;
218
+ padding: 16px 48px 14px 20px;
219
+ background: var(--color-hf-bg-surface, #fff);
220
+ border: 1px solid rgba(72,91,120,.15);
221
+ border-radius: var(--radius-hf-sm, 6px);
222
+ box-shadow: 0 2px 4px 0 rgba(72,91,120,.07);
223
+ overflow: hidden;
224
+ }
225
+ .hf-alert::before {
226
+ content: '';
227
+ position: absolute;
228
+ left: 0; right: 0; top: 0;
229
+ height: 3px;
230
+ background: currentColor;
231
+ }
232
+ .hf-alert__icon {
233
+ flex-shrink: 0;
234
+ width: 26px; height: 26px;
235
+ border-radius: 5px;
236
+ display: inline-flex; align-items: center; justify-content: center;
237
+ background: currentColor;
238
+ }
239
+ .hf-alert__icon > svg {
240
+ width: 16px; height: 16px;
241
+ color: var(--color-hf-on-accent, #fff);
242
+ stroke: var(--color-hf-on-accent, #fff);
243
+ fill: none;
244
+ }
245
+ .hf-alert__body { flex: 1; min-width: 0; color: var(--color-hf-fg, #2B2B2B); }
246
+ .hf-alert__title {
247
+ font-size: var(--font-size-hf-xl, 18px);
248
+ font-weight: var(--font-weight-hf-medium, 500);
249
+ line-height: 1.3;
250
+ margin: 0 0 4px;
251
+ color: var(--color-hf-fg, #2B2B2B);
252
+ }
253
+ .hf-alert__text {
254
+ font-size: var(--font-size-hf-md, 15px);
255
+ line-height: 1.4;
256
+ color: var(--color-hf-gray-700, #50627E); /* TODO Phase 2 — review fg axis */
257
+ margin: 0;
258
+ }
259
+ .hf-alert__close {
260
+ position: absolute; top: 12px; right: 12px;
261
+ width: 24px; height: 24px;
262
+ display: inline-flex; align-items: center; justify-content: center;
263
+ background: transparent; border: 0; border-radius: 4px;
264
+ color: var(--color-hf-gray-500, #99A1B7);
265
+ cursor: pointer;
266
+ transition: color 200ms, background 200ms;
267
+ }
268
+ .hf-alert__close:hover {
269
+ color: var(--color-hf-fg, #2B2B2B);
270
+ background: var(--color-hf-bg-muted, #F1F3F6);
271
+ }
272
+
273
+ /* Variants — set accent color via currentColor */
274
+ .hf-alert--success { color: var(--color-hf-status-success); }
275
+ .hf-alert--info { color: var(--color-hf-accent); }
276
+ .hf-alert--warn { color: var(--color-hf-status-warning); }
277
+ .hf-alert--error { color: var(--color-hf-status-error); }
278
+
279
+ /* Tinted modifier */
280
+ .hf-alert--tinted.hf-alert--success { background: rgba(89,181,157,.06); }
281
+ .hf-alert--tinted.hf-alert--info { background: rgba(36,175,232,.06); }
282
+ .hf-alert--tinted.hf-alert--warn { background: rgba(255,189,90,.10); }
283
+ .hf-alert--tinted.hf-alert--error { background: rgba(234,101,101,.06); }
284
+
285
+ /* Page banner */
286
+ .hf-alert--banner {
287
+ border-radius: 0;
288
+ border-left: 0; border-right: 0;
289
+ box-shadow: none;
290
+ background: currentColor;
291
+ }
292
+ .hf-alert--banner::before { display: none; }
293
+ .hf-alert--banner .hf-alert__body { color: var(--color-hf-on-accent, #fff); }
294
+ .hf-alert--banner .hf-alert__title,
295
+ .hf-alert--banner .hf-alert__text { color: var(--color-hf-on-accent, #fff); }
296
+ .hf-alert--banner .hf-alert__icon { background: rgba(255,255,255,.18); }
297
+ .hf-alert--banner .hf-alert__close { color: rgba(255,255,255,.7); }
298
+ .hf-alert--banner .hf-alert__close:hover { color: var(--color-hf-on-accent, #fff); background: rgba(255,255,255,.12); }
299
+
300
+ /* Compact */
301
+ .hf-alert--compact { padding: 10px 36px 10px 14px; gap: 10px; }
302
+ .hf-alert--compact .hf-alert__icon { width: 20px; height: 20px; border-radius: 4px; }
303
+ .hf-alert--compact .hf-alert__icon > svg { width: 12px; height: 12px; }
304
+ .hf-alert--compact .hf-alert__title {
305
+ font-size: var(--font-size-hf-base, 14px);
306
+ font-weight: var(--font-weight-hf-semibold, 600);
307
+ margin-bottom: 0;
308
+ }
309
+ .hf-alert--compact .hf-alert__text { font-size: var(--font-size-hf-sm, 13px); }
310
+ .hf-alert--compact .hf-alert__close { top: 8px; right: 8px; }
311
+
312
+ /* ════════════════════════════════════════════════════════════════════════════
313
+ * TOAST (.hf-toast)
314
+ * ════════════════════════════════════════════════════════════════════════════ */
315
+ .hf-toast {
316
+ display: inline-flex; align-items: center; gap: 10px;
317
+ padding: 10px 14px;
318
+ background: var(--color-hf-bg-surface, #fff);
319
+ border: 1px solid rgba(72,91,120,.15);
320
+ border-radius: var(--radius-hf-lg, 9px);
321
+ box-shadow: var(--shadow-hf-modal, 0 6px 18px rgba(0,0,0,.10));
322
+ min-width: 280px; max-width: 420px;
323
+ color: var(--color-hf-fg, #2B2B2B);
324
+ font-size: var(--font-size-hf-base, 14px);
325
+ }
326
+ .hf-toast__icon { flex-shrink: 0; width: 20px; height: 20px; }
327
+ .hf-toast__text { flex: 1; }
328
+ .hf-toast__close {
329
+ flex-shrink: 0;
330
+ width: 20px; height: 20px;
331
+ display: inline-flex; align-items: center; justify-content: center;
332
+ background: transparent; border: 0; border-radius: 4px;
333
+ color: var(--color-hf-gray-500, #99A1B7);
334
+ cursor: pointer; transition: color 200ms;
335
+ }
336
+ .hf-toast__close:hover { color: var(--color-hf-fg, #2B2B2B); }
337
+
338
+ /* ════════════════════════════════════════════════════════════════════════════
339
+ * MODAL (.hf-modal)
340
+ * ════════════════════════════════════════════════════════════════════════════ */
341
+ .hf-modal {
342
+ background: var(--color-hf-bg-surface, #fff);
343
+ border: 1px solid rgba(72,91,120,.15);
344
+ border-radius: var(--radius-hf-sm, 6px);
345
+ box-shadow: 0 2px 4px 0 rgba(72,91,120,.18);
346
+ overflow: hidden;
347
+ width: 100%;
348
+ }
349
+ .hf-modal__header {
350
+ display: flex; align-items: center; justify-content: space-between;
351
+ padding: 20px;
352
+ border-bottom: 1px solid var(--color-hf-border-subtle, #E4E8EF);
353
+ }
354
+ .hf-modal__title {
355
+ font-size: var(--font-size-hf-xl, 18px);
356
+ font-weight: var(--font-weight-hf-semibold, 600);
357
+ color: var(--color-hf-fg, #2B2B2B);
358
+ margin: 0;
359
+ }
360
+ .hf-modal__close {
361
+ width: 32px; height: 32px;
362
+ display: inline-flex; align-items: center; justify-content: center;
363
+ background: transparent;
364
+ border: 0;
365
+ border-radius: var(--radius-hf-sm, 6px);
366
+ color: var(--color-hf-gray-700, #50627E);
367
+ cursor: pointer;
368
+ transition: background 200ms, color 200ms;
369
+ }
370
+ .hf-modal__close:hover {
371
+ background: var(--color-hf-bg-muted, #F1F3F6);
372
+ color: var(--color-hf-fg, #2B2B2B);
373
+ }
374
+ .hf-modal__body { padding: 20px; }
375
+ .hf-modal__footer {
376
+ display: flex; justify-content: flex-end; gap: 12px;
377
+ padding: 20px;
378
+ border-top: 0;
379
+ background: transparent;
380
+ }
381
+ .hf-modal--with-footer-border .hf-modal__footer {
382
+ border-top: 1px solid var(--color-hf-border-subtle, #E4E8EF);
383
+ }
384
+
385
+ /* ════════════════════════════════════════════════════════════════════════════
386
+ * CHECKBOX & RADIO (.hf-check, .hf-radio)
387
+ * ════════════════════════════════════════════════════════════════════════════ */
388
+ .hf-check,
389
+ .hf-radio {
390
+ appearance: none; -webkit-appearance: none;
391
+ position: relative; flex-shrink: 0;
392
+ width: 18px; height: 18px;
393
+ margin: 0;
394
+ background: transparent;
395
+ border: 1px solid var(--color-hf-input-border, #DBDFE9);
396
+ cursor: pointer;
397
+ transition: border-color 200ms, background-color 200ms;
398
+ }
399
+ .hf-check { border-radius: var(--radius-hf-sm, 6px); }
400
+ .hf-radio { border-radius: 9999px; }
401
+ .hf-check:hover:not(:disabled),
402
+ .hf-radio:hover:not(:disabled) { border-color: var(--color-hf-accent, #26ADE4); }
403
+ .hf-check:checked,
404
+ .hf-radio:checked {
405
+ background: var(--color-hf-accent, #26ADE4);
406
+ border-color: var(--color-hf-accent, #26ADE4);
407
+ }
408
+ .hf-check:checked::after {
409
+ content: '';
410
+ position: absolute; top: 2px; left: 5px;
411
+ width: 5px; height: 10px;
412
+ border: solid var(--color-hf-on-accent, #fff);
413
+ border-width: 0 2px 2px 0;
414
+ transform: rotate(45deg);
415
+ }
416
+ .hf-radio:checked::after {
417
+ content: '';
418
+ position: absolute; top: 50%; left: 50%;
419
+ width: 6px; height: 6px;
420
+ background: var(--color-hf-on-accent, #fff); border-radius: 9999px;
421
+ transform: translate(-50%, -50%);
422
+ }
423
+ .hf-check:disabled,
424
+ .hf-radio:disabled { cursor: not-allowed; opacity: .5; }
425
+ .hf-check:checked:disabled,
426
+ .hf-radio:checked:disabled {
427
+ background: rgba(38,173,228,.5);
428
+ border-color: rgba(38,173,228,.5);
429
+ opacity: 1;
430
+ }
431
+
432
+ /* ════════════════════════════════════════════════════════════════════════════
433
+ * DROPDOWN (.hf-dropdown-menu)
434
+ * ════════════════════════════════════════════════════════════════════════════ */
435
+ .hf-dropdown-menu {
436
+ background: var(--color-hf-bg-surface, #fff);
437
+ border-radius: var(--radius-hf-lg, 9px);
438
+ box-shadow: 0 1px 10px 0 rgba(0,0,0,.1);
439
+ min-width: 180px;
440
+ padding: 6px 0;
441
+ border: 1px solid rgba(228,232,239,.6);
442
+ }
443
+ .hf-dropdown-header {
444
+ padding: 10px 16px 8px;
445
+ font-size: var(--font-size-hf-xs, 11px);
446
+ font-weight: var(--font-weight-hf-semibold, 600);
447
+ color: var(--color-hf-gray-500, #99A1B7);
448
+ text-transform: uppercase;
449
+ letter-spacing: .04em;
450
+ }
451
+ .hf-dropdown-item {
452
+ display: flex; align-items: center; gap: 10px;
453
+ padding: 8px 16px;
454
+ font-size: var(--font-size-hf-base, 14px);
455
+ color: var(--color-hf-fg, #2B2B2B);
456
+ cursor: pointer; transition: background 150ms;
457
+ line-height: 1.4;
458
+ }
459
+ .hf-dropdown-item:hover { background: var(--color-hf-menu-bg-hover, #F5F5F5); }
460
+ .hf-dropdown-item.is-active {
461
+ background: var(--color-hf-accent-subtler, #EFF6FF);
462
+ color: var(--color-hf-accent, #24AFE8);
463
+ }
464
+ .hf-dropdown-item:focus-visible {
465
+ background: var(--color-hf-menu-bg-hover, #F5F5F5);
466
+ outline: 2px solid var(--color-hf-accent, #24AFE8);
467
+ outline-offset: -2px;
468
+ }
469
+ .hf-dropdown-item.danger { color: var(--color-hf-status-error, #EA6565); }
470
+ .hf-dropdown-item.danger:hover { background: rgba(234,101,101,.08); }
471
+ .hf-dropdown-item[aria-disabled="true"],
472
+ .hf-dropdown-item.is-disabled {
473
+ color: var(--color-hf-fg-faint, #AEBCCF);
474
+ cursor: not-allowed;
475
+ pointer-events: none;
476
+ }
477
+ .hf-dropdown-item__shortcut {
478
+ margin-left: auto;
479
+ color: var(--color-hf-gray-500, #99A1B7);
480
+ font-size: 12px;
481
+ }
482
+ .hf-dropdown-item__icon {
483
+ color: var(--color-hf-gray-500, #99A1B7);
484
+ width: 16px; height: 16px;
485
+ flex-shrink: 0;
486
+ }
487
+ .hf-dropdown-item:hover .hf-dropdown-item__icon { color: var(--color-hf-gray-700, #50627E); }
488
+ .hf-dropdown-item.danger .hf-dropdown-item__icon { color: currentColor; }
489
+ .hf-dropdown-divider {
490
+ height: 1px;
491
+ background: var(--color-hf-border-subtle, #E4E8EF);
492
+ margin: 4px 0;
493
+ }
494
+
495
+ /* ════════════════════════════════════════════════════════════════════════════
496
+ * SKELETON + SPINNER
497
+ * ════════════════════════════════════════════════════════════════════════════ */
498
+ @keyframes hf-shimmer {
499
+ 0% { background-position: -200% 0; }
500
+ 100% { background-position: 200% 0; }
501
+ }
502
+ .skeleton {
503
+ background: linear-gradient(
504
+ 90deg,
505
+ var(--color-hf-bg-muted, #F1F3F6) 25%,
506
+ var(--color-hf-border-subtle, #E4E8EF) 50%,
507
+ var(--color-hf-bg-muted, #F1F3F6) 75%
508
+ );
509
+ background-size: 200% 100%;
510
+ animation: hf-shimmer 1.4s infinite;
511
+ border-radius: 4px;
512
+ }
513
+
514
+ @keyframes hf-spin { to { transform: rotate(360deg); } }
515
+ .hf-spin { animation: hf-spin .6s linear infinite; }