nuxt-layer-core 1.1.0 → 1.1.2

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.
@@ -1,193 +1,256 @@
1
- <template>
2
- <header class="fixed top-0 left-0 right-0 z-50 bg-white/90 backdrop-blur-md border-b border-slate-200/70 shadow-sm">
3
- <div class="max-w-7xl mx-auto px-6 lg:px-8 flex items-center justify-between h-16">
4
- <NuxtLink :to="`/giai-phap-proxmox/khoa-hoc-proxmox-hci`" class="flex items-center gap-2.5">
5
- <img src="/images/logo-lvs-1300x330.png" alt="Long Vân" class="h-8 w-auto" />
6
- </NuxtLink>
7
-
8
- <nav class="hidden md:flex items-center gap-8 text-sm font-medium text-slate-600">
9
- <template v-for="link in navLinks" :key="link.id">
10
- <template v-if="link.path">
11
- <NuxtLink v-if="link.path && link.path != route.path" :to="`${link.path}`" :class="[
12
- 'pb-1 border-b-2 transition-all duration-200 cursor-pointer',
13
- activeSection === link.id
14
- ? 'text-[#004AC6] font-bold border-[#004AC6]'
15
- : 'border-transparent hover:text-[#004AC6]'
16
- ]">
17
- {{ link.label }}
18
- </NuxtLink>
19
- <a v-else :href="`/giai-phap-proxmox/khoa-hoc-proxmox-hci#${link.id}`" @click.prevent="handleAnchorClick(link)" :class="[
20
- 'pb-1 border-b-2 transition-all duration-200 cursor-pointer',
21
- activeSection === link.id
22
- ? 'text-[#004AC6] font-bold border-[#004AC6]'
23
- : 'border-transparent hover:text-[#004AC6]'
24
- ]">
25
- {{ link.label }}
26
- </a>
27
- </template>
28
- <a v-else :href="`/giai-phap-proxmox/khoa-hoc-proxmox-hci#${link.id}`" @click.prevent="handleAnchorClick(link)" :class="[
29
- 'pb-1 border-b-2 transition-all duration-200 cursor-pointer',
30
- activeSection === link.id
31
- ? 'text-[#004AC6] font-bold border-[#004AC6]'
32
- : 'border-transparent hover:text-[#004AC6]'
33
- ]">
34
- {{ link.label }}
35
- </a>
36
- </template>
37
- </nav>
38
-
39
- <!-- Logged in -->
40
- <div v-if="user" class="relative">
41
- <button @click="showMenu = !showMenu"
42
- class="flex items-center gap-2.5 text-sm font-medium text-slate-700 hover:text-[#004AC6] transition-colors">
43
- <span
44
- class="w-8 h-8 rounded-[10px] bg-gradient-to-br from-[#004AC6] to-[#1D4ED8] flex items-center justify-center text-white text-xs font-bold">
45
- {{ avatarInitial }}
46
- </span>
47
- <span class="hidden sm:block">Xin chào, <strong>{{ user.fullName || user.username }}</strong></span>
48
- <Icon name="lucide:chevron-down" class="text-[14px] text-slate-400" />
49
- </button>
50
-
51
- <!-- Dropdown -->
52
- <Transition enter-active-class="transition ease-out duration-100" enter-from-class="opacity-0 scale-95"
53
- enter-to-class="opacity-100 scale-100" leave-active-class="transition ease-in duration-75"
54
- leave-from-class="opacity-100 scale-100" leave-to-class="opacity-0 scale-95">
55
- <div v-if="showMenu"
56
- class="absolute right-0 mt-2 w-48 bg-white rounded-xl shadow-lg border border-slate-200/80 py-1.5 z-50">
57
- <button @click="logout"
58
- class="w-full flex items-center gap-2.5 px-4 py-2.5 text-sm text-red-500 hover:bg-red-50 transition-colors">
59
- <Icon name="lucide:log-out" class="text-[15px]" />
60
- Đăng xuất
61
- </button>
62
- </div>
63
- </Transition>
64
- </div>
65
-
66
- <!-- Not logged in -->
67
- <div v-else class="flex items-center gap-3">
68
- <a v-if="isConsultant" :href="`/giai-phap-proxmox/khoa-hoc-proxmox-hci#lien-he`" @click.prevent="scrollTo('lien-he')"
69
- class="inline-flex items-center gap-1.5 text-sm font-bold px-5 py-2.5 rounded-[10px] border border-[#004AC6] text-[#004AC6] hover:bg-blue-50 transition-all active:scale-95 cursor-pointer">
70
- Liên hệ tư vấn
71
- <Icon name="lucide:headset" class="text-[16px]" />
72
- </a>
73
- <a :href="loginHref"
74
- class="inline-flex items-center gap-1.5 text-sm font-bold px-5 py-2.5 rounded-[10px] bg-[#004AC6] text-white hover:bg-[#1D4ED8] transition-all active:scale-95 shadow-sm shadow-blue-600/20">
75
- Đăng nhập
76
- <Icon name="lucide:log-in" class="text-[16px]" />
77
- </a>
78
- </div>
79
- </div>
80
- </header>
81
- </template>
82
-
83
- <script setup lang="ts">
84
- const props = defineProps({
85
- targetCTA: { type: String, default: '' },
86
- slug: { type: String, default: '' },
87
- })
88
-
89
- const isConsultant = computed(() => props.targetCTA === 'CONSULTANT')
90
-
91
- const { getCookie, deleteCookie } = useCookieHelper()
92
- const { getUserByToken } = useAuthApi()
93
- const { isLoggedIn, userName, setUser, clearUser } = useAuthState()
94
- const config = useRuntimeConfig()
95
- const router = useRouter()
96
- const route = useRoute()
97
-
98
- const tokenKey = config.public.ACCESSTOKEN_NAME as string
99
-
100
- const navLinks = computed(() => [
101
- { id: 'san-pham', label: 'Sản phẩm', path: `/gioi-thieu-san-pham/proxmox-hci` },
102
- { id: 'giai-phap', label: 'Vì sao chọn chúng tôi' },
103
- { id: 'chi-phi', label: 'Học phí' },
104
- { id: 'khach-hang', label: 'Học viên nói gì' },
105
- ])
106
-
107
- const { scrollTo } = useScrollToSection()
108
-
109
- // route.fullPath gồm cả query hiện tại (vd ?plan=...&name=...) để sau khi đăng nhập quay lại đúng chỗ
110
- // Trang đăng nhập nằm ở domain LONGVAN_WEB (vd https://dev.longvan.net), không phải app này
111
- const loginHref = computed(() => `${config.public.LONGVAN_WEB}/dang-nhap?redirectUrl=${encodeURIComponent(route.fullPath)}`)
112
-
113
- const activeSection = useActiveSection(navLinks.value.map(l => l.id))
114
- const user = ref<{ fullName?: string; username?: string } | null>(null)
115
- const showMenu = ref(false)
116
-
117
- function handleNavClick(e: Event, link: any) {
118
- if (route.path === link.path) {
119
- e.preventDefault()
120
- scrollTo(link.id)
121
- }
122
- }
123
-
124
- function handleAnchorClick(link: any) {
125
- const el = document.getElementById(link.id)
126
- if (el) {
127
- scrollTo(link.id)
128
- } else {
129
- router.push(`/giai-phap-proxmox/${props.slug}#${link.id}`)
130
- }
131
- }
132
-
133
- const avatarInitial = computed(() => {
134
- const name = user.value?.fullName || user.value?.username || ''
135
- return name.charAt(0).toUpperCase() || '?'
136
- })
137
-
138
- async function loadUser() {
139
- const token = getCookie(tokenKey)
140
- if (!token) return
141
-
142
- try {
143
- const res = await getUserByToken(token)
144
- if (res) {
145
- user.value = {
146
- fullName: res.fullName,
147
- username: res.username || res.userLoginId,
148
- }
149
- setUser(res.fullName || res.username || '')
150
- }
151
- } catch {
152
- deleteCookie(tokenKey)
153
- clearUser()
154
- }
155
- }
156
-
157
- // Watch auth state changes (when login/register updates state)
158
- watch(isLoggedIn, (loggedIn) => {
159
- if (loggedIn && userName.value) {
160
- user.value = {
161
- fullName: userName.value,
162
- username: userName.value,
163
- }
164
- } else if (!loggedIn) {
165
- user.value = null
166
- }
167
- })
168
-
169
- function logout() {
170
- deleteCookie(tokenKey)
171
- user.value = null
172
- clearUser()
173
- showMenu.value = false
174
- router.push('/giai-phap-proxmox')
175
- }
176
-
177
- // Close menu on outside click
178
- function onClickOutside(e: MouseEvent) {
179
- const target = e.target as HTMLElement
180
- if (!target.closest('.relative')) {
181
- showMenu.value = false
182
- }
183
- }
184
-
185
- onMounted(() => {
186
- loadUser()
187
- document.addEventListener('click', onClickOutside)
188
- })
189
-
190
- onUnmounted(() => {
191
- document.removeEventListener('click', onClickOutside)
192
- })
1
+ <template>
2
+ <header class="fixed top-0 left-0 right-0 z-50 bg-white/90 backdrop-blur-md border-b border-slate-200/70 shadow-sm">
3
+ <div class="max-w-7xl mx-auto px-6 lg:px-8 flex items-center justify-between h-16">
4
+ <NuxtLink :to="`/giai-phap-proxmox/khoa-hoc-proxmox-hci`" class="flex items-center gap-2.5">
5
+ <img src="/images/logo-lvs-1300x330.png" alt="Long Vân" class="h-8 w-auto" />
6
+ </NuxtLink>
7
+
8
+ <nav class="hidden md:flex items-center gap-8 text-sm font-medium text-slate-600">
9
+ <template v-for="link in navLinks" :key="link.id">
10
+ <template v-if="link.path">
11
+ <NuxtLink v-if="link.path && link.path != route.path" :to="`${link.path}`" :class="[
12
+ 'pb-1 border-b-2 transition-all duration-200 cursor-pointer',
13
+ activeSection === link.id
14
+ ? 'text-[#004AC6] font-bold border-[#004AC6]'
15
+ : 'border-transparent hover:text-[#004AC6]'
16
+ ]">
17
+ {{ link.label }}
18
+ </NuxtLink>
19
+ <a v-else :href="`/giai-phap-proxmox/khoa-hoc-proxmox-hci#${link.id}`" @click.prevent="handleAnchorClick(link)" :class="[
20
+ 'pb-1 border-b-2 transition-all duration-200 cursor-pointer',
21
+ activeSection === link.id
22
+ ? 'text-[#004AC6] font-bold border-[#004AC6]'
23
+ : 'border-transparent hover:text-[#004AC6]'
24
+ ]">
25
+ {{ link.label }}
26
+ </a>
27
+ </template>
28
+ <a v-else :href="`/giai-phap-proxmox/khoa-hoc-proxmox-hci#${link.id}`" @click.prevent="handleAnchorClick(link)" :class="[
29
+ 'pb-1 border-b-2 transition-all duration-200 cursor-pointer',
30
+ activeSection === link.id
31
+ ? 'text-[#004AC6] font-bold border-[#004AC6]'
32
+ : 'border-transparent hover:text-[#004AC6]'
33
+ ]">
34
+ {{ link.label }}
35
+ </a>
36
+ </template>
37
+ </nav>
38
+
39
+ <!-- Logged in -->
40
+ <div v-if="user" class="relative hidden md:block">
41
+ <button @click="showMenu = !showMenu"
42
+ class="flex items-center gap-2.5 text-sm font-medium text-slate-700 hover:text-[#004AC6] transition-colors">
43
+ <span
44
+ class="w-8 h-8 rounded-[10px] bg-gradient-to-br from-[#004AC6] to-[#1D4ED8] flex items-center justify-center text-white text-xs font-bold">
45
+ {{ avatarInitial }}
46
+ </span>
47
+ <span class="hidden sm:block">Xin chào, <strong>{{ user.fullName || user.username }}</strong></span>
48
+ <Icon name="lucide:chevron-down" class="text-[14px] text-slate-400" />
49
+ </button>
50
+
51
+ <!-- Dropdown -->
52
+ <Transition enter-active-class="transition ease-out duration-100" enter-from-class="opacity-0 scale-95"
53
+ enter-to-class="opacity-100 scale-100" leave-active-class="transition ease-in duration-75"
54
+ leave-from-class="opacity-100 scale-100" leave-to-class="opacity-0 scale-95">
55
+ <div v-if="showMenu"
56
+ class="absolute right-0 mt-2 w-48 bg-white rounded-xl shadow-lg border border-slate-200/80 py-1.5 z-50">
57
+ <button @click="logout"
58
+ class="w-full flex items-center gap-2.5 px-4 py-2.5 text-sm text-red-500 hover:bg-red-50 transition-colors">
59
+ <Icon name="lucide:log-out" class="text-[15px]" />
60
+ Đăng xuất
61
+ </button>
62
+ </div>
63
+ </Transition>
64
+ </div>
65
+
66
+ <!-- Not logged in -->
67
+ <div v-else class="hidden md:flex items-center gap-3">
68
+ <a v-if="isConsultant" :href="`/giai-phap-proxmox/khoa-hoc-proxmox-hci#lien-he`" @click.prevent="scrollTo('lien-he')"
69
+ class="inline-flex items-center gap-1.5 text-sm font-bold px-5 py-2.5 rounded-[10px] border border-[#004AC6] text-[#004AC6] hover:bg-blue-50 transition-all active:scale-95 cursor-pointer">
70
+ Liên hệ tư vấn
71
+ <Icon name="lucide:headset" class="text-[16px]" />
72
+ </a>
73
+ <a :href="loginHref"
74
+ class="inline-flex items-center gap-1.5 text-sm font-bold px-5 py-2.5 rounded-[10px] bg-[#004AC6] text-white hover:bg-[#1D4ED8] transition-all active:scale-95 shadow-sm shadow-blue-600/20">
75
+ Đăng nhập
76
+ <Icon name="lucide:log-in" class="text-[16px]" />
77
+ </a>
78
+ </div>
79
+
80
+ <!-- Mobile menu toggle -->
81
+ <button type="button" class="md:hidden flex h-9 w-9 items-center justify-center rounded-[10px] text-slate-600 hover:bg-slate-100 transition-colors"
82
+ :aria-expanded="showMobileMenu" aria-label="Mở menu"
83
+ @click="showMobileMenu = !showMobileMenu">
84
+ <Icon :name="showMobileMenu ? 'lucide:x' : 'lucide:menu'" class="text-[22px]" />
85
+ </button>
86
+ </div>
87
+
88
+ <!-- Mobile menu panel -->
89
+ <Transition enter-active-class="transition ease-out duration-150" enter-from-class="opacity-0 -translate-y-1"
90
+ enter-to-class="opacity-100 translate-y-0" leave-active-class="transition ease-in duration-100"
91
+ leave-from-class="opacity-100 translate-y-0" leave-to-class="opacity-0 -translate-y-1">
92
+ <div v-if="showMobileMenu" class="md:hidden border-t border-slate-200/70 bg-white px-6 py-4">
93
+ <nav class="flex flex-col gap-1 text-sm font-medium text-slate-600">
94
+ <template v-for="link in navLinks" :key="link.id">
95
+ <NuxtLink v-if="link.path && link.path != route.path" :to="`${link.path}`" class="rounded-[10px] px-3 py-2.5 transition-colors"
96
+ :class="activeSection === link.id ? 'bg-blue-50 text-[#004AC6] font-bold' : 'hover:bg-slate-50'"
97
+ @click="showMobileMenu = false">
98
+ {{ link.label }}
99
+ </NuxtLink>
100
+ <a v-else :href="`/giai-phap-proxmox/khoa-hoc-proxmox-hci#${link.id}`" class="rounded-[10px] px-3 py-2.5 transition-colors"
101
+ :class="activeSection === link.id ? 'bg-blue-50 text-[#004AC6] font-bold' : 'hover:bg-slate-50'"
102
+ @click.prevent="handleAnchorClick(link); showMobileMenu = false">
103
+ {{ link.label }}
104
+ </a>
105
+ </template>
106
+ </nav>
107
+
108
+ <div class="mt-4 flex flex-col gap-3 border-t border-slate-100 pt-4">
109
+ <template v-if="user">
110
+ <div class="flex items-center gap-2.5 px-3 text-sm text-slate-700">
111
+ <span class="w-8 h-8 rounded-[10px] bg-gradient-to-br from-[#004AC6] to-[#1D4ED8] flex items-center justify-center text-white text-xs font-bold">
112
+ {{ avatarInitial }}
113
+ </span>
114
+ Xin chào, <strong>{{ user.fullName || user.username }}</strong>
115
+ </div>
116
+ <button @click="logout(); showMobileMenu = false"
117
+ class="flex items-center justify-center gap-2.5 px-4 py-2.5 rounded-[10px] text-sm font-bold text-red-500 border border-red-200 hover:bg-red-50 transition-colors">
118
+ <Icon name="lucide:log-out" class="text-[15px]" />
119
+ Đăng xuất
120
+ </button>
121
+ </template>
122
+ <template v-else>
123
+ <a v-if="isConsultant" :href="`/giai-phap-proxmox/khoa-hoc-proxmox-hci#lien-he`"
124
+ @click.prevent="scrollTo('lien-he'); showMobileMenu = false"
125
+ class="inline-flex items-center justify-center gap-1.5 text-sm font-bold px-5 py-2.5 rounded-[10px] border border-[#004AC6] text-[#004AC6] hover:bg-blue-50 transition-all cursor-pointer">
126
+ Liên hệ tư vấn
127
+ <Icon name="lucide:headset" class="text-[16px]" />
128
+ </a>
129
+ <a :href="loginHref"
130
+ class="inline-flex items-center justify-center gap-1.5 text-sm font-bold px-5 py-2.5 rounded-[10px] bg-[#004AC6] text-white hover:bg-[#1D4ED8] transition-all shadow-sm shadow-blue-600/20">
131
+ Đăng nhập
132
+ <Icon name="lucide:log-in" class="text-[16px]" />
133
+ </a>
134
+ </template>
135
+ </div>
136
+ </div>
137
+ </Transition>
138
+ </header>
139
+ </template>
140
+
141
+ <script setup lang="ts">
142
+ const props = defineProps({
143
+ targetCTA: { type: String, default: '' },
144
+ slug: { type: String, default: '' },
145
+ })
146
+
147
+ const isConsultant = computed(() => props.targetCTA === 'CONSULTANT')
148
+
149
+ const { getCookie, deleteCookie } = useCookieHelper()
150
+ const { getUserByToken } = useAuthApi()
151
+ const { isLoggedIn, userName, setUser, clearUser } = useAuthState()
152
+ const config = useRuntimeConfig()
153
+ const router = useRouter()
154
+ const route = useRoute()
155
+
156
+ const tokenKey = config.public.ACCESSTOKEN_NAME as string
157
+
158
+ const navLinks = computed(() => [
159
+ { id: 'san-pham', label: 'Sản phẩm', path: `/gioi-thieu-san-pham/proxmox-hci` },
160
+ { id: 'giai-phap', label: 'Vì sao chọn chúng tôi' },
161
+ { id: 'chi-phi', label: 'Học phí' },
162
+ { id: 'khach-hang', label: 'Học viên nói gì' },
163
+ ])
164
+
165
+ const { scrollTo } = useScrollToSection()
166
+
167
+ // route.fullPath gồm cả query hiện tại (vd ?plan=...&name=...) để sau khi đăng nhập quay lại đúng chỗ
168
+ // Trang đăng nhập nằm ở domain LONGVAN_WEB (vd https://dev.longvan.net), không phải app này
169
+ const loginHref = computed(() => `${config.public.LONGVAN_WEB}/dang-nhap?redirectUrl=${encodeURIComponent(route.fullPath)}`)
170
+
171
+ const activeSection = useActiveSection(navLinks.value.map(l => l.id))
172
+ const user = ref<{ fullName?: string; username?: string } | null>(null)
173
+ const showMenu = ref(false)
174
+ const showMobileMenu = ref(false)
175
+
176
+ watch(() => route.fullPath, () => {
177
+ showMobileMenu.value = false
178
+ })
179
+
180
+ function handleNavClick(e: Event, link: any) {
181
+ if (route.path === link.path) {
182
+ e.preventDefault()
183
+ scrollTo(link.id)
184
+ }
185
+ }
186
+
187
+ function handleAnchorClick(link: any) {
188
+ const el = document.getElementById(link.id)
189
+ if (el) {
190
+ scrollTo(link.id)
191
+ } else {
192
+ router.push(`/giai-phap-proxmox/${props.slug}#${link.id}`)
193
+ }
194
+ }
195
+
196
+ const avatarInitial = computed(() => {
197
+ const name = user.value?.fullName || user.value?.username || ''
198
+ return name.charAt(0).toUpperCase() || '?'
199
+ })
200
+
201
+ async function loadUser() {
202
+ const token = getCookie(tokenKey)
203
+ if (!token) return
204
+
205
+ try {
206
+ const res = await getUserByToken(token)
207
+ if (res) {
208
+ user.value = {
209
+ fullName: res.fullName,
210
+ username: res.username || res.userLoginId,
211
+ }
212
+ setUser(res.fullName || res.username || '')
213
+ }
214
+ } catch {
215
+ deleteCookie(tokenKey)
216
+ clearUser()
217
+ }
218
+ }
219
+
220
+ // Watch auth state changes (when login/register updates state)
221
+ watch(isLoggedIn, (loggedIn) => {
222
+ if (loggedIn && userName.value) {
223
+ user.value = {
224
+ fullName: userName.value,
225
+ username: userName.value,
226
+ }
227
+ } else if (!loggedIn) {
228
+ user.value = null
229
+ }
230
+ })
231
+
232
+ function logout() {
233
+ deleteCookie(tokenKey)
234
+ user.value = null
235
+ clearUser()
236
+ showMenu.value = false
237
+ router.push('/giai-phap-proxmox')
238
+ }
239
+
240
+ // Close menu on outside click
241
+ function onClickOutside(e: MouseEvent) {
242
+ const target = e.target as HTMLElement
243
+ if (!target.closest('.relative')) {
244
+ showMenu.value = false
245
+ }
246
+ }
247
+
248
+ onMounted(() => {
249
+ loadUser()
250
+ document.addEventListener('click', onClickOutside)
251
+ })
252
+
253
+ onUnmounted(() => {
254
+ document.removeEventListener('click', onClickOutside)
255
+ })
193
256
  </script>
@@ -3,7 +3,7 @@ import { comparisonRows } from "../../data/proxmox-content"
3
3
  </script>
4
4
 
5
5
  <template>
6
- <section class="bg-white py-20 sm:py-28">
6
+ <section id="so-sanh" class="bg-white py-20 sm:py-28">
7
7
  <div class="mx-auto max-w-4xl px-6 lg:px-8">
8
8
  <div class="text-center">
9
9
  <div v-reveal class="inline-flex items-center rounded-full bg-blue-50 px-4 py-1.5 text-sm font-medium text-blue-600 ring-1 ring-inset ring-blue-100">
@@ -18,25 +18,25 @@ import { comparisonRows } from "../../data/proxmox-content"
18
18
  </p>
19
19
  </div>
20
20
 
21
- <div v-reveal="{ delay: 180 }" class="mt-12 overflow-hidden rounded-2xl border border-slate-200 shadow-sm">
22
- <table class="w-full border-collapse text-left text-sm">
21
+ <div v-reveal="{ delay: 180 }" class="mt-12 overflow-x-auto rounded-2xl border border-slate-200 shadow-sm">
22
+ <table class="w-full min-w-[520px] border-collapse text-left text-sm">
23
23
  <thead>
24
24
  <tr class="bg-blue-600 text-white">
25
- <th class="px-6 py-4 font-semibold">Tiêu chí</th>
26
- <th class="px-6 py-4 text-center font-semibold">Proxmox HCI</th>
27
- <th class="px-6 py-4 text-center font-semibold">Hạ tầng truyền thống</th>
25
+ <th class="px-4 py-4 font-semibold sm:px-6">Tiêu chí</th>
26
+ <th class="px-4 py-4 text-center font-semibold sm:px-6">Proxmox HCI</th>
27
+ <th class="px-4 py-4 text-center font-semibold sm:px-6">Hạ tầng truyền thống</th>
28
28
  </tr>
29
29
  </thead>
30
30
  <tbody class="divide-y divide-slate-200 bg-white">
31
31
  <tr v-for="row in comparisonRows" :key="row.label">
32
- <td class="px-6 py-4 font-medium text-slate-700">{{ row.label }}</td>
33
- <td class="px-6 py-4">
32
+ <td class="px-4 py-4 font-medium text-slate-700 sm:px-6">{{ row.label }}</td>
33
+ <td class="px-4 py-4 sm:px-6">
34
34
  <span class="mx-auto flex w-fit items-center gap-1.5 font-medium text-emerald-600">
35
35
  <ProxmoxAppIcon name="check" class="h-4 w-4 rounded-full bg-emerald-50 p-0.5" />
36
36
  {{ row.hci }}
37
37
  </span>
38
38
  </td>
39
- <td class="px-6 py-4">
39
+ <td class="px-4 py-4 sm:px-6">
40
40
  <span
41
41
  class="mx-auto flex w-fit items-center gap-1.5 font-medium"
42
42
  :class="row.legacyOk ? 'text-emerald-600' : 'text-rose-500'"
@@ -3,7 +3,7 @@ import { hciOverviewCards } from "../../data/proxmox-content"
3
3
  </script>
4
4
 
5
5
  <template>
6
- <section class="bg-[#F7F9FB] py-20 sm:py-28">
6
+ <section id="hci-la-gi" class="bg-[#F7F9FB] py-20 sm:py-28">
7
7
  <div class="mx-auto max-w-6xl px-6 lg:px-8">
8
8
  <div class="mx-auto max-w-3xl text-center">
9
9
  <div v-reveal class="inline-flex items-center rounded-full bg-blue-50 px-4 py-1.5 text-sm font-medium text-blue-600 ring-1 ring-inset ring-blue-100">
@@ -0,0 +1,43 @@
1
+ <script setup lang="ts">
2
+ import { painPoints } from "../../data/proxmox-content"
3
+ const { scrollTo } = useScrollToSection()
4
+ </script>
5
+
6
+ <template>
7
+ <section class="bg-[#F7F9FB] py-20 sm:py-28">
8
+ <div class="mx-auto max-w-6xl px-6 lg:px-8">
9
+ <div class="mx-auto max-w-3xl text-center">
10
+ <div v-reveal class="inline-flex items-center rounded-full bg-blue-50 px-4 py-1.5 text-sm font-medium text-blue-600 ring-1 ring-inset ring-blue-100">
11
+ Bài toán thường gặp
12
+ </div>
13
+ <h2 v-reveal="{ delay: 60 }" class="mt-5 text-3xl font-extrabold tracking-tight text-slate-900 sm:text-4xl">
14
+ Đâu là bài toán bạn
15
+ <span class="text-blue-600">đang cần giải quyết?</span>
16
+ </h2>
17
+ </div>
18
+
19
+ <div class="mt-14 flex flex-wrap justify-center gap-5">
20
+ <div
21
+ v-for="(item, i) in painPoints"
22
+ :key="item.question"
23
+ v-reveal="{ delay: 100 + i * 70 }"
24
+ class="flex w-full flex-col rounded-2xl border border-slate-200 bg-white p-7 transition hover:-translate-y-1 hover:border-blue-200 hover:shadow-lg hover:shadow-blue-900/5 sm:w-[calc(50%-10px)] lg:w-[calc(33.333%-13.334px)]"
25
+ >
26
+ <span class="flex h-11 w-11 items-center justify-center rounded-xl bg-blue-50 text-blue-600">
27
+ <ProxmoxAppIcon :name="item.icon" class="h-5 w-5" />
28
+ </span>
29
+ <p class="mt-4 font-semibold text-slate-900">{{ item.question }}</p>
30
+ <p class="mt-2 flex-1 text-sm leading-relaxed text-slate-500">{{ item.answer }}</p>
31
+ <a
32
+ href="#"
33
+ class="mt-4 inline-flex items-center gap-1.5 text-sm font-medium text-blue-600 hover:text-blue-700"
34
+ @click.prevent="scrollTo(item.anchor)"
35
+ >
36
+ {{ item.linkText }}
37
+ <ProxmoxAppIcon name="arrowRight" class="h-3.5 w-3.5" />
38
+ </a>
39
+ </div>
40
+ </div>
41
+ </div>
42
+ </section>
43
+ </template>
@@ -3,7 +3,7 @@ import { trainingBenefits } from "../../data/proxmox-content"
3
3
  </script>
4
4
 
5
5
  <template>
6
- <section class="bg-[#F7F9FB] py-20 sm:py-28">
6
+ <section id="loi-ich-dao-tao" class="bg-[#F7F9FB] py-20 sm:py-28">
7
7
  <div class="mx-auto max-w-6xl px-6 lg:px-8">
8
8
  <div class="mx-auto max-w-3xl text-center">
9
9
  <div v-reveal class="inline-flex items-center rounded-full bg-blue-50 px-4 py-1.5 text-sm font-medium text-blue-600 ring-1 ring-inset ring-blue-100">