Feat: navbar frosted glass au scroll
- navigation.svelte.js : ajout isScrolled + setScrolled() - Header : scroll listener (capture) sur .page-scrollable > 100px, reset au changement de slide, classe navbar--scrolled conditionnelle, transition 0.4s sur background-color et backdrop-filter - Expertise : $effect notifie quand currentItem > 0 - Portfolio : $effect notifie quand currentIndex > 0 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
517143fe60
commit
a0798e71d0
4 changed files with 45 additions and 2 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
|
import { onMount } from 'svelte'
|
||||||
import { navigation } from '@state/navigation.svelte'
|
import { navigation } from '@state/navigation.svelte'
|
||||||
import { locale } from '@state/locale.svelte'
|
import { locale } from '@state/locale.svelte'
|
||||||
import { slides } from '@state/slides.svelte'
|
import { slides } from '@state/slides.svelte'
|
||||||
|
|
@ -9,6 +10,13 @@
|
||||||
const currentLang = $derived(locale.current)
|
const currentLang = $derived(locale.current)
|
||||||
const activeId = $derived(slides.active?.id ?? 'home')
|
const activeId = $derived(slides.active?.id ?? 'home')
|
||||||
const menuItems = $derived(slides.all.filter(s => s.id !== 'home'))
|
const menuItems = $derived(slides.all.filter(s => s.id !== 'home'))
|
||||||
|
const isScrolled = $derived(navigation.isScrolled)
|
||||||
|
|
||||||
|
// Reset scroll state when switching slides
|
||||||
|
$effect(() => {
|
||||||
|
void slides.activeIndex
|
||||||
|
navigation.setScrolled(false)
|
||||||
|
})
|
||||||
|
|
||||||
function getTitle(slide) {
|
function getTitle(slide) {
|
||||||
return slide.titles?.[currentLang] || slide.title || slide.id
|
return slide.titles?.[currentLang] || slide.title || slide.id
|
||||||
|
|
@ -17,9 +25,19 @@
|
||||||
function toggleMenu() {
|
function toggleMenu() {
|
||||||
navigation.toggleMenu()
|
navigation.toggleMenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
function onScroll(e) {
|
||||||
|
if (e.target?.classList?.contains('page-scrollable')) {
|
||||||
|
navigation.setScrolled(e.target.scrollTop > 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.addEventListener('scroll', onScroll, { capture: true })
|
||||||
|
return () => window.removeEventListener('scroll', onScroll, { capture: true })
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<nav class="navbar" class:navbar--open={isMenuOpen}>
|
<nav class="navbar" class:navbar--open={isMenuOpen} class:navbar--scrolled={isScrolled && !isMenuOpen}>
|
||||||
<a href="/" class="navbar-logo">
|
<a href="/" class="navbar-logo">
|
||||||
<img src="/assets/img/GIF_world_game_planete.gif" alt="World Game" class="wg-logo" />
|
<img src="/assets/img/GIF_world_game_planete.gif" alt="World Game" class="wg-logo" />
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -64,6 +82,7 @@
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
transition: background-color 0.4s ease, backdrop-filter 0.4s ease;
|
||||||
z-index: var(--z-header);
|
z-index: var(--z-header);
|
||||||
font-family: "Danzza";
|
font-family: "Danzza";
|
||||||
font-size: var(--font-size-paragraph);
|
font-size: var(--font-size-paragraph);
|
||||||
|
|
@ -100,6 +119,11 @@
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.navbar--scrolled {
|
||||||
|
background-color: rgba(0, 0, 0, 0.3);
|
||||||
|
backdrop-filter: blur(15px);
|
||||||
|
}
|
||||||
|
|
||||||
.navbar--open {
|
.navbar--open {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
backdrop-filter: none;
|
backdrop-filter: none;
|
||||||
|
|
@ -174,6 +198,10 @@
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wg-logo {
|
||||||
|
height: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
.navbar-item {
|
.navbar-item {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
let isMenuOpen = $state(false)
|
let isMenuOpen = $state(false)
|
||||||
let isLoading = $state(false)
|
let isLoading = $state(false)
|
||||||
|
let isScrolled = $state(false)
|
||||||
|
|
||||||
export const navigation = {
|
export const navigation = {
|
||||||
get isMenuOpen() { return isMenuOpen },
|
get isMenuOpen() { return isMenuOpen },
|
||||||
get isLoading() { return isLoading },
|
get isLoading() { return isLoading },
|
||||||
|
get isScrolled() { return isScrolled },
|
||||||
|
|
||||||
toggleMenu: () => isMenuOpen = !isMenuOpen,
|
toggleMenu: () => isMenuOpen = !isMenuOpen,
|
||||||
openMenu: () => isMenuOpen = true,
|
openMenu: () => isMenuOpen = true,
|
||||||
closeMenu: () => isMenuOpen = false,
|
closeMenu: () => isMenuOpen = false,
|
||||||
setLoading: (value) => isLoading = value
|
setLoading: (value) => isLoading = value,
|
||||||
|
setScrolled: (value) => isScrolled = value,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte'
|
||||||
import { slides } from '@state/slides.svelte'
|
import { slides } from '@state/slides.svelte'
|
||||||
|
import { navigation } from '@state/navigation.svelte'
|
||||||
import { createScrollNav } from '@composables/useScrollNav.svelte.js'
|
import { createScrollNav } from '@composables/useScrollNav.svelte.js'
|
||||||
|
|
||||||
let { data } = $props()
|
let { data } = $props()
|
||||||
|
|
@ -32,6 +33,10 @@
|
||||||
const items = $derived(data?.items ?? [])
|
const items = $derived(data?.items ?? [])
|
||||||
const itemCount = $derived(items.length)
|
const itemCount = $derived(items.length)
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (isActive) navigation.setScrolled(currentItem > 0)
|
||||||
|
})
|
||||||
|
|
||||||
const segmentEnds = $derived(
|
const segmentEnds = $derived(
|
||||||
itemCount > 0 && videoDuration > 0
|
itemCount > 0 && videoDuration > 0
|
||||||
? Array.from({ length: itemCount }, (_, i) => videoDuration * (i + 1) / itemCount)
|
? Array.from({ length: itemCount }, (_, i) => videoDuration * (i + 1) / itemCount)
|
||||||
|
|
@ -347,6 +352,7 @@
|
||||||
/* Individual text items */
|
/* Individual text items */
|
||||||
.expertise-item {
|
.expertise-item {
|
||||||
font-size: var(--font-size-expertise);
|
font-size: var(--font-size-expertise);
|
||||||
|
font-weight: 350;
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
text-align: left;
|
text-align: left;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
|
|
@ -375,6 +381,7 @@
|
||||||
|
|
||||||
.expertise-item {
|
.expertise-item {
|
||||||
font-size: var(--font-size-expertise-mobile);
|
font-size: var(--font-size-expertise-mobile);
|
||||||
|
transform: scale(0.75) translateX(2rem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte'
|
||||||
import { slides } from '@state/slides.svelte'
|
import { slides } from '@state/slides.svelte'
|
||||||
|
import { navigation } from '@state/navigation.svelte'
|
||||||
import { createScrollNav } from '@composables/useScrollNav.svelte.js'
|
import { createScrollNav } from '@composables/useScrollNav.svelte.js'
|
||||||
import GalleryAnimation from '@components/ui/GalleryAnimation.svelte'
|
import GalleryAnimation from '@components/ui/GalleryAnimation.svelte'
|
||||||
import ResponsivePicture from '@components/ui/ResponsivePicture.svelte'
|
import ResponsivePicture from '@components/ui/ResponsivePicture.svelte'
|
||||||
|
|
@ -17,6 +18,10 @@
|
||||||
const backgroundImage = $derived(data?.backgroundImage ?? null)
|
const backgroundImage = $derived(data?.backgroundImage ?? null)
|
||||||
const currentProject = $derived(projects[currentIndex] ?? null)
|
const currentProject = $derived(projects[currentIndex] ?? null)
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (isActive) navigation.setScrolled(currentIndex > 0)
|
||||||
|
})
|
||||||
|
|
||||||
// Capture du hash synchrone avant que tout effect puisse le modifier
|
// Capture du hash synchrone avant que tout effect puisse le modifier
|
||||||
const initialHash = window.location.hash.slice(1)
|
const initialHash = window.location.hash.slice(1)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue