Skip to content

Commit f3d0639

Browse files
committed
feat(docs): enhance homepage with interactive copy button and updated hero section
1 parent f55b9e7 commit f3d0639

File tree

4 files changed

+423
-77
lines changed

4 files changed

+423
-77
lines changed

docs/.vitepress/theme/VuetifyLayout.vue

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
<script setup lang="ts">
2-
import { useData } from 'vitepress'
2+
import { useData, useRoute } from 'vitepress'
33
import DefaultTheme from 'vitepress/theme'
4-
import { nextTick, provide } from 'vue'
4+
import { createApp, nextTick, onMounted, provide, watch } from 'vue'
5+
import HomeHeroCopy from './components/HomeHeroCopy.vue'
56
67
const { isDark } = useData()
8+
const route = useRoute()
9+
const INSTALL_COMMAND = 'npx nuxi@latest module add vuetify-nuxt-module'
10+
const HERO_COPY_SELECTOR = '.VPHome .VPHero .actions .action:nth-child(1) a'
711
812
function enableTransitions () {
913
return 'startViewTransition' in document
@@ -38,6 +42,28 @@
3842
},
3943
)
4044
})
45+
46+
function mountHeroCopy () {
47+
nextTick(() => {
48+
const element = document.querySelector(HERO_COPY_SELECTOR)
49+
if (element) {
50+
const container = document.createElement('div')
51+
element.replaceWith(container)
52+
createApp(HomeHeroCopy, { command: INSTALL_COMMAND }).mount(container)
53+
}
54+
})
55+
}
56+
57+
onMounted(() => {
58+
mountHeroCopy()
59+
})
60+
61+
watch(
62+
() => route.path,
63+
() => {
64+
mountHeroCopy()
65+
},
66+
)
4167
</script>
4268

4369
<template>
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
<script setup lang="ts">
2+
import { ref } from 'vue'
3+
4+
const props = defineProps<{
5+
command: string
6+
}>()
7+
8+
const copied = ref(false)
9+
10+
async function copy () {
11+
try {
12+
await navigator.clipboard.writeText(props.command)
13+
copied.value = true
14+
setTimeout(() => {
15+
copied.value = false
16+
}, 2000)
17+
} catch (error) {
18+
console.error('Failed to copy', error)
19+
}
20+
}
21+
</script>
22+
23+
<template>
24+
<div
25+
class="hero-copy-btn"
26+
:class="{ copied }"
27+
role="button"
28+
tabindex="0"
29+
@click="copy"
30+
@keydown.enter="copy"
31+
@keydown.space.prevent="copy"
32+
>
33+
<div class="content">
34+
<span class="prompt">$</span>
35+
<span class="command">{{ command }}</span>
36+
</div>
37+
<div class="icon-wrapper">
38+
<transition mode="out-in" name="fade">
39+
<svg
40+
v-if="!copied"
41+
class="lucide lucide-copy"
42+
fill="none"
43+
height="18"
44+
stroke="currentColor"
45+
stroke-linecap="round"
46+
stroke-linejoin="round"
47+
stroke-width="2"
48+
viewBox="0 0 24 24"
49+
width="18"
50+
xmlns="http://www.w3.org/2000/svg"
51+
>
52+
<rect
53+
height="14"
54+
rx="2"
55+
ry="2"
56+
width="14"
57+
x="8"
58+
y="8"
59+
/>
60+
<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
61+
</svg>
62+
<svg
63+
v-else
64+
class="lucide lucide-check"
65+
fill="none"
66+
height="18"
67+
stroke="currentColor"
68+
stroke-linecap="round"
69+
stroke-linejoin="round"
70+
stroke-width="2"
71+
viewBox="0 0 24 24"
72+
width="18"
73+
xmlns="http://www.w3.org/2000/svg"
74+
>
75+
<path d="M20 6 9 17l-5-5" />
76+
</svg>
77+
</transition>
78+
</div>
79+
<transition name="slide-up">
80+
<div v-if="copied" class="tooltip">Copied!</div>
81+
</transition>
82+
</div>
83+
</template>
84+
85+
<style scoped>
86+
.hero-copy-btn {
87+
display: flex;
88+
align-items: center;
89+
justify-content: space-between;
90+
gap: 12px;
91+
background: var(--vp-c-bg-soft);
92+
border: 1px solid var(--vp-c-divider);
93+
border-radius: 8px;
94+
padding: 0 16px;
95+
height: 48px;
96+
cursor: pointer;
97+
transition: all 0.25s ease;
98+
position: relative;
99+
user-select: none;
100+
max-width: 100%;
101+
min-width: 300px;
102+
}
103+
104+
.hero-copy-btn:hover {
105+
border-color: var(--vp-c-brand-1);
106+
background: var(--vp-c-bg-mute);
107+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
108+
transform: translateY(-1px);
109+
}
110+
111+
.hero-copy-btn:active {
112+
transform: translateY(0);
113+
}
114+
115+
.hero-copy-btn.copied {
116+
border-color: var(--vp-c-green-1);
117+
background: var(--vp-c-green-dimm-1);
118+
}
119+
120+
.content {
121+
display: flex;
122+
align-items: center;
123+
gap: 10px;
124+
font-family: var(--vp-font-family-mono);
125+
font-size: 14px;
126+
color: var(--vp-c-text-2);
127+
overflow: hidden;
128+
text-overflow: ellipsis;
129+
white-space: nowrap;
130+
flex: 1;
131+
}
132+
133+
.prompt {
134+
color: var(--vp-c-text-3);
135+
user-select: none;
136+
font-weight: 600;
137+
}
138+
139+
.command {
140+
color: var(--vp-c-text-1);
141+
font-weight: 500;
142+
}
143+
144+
.icon-wrapper {
145+
display: flex;
146+
align-items: center;
147+
justify-content: center;
148+
color: var(--vp-c-text-3);
149+
transition: color 0.2s;
150+
flex-shrink: 0;
151+
}
152+
153+
.hero-copy-btn:hover .icon-wrapper {
154+
color: var(--vp-c-text-1);
155+
}
156+
157+
.hero-copy-btn.copied .icon-wrapper {
158+
color: var(--vp-c-green-1);
159+
}
160+
161+
.tooltip {
162+
position: absolute;
163+
top: -36px;
164+
right: 0;
165+
background: var(--vp-c-text-1);
166+
color: var(--vp-c-bg);
167+
padding: 6px 10px;
168+
border-radius: 6px;
169+
font-size: 12px;
170+
font-weight: 600;
171+
pointer-events: none;
172+
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
173+
z-index: 10;
174+
}
175+
176+
/* Tooltip arrow */
177+
.tooltip::after {
178+
content: '';
179+
position: absolute;
180+
bottom: -4px;
181+
right: 12px;
182+
width: 8px;
183+
height: 8px;
184+
background: var(--vp-c-text-1);
185+
transform: rotate(45deg);
186+
}
187+
188+
.fade-enter-active,
189+
.fade-leave-active {
190+
transition: opacity 0.2s ease, transform 0.2s ease;
191+
}
192+
193+
.fade-enter-from,
194+
.fade-leave-to {
195+
opacity: 0;
196+
transform: scale(0.8);
197+
}
198+
199+
.slide-up-enter-active,
200+
.slide-up-leave-active {
201+
transition: all 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275);
202+
}
203+
204+
.slide-up-enter-from,
205+
.slide-up-leave-to {
206+
opacity: 0;
207+
transform: translateY(10px);
208+
}
209+
</style>

docs/.vitepress/theme/styles/main.css

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,144 @@ details > summary:hover {
6565
.dark::view-transition-old(root) {
6666
z-index: 9999;
6767
}
68+
69+
.VPHome {
70+
background:
71+
radial-gradient(1200px 550px at 12% 4%, color-mix(in srgb, var(--vp-c-brand-1) 18%, transparent) 0%, transparent 68%),
72+
radial-gradient(1000px 600px at 88% 2%, color-mix(in srgb, #26c6da 12%, transparent) 0%, transparent 65%);
73+
}
74+
75+
.VPHome .VPHero {
76+
padding-top: 184px;
77+
}
78+
79+
.VPHome .VPHero .name,
80+
.VPHome .VPHero .text {
81+
letter-spacing: -0.03em;
82+
}
83+
84+
.VPHome .VPHero .text {
85+
max-width: 16ch;
86+
font-size: clamp(2.2rem, 5.5vw, 4.4rem);
87+
line-height: 1;
88+
}
89+
90+
.VPHome .VPHero .tagline {
91+
max-width: 48ch;
92+
font-size: 1.1rem;
93+
}
94+
95+
.VPHome .VPHero .actions {
96+
display: flex;
97+
flex-wrap: wrap;
98+
gap: 10px;
99+
margin-top: 20px;
100+
}
101+
102+
.VPHome .VPHero .actions .action:nth-child(1) {
103+
flex: 1 0 100%;
104+
}
105+
106+
.VPHome .VPHero .actions .action:nth-child(1) .VPButton {
107+
justify-content: flex-start;
108+
width: 100%;
109+
border: 1px solid color-mix(in srgb, var(--vp-c-brand-1) 36%, var(--vp-c-divider));
110+
background: color-mix(in srgb, var(--vp-c-bg-soft) 88%, transparent);
111+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
112+
font-size: 0.82rem;
113+
letter-spacing: 0;
114+
}
115+
116+
.VPHome .VPFeatures {
117+
padding-top: 36px;
118+
}
119+
120+
.VPHome .VPFeature {
121+
border: 1px solid color-mix(in srgb, var(--vp-c-brand-1) 22%, var(--vp-c-divider));
122+
background: linear-gradient(160deg, color-mix(in srgb, var(--vp-c-brand-1) 8%, transparent), transparent 58%);
123+
backdrop-filter: blur(8px);
124+
}
125+
126+
.home-benefits {
127+
margin: 20px auto 30px;
128+
max-width: 1152px;
129+
border: 1px solid var(--vp-c-divider);
130+
border-radius: 20px;
131+
padding: 10px 20px 20px;
132+
background:
133+
linear-gradient(160deg, color-mix(in srgb, var(--vp-c-brand-1) 10%, transparent), transparent 64%),
134+
color-mix(in srgb, var(--vp-c-bg) 92%, transparent);
135+
}
136+
137+
.home-benefits__head h2 {
138+
margin: 0;
139+
font-size: clamp(1.65rem, 3vw, 2.3rem);
140+
line-height: 1.08;
141+
}
142+
143+
.home-benefits__kicker {
144+
margin: 0 0 10px;
145+
color: var(--vp-c-brand-1);
146+
font-weight: 600;
147+
letter-spacing: 0.08em;
148+
text-transform: uppercase;
149+
font-size: 0.76rem;
150+
}
151+
152+
.home-benefits__grid {
153+
margin-top: 16px;
154+
display: grid;
155+
grid-template-columns: repeat(2, minmax(0, 1fr));
156+
gap: 10px;
157+
}
158+
159+
.home-benefits__item {
160+
border: 1px solid var(--vp-c-divider);
161+
border-radius: 14px;
162+
padding: 14px;
163+
background: color-mix(in srgb, var(--vp-c-default-soft) 84%, transparent);
164+
}
165+
166+
.home-benefits__item p {
167+
margin: 0;
168+
}
169+
170+
.home-benefits__title {
171+
margin-bottom: 9px !important;
172+
font-weight: 600;
173+
}
174+
175+
.home-benefits__item pre {
176+
margin: 0;
177+
border: 1px solid var(--vp-c-divider);
178+
border-radius: 9px;
179+
padding: 9px 10px;
180+
background: color-mix(in srgb, var(--vp-c-bg) 86%, transparent);
181+
overflow-x: auto;
182+
}
183+
184+
.home-benefits__item a {
185+
display: inline-flex;
186+
margin-top: 10px;
187+
color: var(--vp-c-brand-1);
188+
font-weight: 500;
189+
}
190+
191+
@media (max-width: 768px) {
192+
.VPHome .VPHero {
193+
padding-top: 64px;
194+
}
195+
196+
.VPHome .VPHero .actions .action:nth-child(1) .VPButton {
197+
font-size: 0.74rem;
198+
}
199+
200+
.home-benefits {
201+
margin-top: 16px;
202+
padding: 16px;
203+
}
204+
205+
.home-benefits__grid {
206+
grid-template-columns: 1fr;
207+
}
208+
}

0 commit comments

Comments
 (0)