From 2b29d55688a4f7dba24ac8a0c69d50972afb0ec6 Mon Sep 17 00:00:00 2001 From: Aran Roig Date: Mon, 21 Sep 2026 21:53:40 +0200 Subject: [PATCH] More responsiveness --- .../app/components/parts/StickyHeader.vue | 144 +++++++++++++++--- 1 file changed, 124 insertions(+), 20 deletions(-) diff --git a/frontend/app/components/parts/StickyHeader.vue b/frontend/app/components/parts/StickyHeader.vue index ce07c59..cfc5c20 100644 --- a/frontend/app/components/parts/StickyHeader.vue +++ b/frontend/app/components/parts/StickyHeader.vue @@ -3,20 +3,47 @@ import HeaderLinks from './HeaderLinks.vue'; import SiteOptions from './site_options/SiteOptions.vue'; import { ref, onMounted, onUnmounted } from 'vue'; - -const SCROLL_THRESHOLD = 120; // px scrolled before sticky header appears + +const localePath = useLocalePath(); + +const SCROLL_THRESHOLD = 120; // px from top before the bar can appear +const SCROLL_DELTA = 8; // min px movement to register a direction (avoids jitter) const props = defineProps({ alwaysVisible: { type: Boolean, default: false } }); - + const isVisible = ref(props.alwaysVisible); - + +let lastScrollY = 0; + function onScroll() { - isVisible.value = props.alwaysVisible || window.scrollY > SCROLL_THRESHOLD; + const currentY = window.scrollY; + + if (props.alwaysVisible) { + isVisible.value = true; + lastScrollY = currentY; + return; + } + + // Near the top the real header is visible, so keep the bar hidden. + if (currentY <= SCROLL_THRESHOLD) { + isVisible.value = false; + } else if (currentY > lastScrollY + SCROLL_DELTA) { + // scrolling down -> hide + isVisible.value = false; + } else if (currentY < lastScrollY - SCROLL_DELTA) { + // scrolling up -> show + isVisible.value = true; + } + + lastScrollY = currentY; } - -onMounted(() => window.addEventListener('scroll', onScroll, { passive: true })); + +onMounted(() => { + lastScrollY = window.scrollY; + window.addEventListener('scroll', onScroll, { passive: true }); +}); onUnmounted(() => window.removeEventListener('scroll', onScroll)); @@ -25,7 +52,10 @@ onUnmounted(() => window.removeEventListener('scroll', onScroll));
- + + + ARANROIG.COM +
@@ -41,27 +71,31 @@ onUnmounted(() => window.removeEventListener('scroll', onScroll)); left: 0; right: 0; z-index: 1000; - + background: var(--color-sticky-header-bg); backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); box-shadow: 0 4px 0px 0px var(--color-container-shadow); - + + // Respect notched / status-bar insets on mobile. + padding-top: env(safe-area-inset-top); + // Hidden state — translated up and invisible transform: translateY(-100%); opacity: 0; pointer-events: none; - + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease; - + &.visible { transform: translateY(0); opacity: 1; pointer-events: auto; transition: - transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), - opacity 0.3s ease; + transform 0.25s cubic-bezier(0.4, 0, 0.2, 1), + opacity 0.25s ease; } } @@ -76,9 +110,21 @@ onUnmounted(() => window.removeEventListener('scroll', onScroll)); padding: 5px 16px; } - @media screen and (max-width: 600px) { - padding: 4px 12px; + @media screen and (max-width: 800px) { + padding: 6px 12px; gap: 0.5rem; + + // Compact the option dropdown buttons so they fit the slim bar. + :deep(.site-options) { + margin-top: 0; + gap: 6px; + } + + :deep(.menu-button) { + padding: 5px 8px; + font-size: 0.72rem; + margin-right: 0; + } } } @@ -86,25 +132,82 @@ onUnmounted(() => window.removeEventListener('scroll', onScroll)); display: flex; align-items: center; gap: 1rem; - - @media screen and (max-width: 600px) { + min-width: 0; + + @media screen and (max-width: 800px) { gap: 0.5rem; + flex: 1; + justify-content: space-between; + } + + // Keep the nav single-line and compact inside the sticky bar, + // overriding the wrap layout used by the full page header. + :deep(.tui-tabs) { + @media screen and (max-width: 800px) { + flex-wrap: nowrap; + overflow-x: auto; + max-width: 100%; + scrollbar-width: none; + + &::-webkit-scrollbar { + display: none; + } + } + } + + :deep(.tui-tab) { + @media screen and (max-width: 800px) { + flex: 0 0 auto; + min-height: 0; + padding: 5px 7px; + font-size: 0.7rem; + } + + .tab-sep { + @media screen and (max-width: 800px) { + display: none; + } + } } } .sticky-title { + display: inline-flex; + align-items: center; + gap: 6px; + text-decoration: none; font-family: 'Hurmit', monospace; font-size: 0.8rem; color: var(--color-text); letter-spacing: 1px; white-space: nowrap; - + + .title-mark { + display: block; + width: 24px; + height: 24px; + image-rendering: pixelated; + flex-shrink: 0; + transform: translateY(-2px); + } + + &:hover .title-text { + color: var(--color-link); + } + @media screen and (max-width: 900px) { font-size: 0.75rem; } @media screen and (max-width: 480px) { - display: none; + .title-text { + display: none; + } + + .title-mark { + width: 28px; + height: 28px; + } } } @@ -112,5 +215,6 @@ onUnmounted(() => window.removeEventListener('scroll', onScroll)); display: flex; gap: 8px; align-items: center; + flex-shrink: 0; }