gavl-ui 1.1.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,1472 @@
1
+ // ============================================================
2
+ // GAVL UI — Pixel RPG CSS Library v1.1
3
+ // Inspired by GAVL.IO.VN — Game News for Vietnamese Gen Z
4
+ // Usage: @import 'gavl-ui.scss';
5
+ //
6
+ // CHANGELOG v1.1:
7
+ // - [FIX] Thêm font "Be Vietnam Pro" hỗ trợ đầy đủ tiếng Việt
8
+ // - [FIX] line-height tăng lên cho text tiếng Việt (dấu thanh không bị cắt)
9
+ // - [FIX] gv-form-label, gv-alert-title, gv-modal-title, gv-tab-item
10
+ // dùng font-pixel cho ASCII, fallback body font cho Việt
11
+ // - [FIX] gv-btn-loading::after thiếu display:inline-block trong SCSS
12
+ // - [FIX] gv-progress-animated thiếu background-size
13
+ // - [FIX] gv-tag bỏ @extend .gv-badge để tránh specificity conflict
14
+ // - [NEW] gv-heading: heading tiếng Việt dùng body font (không pixel)
15
+ // - [NEW] gv-vi-* utilities: helpers tối ưu hiển thị tiếng Việt
16
+ // - [NEW] gv-purple-dark thêm vào token
17
+ // - [NEW] gv-btn-purple variant
18
+ // - [NEW] Dark mode support via .gv-dark
19
+ // ============================================================
20
+
21
+ // ============================================================
22
+ // 1. DESIGN TOKENS
23
+ // ============================================================
24
+ :root {
25
+ // --- Color Palette ---
26
+ --gv-brown-darkest: #1a1008;
27
+ --gv-brown-dark: #2a1f14;
28
+ --gv-brown-mid: #5c3d1e;
29
+ --gv-brown-light: #c8a06e;
30
+ --gv-cream: #f5e9d3;
31
+ --gv-cream-2: #ede0c4;
32
+ --gv-cream-3: #e4d5b0;
33
+
34
+ --gv-gold: #e6b44c;
35
+ --gv-gold-dark: #c9952a;
36
+ --gv-gold-light: #f5ce7a;
37
+
38
+ --gv-blue: #4a90d9;
39
+ --gv-blue-dark: #2d6baa;
40
+ --gv-blue-light: #7ab8f0;
41
+
42
+ --gv-red: #e05252;
43
+ --gv-red-dark: #b83a3a;
44
+ --gv-red-light: #f07878;
45
+
46
+ --gv-green: #5ab552;
47
+ --gv-green-dark: #3a8c34;
48
+ --gv-green-light: #82d67a;
49
+
50
+ --gv-yellow: #f0c830;
51
+ --gv-purple: #9b72cf;
52
+ --gv-purple-dark: #7450a8; // [FIX] thêm vào tokens (v1.0 thiếu)
53
+
54
+ --gv-white: #ffffff;
55
+ --gv-gray-1: #f0f0f0;
56
+ --gv-gray-2: #cccccc;
57
+ --gv-gray-3: #888888;
58
+ --gv-gray-4: #555555;
59
+
60
+ // --- Pixel Shadow System ---
61
+ --gv-shadow-sm: 2px 2px 0;
62
+ --gv-shadow-md: 3px 3px 0;
63
+ --gv-shadow-lg: 4px 4px 0;
64
+ --gv-shadow-xl: 6px 6px 0;
65
+
66
+ // --- Border ---
67
+ --gv-border: 2px solid var(--gv-brown-mid);
68
+ --gv-border-gold: 2px solid var(--gv-gold);
69
+ --gv-border-sm: 1px solid var(--gv-brown-light);
70
+
71
+ // --- Radius ---
72
+ --gv-radius-sm: 4px;
73
+ --gv-radius-md: 8px;
74
+ --gv-radius-lg: 12px;
75
+ --gv-radius-xl: 16px;
76
+
77
+ // --- Typography ---
78
+ // [NEW] Be Vietnam Pro: hỗ trợ đầy đủ Unicode Latin Extended với dấu thanh tiếng Việt
79
+ // Nunito làm fallback thứ hai, sau đó mới đến system-ui
80
+ --gv-font-pixel: 'Press Start 2P', monospace;
81
+ --gv-font-body: 'Be Vietnam Pro', 'Nunito', 'Segoe UI', system-ui, sans-serif;
82
+
83
+ // --- Spacing ---
84
+ --gv-space-1: 4px;
85
+ --gv-space-2: 8px;
86
+ --gv-space-3: 12px;
87
+ --gv-space-4: 16px;
88
+ --gv-space-5: 24px;
89
+ --gv-space-6: 32px;
90
+ --gv-space-7: 48px;
91
+
92
+ // --- Transition ---
93
+ --gv-transition: all 0.15s ease;
94
+
95
+ // --- Vietnamese line-height (dấu thanh cần thêm khoảng trống phía trên) ---
96
+ --gv-lh-vi: 1.75; // dùng cho text tiếng Việt dài
97
+ --gv-lh-vi-tight: 1.5; // cho headings tiếng Việt
98
+ }
99
+
100
+ // ============================================================
101
+ // 2. RESET & BASE
102
+ // ============================================================
103
+ *, *::before, *::after { box-sizing: border-box; }
104
+
105
+ .gv-root {
106
+ font-family: var(--gv-font-body);
107
+ background-color: var(--gv-cream);
108
+ color: var(--gv-brown-dark);
109
+ // [FIX] line-height cao hơn cho tiếng Việt — dấu thanh (ộ, ề, ẫ...) cần không gian dọc
110
+ line-height: var(--gv-lh-vi);
111
+ }
112
+
113
+ // ============================================================
114
+ // 3. TYPOGRAPHY
115
+ // ============================================================
116
+
117
+ // ── Pixel Display Headings ────────────────────────────────────
118
+ // QUAN TRỌNG: Press Start 2P KHÔNG có ký tự tiếng Việt.
119
+ // Các class .gv-display-* CHỈ dùng cho text ASCII (tiêu đề section, label UI).
120
+ // Với tiêu đề bằng tiếng Việt, dùng .gv-heading-* thay thế.
121
+
122
+ .gv-display-1 {
123
+ font-family: var(--gv-font-pixel);
124
+ font-size: 24px;
125
+ color: var(--gv-brown-dark);
126
+ line-height: 1.8; // [FIX] tăng từ 1.6 — pixel font cần nhiều hơn
127
+ letter-spacing: 2px;
128
+ }
129
+ .gv-display-2 {
130
+ font-family: var(--gv-font-pixel);
131
+ font-size: 18px;
132
+ color: var(--gv-brown-dark);
133
+ line-height: 1.8;
134
+ }
135
+ .gv-display-3 {
136
+ font-family: var(--gv-font-pixel);
137
+ font-size: 14px;
138
+ color: var(--gv-brown-dark);
139
+ line-height: 1.8;
140
+ }
141
+ .gv-display-4 {
142
+ font-family: var(--gv-font-pixel);
143
+ font-size: 10px;
144
+ color: var(--gv-brown-dark);
145
+ line-height: 2; // [FIX] tăng từ 1.8
146
+ }
147
+ .gv-display-5 {
148
+ font-family: var(--gv-font-pixel);
149
+ font-size: 8px;
150
+ color: var(--gv-brown-mid);
151
+ line-height: 2;
152
+ letter-spacing: 1px;
153
+ }
154
+ .gv-display-6 {
155
+ font-family: var(--gv-font-pixel);
156
+ font-size: 7px;
157
+ color: var(--gv-brown-mid);
158
+ line-height: 2;
159
+ letter-spacing: 1px;
160
+ }
161
+
162
+ // ── [NEW] Headings tiếng Việt ─────────────────────────────────
163
+ // Dùng Be Vietnam Pro — render sắc nét, đầy đủ dấu thanh
164
+ .gv-heading-1 {
165
+ font-family: var(--gv-font-body);
166
+ font-size: 28px;
167
+ font-weight: 800;
168
+ color: var(--gv-brown-dark);
169
+ line-height: var(--gv-lh-vi-tight);
170
+ letter-spacing: -0.3px;
171
+ }
172
+ .gv-heading-2 {
173
+ font-family: var(--gv-font-body);
174
+ font-size: 22px;
175
+ font-weight: 800;
176
+ color: var(--gv-brown-dark);
177
+ line-height: var(--gv-lh-vi-tight);
178
+ }
179
+ .gv-heading-3 {
180
+ font-family: var(--gv-font-body);
181
+ font-size: 18px;
182
+ font-weight: 700;
183
+ color: var(--gv-brown-dark);
184
+ line-height: var(--gv-lh-vi-tight);
185
+ }
186
+ .gv-heading-4 {
187
+ font-family: var(--gv-font-body);
188
+ font-size: 16px;
189
+ font-weight: 700;
190
+ color: var(--gv-brown-dark);
191
+ line-height: var(--gv-lh-vi-tight);
192
+ }
193
+
194
+ // ── Body Text ─────────────────────────────────────────────────
195
+ // [FIX] line-height tăng thêm để dấu thanh tiếng Việt không bị clip
196
+ .gv-text-xl { font-size: 20px; font-weight: 700; line-height: var(--gv-lh-vi); }
197
+ .gv-text-lg { font-size: 17px; font-weight: 600; line-height: var(--gv-lh-vi); }
198
+ .gv-text-md { font-size: 15px; font-weight: 400; line-height: var(--gv-lh-vi); }
199
+ .gv-text-sm { font-size: 13px; font-weight: 400; line-height: var(--gv-lh-vi); }
200
+ .gv-text-xs { font-size: 11px; font-weight: 500; line-height: 1.6; }
201
+ .gv-text-2xs { font-size: 10px; font-weight: 600; line-height: 1.6; letter-spacing: 0.5px; }
202
+
203
+ // Color Modifiers
204
+ .gv-text-gold { color: var(--gv-gold-dark); }
205
+ .gv-text-brown { color: var(--gv-brown-mid); }
206
+ .gv-text-cream { color: var(--gv-cream); }
207
+ .gv-text-muted { color: var(--gv-gray-3); }
208
+ .gv-text-danger { color: var(--gv-red); }
209
+ .gv-text-success { color: var(--gv-green-dark); }
210
+ .gv-text-info { color: var(--gv-blue-dark); }
211
+ .gv-text-purple { color: var(--gv-purple); }
212
+
213
+ // ── Label / Caption ───────────────────────────────────────────
214
+ // [FIX] gv-label dùng pixel font — CHỈ cho ASCII. Nếu label là tiếng Việt,
215
+ // dùng .gv-vi-label thay thế.
216
+ .gv-label {
217
+ font-family: var(--gv-font-pixel);
218
+ font-size: 8px;
219
+ color: var(--gv-brown-mid);
220
+ letter-spacing: 1px;
221
+ text-transform: uppercase;
222
+ line-height: 2;
223
+ }
224
+
225
+ // [NEW] gv-vi-label: label tiếng Việt — body font, vẫn giữ cảm giác nhỏ gọn
226
+ .gv-vi-label {
227
+ font-family: var(--gv-font-body);
228
+ font-size: 11px;
229
+ font-weight: 700;
230
+ color: var(--gv-brown-mid);
231
+ letter-spacing: 0.5px;
232
+ text-transform: uppercase;
233
+ line-height: 1.5;
234
+ }
235
+
236
+ .gv-caption {
237
+ font-size: 11px;
238
+ color: var(--gv-gray-3);
239
+ font-weight: 600;
240
+ line-height: 1.6;
241
+ }
242
+
243
+ // ── [NEW] Vietnamese text utilities ───────────────────────────
244
+ // .gv-vi: áp dụng cho block text tiếng Việt — đảm bảo rendering đúng
245
+ .gv-vi {
246
+ font-family: var(--gv-font-body);
247
+ line-height: var(--gv-lh-vi);
248
+ // Đảm bảo dấu thanh không bị clip trên các trình duyệt cũ
249
+ overflow: visible;
250
+ // word-break phù hợp với tiếng Việt (không ngắt giữa âm tiết)
251
+ word-break: keep-all;
252
+ overflow-wrap: break-word;
253
+ }
254
+
255
+ // .gv-vi-tight: cho UI compact (badge, tab, button label)
256
+ .gv-vi-tight {
257
+ font-family: var(--gv-font-body);
258
+ line-height: var(--gv-lh-vi-tight);
259
+ word-break: keep-all;
260
+ overflow-wrap: break-word;
261
+ }
262
+
263
+ // ============================================================
264
+ // 4. BUTTONS
265
+ // ============================================================
266
+ .gv-btn {
267
+ display: inline-flex;
268
+ align-items: center;
269
+ justify-content: center;
270
+ gap: 8px;
271
+ font-family: var(--gv-font-body);
272
+ font-weight: 800;
273
+ border-radius: var(--gv-radius-md);
274
+ border: 2px solid transparent;
275
+ cursor: pointer;
276
+ text-decoration: none;
277
+ transition: var(--gv-transition);
278
+ white-space: nowrap;
279
+ // [FIX] line-height 1.2 thay vì 1 — đảm bảo dấu tiếng Việt trong button không bị cắt
280
+ line-height: 1.2;
281
+ position: relative;
282
+
283
+ // Default size
284
+ padding: 10px 20px;
285
+ font-size: 14px;
286
+ box-shadow: var(--gv-shadow-sm) var(--gv-brown-mid);
287
+
288
+ &:hover { transform: translate(-1px, -1px); box-shadow: var(--gv-shadow-md) var(--gv-brown-mid); }
289
+ &:active { transform: translate(1px, 1px); box-shadow: none; }
290
+ &:disabled, &.gv-disabled {
291
+ opacity: 0.45;
292
+ cursor: not-allowed;
293
+ pointer-events: none;
294
+ }
295
+ }
296
+
297
+ // Sizes
298
+ .gv-btn-xs { padding: 5px 10px; font-size: 11px; box-shadow: var(--gv-shadow-sm) var(--gv-brown-mid); }
299
+ .gv-btn-sm { padding: 7px 14px; font-size: 12px; }
300
+ .gv-btn-lg { padding: 13px 28px; font-size: 16px; box-shadow: var(--gv-shadow-md) var(--gv-brown-mid); }
301
+ .gv-btn-xl { padding: 16px 36px; font-size: 18px; box-shadow: var(--gv-shadow-lg) var(--gv-brown-mid); }
302
+ .gv-btn-block { width: 100%; }
303
+
304
+ // Pixel font variant — CHỈ dùng cho text ASCII (PLAY NOW, START, etc.)
305
+ .gv-btn-pixel {
306
+ font-family: var(--gv-font-pixel);
307
+ font-size: 9px;
308
+ letter-spacing: 1px;
309
+ padding: 12px 20px;
310
+ line-height: 1.8; // [FIX] pixel font cần line-height cao hơn
311
+ }
312
+
313
+ // Variants
314
+ .gv-btn-primary {
315
+ background: var(--gv-gold);
316
+ color: var(--gv-brown-dark);
317
+ border-color: var(--gv-gold-dark);
318
+ &:hover { background: var(--gv-gold-light); }
319
+ }
320
+ .gv-btn-secondary {
321
+ background: var(--gv-cream-2);
322
+ color: var(--gv-brown-dark);
323
+ border-color: var(--gv-brown-light);
324
+ &:hover { background: var(--gv-cream-3); }
325
+ }
326
+ .gv-btn-dark {
327
+ background: var(--gv-brown-dark);
328
+ color: var(--gv-cream);
329
+ border-color: var(--gv-brown-darkest);
330
+ box-shadow: var(--gv-shadow-sm) var(--gv-brown-darkest);
331
+ &:hover { background: var(--gv-brown-mid); }
332
+ }
333
+ .gv-btn-danger {
334
+ background: var(--gv-red);
335
+ color: var(--gv-white);
336
+ border-color: var(--gv-red-dark);
337
+ box-shadow: var(--gv-shadow-sm) var(--gv-red-dark);
338
+ &:hover { background: var(--gv-red-light); }
339
+ }
340
+ .gv-btn-success {
341
+ background: var(--gv-green);
342
+ color: var(--gv-white);
343
+ border-color: var(--gv-green-dark);
344
+ box-shadow: var(--gv-shadow-sm) var(--gv-green-dark);
345
+ &:hover { background: var(--gv-green-light); }
346
+ }
347
+ .gv-btn-info {
348
+ background: var(--gv-blue);
349
+ color: var(--gv-white);
350
+ border-color: var(--gv-blue-dark);
351
+ box-shadow: var(--gv-shadow-sm) var(--gv-blue-dark);
352
+ &:hover { background: var(--gv-blue-light); }
353
+ }
354
+ // [NEW] Purple variant
355
+ .gv-btn-purple {
356
+ background: var(--gv-purple);
357
+ color: var(--gv-white);
358
+ border-color: var(--gv-purple-dark);
359
+ box-shadow: var(--gv-shadow-sm) var(--gv-purple-dark);
360
+ &:hover { background: #b08de0; }
361
+ }
362
+ .gv-btn-ghost {
363
+ background: transparent;
364
+ color: var(--gv-brown-mid);
365
+ border-color: var(--gv-brown-light);
366
+ box-shadow: none;
367
+ &:hover { background: var(--gv-cream-2); box-shadow: var(--gv-shadow-sm) var(--gv-brown-mid); }
368
+ }
369
+ .gv-btn-link {
370
+ background: transparent;
371
+ color: var(--gv-blue-dark);
372
+ border-color: transparent;
373
+ box-shadow: none;
374
+ text-decoration: underline;
375
+ text-decoration-style: dashed;
376
+ &:hover { color: var(--gv-blue); transform: none; }
377
+ }
378
+
379
+ // Icon-only button
380
+ .gv-btn-icon {
381
+ width: 40px;
382
+ height: 40px;
383
+ padding: 0;
384
+ border-radius: var(--gv-radius-md);
385
+ }
386
+ .gv-btn-icon-sm { width: 32px; height: 32px; padding: 0; border-radius: var(--gv-radius-sm); }
387
+ .gv-btn-icon-lg { width: 48px; height: 48px; padding: 0; border-radius: var(--gv-radius-lg); }
388
+
389
+ // [FIX] Loading state — thêm display:inline-block (bị thiếu trong v1.0)
390
+ .gv-btn-loading {
391
+ pointer-events: none;
392
+ &::after {
393
+ content: '';
394
+ display: inline-block; // [FIX] thiếu trong v1.0
395
+ width: 12px; height: 12px;
396
+ border: 2px solid currentColor;
397
+ border-top-color: transparent;
398
+ border-radius: 50%;
399
+ animation: gv-spin 0.6s linear infinite;
400
+ margin-left: 6px;
401
+ vertical-align: middle;
402
+ }
403
+ }
404
+
405
+ // Button Group
406
+ .gv-btn-group {
407
+ display: inline-flex;
408
+ .gv-btn {
409
+ border-radius: 0;
410
+ &:first-child { border-radius: var(--gv-radius-md) 0 0 var(--gv-radius-md); }
411
+ &:last-child { border-radius: 0 var(--gv-radius-md) var(--gv-radius-md) 0; }
412
+ &:not(:first-child) { border-left: none; }
413
+ }
414
+ }
415
+
416
+ // ============================================================
417
+ // 5. FORM INPUTS
418
+ // ============================================================
419
+
420
+ %gv-input-base {
421
+ font-family: var(--gv-font-body);
422
+ font-size: 14px;
423
+ font-weight: 600;
424
+ color: var(--gv-brown-dark);
425
+ background: var(--gv-cream);
426
+ border: var(--gv-border);
427
+ border-radius: var(--gv-radius-md);
428
+ padding: 10px 14px;
429
+ width: 100%;
430
+ transition: var(--gv-transition);
431
+ outline: none;
432
+ box-shadow: var(--gv-shadow-sm) var(--gv-brown-light);
433
+ appearance: none;
434
+ -webkit-appearance: none;
435
+ // [FIX] tiếng Việt trong input cần line-height đủ để dấu thanh hiển thị
436
+ line-height: 1.5;
437
+
438
+ &::placeholder { color: var(--gv-brown-light); font-weight: 500; }
439
+
440
+ &:focus {
441
+ border-color: var(--gv-gold-dark);
442
+ box-shadow: var(--gv-shadow-sm) var(--gv-gold-dark), 0 0 0 3px rgba(230,180,76,0.2);
443
+ }
444
+ &:disabled {
445
+ background: var(--gv-gray-1);
446
+ color: var(--gv-gray-3);
447
+ cursor: not-allowed;
448
+ box-shadow: none;
449
+ }
450
+ &.gv-input-error {
451
+ border-color: var(--gv-red);
452
+ box-shadow: var(--gv-shadow-sm) var(--gv-red-dark);
453
+ &:focus { box-shadow: var(--gv-shadow-sm) var(--gv-red-dark), 0 0 0 3px rgba(224,82,82,0.2); }
454
+ }
455
+ &.gv-input-success {
456
+ border-color: var(--gv-green);
457
+ box-shadow: var(--gv-shadow-sm) var(--gv-green-dark);
458
+ }
459
+ }
460
+
461
+ .gv-input { @extend %gv-input-base; }
462
+ .gv-input-sm { @extend %gv-input-base; padding: 6px 10px; font-size: 12px; }
463
+ .gv-input-lg { @extend %gv-input-base; padding: 13px 18px; font-size: 16px; }
464
+ // [FIX] textarea: min-height tăng và line-height đủ cho tiếng Việt
465
+ .gv-textarea { @extend %gv-input-base; resize: vertical; min-height: 110px; line-height: var(--gv-lh-vi); }
466
+ .gv-select {
467
+ @extend %gv-input-base;
468
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='%235c3d1e' d='M0 0l6 8 6-8z'/%3E%3C/svg%3E");
469
+ background-repeat: no-repeat;
470
+ background-position: right 14px center;
471
+ padding-right: 38px;
472
+ cursor: pointer;
473
+ }
474
+
475
+ // Form Group
476
+ .gv-form-group {
477
+ display: flex;
478
+ flex-direction: column;
479
+ gap: 6px;
480
+ margin-bottom: var(--gv-space-4);
481
+ }
482
+
483
+ // [FIX] Form Label — pixel font CHỈ cho ASCII labels
484
+ // Dùng .gv-form-label-vi cho label tiếng Việt
485
+ .gv-form-label {
486
+ font-family: var(--gv-font-pixel);
487
+ font-size: 8px;
488
+ color: var(--gv-brown-mid);
489
+ letter-spacing: 1px;
490
+ line-height: 2;
491
+ }
492
+
493
+ // [NEW] Form Label tiếng Việt
494
+ .gv-form-label-vi {
495
+ font-family: var(--gv-font-body);
496
+ font-size: 12px;
497
+ font-weight: 700;
498
+ color: var(--gv-brown-mid);
499
+ letter-spacing: 0.3px;
500
+ line-height: 1.5;
501
+ }
502
+
503
+ // Helper / Error text
504
+ .gv-form-helper { font-size: 11px; color: var(--gv-gray-3); font-weight: 500; line-height: 1.5; }
505
+ .gv-form-error { font-size: 11px; color: var(--gv-red); font-weight: 600; line-height: 1.5; }
506
+
507
+ // Input with prefix/suffix
508
+ .gv-input-group {
509
+ position: relative;
510
+ display: flex;
511
+ align-items: stretch;
512
+
513
+ .gv-input-addon {
514
+ display: flex;
515
+ align-items: center;
516
+ padding: 0 12px;
517
+ background: var(--gv-cream-2);
518
+ border: var(--gv-border);
519
+ color: var(--gv-brown-mid);
520
+ font-weight: 700;
521
+ font-size: 13px;
522
+ white-space: nowrap;
523
+ }
524
+ .gv-input-addon-left {
525
+ border-right: none;
526
+ border-radius: var(--gv-radius-md) 0 0 var(--gv-radius-md);
527
+ }
528
+ .gv-input-addon-right {
529
+ border-left: none;
530
+ border-radius: 0 var(--gv-radius-md) var(--gv-radius-md) 0;
531
+ }
532
+ .gv-input {
533
+ flex: 1;
534
+ &:not(:first-child) { border-radius: 0 var(--gv-radius-md) var(--gv-radius-md) 0; }
535
+ &:not(:last-child) { border-radius: var(--gv-radius-md) 0 0 var(--gv-radius-md); }
536
+ &:only-child { border-radius: var(--gv-radius-md); }
537
+ }
538
+ }
539
+
540
+ // Checkbox & Radio
541
+ .gv-check-group {
542
+ display: flex;
543
+ align-items: center;
544
+ gap: 10px;
545
+ cursor: pointer;
546
+ user-select: none;
547
+
548
+ input[type="checkbox"],
549
+ input[type="radio"] { display: none; }
550
+
551
+ .gv-check-box {
552
+ width: 20px; height: 20px;
553
+ background: var(--gv-cream);
554
+ border: var(--gv-border);
555
+ border-radius: var(--gv-radius-sm);
556
+ flex-shrink: 0;
557
+ box-shadow: var(--gv-shadow-sm) var(--gv-brown-light);
558
+ display: flex; align-items: center; justify-content: center;
559
+ transition: var(--gv-transition);
560
+ &::after { content: ''; display: none; }
561
+ }
562
+ input:checked + .gv-check-box {
563
+ background: var(--gv-gold);
564
+ border-color: var(--gv-gold-dark);
565
+ box-shadow: var(--gv-shadow-sm) var(--gv-gold-dark);
566
+ &::after {
567
+ display: block;
568
+ content: '✓';
569
+ font-size: 12px;
570
+ font-weight: 900;
571
+ color: var(--gv-brown-dark);
572
+ line-height: 1;
573
+ }
574
+ }
575
+
576
+ .gv-radio-box {
577
+ width: 20px; height: 20px;
578
+ background: var(--gv-cream);
579
+ border: var(--gv-border);
580
+ border-radius: 50%;
581
+ flex-shrink: 0;
582
+ box-shadow: var(--gv-shadow-sm) var(--gv-brown-light);
583
+ display: flex; align-items: center; justify-content: center;
584
+ transition: var(--gv-transition);
585
+ &::after { content: ''; display: none; }
586
+ }
587
+ input:checked + .gv-radio-box {
588
+ border-color: var(--gv-gold-dark);
589
+ box-shadow: var(--gv-shadow-sm) var(--gv-gold-dark);
590
+ &::after {
591
+ display: block;
592
+ width: 10px; height: 10px;
593
+ background: var(--gv-gold);
594
+ border-radius: 50%;
595
+ }
596
+ }
597
+
598
+ // [FIX] line-height cao hơn cho label tiếng Việt
599
+ .gv-check-label { font-size: 14px; font-weight: 700; color: var(--gv-brown-dark); line-height: var(--gv-lh-vi-tight); }
600
+ }
601
+
602
+ // Toggle / Switch
603
+ .gv-toggle {
604
+ display: inline-flex;
605
+ align-items: center;
606
+ gap: 10px;
607
+ cursor: pointer;
608
+ user-select: none;
609
+
610
+ input { display: none; }
611
+
612
+ .gv-toggle-track {
613
+ width: 44px; height: 24px;
614
+ background: var(--gv-gray-2);
615
+ border: var(--gv-border);
616
+ border-radius: 20px;
617
+ position: relative;
618
+ transition: var(--gv-transition);
619
+ box-shadow: var(--gv-shadow-sm) var(--gv-brown-light);
620
+ flex-shrink: 0;
621
+
622
+ &::after {
623
+ content: '';
624
+ position: absolute;
625
+ left: 2px; top: 2px;
626
+ width: 16px; height: 16px;
627
+ background: var(--gv-white);
628
+ border-radius: 50%;
629
+ border: 2px solid var(--gv-brown-light);
630
+ transition: var(--gv-transition);
631
+ }
632
+ }
633
+ input:checked + .gv-toggle-track {
634
+ background: var(--gv-gold);
635
+ border-color: var(--gv-gold-dark);
636
+ box-shadow: var(--gv-shadow-sm) var(--gv-gold-dark);
637
+ &::after { left: calc(100% - 18px); border-color: var(--gv-gold-dark); }
638
+ }
639
+
640
+ .gv-toggle-label { font-size: 14px; font-weight: 700; color: var(--gv-brown-dark); line-height: var(--gv-lh-vi-tight); }
641
+ }
642
+
643
+ // Range Slider
644
+ .gv-range {
645
+ -webkit-appearance: none;
646
+ appearance: none;
647
+ width: 100%;
648
+ height: 8px;
649
+ border-radius: var(--gv-radius-sm);
650
+ background: var(--gv-cream-2);
651
+ border: var(--gv-border);
652
+ box-shadow: var(--gv-shadow-sm) var(--gv-brown-light);
653
+ outline: none;
654
+ cursor: pointer;
655
+
656
+ &::-webkit-slider-thumb {
657
+ -webkit-appearance: none;
658
+ width: 20px; height: 20px;
659
+ border-radius: var(--gv-radius-sm);
660
+ background: var(--gv-gold);
661
+ border: var(--gv-border);
662
+ box-shadow: var(--gv-shadow-sm) var(--gv-brown-mid);
663
+ cursor: pointer;
664
+ transition: var(--gv-transition);
665
+ &:hover { background: var(--gv-gold-light); transform: scale(1.1); }
666
+ }
667
+ &::-moz-range-thumb {
668
+ width: 20px; height: 20px;
669
+ border-radius: var(--gv-radius-sm);
670
+ background: var(--gv-gold);
671
+ border: var(--gv-border);
672
+ box-shadow: var(--gv-shadow-sm) var(--gv-brown-mid);
673
+ cursor: pointer;
674
+ }
675
+ }
676
+
677
+ // ============================================================
678
+ // 6. CARDS
679
+ // ============================================================
680
+ .gv-card {
681
+ background: var(--gv-cream);
682
+ border: var(--gv-border);
683
+ border-radius: var(--gv-radius-lg);
684
+ box-shadow: var(--gv-shadow-md) var(--gv-brown-mid);
685
+ overflow: hidden;
686
+ transition: var(--gv-transition);
687
+
688
+ &.gv-card-hover:hover {
689
+ transform: translate(-2px, -2px);
690
+ box-shadow: var(--gv-shadow-lg) var(--gv-brown-mid);
691
+ }
692
+ }
693
+ .gv-card-dark {
694
+ background: linear-gradient(135deg, var(--gv-brown-dark), var(--gv-brown-mid));
695
+ border-color: var(--gv-brown-darkest);
696
+ box-shadow: var(--gv-shadow-md) var(--gv-brown-darkest);
697
+ color: var(--gv-cream);
698
+ }
699
+ .gv-card-gold {
700
+ background: linear-gradient(135deg, var(--gv-gold), var(--gv-gold-light));
701
+ border-color: var(--gv-gold-dark);
702
+ box-shadow: var(--gv-shadow-md) var(--gv-gold-dark);
703
+ color: var(--gv-brown-dark);
704
+ }
705
+ .gv-card-header {
706
+ padding: 16px 20px;
707
+ border-bottom: var(--gv-border);
708
+ background: var(--gv-cream-2);
709
+ display: flex;
710
+ align-items: center;
711
+ justify-content: space-between;
712
+ gap: 10px;
713
+ }
714
+ .gv-card-body { padding: 20px; }
715
+ .gv-card-footer {
716
+ padding: 14px 20px;
717
+ border-top: var(--gv-border);
718
+ background: var(--gv-cream-2);
719
+ }
720
+ .gv-card-image {
721
+ width: 100%;
722
+ aspect-ratio: 16/9;
723
+ object-fit: cover;
724
+ display: block;
725
+ border-bottom: var(--gv-border);
726
+ }
727
+ // [FIX] gv-card-title: pixel font CHỈ dùng cho ASCII
728
+ // Với tiêu đề tiếng Việt, dùng .gv-card-title-vi
729
+ .gv-card-title {
730
+ font-family: var(--gv-font-pixel);
731
+ font-size: 10px;
732
+ color: var(--gv-brown-dark);
733
+ line-height: 2; // [FIX] tăng line-height
734
+ }
735
+ // [NEW] Card title tiếng Việt
736
+ .gv-card-title-vi {
737
+ font-family: var(--gv-font-body);
738
+ font-size: 16px;
739
+ font-weight: 800;
740
+ color: var(--gv-brown-dark);
741
+ line-height: var(--gv-lh-vi-tight);
742
+ }
743
+ .gv-card-desc {
744
+ font-size: 13px;
745
+ color: var(--gv-brown-mid);
746
+ line-height: var(--gv-lh-vi); // [FIX]
747
+ margin-top: 8px;
748
+ }
749
+
750
+ // ============================================================
751
+ // 7. BADGES & TAGS
752
+ // ============================================================
753
+ .gv-badge {
754
+ display: inline-flex;
755
+ align-items: center;
756
+ gap: 4px;
757
+ padding: 3px 9px;
758
+ border-radius: var(--gv-radius-sm);
759
+ font-size: 11px;
760
+ font-weight: 800;
761
+ border: 2px solid transparent;
762
+ white-space: nowrap;
763
+ line-height: 1.4;
764
+ }
765
+ // [FIX] gv-badge-pixel: CHỈ dùng cho text ASCII ngắn (HOT, NEW, LIVE...)
766
+ .gv-badge-pixel {
767
+ font-family: var(--gv-font-pixel);
768
+ font-size: 7px;
769
+ padding: 4px 8px;
770
+ line-height: 1.8; // [FIX]
771
+ }
772
+ .gv-badge-primary { background: var(--gv-gold); color: var(--gv-brown-dark); border-color: var(--gv-gold-dark); box-shadow: 2px 2px 0 var(--gv-gold-dark); }
773
+ .gv-badge-dark { background: var(--gv-brown-dark); color: var(--gv-cream); border-color: var(--gv-brown-darkest); box-shadow: 2px 2px 0 var(--gv-brown-darkest); }
774
+ .gv-badge-danger { background: var(--gv-red); color: var(--gv-white); border-color: var(--gv-red-dark); box-shadow: 2px 2px 0 var(--gv-red-dark); }
775
+ .gv-badge-success { background: var(--gv-green); color: var(--gv-white); border-color: var(--gv-green-dark); box-shadow: 2px 2px 0 var(--gv-green-dark); }
776
+ .gv-badge-info { background: var(--gv-blue); color: var(--gv-white); border-color: var(--gv-blue-dark); box-shadow: 2px 2px 0 var(--gv-blue-dark); }
777
+ .gv-badge-purple { background: var(--gv-purple); color: var(--gv-white); border-color: var(--gv-purple-dark); box-shadow: 2px 2px 0 var(--gv-purple-dark); }
778
+ .gv-badge-outline {
779
+ background: transparent;
780
+ color: var(--gv-brown-mid);
781
+ border-color: var(--gv-brown-light);
782
+ box-shadow: 2px 2px 0 var(--gv-brown-light);
783
+ }
784
+
785
+ // [FIX] Tag: không dùng @extend .gv-badge để tránh specificity conflict
786
+ .gv-tag {
787
+ display: inline-flex;
788
+ align-items: center;
789
+ gap: 4px;
790
+ padding: 5px 12px;
791
+ border-radius: var(--gv-radius-sm);
792
+ font-size: 12px;
793
+ font-weight: 700;
794
+ font-family: var(--gv-font-body);
795
+ line-height: 1.4;
796
+ border: 2px solid var(--gv-brown-light);
797
+ white-space: nowrap;
798
+ cursor: pointer;
799
+ text-decoration: none;
800
+ transition: var(--gv-transition);
801
+ background: var(--gv-cream-2);
802
+ color: var(--gv-brown-mid);
803
+ box-shadow: var(--gv-shadow-sm) var(--gv-brown-mid);
804
+
805
+ &::before { content: '#'; opacity: 0.5; }
806
+ &:hover {
807
+ background: var(--gv-gold);
808
+ border-color: var(--gv-gold-dark);
809
+ color: var(--gv-brown-dark);
810
+ transform: translate(-1px, -1px);
811
+ box-shadow: var(--gv-shadow-md) var(--gv-brown-mid);
812
+ }
813
+ }
814
+
815
+ // ============================================================
816
+ // 8. ALERTS
817
+ // ============================================================
818
+ .gv-alert {
819
+ display: flex;
820
+ align-items: flex-start;
821
+ gap: 12px;
822
+ padding: 14px 18px;
823
+ border-radius: var(--gv-radius-md);
824
+ border: var(--gv-border);
825
+ font-size: 13px;
826
+ font-weight: 600;
827
+ line-height: var(--gv-lh-vi); // [FIX]
828
+ box-shadow: var(--gv-shadow-sm) var(--gv-brown-light);
829
+ }
830
+ .gv-alert-icon { flex-shrink: 0; margin-top: 2px; }
831
+ // [FIX] gv-alert-title: dùng body font với weight cao để vừa nhìn "pixelish" vừa đọc được tiếng Việt
832
+ .gv-alert-title {
833
+ font-family: var(--gv-font-body);
834
+ font-size: 12px;
835
+ font-weight: 800;
836
+ letter-spacing: 0.5px;
837
+ text-transform: uppercase;
838
+ margin-bottom: 4px;
839
+ line-height: 1.4;
840
+ }
841
+ .gv-alert-info { background: #ddeeff; border-color: var(--gv-blue); color: var(--gv-blue-dark); box-shadow: var(--gv-shadow-sm) var(--gv-blue-dark); }
842
+ .gv-alert-success { background: #ddf5dd; border-color: var(--gv-green); color: var(--gv-green-dark); box-shadow: var(--gv-shadow-sm) var(--gv-green-dark); }
843
+ .gv-alert-warning { background: #fff8dd; border-color: var(--gv-gold); color: var(--gv-gold-dark); box-shadow: var(--gv-shadow-sm) var(--gv-gold-dark); }
844
+ .gv-alert-danger { background: #ffeeee; border-color: var(--gv-red); color: var(--gv-red-dark); box-shadow: var(--gv-shadow-sm) var(--gv-red-dark); }
845
+
846
+ // ============================================================
847
+ // 9. PROGRESS BAR
848
+ // ============================================================
849
+ .gv-progress-wrap {
850
+ height: 16px;
851
+ background: var(--gv-cream-2);
852
+ border: var(--gv-border);
853
+ border-radius: var(--gv-radius-sm);
854
+ overflow: hidden;
855
+ box-shadow: var(--gv-shadow-sm) var(--gv-brown-light);
856
+ }
857
+ .gv-progress-bar {
858
+ height: 100%;
859
+ background: var(--gv-gold);
860
+ border-right: 2px solid var(--gv-gold-dark);
861
+ transition: width 0.4s ease;
862
+ position: relative;
863
+
864
+ &.gv-progress-striped {
865
+ background-image: repeating-linear-gradient(
866
+ 45deg,
867
+ transparent, transparent 6px,
868
+ rgba(255,255,255,0.25) 6px, rgba(255,255,255,0.25) 12px
869
+ );
870
+ }
871
+ // [FIX] thêm background-size để animation hoạt động đúng
872
+ &.gv-progress-animated {
873
+ animation: gv-progress-move 1s linear infinite;
874
+ background-size: 24px 24px;
875
+ }
876
+ &.gv-progress-danger { background: var(--gv-red); border-right-color: var(--gv-red-dark); }
877
+ &.gv-progress-success { background: var(--gv-green); border-right-color: var(--gv-green-dark); }
878
+ &.gv-progress-info { background: var(--gv-blue); border-right-color: var(--gv-blue-dark); }
879
+ }
880
+ .gv-progress-sm .gv-progress-wrap { height: 8px; }
881
+ .gv-progress-lg .gv-progress-wrap { height: 24px; }
882
+
883
+ // ============================================================
884
+ // 10. AVATAR
885
+ // ============================================================
886
+ .gv-avatar {
887
+ display: inline-flex;
888
+ align-items: center;
889
+ justify-content: center;
890
+ border-radius: var(--gv-radius-md);
891
+ border: var(--gv-border);
892
+ box-shadow: var(--gv-shadow-sm) var(--gv-brown-mid);
893
+ background: var(--gv-brown-mid);
894
+ color: var(--gv-cream);
895
+ font-weight: 800;
896
+ overflow: hidden;
897
+ flex-shrink: 0;
898
+
899
+ img { width: 100%; height: 100%; object-fit: cover; }
900
+
901
+ &.gv-avatar-circle { border-radius: 50%; }
902
+ &.gv-avatar-gold { border-color: var(--gv-gold); box-shadow: var(--gv-shadow-sm) var(--gv-gold-dark); }
903
+ }
904
+ .gv-avatar-xs { width: 24px; height: 24px; font-size: 10px; }
905
+ .gv-avatar-sm { width: 32px; height: 32px; font-size: 12px; }
906
+ .gv-avatar-md { width: 48px; height: 48px; font-size: 16px; }
907
+ .gv-avatar-lg { width: 64px; height: 64px; font-size: 20px; }
908
+ .gv-avatar-xl { width: 80px; height: 80px; font-size: 24px; }
909
+
910
+ .gv-avatar-group {
911
+ display: inline-flex;
912
+ .gv-avatar {
913
+ margin-left: -10px;
914
+ &:first-child { margin-left: 0; }
915
+ &:hover { z-index: 10; transform: translateY(-3px); }
916
+ }
917
+ }
918
+
919
+ // ============================================================
920
+ // 11. MODAL
921
+ // ============================================================
922
+ .gv-modal-backdrop {
923
+ // [FIX] mặc định ẩn, chỉ hiện khi .active
924
+ display: none;
925
+ position: fixed;
926
+ inset: 0;
927
+ background: rgba(26, 16, 8, 0.75);
928
+ backdrop-filter: blur(2px);
929
+ -webkit-backdrop-filter: blur(2px);
930
+ z-index: 1000;
931
+ align-items: center;
932
+ justify-content: center;
933
+ padding: 16px;
934
+
935
+ &.active {
936
+ display: flex;
937
+ animation: gv-fade-in 0.15s ease;
938
+ }
939
+ }
940
+ .gv-modal {
941
+ background: var(--gv-cream);
942
+ border: var(--gv-border);
943
+ border-radius: var(--gv-radius-xl);
944
+ box-shadow: var(--gv-shadow-xl) var(--gv-brown-dark);
945
+ width: 100%;
946
+ max-width: 520px;
947
+ max-height: 90vh;
948
+ overflow-y: auto;
949
+ animation: gv-slide-up 0.2s ease;
950
+ }
951
+ .gv-modal-sm { max-width: 360px; }
952
+ .gv-modal-lg { max-width: 720px; }
953
+ .gv-modal-header {
954
+ padding: 18px 24px;
955
+ border-bottom: var(--gv-border);
956
+ background: var(--gv-cream-2);
957
+ border-radius: var(--gv-radius-xl) var(--gv-radius-xl) 0 0;
958
+ display: flex;
959
+ align-items: center;
960
+ justify-content: space-between;
961
+ }
962
+ // [FIX] Modal title: dùng body font để hiển thị tiếng Việt đúng
963
+ .gv-modal-title {
964
+ font-family: var(--gv-font-body);
965
+ font-size: 14px;
966
+ font-weight: 800;
967
+ color: var(--gv-brown-dark);
968
+ letter-spacing: 0.3px;
969
+ line-height: var(--gv-lh-vi-tight);
970
+ }
971
+ // Optional: dùng .gv-modal-title-pixel nếu title là ASCII
972
+ .gv-modal-title-pixel {
973
+ font-family: var(--gv-font-pixel);
974
+ font-size: 10px;
975
+ color: var(--gv-brown-dark);
976
+ line-height: 2;
977
+ }
978
+ .gv-modal-close {
979
+ background: none;
980
+ border: none;
981
+ cursor: pointer;
982
+ color: var(--gv-brown-mid);
983
+ font-size: 18px;
984
+ padding: 4px 8px;
985
+ border-radius: var(--gv-radius-sm);
986
+ flex-shrink: 0;
987
+ &:hover { background: var(--gv-cream-3); }
988
+ }
989
+ .gv-modal-body { padding: 24px; }
990
+ .gv-modal-footer {
991
+ padding: 16px 24px;
992
+ border-top: var(--gv-border);
993
+ background: var(--gv-cream-2);
994
+ border-radius: 0 0 var(--gv-radius-xl) var(--gv-radius-xl);
995
+ display: flex;
996
+ justify-content: flex-end;
997
+ gap: 10px;
998
+ }
999
+
1000
+ // ============================================================
1001
+ // 12. TOOLTIP
1002
+ // ============================================================
1003
+ [data-gv-tooltip] {
1004
+ position: relative;
1005
+ cursor: default;
1006
+
1007
+ &::before {
1008
+ content: attr(data-gv-tooltip);
1009
+ position: absolute;
1010
+ bottom: calc(100% + 8px);
1011
+ left: 50%;
1012
+ transform: translateX(-50%);
1013
+ background: var(--gv-brown-dark);
1014
+ color: var(--gv-cream);
1015
+ font-family: var(--gv-font-body); // [FIX] body font để tiếng Việt hiển thị
1016
+ font-size: 11px;
1017
+ font-weight: 700;
1018
+ padding: 5px 10px;
1019
+ border-radius: var(--gv-radius-sm);
1020
+ white-space: nowrap;
1021
+ border: 2px solid var(--gv-brown-mid);
1022
+ box-shadow: var(--gv-shadow-sm) var(--gv-brown-darkest);
1023
+ opacity: 0;
1024
+ pointer-events: none;
1025
+ transition: opacity 0.15s;
1026
+ z-index: 9999;
1027
+ }
1028
+ &::after {
1029
+ content: '';
1030
+ position: absolute;
1031
+ bottom: calc(100% + 2px);
1032
+ left: 50%;
1033
+ transform: translateX(-50%);
1034
+ border: 6px solid transparent;
1035
+ border-top-color: var(--gv-brown-dark);
1036
+ opacity: 0;
1037
+ pointer-events: none;
1038
+ transition: opacity 0.15s;
1039
+ z-index: 9999;
1040
+ }
1041
+ &:hover::before, &:hover::after { opacity: 1; }
1042
+ }
1043
+
1044
+ // ============================================================
1045
+ // 13. TABLE
1046
+ // ============================================================
1047
+ .gv-table-wrap {
1048
+ border: var(--gv-border);
1049
+ border-radius: var(--gv-radius-lg);
1050
+ overflow: hidden;
1051
+ box-shadow: var(--gv-shadow-md) var(--gv-brown-mid);
1052
+ // [NEW] responsive scroll
1053
+ overflow-x: auto;
1054
+ }
1055
+ .gv-table {
1056
+ width: 100%;
1057
+ border-collapse: collapse;
1058
+ font-size: 13px;
1059
+ // [NEW] min-width để không vỡ layout trên mobile
1060
+ min-width: 480px;
1061
+
1062
+ thead {
1063
+ background: var(--gv-brown-dark);
1064
+ color: var(--gv-cream);
1065
+ th {
1066
+ padding: 12px 16px;
1067
+ text-align: left;
1068
+ // [FIX] dùng body font cho th tiếng Việt, pixel font cho ASCII
1069
+ font-family: var(--gv-font-body);
1070
+ font-size: 11px;
1071
+ font-weight: 800;
1072
+ letter-spacing: 0.5px;
1073
+ text-transform: uppercase;
1074
+ border-bottom: var(--gv-border);
1075
+ white-space: nowrap;
1076
+ }
1077
+ }
1078
+ tbody {
1079
+ tr {
1080
+ background: var(--gv-cream);
1081
+ border-bottom: 2px solid var(--gv-cream-2);
1082
+ transition: background 0.1s;
1083
+ &:nth-child(even) { background: var(--gv-cream-2); }
1084
+ &:hover { background: #f0e0c0; }
1085
+ &:last-child { border-bottom: none; }
1086
+ }
1087
+ td { padding: 11px 16px; color: var(--gv-brown-dark); font-weight: 600; line-height: var(--gv-lh-vi-tight); }
1088
+ }
1089
+ }
1090
+
1091
+ // ============================================================
1092
+ // 14. TABS
1093
+ // ============================================================
1094
+ .gv-tabs {
1095
+ display: flex;
1096
+ flex-direction: column;
1097
+ }
1098
+ .gv-tab-list {
1099
+ display: flex;
1100
+ border-bottom: var(--gv-border);
1101
+ gap: 2px;
1102
+ overflow-x: auto;
1103
+ // [NEW] ẩn scrollbar nhưng vẫn scroll được
1104
+ scrollbar-width: none;
1105
+ &::-webkit-scrollbar { display: none; }
1106
+ }
1107
+ // [FIX] Tab item: dùng body font để tiếng Việt hiển thị đúng
1108
+ .gv-tab-item {
1109
+ padding: 10px 18px;
1110
+ font-family: var(--gv-font-body); // [FIX] đổi từ pixel font
1111
+ font-size: 12px;
1112
+ font-weight: 800;
1113
+ letter-spacing: 0.3px;
1114
+ color: var(--gv-brown-light);
1115
+ cursor: pointer;
1116
+ border: var(--gv-border);
1117
+ border-bottom: none;
1118
+ border-radius: var(--gv-radius-md) var(--gv-radius-md) 0 0;
1119
+ background: var(--gv-cream-2);
1120
+ transition: var(--gv-transition);
1121
+ white-space: nowrap;
1122
+ margin-bottom: -2px;
1123
+ line-height: var(--gv-lh-vi-tight);
1124
+
1125
+ &:hover { background: var(--gv-cream); color: var(--gv-brown-mid); }
1126
+ &.gv-tab-active {
1127
+ background: var(--gv-cream);
1128
+ color: var(--gv-brown-dark);
1129
+ border-bottom: 2px solid var(--gv-cream);
1130
+ box-shadow: var(--gv-shadow-sm) var(--gv-brown-light);
1131
+ }
1132
+ }
1133
+ // Optional: giữ pixel tab cho ASCII-only tabs
1134
+ .gv-tab-item-pixel {
1135
+ font-family: var(--gv-font-pixel);
1136
+ font-size: 8px;
1137
+ line-height: 2;
1138
+ }
1139
+ .gv-tab-panel {
1140
+ padding: 20px;
1141
+ background: var(--gv-cream);
1142
+ border: var(--gv-border);
1143
+ border-top: none;
1144
+ border-radius: 0 var(--gv-radius-md) var(--gv-radius-md) var(--gv-radius-md);
1145
+ display: none;
1146
+ &.gv-tab-active { display: block; animation: gv-fade-in 0.15s ease; }
1147
+ }
1148
+
1149
+ // ============================================================
1150
+ // 15. DIVIDERS
1151
+ // ============================================================
1152
+ .gv-divider {
1153
+ border: none;
1154
+ height: 3px;
1155
+ background: repeating-linear-gradient(
1156
+ 90deg,
1157
+ var(--gv-brown-light) 0, var(--gv-brown-light) 8px,
1158
+ transparent 8px, transparent 16px
1159
+ );
1160
+ margin: var(--gv-space-5) 0;
1161
+ }
1162
+ .gv-divider-solid {
1163
+ border: none;
1164
+ height: 2px;
1165
+ background: var(--gv-brown-light);
1166
+ margin: var(--gv-space-5) 0;
1167
+ }
1168
+ .gv-divider-with-text {
1169
+ display: flex;
1170
+ align-items: center;
1171
+ gap: 12px;
1172
+ margin: var(--gv-space-5) 0;
1173
+
1174
+ &::before, &::after {
1175
+ content: '';
1176
+ flex: 1;
1177
+ height: 2px;
1178
+ background: var(--gv-brown-light);
1179
+ }
1180
+ span {
1181
+ // [FIX] dùng body font để hiển thị tiếng Việt đúng
1182
+ font-family: var(--gv-font-body);
1183
+ font-size: 11px;
1184
+ font-weight: 700;
1185
+ color: var(--gv-brown-light);
1186
+ white-space: nowrap;
1187
+ text-transform: uppercase;
1188
+ letter-spacing: 0.5px;
1189
+ }
1190
+ }
1191
+ .gv-pixel-divider {
1192
+ display: flex;
1193
+ align-items: center;
1194
+ margin: var(--gv-space-5) 0;
1195
+
1196
+ span {
1197
+ display: inline-block;
1198
+ height: 4px;
1199
+ flex: 1;
1200
+ background: repeating-linear-gradient(
1201
+ 90deg, var(--gv-brown-mid) 0, var(--gv-brown-mid) 8px, transparent 8px, transparent 16px
1202
+ );
1203
+ }
1204
+ .gv-diamond {
1205
+ width: 12px; height: 12px;
1206
+ background: var(--gv-gold);
1207
+ transform: rotate(45deg);
1208
+ flex-shrink: 0;
1209
+ margin: 0 8px;
1210
+ }
1211
+ }
1212
+
1213
+ // ============================================================
1214
+ // 16. SPINNER / LOADING
1215
+ // ============================================================
1216
+ .gv-spinner {
1217
+ display: inline-block;
1218
+ width: 32px; height: 32px;
1219
+ border: 3px solid var(--gv-brown-light);
1220
+ border-top-color: var(--gv-gold);
1221
+ border-radius: 50%;
1222
+ animation: gv-spin 0.7s linear infinite;
1223
+ }
1224
+ .gv-spinner-sm { width: 18px; height: 18px; border-width: 2px; }
1225
+ .gv-spinner-lg { width: 48px; height: 48px; border-width: 4px; }
1226
+
1227
+ .gv-loader-pixel {
1228
+ display: inline-flex;
1229
+ gap: 5px;
1230
+ span {
1231
+ display: block;
1232
+ width: 10px; height: 10px;
1233
+ background: var(--gv-gold);
1234
+ border: 2px solid var(--gv-gold-dark);
1235
+ border-radius: var(--gv-radius-sm);
1236
+ animation: gv-bounce 0.6s ease infinite alternate;
1237
+ &:nth-child(2) { animation-delay: 0.15s; background: var(--gv-brown-mid); border-color: var(--gv-brown-dark); }
1238
+ &:nth-child(3) { animation-delay: 0.3s; background: var(--gv-red); border-color: var(--gv-red-dark); }
1239
+ }
1240
+ }
1241
+
1242
+ // ============================================================
1243
+ // 17. TOAST / NOTIFICATION SNACKBAR
1244
+ // ============================================================
1245
+ .gv-toast-container {
1246
+ position: fixed;
1247
+ bottom: 24px;
1248
+ right: 24px;
1249
+ z-index: 9999;
1250
+ display: flex;
1251
+ flex-direction: column;
1252
+ gap: 10px;
1253
+ pointer-events: none;
1254
+ }
1255
+ .gv-toast {
1256
+ pointer-events: auto;
1257
+ display: flex;
1258
+ align-items: center;
1259
+ gap: 10px;
1260
+ padding: 12px 18px;
1261
+ background: var(--gv-brown-dark);
1262
+ color: var(--gv-cream);
1263
+ border: var(--gv-border);
1264
+ border-radius: var(--gv-radius-lg);
1265
+ box-shadow: var(--gv-shadow-md) var(--gv-brown-darkest);
1266
+ font-size: 13px;
1267
+ font-weight: 700;
1268
+ // [FIX] line-height cao hơn cho text tiếng Việt trong toast
1269
+ line-height: var(--gv-lh-vi-tight);
1270
+ min-width: 220px;
1271
+ max-width: 380px;
1272
+ animation: gv-slide-up 0.2s ease;
1273
+
1274
+ .gv-toast-icon { flex-shrink: 0; font-size: 16px; }
1275
+ .gv-toast-close {
1276
+ margin-left: auto;
1277
+ background: none;
1278
+ border: none;
1279
+ color: var(--gv-brown-light);
1280
+ cursor: pointer;
1281
+ font-size: 16px;
1282
+ padding: 0 4px;
1283
+ flex-shrink: 0;
1284
+ &:hover { color: var(--gv-cream); }
1285
+ }
1286
+ }
1287
+ .gv-toast-success { border-color: var(--gv-green); .gv-toast-icon { color: var(--gv-green-light); } }
1288
+ .gv-toast-danger { border-color: var(--gv-red); .gv-toast-icon { color: var(--gv-red-light); } }
1289
+ .gv-toast-warning { border-color: var(--gv-gold); .gv-toast-icon { color: var(--gv-gold); } }
1290
+ .gv-toast-info { border-color: var(--gv-blue); .gv-toast-icon { color: var(--gv-blue-light); } }
1291
+
1292
+ // ============================================================
1293
+ // 18. LIST GROUP
1294
+ // ============================================================
1295
+ .gv-list {
1296
+ display: flex;
1297
+ flex-direction: column;
1298
+ border: var(--gv-border);
1299
+ border-radius: var(--gv-radius-lg);
1300
+ overflow: hidden;
1301
+ box-shadow: var(--gv-shadow-sm) var(--gv-brown-light);
1302
+ }
1303
+ .gv-list-item {
1304
+ display: flex;
1305
+ align-items: center;
1306
+ gap: 12px;
1307
+ padding: 12px 16px;
1308
+ background: var(--gv-cream);
1309
+ border-bottom: 2px solid var(--gv-cream-2);
1310
+ font-size: 14px;
1311
+ font-weight: 600;
1312
+ color: var(--gv-brown-dark);
1313
+ transition: background 0.1s;
1314
+ // [FIX] line-height cho tiếng Việt
1315
+ line-height: var(--gv-lh-vi-tight);
1316
+
1317
+ &:last-child { border-bottom: none; }
1318
+ &.gv-list-item-action {
1319
+ cursor: pointer;
1320
+ text-decoration: none;
1321
+ &:hover { background: var(--gv-cream-2); }
1322
+ }
1323
+ &.gv-list-item-active {
1324
+ background: var(--gv-gold);
1325
+ color: var(--gv-brown-dark);
1326
+ border-bottom-color: var(--gv-gold-dark);
1327
+ }
1328
+ }
1329
+
1330
+ // ============================================================
1331
+ // 19. BREADCRUMB
1332
+ // ============================================================
1333
+ .gv-breadcrumb {
1334
+ display: flex;
1335
+ align-items: center;
1336
+ flex-wrap: wrap;
1337
+ gap: 4px;
1338
+ font-size: 12px;
1339
+ font-weight: 700;
1340
+ line-height: 1.5;
1341
+ }
1342
+ .gv-breadcrumb-item {
1343
+ display: flex;
1344
+ align-items: center;
1345
+ gap: 4px;
1346
+ color: var(--gv-brown-light);
1347
+ text-decoration: none;
1348
+ transition: color 0.15s;
1349
+ &:hover { color: var(--gv-gold-dark); }
1350
+ &.gv-breadcrumb-active { color: var(--gv-brown-dark); pointer-events: none; }
1351
+ }
1352
+ .gv-breadcrumb-sep {
1353
+ color: var(--gv-brown-light);
1354
+ // [FIX] dùng body font, separator không cần pixel font
1355
+ font-family: var(--gv-font-body);
1356
+ font-size: 12px;
1357
+ font-weight: 400;
1358
+ margin: 0 2px;
1359
+ opacity: 0.6;
1360
+ }
1361
+
1362
+ // ============================================================
1363
+ // 20. PAGINATION
1364
+ // ============================================================
1365
+ .gv-pagination {
1366
+ display: flex;
1367
+ align-items: center;
1368
+ gap: 6px;
1369
+ flex-wrap: wrap;
1370
+ }
1371
+ .gv-page-item {
1372
+ display: inline-flex;
1373
+ align-items: center;
1374
+ justify-content: center;
1375
+ width: 38px; height: 38px;
1376
+ border: var(--gv-border);
1377
+ border-radius: var(--gv-radius-md);
1378
+ background: var(--gv-cream);
1379
+ color: var(--gv-brown-mid);
1380
+ font-weight: 800;
1381
+ font-size: 13px;
1382
+ cursor: pointer;
1383
+ text-decoration: none;
1384
+ transition: var(--gv-transition);
1385
+ box-shadow: var(--gv-shadow-sm) var(--gv-brown-light);
1386
+
1387
+ &:hover { background: var(--gv-cream-2); transform: translate(-1px, -1px); box-shadow: var(--gv-shadow-md) var(--gv-brown-light); }
1388
+ &.gv-page-active { background: var(--gv-gold); border-color: var(--gv-gold-dark); color: var(--gv-brown-dark); box-shadow: var(--gv-shadow-sm) var(--gv-gold-dark); }
1389
+ &.gv-page-disabled { opacity: 0.4; pointer-events: none; }
1390
+ }
1391
+
1392
+ // ============================================================
1393
+ // 21. LAYOUT UTILITIES
1394
+ // ============================================================
1395
+
1396
+ // Container
1397
+ .gv-container { width: 100%; max-width: 1200px; margin: 0 auto; padding: 0 16px; }
1398
+ .gv-container-sm { max-width: 720px; }
1399
+ .gv-container-xs { max-width: 520px; }
1400
+
1401
+ // Flex helpers
1402
+ .gv-flex { display: flex; }
1403
+ .gv-flex-center { display: flex; align-items: center; justify-content: center; }
1404
+ .gv-flex-between{ display: flex; align-items: center; justify-content: space-between; }
1405
+ .gv-flex-col { display: flex; flex-direction: column; }
1406
+ .gv-flex-wrap { flex-wrap: wrap; }
1407
+ .gv-gap-1 { gap: var(--gv-space-1); }
1408
+ .gv-gap-2 { gap: var(--gv-space-2); }
1409
+ .gv-gap-3 { gap: var(--gv-space-3); }
1410
+ .gv-gap-4 { gap: var(--gv-space-4); }
1411
+ .gv-gap-5 { gap: var(--gv-space-5); }
1412
+
1413
+ // Grid
1414
+ .gv-grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--gv-space-4); }
1415
+ .gv-grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--gv-space-4); }
1416
+ .gv-grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--gv-space-4); }
1417
+ @media (max-width: 768px) {
1418
+ .gv-grid-2, .gv-grid-3, .gv-grid-4 { grid-template-columns: 1fr; }
1419
+ }
1420
+ @media (min-width: 769px) and (max-width: 1024px) {
1421
+ .gv-grid-3, .gv-grid-4 { grid-template-columns: repeat(2, 1fr); }
1422
+ }
1423
+
1424
+ // Spacing
1425
+ .gv-mt-1 { margin-top: var(--gv-space-1); }
1426
+ .gv-mt-2 { margin-top: var(--gv-space-2); }
1427
+ .gv-mt-3 { margin-top: var(--gv-space-3); }
1428
+ .gv-mt-4 { margin-top: var(--gv-space-4); }
1429
+ .gv-mt-5 { margin-top: var(--gv-space-5); }
1430
+ .gv-mb-1 { margin-bottom: var(--gv-space-1); }
1431
+ .gv-mb-2 { margin-bottom: var(--gv-space-2); }
1432
+ .gv-mb-3 { margin-bottom: var(--gv-space-3); }
1433
+ .gv-mb-4 { margin-bottom: var(--gv-space-4); }
1434
+ .gv-mb-5 { margin-bottom: var(--gv-space-5); }
1435
+ .gv-p-1 { padding: var(--gv-space-1); }
1436
+ .gv-p-2 { padding: var(--gv-space-2); }
1437
+ .gv-p-3 { padding: var(--gv-space-3); }
1438
+ .gv-p-4 { padding: var(--gv-space-4); }
1439
+ .gv-p-5 { padding: var(--gv-space-5); }
1440
+
1441
+ // Text align
1442
+ .gv-text-center { text-align: center; }
1443
+ .gv-text-right { text-align: right; }
1444
+ .gv-text-left { text-align: left; }
1445
+
1446
+ // ============================================================
1447
+ // 22. KEYFRAMES
1448
+ // ============================================================
1449
+ @keyframes gv-spin {
1450
+ to { transform: rotate(360deg); }
1451
+ }
1452
+ @keyframes gv-bounce {
1453
+ from { transform: translateY(0); }
1454
+ to { transform: translateY(-8px); }
1455
+ }
1456
+ @keyframes gv-fade-in {
1457
+ from { opacity: 0; }
1458
+ to { opacity: 1; }
1459
+ }
1460
+ @keyframes gv-slide-up {
1461
+ from { opacity: 0; transform: translateY(16px); }
1462
+ to { opacity: 1; transform: translateY(0); }
1463
+ }
1464
+ // [FIX] thêm background-size vào animation để stripe hoạt động
1465
+ @keyframes gv-progress-move {
1466
+ from { background-position: 0 0; }
1467
+ to { background-position: 24px 0; }
1468
+ }
1469
+ @keyframes gv-pixel-blink {
1470
+ 0%, 100% { opacity: 1; }
1471
+ 50% { opacity: 0; }
1472
+ }