footer : fix show/hide logic, transition and scroll throttle. related to #51
All checks were successful
Deploy / Deploy to Production (push) Successful in 23s
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:
parent
77a1c58573
commit
0afbcf4088
9 changed files with 48 additions and 53 deletions
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
import Header from '@components/layout/Header.svelte'
|
import Header from '@components/layout/Header.svelte'
|
||||||
import Cursor from '@components/layout/Cursor.svelte'
|
import Cursor from '@components/layout/Cursor.svelte'
|
||||||
|
import Footer from '@components/layout/Footer.svelte'
|
||||||
import LanguageSwitcher from '@components/ui/LanguageSwitcher.svelte'
|
import LanguageSwitcher from '@components/ui/LanguageSwitcher.svelte'
|
||||||
|
|
||||||
import Home from '@views/Home.svelte'
|
import Home from '@views/Home.svelte'
|
||||||
|
|
@ -173,6 +174,8 @@
|
||||||
</main>
|
</main>
|
||||||
<LanguageSwitcher />
|
<LanguageSwitcher />
|
||||||
|
|
||||||
|
<Footer />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
:global(#app) {
|
:global(#app) {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
|
|
||||||
|
|
@ -2,38 +2,45 @@
|
||||||
import { site } from '@state/site.svelte'
|
import { site } from '@state/site.svelte'
|
||||||
import { locale } from '@state/locale.svelte'
|
import { locale } from '@state/locale.svelte'
|
||||||
import { t } from '@i18n'
|
import { t } from '@i18n'
|
||||||
|
import { slides } from '@state/slides.svelte'
|
||||||
|
|
||||||
const logo = $derived(site.logo)
|
const logo = $derived(site.logo)
|
||||||
const title = $derived(site.title || 'World Game')
|
const title = $derived(site.title || 'World Game')
|
||||||
const contact = $derived(site.contact || {})
|
const contact = $derived(site.contact || {})
|
||||||
const socials = $derived(contact.socials ?? [])
|
const socials = $derived(contact.socials ?? [])
|
||||||
const year = new Date().getFullYear()
|
const year = new Date().getFullYear()
|
||||||
|
let isHidden = $state(true)
|
||||||
|
|
||||||
let email = $state('')
|
$effect(() => {
|
||||||
let status = $state(null)
|
const activeSlide = document.querySelector('.slide[data-slide="' + slides.active?.id + '"]')
|
||||||
|
const scrollableContainer = activeSlide?.querySelector('.page-scrollable')
|
||||||
|
|
||||||
async function handleSubscribe(e) {
|
isHidden = true
|
||||||
e.preventDefault()
|
|
||||||
if (!email) return
|
if (!scrollableContainer) return
|
||||||
try {
|
|
||||||
const res = await fetch('/newsletter.json', {
|
let rafId = null
|
||||||
method: 'POST',
|
function onScroll() {
|
||||||
headers: { 'Content-Type': 'application/json' },
|
if (rafId) return
|
||||||
body: JSON.stringify({ email })
|
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>
|
</script>
|
||||||
|
|
||||||
<footer class="page-scrollable-footer">
|
<footer class={["page-scrollable-footer", {hidden: isHidden}]}>
|
||||||
<div class="footer-main">
|
<div class="footer-main">
|
||||||
|
|
||||||
<!-- Logo -->
|
<!-- Logo -->
|
||||||
|
|
@ -98,18 +105,17 @@
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
footer {
|
footer {
|
||||||
|
position: absolute;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
|
bottom: 0;
|
||||||
background: #0d0e22;
|
background: #0d0e22;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
|
||||||
|
transition: transform .3s var(--ease-standard);
|
||||||
:global(.collection .page-scrollable-footer) {
|
|
||||||
margin-left: -10.3rem;
|
|
||||||
margin-top: 5rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.article-wrapper .page-scrollable-footer) {
|
footer.hidden {
|
||||||
margin-left: -3.3rem;
|
transform: translateY(var(--footer-height));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Main row --- */
|
/* --- Main row --- */
|
||||||
|
|
@ -222,17 +228,6 @@
|
||||||
width: 7%;
|
width: 7%;
|
||||||
height: 2px;
|
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 (701px–912px) --- */
|
/* --- Tablet (701px–912px) --- */
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,10 @@
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page-scrollable:not(.white-paper) .page-container > *:last-child {
|
||||||
|
padding-bottom: calc(var(--footer-height) + 5rem);
|
||||||
|
}
|
||||||
|
|
||||||
/* Vertical Lines — utilisées dans Menu.svelte (golden-grid) */
|
/* Vertical Lines — utilisées dans Menu.svelte (golden-grid) */
|
||||||
.vertical-line {
|
.vertical-line {
|
||||||
grid-area: auto;
|
grid-area: auto;
|
||||||
|
|
|
||||||
|
|
@ -62,4 +62,12 @@
|
||||||
--font-size-expertise: 22px;
|
--font-size-expertise: 22px;
|
||||||
--font-size-expertise-mobile: 18px;
|
--font-size-expertise-mobile: 18px;
|
||||||
--font-size-expertise-tablet: 20px;
|
--font-size-expertise-tablet: 20px;
|
||||||
|
|
||||||
|
--footer-height: 13rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 800px) {
|
||||||
|
:root {
|
||||||
|
--footer-height: 29rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
import { navigation } from '@state/navigation.svelte'
|
import { navigation } from '@state/navigation.svelte'
|
||||||
|
|
||||||
import { t } from '@i18n'
|
import { t } from '@i18n'
|
||||||
import Footer from '@components/layout/Footer.svelte'
|
|
||||||
|
|
||||||
let { data } = $props()
|
let { data } = $props()
|
||||||
|
|
||||||
|
|
@ -168,8 +167,6 @@
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<Footer />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@
|
||||||
|
|
||||||
import { t } from '@i18n'
|
import { t } from '@i18n'
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte'
|
||||||
import Footer from '@components/layout/Footer.svelte'
|
|
||||||
import WhitePaperDialog from '@components/WhitePaperDialog.svelte'
|
import WhitePaperDialog from '@components/WhitePaperDialog.svelte'
|
||||||
import ShareButtons from '@components/blocks/ShareButtons.svelte'
|
import ShareButtons from '@components/blocks/ShareButtons.svelte'
|
||||||
import ArticleRelated from '@components/blocks/ArticleRelated.svelte'
|
import ArticleRelated from '@components/blocks/ArticleRelated.svelte'
|
||||||
|
|
@ -144,8 +143,6 @@
|
||||||
<ArticleRelated related={data.related} />
|
<ArticleRelated related={data.related} />
|
||||||
{/if}
|
{/if}
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<Footer />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<WhitePaperDialog uri={activeWhitePaperUri} onClose={() => activeWhitePaperUri = null} />
|
<WhitePaperDialog uri={activeWhitePaperUri} onClose={() => activeWhitePaperUri = null} />
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@
|
||||||
|
|
||||||
import Article from '@views/Article.svelte'
|
import Article from '@views/Article.svelte'
|
||||||
import { t } from '@i18n'
|
import { t } from '@i18n'
|
||||||
import Footer from '@components/layout/Footer.svelte'
|
|
||||||
|
|
||||||
let { data } = $props()
|
let { data } = $props()
|
||||||
|
|
||||||
|
|
@ -183,8 +182,6 @@
|
||||||
{#if articleLoading}
|
{#if articleLoading}
|
||||||
<p class="collection-loading">{t('loading')}</p>
|
<p class="collection-loading">{t('loading')}</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<Footer />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { navigation } from '@state/navigation.svelte'
|
import { navigation } from '@state/navigation.svelte'
|
||||||
import Footer from '@components/layout/Footer.svelte'
|
|
||||||
|
|
||||||
let { data } = $props()
|
let { data } = $props()
|
||||||
|
|
||||||
|
|
@ -28,8 +27,6 @@
|
||||||
{/each}
|
{/each}
|
||||||
</section>
|
</section>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<Footer />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
import { locale } from '@state/locale.svelte'
|
import { locale } from '@state/locale.svelte'
|
||||||
import WhitePaper from '@views/WhitePaper.svelte'
|
import WhitePaper from '@views/WhitePaper.svelte'
|
||||||
import { t } from '@i18n'
|
import { t } from '@i18n'
|
||||||
import Footer from '@components/layout/Footer.svelte'
|
|
||||||
|
|
||||||
let { data } = $props()
|
let { data } = $props()
|
||||||
|
|
||||||
|
|
@ -154,8 +153,6 @@
|
||||||
{#if itemLoading}
|
{#if itemLoading}
|
||||||
<p class="collection-loading">{t('loading')}</p>
|
<p class="collection-loading">{t('loading')}</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<Footer class="page-scrollable-footer" />
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue