Skip to content

Commit 1f74353

Browse files
committed
feat: remove sticky footer and add /about page
1 parent 803a38f commit 1f74353

9 files changed

Lines changed: 443 additions & 135 deletions

File tree

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@
99
</p>
1010

1111
- [👉 &nbsp;Check it out](https://npmx.dev/)
12+
- [📖 &nbsp;About npmx](https://npmx.dev/about)
1213

1314
## Vision
1415

15-
The aim of [npmx.dev](https://npmx.dev) is to provide a better browser for the npm registry &ndash; fast, modern, and accessible. We don't aim to replace the [npmjs.com](https://www.npmjs.com/) registry, just provide a better UI and DX.
16+
The aim of [npmx.dev](https://npmx.dev) is to provide a better browser for the npm registry &ndash; fast, modern, and accessible. We don't aim to replace the [npmjs.com](https://www.npmjs.com/) registry, just provide a better UI, DX, and admin experience.
1617

1718
- **Speed first** &ndash; Layout shift, flakiness, slowness is The Worst. Fast searching, filtering, and navigation.
1819
- **URL compatible** &ndash; Replace `npmjs.com` with `xnpmjs.com` or `npmx.dev` in any URL and it just works.
1920
- **Simplicity** &ndash; No noise, cluttered display, or confusing UI. If in doubt: choose simplicity.
21+
- **Admin UI** &ndash; Manage your packages, teams, and organizations from the browser, powered by your local npm CLI.
2022

2123
## Shortcuts
2224

app/assets/main.css

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ body {
7676
background-color: var(--bg);
7777
color: var(--fg);
7878
line-height: 1.6;
79-
padding-bottom: var(--footer-height, 0);
8079
}
8180

8281
/* Default link styling for accessibility on dark background */

app/components/AppFooter.vue

Lines changed: 21 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,166 +1,57 @@
1-
<script setup lang="ts">
2-
const isMounted = shallowRef(false)
3-
const isVisible = shallowRef(false)
4-
const isScrollable = shallowRef(true)
5-
const lastScrollY = shallowRef(0)
6-
const footerRef = useTemplateRef('footerRef')
7-
8-
// Check if CSS scroll-state container queries are supported
9-
// Once this becomes baseline, we can remove the JS scroll handling entirely
10-
const supportsScrollStateQueries = useSupported(() => {
11-
return isMounted.value && CSS.supports('container-type', 'scroll-state')
12-
})
13-
14-
function checkScrollable() {
15-
return document.documentElement.scrollHeight > window.innerHeight
16-
}
17-
18-
function onScroll() {
19-
// Skip JS-based visibility logic if CSS scroll-state queries handle it
20-
if (supportsScrollStateQueries.value) return
21-
22-
const currentY = window.scrollY
23-
const diff = lastScrollY.value - currentY
24-
const nearBottom = currentY + window.innerHeight >= document.documentElement.scrollHeight - 50
25-
26-
// Scrolling UP or near bottom -> show
27-
if (Math.abs(diff) > 10) {
28-
isVisible.value = diff > 0 || nearBottom
29-
lastScrollY.value = currentY
30-
}
31-
32-
// At top -> hide
33-
if (currentY < 100) {
34-
isVisible.value = false
35-
}
36-
37-
// Near bottom -> always show
38-
if (nearBottom) {
39-
isVisible.value = true
40-
}
41-
}
42-
43-
function updateFooterPadding() {
44-
const height = isScrollable.value && footerRef.value ? footerRef.value.offsetHeight : 0
45-
document.documentElement.style.setProperty('--footer-height', `${height}px`)
46-
}
47-
48-
function onResize() {
49-
isScrollable.value = checkScrollable()
50-
updateFooterPadding()
51-
}
52-
53-
useEventListener('scroll', onScroll, { passive: true })
54-
useEventListener('resize', onResize, { passive: true })
55-
56-
onMounted(() => {
57-
nextTick(() => {
58-
lastScrollY.value = window.scrollY
59-
isScrollable.value = checkScrollable()
60-
updateFooterPadding()
61-
// Only apply dynamic classes after mount to avoid hydration mismatch
62-
isMounted.value = true
63-
})
64-
})
65-
</script>
66-
671
<template>
68-
<footer
69-
ref="footerRef"
70-
aria-label="Site footer"
71-
class="border-t border-border bg-bg/90 backdrop-blur-md"
72-
:class="[
73-
// When CSS scroll-state queries are supported, use CSS-only approach
74-
supportsScrollStateQueries
75-
? 'footer-scroll-state'
76-
: // JS-controlled: fixed position, hidden by default, transition only after mount
77-
isScrollable
78-
? [
79-
'fixed bottom-0 left-0 right-0 z-40',
80-
isMounted && 'transition-transform duration-300 ease-out',
81-
isVisible ? 'translate-y-0' : 'translate-y-full',
82-
]
83-
: 'mt-auto',
84-
]"
85-
>
86-
<div class="container py-2 sm:py-6 flex flex-col gap-1 sm:gap-3 text-fg-subtle text-sm">
87-
<div class="flex flex-row items-center justify-between gap-2 sm:gap-4">
2+
<footer class="border-t border-border mt-auto" aria-label="Site footer">
3+
<div class="container py-3 sm:py-8 flex flex-col gap-2 sm:gap-4 text-fg-subtle text-sm">
4+
<div class="flex flex-col sm:flex-row items-center justify-between gap-2 sm:gap-4">
885
<p class="font-mono m-0 hidden sm:block">{{ $t('tagline') }}</p>
89-
<!-- On mobile, show disclaimer here instead of tagline -->
90-
<p class="text-xs text-fg-muted m-0 sm:hidden">{{ $t('non_affiliation_disclaimer') }}</p>
91-
<div class="flex items-center gap-4 sm:gap-6">
6+
<div class="flex items-center gap-3 sm:gap-6">
7+
<NuxtLink
8+
to="/about"
9+
class="link-subtle font-mono text-xs min-h-8 sm:min-h-11 flex items-center"
10+
>
11+
{{ $t('footer.about') }}
12+
</NuxtLink>
9213
<a
9314
href="https://docs.npmx.dev"
9415
target="_blank"
9516
rel="noopener noreferrer"
96-
class="link-subtle font-mono text-xs min-h-11 min-w- flex items-center"
17+
class="link-subtle font-mono text-xs min-h-8 sm:min-h-11 flex items-center gap-1"
9718
>
9819
{{ $t('footer.docs') }}
20+
<span class="i-carbon-launch w-3 h-3" aria-hidden="true" />
9921
</a>
10022
<a
10123
href="https://repo.npmx.dev"
10224
target="_blank"
10325
rel="noopener noreferrer"
104-
class="link-subtle font-mono text-xs min-h-11 min-w- flex items-center"
26+
class="link-subtle font-mono text-xs min-h-8 sm:min-h-11 flex items-center gap-1"
10527
>
10628
{{ $t('footer.source') }}
29+
<span class="i-carbon-launch w-3 h-3" aria-hidden="true" />
10730
</a>
10831
<a
10932
href="https://social.npmx.dev"
11033
target="_blank"
11134
rel="noopener noreferrer"
112-
class="link-subtle font-mono text-xs min-h-11 min-w-11 flex items-center"
35+
class="link-subtle font-mono text-xs min-h-8 sm:min-h-11 flex items-center gap-1"
11336
>
11437
{{ $t('footer.social') }}
38+
<span class="i-carbon-launch w-3 h-3" aria-hidden="true" />
11539
</a>
11640
<a
11741
href="https://chat.npmx.dev"
11842
target="_blank"
11943
rel="noopener noreferrer"
120-
class="link-subtle font-mono text-xs min-h-11 min-w-11 flex items-center"
44+
class="link-subtle font-mono text-xs min-h-8 sm:min-h-11 flex items-center gap-1"
12145
>
12246
{{ $t('footer.chat') }}
47+
<span class="i-carbon-launch w-3 h-3" aria-hidden="true" />
12348
</a>
12449
</div>
12550
</div>
126-
<p class="text-xs text-fg-muted text-center sm:text-left m-0 hidden sm:block">
127-
{{ $t('trademark_disclaimer') }}
51+
<p class="text-xs text-fg-muted text-center sm:text-left m-0">
52+
<span class="sm:hidden">{{ $t('non_affiliation_disclaimer') }}</span>
53+
<span class="hidden sm:inline">{{ $t('trademark_disclaimer') }}</span>
12854
</p>
12955
</div>
13056
</footer>
13157
</template>
132-
133-
<style scoped>
134-
/*
135-
* CSS scroll-state container queries (Chrome 133+)
136-
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/@container#scroll-state_container_descriptors
137-
*
138-
* This provides a pure CSS solution for showing/hiding the footer based on scroll state.
139-
* The JS fallback handles browsers without support.
140-
* Once scroll-state queries become baseline, we can remove the JS scroll handling entirely.
141-
*/
142-
@supports (container-type: scroll-state) {
143-
.footer-scroll-state {
144-
position: fixed;
145-
bottom: 0;
146-
left: 0;
147-
right: 0;
148-
z-index: 40;
149-
/* Hidden by default (translated off-screen) */
150-
transform: translateY(100%);
151-
}
152-
153-
@media (prefers-reduced-motion: no-preference) {
154-
.footer-scroll-state {
155-
transition: transform 0.3s ease-out;
156-
}
157-
}
158-
159-
/* Show footer when user can scroll up (meaning they've scrolled down) */
160-
@container scroll-state(scrollable: top) {
161-
.footer-scroll-state {
162-
transform: translateY(0);
163-
}
164-
}
165-
}
166-
</style>

0 commit comments

Comments
 (0)