Skip to content

Commit 2adce79

Browse files
committed
feat: add magic footer
1 parent 4e7aef9 commit 2adce79

3 files changed

Lines changed: 83 additions & 9 deletions

File tree

app/app.vue

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,17 @@ onUnmounted(() => {
5050

5151
<AppHeader :show-logo="!isHomepage" />
5252

53-
<div id="main-content" class="flex-1">
53+
<div id="main-content" class="flex-1 flex flex-col">
5454
<NuxtPage />
5555
</div>
5656

5757
<AppFooter />
58+
59+
<ScrollToTop />
5860
</div>
5961
</template>
6062

61-
<style>
63+
<style lang="postcss">
6264
/* Base reset and defaults */
6365
*,
6466
*::before,
@@ -77,6 +79,7 @@ body {
7779
background-color: #0a0a0a;
7880
color: #fafafa;
7981
line-height: 1.6;
82+
padding-bottom: var(--footer-height, 0);
8083
}
8184
8285
/* Default link styling for accessibility on dark background */

app/components/AppFooter.vue

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,79 @@
1+
<script setup lang="ts">
2+
const isVisible = ref(false)
3+
const isScrollable = ref(true)
4+
const lastScrollY = ref(0)
5+
const footerRef = ref<HTMLElement>()
6+
7+
function checkScrollable() {
8+
const mainContent = document.getElementById('main-content')
9+
if (!mainContent) return true
10+
return mainContent.scrollHeight > window.innerHeight
11+
}
12+
13+
function onScroll() {
14+
const currentY = window.scrollY
15+
const diff = lastScrollY.value - currentY
16+
const nearBottom = currentY + window.innerHeight >= document.documentElement.scrollHeight - 50
17+
18+
// Scrolling UP or near bottom -> show
19+
if (Math.abs(diff) > 10) {
20+
isVisible.value = diff > 0 || nearBottom
21+
lastScrollY.value = currentY
22+
}
23+
24+
// At top -> hide
25+
if (currentY < 100) {
26+
isVisible.value = false
27+
}
28+
29+
// Near bottom -> always show
30+
if (nearBottom) {
31+
isVisible.value = true
32+
}
33+
}
34+
35+
function updateFooterPadding() {
36+
const height = isScrollable.value && footerRef.value ? footerRef.value.offsetHeight : 0
37+
document.documentElement.style.setProperty('--footer-height', `${height}px`)
38+
}
39+
40+
onMounted(() => {
41+
nextTick(() => {
42+
lastScrollY.value = window.scrollY
43+
isScrollable.value = checkScrollable()
44+
updateFooterPadding()
45+
})
46+
47+
window.addEventListener('scroll', onScroll, { passive: true })
48+
window.addEventListener(
49+
'resize',
50+
() => {
51+
isScrollable.value = checkScrollable()
52+
updateFooterPadding()
53+
},
54+
{ passive: true },
55+
)
56+
})
57+
58+
onUnmounted(() => {
59+
window.removeEventListener('scroll', onScroll)
60+
})
61+
</script>
62+
163
<template>
2-
<footer class="border-t border-border mt-auto">
3-
<div class="container py-8 flex flex-col gap-4 text-fg-subtle text-sm">
64+
<footer
65+
ref="footerRef"
66+
class="border-t border-border bg-bg"
67+
:class="
68+
isScrollable
69+
? [
70+
'fixed bottom-0 left-0 right-0 z-40 transition-transform duration-300 ease-out',
71+
isVisible ? 'translate-y-0' : 'translate-y-full',
72+
]
73+
: 'mt-auto'
74+
"
75+
>
76+
<div class="container py-6 flex flex-col gap-3 text-fg-subtle text-sm">
477
<div class="flex flex-col sm:flex-row items-center justify-between gap-4">
578
<p class="font-mono m-0">a better browser for the npm registry</p>
679
<div class="flex items-center gap-6">

app/pages/index.vue

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,9 @@ defineOgImageComponent('Default')
2020
</script>
2121

2222
<template>
23-
<main class="container">
24-
<!-- Hero section with dramatic vertical centering -->
25-
<header
26-
class="min-h-[calc(100vh-12rem)] flex flex-col items-center justify-center text-center py-20"
27-
>
23+
<main class="container min-h-screen flex flex-col">
24+
<!-- Hero section with vertical centering -->
25+
<header class="flex-1 flex flex-col items-center justify-center text-center py-20">
2826
<!-- Animated title -->
2927
<h1
3028
class="font-mono text-5xl sm:text-7xl md:text-8xl font-medium tracking-tight mb-4 animate-fade-in animate-fill-both"

0 commit comments

Comments
 (0)