diff options
Diffstat (limited to 'src/routes')
| -rw-r--r-- | src/routes/+layout.svelte | 132 |
1 files changed, 130 insertions, 2 deletions
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 0cfde36d..a7a03066 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -54,6 +54,7 @@ let previousScrollPosition = 0; let notificationInterval: ReturnType<typeof setInterval> | undefined = undefined; let lenis: Lenis | undefined = undefined; +let isMenuOpen = false; addMessages("en", english as unknown as LocaleDictionary); addMessages("ja", japanese as unknown as LocaleDictionary); @@ -87,6 +88,8 @@ $: way = data.url.includes("/user") ? 200 : -200; +$: if ($navigating) isMenuOpen = false; + const handleScroll = () => { const currentScrollPosition = window.scrollY; @@ -214,6 +217,8 @@ $: { <HeadTitle /> +<svelte:window on:keydown={(e) => { if (e.key === 'Escape' && isMenuOpen) isMenuOpen = false; }} /> + <CommandPalette items={[ ...defaultActions, @@ -239,8 +244,25 @@ $: { <Announcement /> <div class="container"> - <div class="card card-centered card-glass header" class:header-hidden={!isHeaderVisible}> - <div> + <div + class="card card-centered card-glass header" + class:header-hidden={!isHeaderVisible && !isMenuOpen} + class:menu-open={isMenuOpen} + > + <button + type="button" + class="menu-toggle" + aria-label="Menu" + aria-expanded={isMenuOpen} + aria-controls="primary-nav" + onclick={() => (isMenuOpen = !isMenuOpen)} + > + <span class="menu-bar"></span> + <span class="menu-bar"></span> + <span class="menu-bar"></span> + </button> + + <div class="nav-items" id="primary-nav"> <a href={root('/')} class="header-item">{$locale().navigation.home}</a><a href={root('/completed')} class="header-item" @@ -425,4 +447,110 @@ $: { .separator { color: var(--base04); } + + .menu-toggle { + display: none; + } + + @media (max-width: 800px) { + .header { + position: fixed; + top: 1.25rem; + right: 1.25rem; + left: auto; + width: auto; + margin: 0; + padding: 0; + background: transparent; + box-shadow: none; + backdrop-filter: none; + -webkit-backdrop-filter: none; + z-index: 10; + } + + .header.header-hidden { + transform: none; + } + + .menu-toggle { + box-sizing: border-box; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 5px; + width: 44px; + height: 44px; + padding: 0; + border: none; + border-radius: 8px; + background: var(--base0011); + box-shadow: + var(--shadow-card-emphasized), + 0 0 0 5px var(--base02); + backdrop-filter: blur(4px); + -webkit-backdrop-filter: blur(4px); + cursor: pointer; + } + + .menu-toggle:hover { + background: var(--base0011); + } + + .menu-bar { + display: block; + width: 20px; + height: 2px; + background: var(--base06); + border-radius: 2px; + transition: + transform var(--duration-fast) var(--ease-in-out-quart), + opacity var(--duration-fast) var(--ease-in-out-quart); + } + + .menu-toggle[aria-expanded='true'] .menu-bar:nth-child(1) { + transform: translateY(7px) rotate(45deg); + } + + .menu-toggle[aria-expanded='true'] .menu-bar:nth-child(2) { + opacity: 0; + } + + .menu-toggle[aria-expanded='true'] .menu-bar:nth-child(3) { + transform: translateY(-7px) rotate(-45deg); + } + + .nav-items { + display: none; + } + + .header.menu-open .nav-items { + display: flex; + flex-direction: column; + align-items: stretch; + position: absolute; + top: calc(100% + 0.75rem); + right: 0; + min-width: 200px; + padding: 0.5rem; + border-radius: 8px; + background: var(--base0011); + box-shadow: + var(--shadow-card-emphasized), + 0 0 0 5px var(--base02); + backdrop-filter: blur(4px); + -webkit-backdrop-filter: blur(4px); + } + + .header.menu-open .nav-items :global(.header-item) { + display: block; + margin: 0; + padding: 0.6rem 0.75rem; + } + + .header.menu-open .nav-items :global(.separator), + .header.menu-open .nav-items :global(.header-item:has(.avatar)) { + display: none; + } + } </style> |