create-m2m-bo-template-vue 1.0.6 → 1.0.7

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.
Files changed (60) hide show
  1. package/package.json +1 -1
  2. package/template/src/components/DataGrid/DataGrid.vue +9 -1
  3. package/template/src/components/shell/GlobalSearchBar.module.css +6 -0
  4. package/template/src/components/shell/GlobalSearchBar.vue +49 -6
  5. package/template/src/components/shell/QuickSearchPanel.module.css +547 -0
  6. package/template/src/components/shell/QuickSearchPanel.vue +446 -0
  7. package/template/src/lib/globalSearch.ts +7 -0
  8. package/template/src/lib/recentSearches.ts +29 -0
  9. package/template/src/lib/relatedKeywords.ts +134 -0
  10. package/template/src/lib/searchHistory.ts +169 -0
  11. package/template/src/lib/searchIndex.ts +14 -0
  12. package/template/src/pages/admin/AdminRolesPage.vue +7 -7
  13. package/template/src/pages/b2c/products/B2cProductCreatePage.module.css +383 -14
  14. package/template/src/pages/b2c/products/B2cProductCreatePage.vue +315 -113
  15. package/template/src/pages/b2c/products/B2cProductDetailDrawer.module.css +227 -128
  16. package/template/src/pages/b2c/products/B2cProductDetailDrawer.vue +332 -193
  17. package/template/src/pages/b2c/products/B2cProductListPage.vue +5 -7
  18. package/template/src/pages/b2c/products/B2cProductManagementPage.vue +34 -5
  19. package/template/src/pages/b2c/products/B2cStandardInfoPage.vue +0 -8
  20. package/template/src/pages/b2c/products/b2cProductCreateModel.ts +38 -8
  21. package/template/src/pages/c2c/chat/ChatListPage.vue +1 -0
  22. package/template/src/pages/c2c/chat/MessagePolicyPage.vue +1 -0
  23. package/template/src/pages/c2c/products/UserProductModerationPage.vue +0 -1
  24. package/template/src/pages/c2c/purchases/DisputeHistoryPage.vue +1 -0
  25. package/template/src/pages/c2c/purchases/PurchaseActivityHistoryPage.vue +200 -0
  26. package/template/src/pages/c2c/purchases/PurchaseDisputesPage.vue +1 -0
  27. package/template/src/pages/c2c/sales/SalesTradesPage.vue +1 -1
  28. package/template/src/pages/c2c/sales/SellerListPage.vue +1 -1
  29. package/template/src/pages/c2c/sales/SellerProductsPage.vue +1 -1
  30. package/template/src/pages/c2c/sanctions/SanctionHistoryPage.vue +1 -0
  31. package/template/src/pages/c2c/sanctions/SanctionPolicyPage.vue +1 -0
  32. package/template/src/pages/c2c/sanctions/SanctionProcessingPage.vue +1 -0
  33. package/template/src/pages/cartconversion/CartConversionPage.vue +5 -43
  34. package/template/src/pages/coupons/CouponPolicyPage.vue +30 -21
  35. package/template/src/pages/deliverypolicy/RegionalShippingFeePage.vue +4 -4
  36. package/template/src/pages/deliverypolicy/ShippingBaseFeePage.module.css +1 -1
  37. package/template/src/pages/deliverypolicy/ShippingBaseFeePage.vue +4 -6
  38. package/template/src/pages/logs/SystemLogPage.vue +2 -2
  39. package/template/src/pages/orders/ordersShared.module.css +12 -0
  40. package/template/src/pages/points/PointPolicyPage.vue +103 -32
  41. package/template/src/pages/products/MinOrderQtyPage.vue +1 -2
  42. package/template/src/pages/products/PartnerPricingPage.vue +1 -2
  43. package/template/src/pages/products/ProductDetailDrawer.vue +17 -11
  44. package/template/src/pages/products/ProductsListPage.vue +223 -89
  45. package/template/src/pages/products/SupplyPricePage.vue +1 -2
  46. package/template/src/pages/products/productsData.ts +21 -0
  47. package/template/src/pages/promotions/ExposureTooltipCell.vue +108 -0
  48. package/template/src/pages/promotions/PromotionDetailDrawer.module.css +553 -0
  49. package/template/src/pages/promotions/PromotionDetailDrawer.vue +438 -100
  50. package/template/src/pages/promotions/PromotionsListPage.module.css +209 -0
  51. package/template/src/pages/promotions/PromotionsListPage.vue +107 -17
  52. package/template/src/pages/promotions/applicationsData.ts +46 -15
  53. package/template/src/pages/promotions/promotionsData.ts +349 -28
  54. package/template/src/pages/stats/StatisticsFilterToolbar.vue +3 -2
  55. package/template/src/pages/system/CommonCodesPage.vue +3 -2
  56. package/template/src/pages/system/DisplaySettingsPage.vue +3 -2
  57. package/template/src/pages/system/IntegrationManagementPage.vue +3 -2
  58. package/template/src/pages/system/JobManagementPage.vue +3 -2
  59. package/template/src/pages/system/ServiceSettingsPage.vue +3 -2
  60. package/template/src/router/routes.c2cPurchases.ts +2 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-m2m-bo-template-vue",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "Scaffolding CLI for the M2M back-office vue template.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -172,6 +172,7 @@ function cellFgColor(cell: Cell): string | undefined {
172
172
 
173
173
  const rootRef = ref<HTMLDivElement | null>(null);
174
174
  const internalSelected = ref<Set<string | number>>(new Set());
175
+ const autoSelectable = ref(false);
175
176
  const pageSize = ref<number | null>(null);
176
177
  const currentPage = ref(1);
177
178
  const sortColumnIndex = ref<number | null>(props.defaultSortColumn ?? null);
@@ -185,12 +186,19 @@ function handlePageSize(event: Event) {
185
186
  currentPage.value = 1;
186
187
  }
187
188
 
189
+ function updateAutoSelectable() {
190
+ if (props.selectable !== undefined) return;
191
+ autoSelectable.value = Boolean(rootRef.value?.ownerDocument.querySelector('[data-excel-download]'));
192
+ }
193
+
188
194
  onMounted(() => {
189
195
  rootRef.value?.addEventListener(DATA_GRID_PAGE_SIZE_EVENT, handlePageSize);
196
+ updateAutoSelectable();
190
197
  });
191
198
  onUnmounted(() => {
192
199
  rootRef.value?.removeEventListener(DATA_GRID_PAGE_SIZE_EVENT, handlePageSize);
193
200
  });
201
+ watch(() => props.selectable, updateAutoSelectable);
194
202
 
195
203
  const totalPageCount = computed(() => (pageSize.value ? Math.max(1, Math.ceil(props.rows.length / pageSize.value)) : 1));
196
204
  watch(totalPageCount, (count) => {
@@ -206,7 +214,7 @@ watch(
206
214
  }
207
215
  );
208
216
 
209
- const effectiveSelectable = computed(() => props.selectable ?? false);
217
+ const effectiveSelectable = computed(() => props.selectable ?? autoSelectable.value);
210
218
  const pageStart = computed(() => (pageSize.value ? (currentPage.value - 1) * pageSize.value : 0));
211
219
  const visibleSourceRows = computed(() => (pageSize.value ? props.rows.slice(pageStart.value, pageStart.value + pageSize.value) : props.rows));
212
220
 
@@ -1,3 +1,9 @@
1
+ .wrapper {
2
+ width: 100%;
3
+ position: relative;
4
+ /* 퀵서치 패널이 wrapper를 기준으로 absolute 포지셔닝되도록 */
5
+ }
6
+
1
7
  .bar {
2
8
  width: 100%;
3
9
  }
@@ -2,6 +2,8 @@
2
2
  import { onMounted, onUnmounted, ref } from 'vue';
3
3
  import { useRouter } from 'vue-router';
4
4
  import SearchField from '../SearchField/SearchField.vue';
5
+ import QuickSearchPanel from './QuickSearchPanel.vue';
6
+ import { addRecentSearch } from '../../lib/recentSearches';
5
7
  import styles from './GlobalSearchBar.module.css';
6
8
 
7
9
  withDefaults(defineProps<{ placeholder?: string; className?: string }>(), {
@@ -12,12 +14,18 @@ withDefaults(defineProps<{ placeholder?: string; className?: string }>(), {
12
14
  const router = useRouter();
13
15
  const wrapRef = ref<HTMLElement | null>(null);
14
16
  const keyword = ref('');
17
+ const panelOpen = ref(false);
18
+
19
+ function getInput() {
20
+ return wrapRef.value?.querySelector('input') ?? null;
21
+ }
15
22
 
16
23
  function focusInput() {
17
- wrapRef.value?.querySelector('input')?.focus();
18
- wrapRef.value?.querySelector('input')?.select();
24
+ getInput()?.focus();
25
+ getInput()?.select();
19
26
  }
20
27
 
28
+ // Ctrl/⌘ + K 로 검색창 포커스
21
29
  function handler(e: KeyboardEvent) {
22
30
  if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'k') {
23
31
  e.preventDefault();
@@ -31,22 +39,46 @@ onUnmounted(() => window.removeEventListener('keydown', handler));
31
39
  function runSearch(value: string) {
32
40
  const query = value.trim();
33
41
  if (!query) {
34
- wrapRef.value?.querySelector('input')?.focus();
42
+ focusInput();
35
43
  return;
36
44
  }
45
+ addRecentSearch(query);
46
+ panelOpen.value = false;
37
47
  router.push(`/search?q=${encodeURIComponent(query)}`);
38
48
  }
39
49
 
50
+ function handleSelectKeyword(kw: string) {
51
+ keyword.value = kw;
52
+ runSearch(kw);
53
+ }
54
+
55
+ function handleValueChange(value: string) {
56
+ keyword.value = value;
57
+ panelOpen.value = true;
58
+ }
59
+
60
+ function handleFocus() {
61
+ panelOpen.value = true;
62
+ }
63
+
64
+ function handleClose() {
65
+ panelOpen.value = false;
66
+ focusInput();
67
+ }
68
+
40
69
  function handleKeyDown(e: KeyboardEvent) {
41
70
  if (e.key === 'Enter') {
42
71
  e.preventDefault();
43
72
  runSearch(keyword.value);
44
73
  }
74
+ if ((e.key === 'ArrowDown' || e.key === 'ArrowUp') && panelOpen.value) {
75
+ e.preventDefault();
76
+ }
45
77
  }
46
78
  </script>
47
79
 
48
80
  <template>
49
- <span ref="wrapRef">
81
+ <div ref="wrapRef" :class="`${styles.wrapper} ${className}`" style="position: relative">
50
82
  <SearchField
51
83
  size="sm"
52
84
  clearable
@@ -55,10 +87,21 @@ function handleKeyDown(e: KeyboardEvent) {
55
87
  :placeholder="placeholder"
56
88
  shortcut-hint="Ctrl K"
57
89
  :value="keyword"
58
- :on-value-change="(v: string) => (keyword = v)"
90
+ :on-value-change="handleValueChange"
59
91
  :on-search="runSearch"
60
92
  @keydown="handleKeyDown"
93
+ @focusin="handleFocus"
61
94
  aria-label="통합 검색"
95
+ :aria-expanded="panelOpen"
96
+ aria-haspopup="listbox"
97
+ />
98
+
99
+ <QuickSearchPanel
100
+ v-if="panelOpen"
101
+ :query="keyword.trim()"
102
+ :anchor-el="wrapRef"
103
+ @close="handleClose"
104
+ @select-keyword="handleSelectKeyword"
62
105
  />
63
- </span>
106
+ </div>
64
107
  </template>
@@ -0,0 +1,547 @@
1
+ /* ─── 패널 래퍼 ─── */
2
+ .overlay {
3
+ position: fixed;
4
+ inset: 0;
5
+ z-index: 1200;
6
+ }
7
+
8
+ .panel {
9
+ position: absolute;
10
+ z-index: 1200;
11
+ top: calc(100% + 6px);
12
+ left: 50%;
13
+ transform: translateX(-50%);
14
+ width: 580px;
15
+ max-width: calc(100vw - 32px);
16
+ background: var(--common-bg, #fff);
17
+ border: 1px solid var(--common-border, rgba(0, 0, 0, 0.09));
18
+ border-radius: 16px;
19
+ box-shadow:
20
+ 0 4px 6px -1px rgba(0, 0, 0, 0.07),
21
+ 0 20px 48px -8px rgba(0, 0, 0, 0.15);
22
+ display: flex;
23
+ flex-direction: column;
24
+ overflow: hidden;
25
+ max-height: min(440px, calc(100vh - 80px));
26
+ animation: panelIn 0.15s cubic-bezier(0.16, 1, 0.3, 1);
27
+ }
28
+
29
+ @keyframes panelIn {
30
+ from {
31
+ opacity: 0;
32
+ transform: translateX(-50%) translateY(-6px) scale(0.98);
33
+ }
34
+ to {
35
+ opacity: 1;
36
+ transform: translateX(-50%) translateY(0) scale(1);
37
+ }
38
+ }
39
+
40
+ /* ─── 탭 바 ─── */
41
+ .tabBar {
42
+ display: flex;
43
+ align-items: center;
44
+ gap: 0;
45
+ padding: 0 14px;
46
+ border-bottom: 1px solid var(--common-border, rgba(0, 0, 0, 0.08));
47
+ background: var(--common-bg-subtle, #fafafa);
48
+ flex: none;
49
+ overflow-x: auto;
50
+ scrollbar-width: none;
51
+ }
52
+ .tabBar::-webkit-scrollbar {
53
+ display: none;
54
+ }
55
+
56
+ .tabItem {
57
+ display: inline-flex;
58
+ align-items: center;
59
+ gap: 5px;
60
+ padding: 0 10px;
61
+ height: 38px;
62
+ border: 0;
63
+ border-bottom: 2px solid transparent;
64
+ background: transparent;
65
+ color: var(--common-text-muted, #71717a);
66
+ font-size: 0.71875rem;
67
+ font-weight: 500;
68
+ white-space: nowrap;
69
+ cursor: pointer;
70
+ transition:
71
+ color 0.12s ease,
72
+ border-color 0.12s ease;
73
+ font-family: inherit;
74
+ margin-bottom: -1px;
75
+ }
76
+ .tabItem:hover {
77
+ color: var(--common-text, #18181b);
78
+ }
79
+ .tabActive {
80
+ color: var(--common-primary, #2563eb);
81
+ border-bottom-color: var(--common-primary, #2563eb);
82
+ font-weight: 700;
83
+ }
84
+
85
+ .tabBadge {
86
+ display: inline-flex;
87
+ align-items: center;
88
+ justify-content: center;
89
+ min-width: 16px;
90
+ height: 16px;
91
+ padding: 0 4px;
92
+ border-radius: 8px;
93
+ background: var(--common-bg-subtle, #f4f4f5);
94
+ color: var(--common-text-muted, #71717a);
95
+ font-size: 0.59375rem;
96
+ font-weight: 700;
97
+ font-variant-numeric: tabular-nums;
98
+ transition:
99
+ background 0.12s ease,
100
+ color 0.12s ease;
101
+ }
102
+ .tabActive .tabBadge {
103
+ background: var(--common-primary-light, #eff6ff);
104
+ color: var(--common-primary, #2563eb);
105
+ }
106
+
107
+ /* ─── 결과 스크롤 영역 ─── */
108
+ .resultScroll {
109
+ flex: 1;
110
+ min-height: 0;
111
+ overflow-y: auto;
112
+ overflow-x: hidden;
113
+ scrollbar-gutter: stable;
114
+ overscroll-behavior: contain;
115
+ scrollbar-width: thin;
116
+ scrollbar-color: #d4d4d8 transparent;
117
+ padding: 6px 0;
118
+ }
119
+ .resultScroll::-webkit-scrollbar {
120
+ width: 5px;
121
+ }
122
+ .resultScroll::-webkit-scrollbar-thumb {
123
+ background: #d4d4d8;
124
+ border-radius: 99px;
125
+ }
126
+
127
+ /* ─── 최근 검색어 섹션 ─── */
128
+ .recentSection {
129
+ padding: 6px 0 10px 0;
130
+ border-bottom: 1px solid var(--common-border, rgba(0, 0, 0, 0.06));
131
+ margin-bottom: 4px;
132
+ }
133
+
134
+ .recentHeader {
135
+ display: flex;
136
+ align-items: center;
137
+ justify-content: space-between;
138
+ padding: 4px 18px 8px 18px;
139
+ }
140
+
141
+ .recentTitle {
142
+ font-size: 0.71875rem;
143
+ font-weight: 600;
144
+ color: var(--common-text-muted, #71717a);
145
+ letter-spacing: -0.01em;
146
+ }
147
+
148
+ .recentClearBtn {
149
+ font-size: 0.6875rem;
150
+ color: var(--common-text-muted, #a1a1aa);
151
+ background: transparent;
152
+ border: none;
153
+ padding: 2px 4px;
154
+ cursor: pointer;
155
+ border-radius: 4px;
156
+ transition: color 0.12s ease, background 0.12s ease;
157
+ line-height: 1;
158
+ }
159
+
160
+ .recentClearBtn:hover {
161
+ color: var(--common-text, #3f3f46);
162
+ text-decoration: underline;
163
+ }
164
+
165
+ .recentChips {
166
+ display: flex;
167
+ align-items: center;
168
+ gap: 6px;
169
+ padding: 0 18px 2px 18px;
170
+ overflow-x: auto;
171
+ scrollbar-width: none;
172
+ }
173
+
174
+ .recentChips::-webkit-scrollbar {
175
+ display: none;
176
+ }
177
+
178
+ .recentChip {
179
+ display: inline-flex;
180
+ align-items: center;
181
+ gap: 6px;
182
+ height: 28px;
183
+ padding: 0 8px 0 12px;
184
+ background: var(--common-bg, #ffffff);
185
+ border: 1px solid var(--common-border, #e4e4e7);
186
+ border-radius: 9999px;
187
+ color: var(--common-text, #3f3f46);
188
+ font-size: 0.71875rem;
189
+ white-space: nowrap;
190
+ cursor: pointer;
191
+ transition: all 0.15s ease;
192
+ user-select: none;
193
+ }
194
+
195
+ .recentChip:hover {
196
+ background: var(--common-bg-subtle, #f4f4f5);
197
+ border-color: #d4d4d8;
198
+ color: var(--common-text, #18181b);
199
+ }
200
+
201
+ .recentChipText {
202
+ line-height: 1;
203
+ }
204
+
205
+ .recentChipRemove {
206
+ display: inline-flex;
207
+ align-items: center;
208
+ justify-content: center;
209
+ width: 16px;
210
+ height: 16px;
211
+ border: none;
212
+ background: transparent;
213
+ color: #a1a1aa;
214
+ cursor: pointer;
215
+ padding: 0;
216
+ border-radius: 50%;
217
+ transition: color 0.12s ease, background 0.12s ease;
218
+ }
219
+
220
+ .recentChipRemove:hover {
221
+ color: #ef4444;
222
+ background: rgba(239, 68, 68, 0.1);
223
+ }
224
+
225
+ /* ─── 검색 히스토리 상세 뷰 ─── */
226
+ .historyView {
227
+ padding: 6px 0;
228
+ }
229
+
230
+ .historyHeader {
231
+ display: flex;
232
+ align-items: center;
233
+ justify-content: space-between;
234
+ padding: 6px 18px 8px 18px;
235
+ border-bottom: 1px solid var(--common-border, rgba(0, 0, 0, 0.06));
236
+ }
237
+
238
+ .historyTitleWrap {
239
+ display: flex;
240
+ align-items: center;
241
+ gap: 6px;
242
+ color: var(--common-text, #18181b);
243
+ font-size: 0.75rem;
244
+ font-weight: 700;
245
+ }
246
+
247
+ .historyList {
248
+ list-style: none;
249
+ margin: 0;
250
+ padding: 4px 0;
251
+ }
252
+
253
+ .historyItem {
254
+ display: flex;
255
+ align-items: center;
256
+ justify-content: space-between;
257
+ padding: 8px 18px;
258
+ cursor: pointer;
259
+ transition: background 0.12s ease;
260
+ }
261
+
262
+ .historyItem:hover {
263
+ background: var(--common-bg-subtle, #f4f4f5);
264
+ }
265
+
266
+ .historyMain {
267
+ display: flex;
268
+ align-items: center;
269
+ gap: 10px;
270
+ flex: 1;
271
+ min-width: 0;
272
+ }
273
+
274
+ .historyClockIcon {
275
+ color: var(--common-text-muted, #a1a1aa);
276
+ flex: none;
277
+ }
278
+
279
+ .historyQuery {
280
+ font-size: 0.75rem;
281
+ font-weight: 500;
282
+ color: var(--common-text, #27272a);
283
+ overflow: hidden;
284
+ text-overflow: ellipsis;
285
+ white-space: nowrap;
286
+ }
287
+
288
+ .historyMeta {
289
+ display: flex;
290
+ align-items: center;
291
+ gap: 8px;
292
+ flex: none;
293
+ }
294
+
295
+ .historyResultCount {
296
+ font-size: 0.6875rem;
297
+ color: var(--common-primary, #2563eb);
298
+ background: var(--common-primary-light, #eff6ff);
299
+ padding: 1px 6px;
300
+ border-radius: 4px;
301
+ font-weight: 600;
302
+ }
303
+
304
+ .historyTime {
305
+ font-size: 0.6875rem;
306
+ color: var(--common-text-muted, #a1a1aa);
307
+ }
308
+
309
+ .historyRemoveBtn {
310
+ display: inline-flex;
311
+ align-items: center;
312
+ justify-content: center;
313
+ width: 20px;
314
+ height: 20px;
315
+ border: none;
316
+ background: transparent;
317
+ color: #a1a1aa;
318
+ cursor: pointer;
319
+ border-radius: 4px;
320
+ margin-left: 6px;
321
+ transition: color 0.12s ease, background 0.12s ease;
322
+ }
323
+
324
+ .historyRemoveBtn:hover {
325
+ color: #ef4444;
326
+ background: rgba(239, 68, 68, 0.1);
327
+ }
328
+
329
+ .historyEmpty {
330
+ display: flex;
331
+ flex-direction: column;
332
+ align-items: center;
333
+ justify-content: center;
334
+ padding: 36px 18px;
335
+ gap: 8px;
336
+ color: var(--common-text-muted, #a1a1aa);
337
+ font-size: 0.75rem;
338
+ }
339
+
340
+ /* ─── 도메인 섹션 ─── */
341
+ .domainSection {
342
+ padding: 0;
343
+ }
344
+
345
+ .domainLabel {
346
+ display: block;
347
+ padding: 8px 18px 3px;
348
+ font-size: 0.625rem;
349
+ font-weight: 700;
350
+ color: var(--common-text-muted, #a1a1aa);
351
+ letter-spacing: 0.04em;
352
+ text-transform: uppercase;
353
+ }
354
+
355
+ /* ─── 결과 항목 ─── */
356
+ .resultItem {
357
+ display: flex;
358
+ align-items: flex-start;
359
+ gap: 10px;
360
+ padding: 8px 18px;
361
+ cursor: pointer;
362
+ border: 0;
363
+ background: transparent;
364
+ width: 100%;
365
+ text-align: left;
366
+ font-family: inherit;
367
+ transition: background 0.12s ease;
368
+ }
369
+ .resultItem:hover,
370
+ .resultItemFocused {
371
+ background: var(--common-bg-subtle, #f4f4f5);
372
+ }
373
+
374
+ .itemDomainBar {
375
+ flex: none;
376
+ width: 2.5px;
377
+ height: 24px;
378
+ border-radius: 2px;
379
+ margin-top: 2px;
380
+ }
381
+
382
+ .itemBody {
383
+ flex: 1;
384
+ min-width: 0;
385
+ }
386
+
387
+ .itemTitle {
388
+ display: block;
389
+ font-size: 0.78125rem;
390
+ font-weight: 600;
391
+ color: var(--common-text, #18181b);
392
+ overflow: hidden;
393
+ text-overflow: ellipsis;
394
+ white-space: nowrap;
395
+ }
396
+
397
+ .itemMeta {
398
+ display: block;
399
+ font-size: 0.625rem;
400
+ color: var(--common-text-muted, #a1a1aa);
401
+ margin-top: 2px;
402
+ overflow: hidden;
403
+ text-overflow: ellipsis;
404
+ white-space: nowrap;
405
+ }
406
+
407
+ .itemMetaSep {
408
+ margin: 0 4px;
409
+ color: var(--common-border-strong, #d4d4d8);
410
+ }
411
+
412
+ .hit {
413
+ background: #fef08a;
414
+ color: inherit;
415
+ border-radius: 2px;
416
+ padding: 0 1px;
417
+ }
418
+
419
+ /* ─── 연관 검색어 ─── */
420
+ .relatedSection {
421
+ padding: 8px 18px 10px;
422
+ border-top: 1px solid var(--common-border, rgba(0, 0, 0, 0.07));
423
+ margin-top: 4px;
424
+ }
425
+
426
+ .relatedLabel {
427
+ display: block;
428
+ font-size: 0.625rem;
429
+ font-weight: 700;
430
+ color: var(--common-text-muted, #a1a1aa);
431
+ letter-spacing: 0.04em;
432
+ margin-bottom: 8px;
433
+ }
434
+
435
+ .relatedChips {
436
+ display: flex;
437
+ flex-wrap: wrap;
438
+ gap: 6px;
439
+ }
440
+
441
+ .relatedChip {
442
+ display: inline-flex;
443
+ align-items: center;
444
+ height: 26px;
445
+ padding: 0 10px;
446
+ border: 1px solid var(--common-border, rgba(0, 0, 0, 0.1));
447
+ border-radius: 999px;
448
+ background: var(--common-bg, #fff);
449
+ color: var(--common-text-sub, #52525b);
450
+ font-size: 0.6875rem;
451
+ font-weight: 500;
452
+ cursor: pointer;
453
+ font-family: inherit;
454
+ transition:
455
+ background 0.12s ease,
456
+ border-color 0.12s ease,
457
+ color 0.12s ease;
458
+ }
459
+ .relatedChip:hover {
460
+ background: var(--common-primary-light, #eff6ff);
461
+ border-color: var(--common-primary-border, #bfdbfe);
462
+ color: var(--common-primary, #2563eb);
463
+ }
464
+
465
+ /* ─── 빈 결과 ─── */
466
+ .emptyContainer {
467
+ display: flex;
468
+ align-items: center;
469
+ justify-content: center;
470
+ padding: 28px 16px;
471
+ min-height: 180px;
472
+ }
473
+
474
+ /* ─── 하단 액션 바 ─── */
475
+ .footer {
476
+ display: flex;
477
+ align-items: center;
478
+ justify-content: space-between;
479
+ gap: 12px;
480
+ padding: 8px 14px;
481
+ border-top: 1px solid var(--common-border, rgba(0, 0, 0, 0.08));
482
+ background: var(--common-bg-subtle, #fafafa);
483
+ flex: none;
484
+ }
485
+
486
+ .keyHints {
487
+ display: flex;
488
+ align-items: center;
489
+ gap: 10px;
490
+ }
491
+
492
+ .keyHint {
493
+ display: inline-flex;
494
+ align-items: center;
495
+ gap: 4px;
496
+ color: var(--common-text-muted, #a1a1aa);
497
+ font-size: 0.59375rem;
498
+ }
499
+
500
+ .keyBadge {
501
+ display: inline-flex;
502
+ align-items: center;
503
+ justify-content: center;
504
+ min-width: 20px;
505
+ height: 18px;
506
+ padding: 0 4px;
507
+ background: var(--common-bg, #fff);
508
+ border: 1px solid var(--common-border-strong, #d4d4d8);
509
+ border-radius: 4px;
510
+ color: var(--common-text-sub, #52525b);
511
+ font-size: 0.59375rem;
512
+ font-weight: 600;
513
+ font-family: inherit;
514
+ box-shadow: 0 1px 0 var(--common-border-strong, #d4d4d8);
515
+ }
516
+
517
+ .viewAllBtn {
518
+ display: inline-flex;
519
+ align-items: center;
520
+ gap: 5px;
521
+ height: 32px;
522
+ padding: 0 14px;
523
+ background: var(--common-text, #18181b);
524
+ color: #fff;
525
+ border: 0;
526
+ border-radius: 8px;
527
+ font-size: 0.71875rem;
528
+ font-weight: 700;
529
+ cursor: pointer;
530
+ white-space: nowrap;
531
+ font-family: inherit;
532
+ transition:
533
+ background 0.15s ease,
534
+ transform 0.1s ease;
535
+ }
536
+ .viewAllBtn:hover {
537
+ background: var(--common-primary, #2563eb);
538
+ transform: translateY(-1px);
539
+ }
540
+ .viewAllBtn:active {
541
+ transform: translateY(0);
542
+ }
543
+
544
+ .viewAllArrow {
545
+ font-size: 0.875rem;
546
+ font-weight: 400;
547
+ }