footer : fix show/hide logic, transition and scroll throttle. related to #51
All checks were successful
Deploy / Deploy to Production (push) Successful in 23s

- Fix scroll listener (cleanup, local querySelector, scrollHeight calc)
- Fix media query syntax in variables.css (missing space in `and (`)
- Use transform: translateY instead of bottom for GPU-accelerated transition
- Throttle scroll handler with requestAnimationFrame
- Move Footer to App.svelte (global), remove per-view imports

refs #51

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
isUnknown 2026-04-01 19:16:58 +02:00
parent 77a1c58573
commit 0afbcf4088
9 changed files with 48 additions and 53 deletions

View file

@ -2,38 +2,45 @@
import { site } from '@state/site.svelte'
import { locale } from '@state/locale.svelte'
import { t } from '@i18n'
import { slides } from '@state/slides.svelte'
const logo = $derived(site.logo)
const title = $derived(site.title || 'World Game')
const contact = $derived(site.contact || {})
const socials = $derived(contact.socials ?? [])
const year = new Date().getFullYear()
let isHidden = $state(true)
let email = $state('')
let status = $state(null)
$effect(() => {
const activeSlide = document.querySelector('.slide[data-slide="' + slides.active?.id + '"]')
const scrollableContainer = activeSlide?.querySelector('.page-scrollable')
async function handleSubscribe(e) {
e.preventDefault()
if (!email) return
try {
const res = await fetch('/newsletter.json', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email })
isHidden = true
if (!scrollableContainer) return
let rafId = null
function onScroll() {
if (rafId) return
rafId = requestAnimationFrame(() => {
const threshold = window.innerWidth > 800 ? 100 : 200
const atBottom = scrollableContainer.scrollTop >= scrollableContainer.scrollHeight - scrollableContainer.clientHeight - threshold
isHidden = !atBottom
rafId = null
})
if (res.ok) {
status = { type: 'success', message: t('newsletter_success') }
email = ''
} else {
status = { type: 'error', message: t('newsletter_error') }
}
} catch {
status = { type: 'error', message: t('newsletter_error') }
}
}
scrollableContainer.addEventListener('scroll', onScroll)
onScroll()
return () => {
scrollableContainer.removeEventListener('scroll', onScroll)
if (rafId) cancelAnimationFrame(rafId)
}
})
</script>
<footer class="page-scrollable-footer">
<footer class={["page-scrollable-footer", {hidden: isHidden}]}>
<div class="footer-main">
<!-- Logo -->
@ -98,18 +105,17 @@
<style>
footer {
position: absolute;
width: 100vw;
bottom: 0;
background: #0d0e22;
z-index: 2;
}
:global(.collection .page-scrollable-footer) {
margin-left: -10.3rem;
margin-top: 5rem;
transition: transform .3s var(--ease-standard);
}
:global(.article-wrapper .page-scrollable-footer) {
margin-left: -3.3rem;
footer.hidden {
transform: translateY(var(--footer-height));
}
/* --- Main row --- */
@ -222,17 +228,6 @@
width: 7%;
height: 2px;
}
:global(.collection .page-scrollable-footer) {
margin-left: -1.5rem;
}
:global(.article-wrapper .page-scrollable-footer) {
margin-left: -1.3rem;
}
:global(.about .page-scrollable-footer) {
margin-left: 0;
}
}
/* --- Tablet (701px912px) --- */