@anja.codes/create-vitakit 0.4.0 → 0.5.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.
package/package.json
CHANGED
|
@@ -1,39 +1,183 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
const profile = useProfile()
|
|
3
|
+
const route = useRoute()
|
|
4
|
+
|
|
5
|
+
// Mobile menu (below the `lg` breakpoint, i.e. md and smaller screens).
|
|
6
|
+
const menuOpen = ref(false)
|
|
7
|
+
const menuButton = ref<HTMLButtonElement | null>(null)
|
|
8
|
+
const closeButton = ref<HTMLButtonElement | null>(null)
|
|
9
|
+
|
|
10
|
+
function openMenu() {
|
|
11
|
+
menuOpen.value = true
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function closeMenu() {
|
|
15
|
+
menuOpen.value = false
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Close after navigating, so the new page is visible.
|
|
19
|
+
watch(() => route.fullPath, closeMenu)
|
|
20
|
+
|
|
21
|
+
function onKeydown(event: KeyboardEvent) {
|
|
22
|
+
if (event.key === 'Escape') closeMenu()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Lock page scrolling while the menu is open, and move focus into the
|
|
26
|
+
// panel (and back to the toggle when it closes).
|
|
27
|
+
watch(menuOpen, async (open) => {
|
|
28
|
+
if (!import.meta.client) return
|
|
29
|
+
const root = document.documentElement
|
|
30
|
+
if (open) {
|
|
31
|
+
// Keep the layout from shifting when the scrollbar disappears.
|
|
32
|
+
const scrollbar = window.innerWidth - root.clientWidth
|
|
33
|
+
root.style.overflow = 'hidden'
|
|
34
|
+
if (scrollbar > 0) root.style.paddingRight = `${scrollbar}px`
|
|
35
|
+
document.addEventListener('keydown', onKeydown)
|
|
36
|
+
await nextTick()
|
|
37
|
+
closeButton.value?.focus()
|
|
38
|
+
} else {
|
|
39
|
+
unlockPage()
|
|
40
|
+
menuButton.value?.focus()
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
function unlockPage() {
|
|
45
|
+
const root = document.documentElement
|
|
46
|
+
root.style.overflow = ''
|
|
47
|
+
root.style.paddingRight = ''
|
|
48
|
+
document.removeEventListener('keydown', onKeydown)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Close the menu if the window grows past the mobile breakpoint.
|
|
52
|
+
let desktopQuery: MediaQueryList | undefined
|
|
53
|
+
function onBreakpointChange(event: MediaQueryListEvent) {
|
|
54
|
+
if (event.matches) closeMenu()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
onMounted(() => {
|
|
58
|
+
desktopQuery = window.matchMedia('(min-width: 64rem)')
|
|
59
|
+
desktopQuery.addEventListener('change', onBreakpointChange)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
onBeforeUnmount(() => {
|
|
63
|
+
desktopQuery?.removeEventListener('change', onBreakpointChange)
|
|
64
|
+
unlockPage()
|
|
65
|
+
})
|
|
3
66
|
</script>
|
|
4
67
|
|
|
5
68
|
<template>
|
|
6
|
-
<div
|
|
7
|
-
<!--
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
<NuxtLink
|
|
17
|
-
v-for="item in profile.nav"
|
|
18
|
-
:key="item.url"
|
|
19
|
-
:to="item.url"
|
|
20
|
-
class="no-underline hover:underline"
|
|
21
|
-
>
|
|
22
|
-
{{ item.label }}
|
|
69
|
+
<div>
|
|
70
|
+
<!-- `inert` keeps the page behind the open menu from being focused or clicked. -->
|
|
71
|
+
<div class="min-h-screen flex flex-col" :inert="menuOpen">
|
|
72
|
+
<!-- Shows content problems while developing; a build fails on them instead. -->
|
|
73
|
+
<VitaIssues />
|
|
74
|
+
|
|
75
|
+
<header class="border-b border-neutral-200">
|
|
76
|
+
<div class="mx-auto flex max-w-3xl items-center gap-x-6 px-4 py-6">
|
|
77
|
+
<NuxtLink to="/" class="text-lg font-semibold no-underline">
|
|
78
|
+
{{ profile.name }}
|
|
23
79
|
</NuxtLink>
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
80
|
+
|
|
81
|
+
<!-- Desktop navigation -->
|
|
82
|
+
<nav class="ml-auto hidden gap-4 text-sm lg:flex" aria-label="Main">
|
|
83
|
+
<NuxtLink
|
|
84
|
+
v-for="item in profile.nav"
|
|
85
|
+
:key="item.url"
|
|
86
|
+
:to="item.url"
|
|
87
|
+
class="no-underline hover:underline"
|
|
88
|
+
>
|
|
89
|
+
{{ item.label }}
|
|
90
|
+
</NuxtLink>
|
|
91
|
+
</nav>
|
|
92
|
+
|
|
93
|
+
<!-- Mobile menu toggle -->
|
|
94
|
+
<button
|
|
95
|
+
ref="menuButton"
|
|
96
|
+
type="button"
|
|
97
|
+
class="-mr-2 ml-auto rounded p-2 text-neutral-700 hover:bg-neutral-100 lg:hidden"
|
|
98
|
+
aria-controls="mobile-menu"
|
|
99
|
+
:aria-expanded="menuOpen"
|
|
100
|
+
@click="openMenu"
|
|
101
|
+
>
|
|
102
|
+
<span class="sr-only">Open menu</span>
|
|
103
|
+
<svg class="size-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true">
|
|
104
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5M3.75 17.25h16.5" />
|
|
105
|
+
</svg>
|
|
106
|
+
</button>
|
|
107
|
+
</div>
|
|
108
|
+
</header>
|
|
109
|
+
|
|
110
|
+
<main class="mx-auto w-full max-w-3xl flex-1 px-4 py-10">
|
|
111
|
+
<slot />
|
|
112
|
+
</main>
|
|
113
|
+
|
|
114
|
+
<footer class="border-t border-neutral-200">
|
|
115
|
+
<div class="mx-auto flex max-w-3xl flex-wrap items-baseline gap-2 px-4 py-6 text-sm text-neutral-600">
|
|
116
|
+
<p>© {{ new Date().getFullYear() }} {{ profile.name }}</p>
|
|
117
|
+
<VitaMarkdown class="ml-auto text-xs text-neutral-400" :html="profile.footerHtml" />
|
|
118
|
+
</div>
|
|
119
|
+
</footer>
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
<!-- Mobile slide-over menu -->
|
|
123
|
+
<Teleport to="body">
|
|
124
|
+
<Transition
|
|
125
|
+
enter-active-class="transition-opacity duration-300 ease-out"
|
|
126
|
+
enter-from-class="opacity-0"
|
|
127
|
+
leave-active-class="transition-opacity duration-200 ease-in"
|
|
128
|
+
leave-to-class="opacity-0"
|
|
129
|
+
>
|
|
130
|
+
<div
|
|
131
|
+
v-if="menuOpen"
|
|
132
|
+
class="fixed inset-0 z-40 bg-neutral-900/40 lg:hidden"
|
|
133
|
+
aria-hidden="true"
|
|
134
|
+
@click="closeMenu"
|
|
135
|
+
/>
|
|
136
|
+
</Transition>
|
|
137
|
+
|
|
138
|
+
<Transition
|
|
139
|
+
enter-active-class="transition-transform duration-300 ease-out"
|
|
140
|
+
enter-from-class="translate-x-full"
|
|
141
|
+
leave-active-class="transition-transform duration-200 ease-in"
|
|
142
|
+
leave-to-class="translate-x-full"
|
|
143
|
+
>
|
|
144
|
+
<div
|
|
145
|
+
v-if="menuOpen"
|
|
146
|
+
id="mobile-menu"
|
|
147
|
+
class="fixed inset-y-0 right-0 z-50 flex w-full max-w-xs flex-col overflow-y-auto bg-white shadow-xl lg:hidden"
|
|
148
|
+
role="dialog"
|
|
149
|
+
aria-modal="true"
|
|
150
|
+
aria-label="Menu"
|
|
151
|
+
>
|
|
152
|
+
<div class="flex items-center justify-between border-b border-neutral-200 px-4 py-6">
|
|
153
|
+
<span class="text-lg font-semibold">{{ profile.name }}</span>
|
|
154
|
+
<button
|
|
155
|
+
ref="closeButton"
|
|
156
|
+
type="button"
|
|
157
|
+
class="-mr-2 rounded p-2 text-neutral-700 hover:bg-neutral-100"
|
|
158
|
+
@click="closeMenu"
|
|
159
|
+
>
|
|
160
|
+
<span class="sr-only">Close menu</span>
|
|
161
|
+
<svg class="size-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true">
|
|
162
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
|
|
163
|
+
</svg>
|
|
164
|
+
</button>
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
<nav class="flex flex-col px-2 py-4" aria-label="Main">
|
|
168
|
+
<NuxtLink
|
|
169
|
+
v-for="item in profile.nav"
|
|
170
|
+
:key="item.url"
|
|
171
|
+
:to="item.url"
|
|
172
|
+
class="rounded px-2 py-3 text-base no-underline hover:bg-neutral-100"
|
|
173
|
+
active-class="font-semibold"
|
|
174
|
+
@click="closeMenu"
|
|
175
|
+
>
|
|
176
|
+
{{ item.label }}
|
|
177
|
+
</NuxtLink>
|
|
178
|
+
</nav>
|
|
179
|
+
</div>
|
|
180
|
+
</Transition>
|
|
181
|
+
</Teleport>
|
|
38
182
|
</div>
|
|
39
183
|
</template>
|
package/template/cv-tex/cv.yml
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Selection for this document. Order lives in cv.tex.
|
|
2
2
|
visibility: cv # include entries marked public and cv, not private
|
|
3
|
+
# portrait: /img/portrait.jpg # a photo beside the header: JPG, PNG or PDF below public/;
|
|
4
|
+
# # true uses avatar from content/profile.yml
|
|
3
5
|
|
|
4
6
|
sections:
|
|
5
7
|
talks:
|