|
1 | 1 | <script lang="ts"> |
2 | 2 | import { page } from "$app/state"; |
3 | 3 | import Icon from "@iconify/svelte"; |
4 | | - import { onMount } from "svelte"; |
| 4 | + import { onMount, untrack } from "svelte"; |
5 | 5 |
|
| 6 | + type MobileNavButton = { |
| 7 | + element: Element, |
| 8 | + icon?: string, |
| 9 | + href?: string, |
| 10 | + highlight: number, |
| 11 | + } |
6 | 12 |
|
7 | 13 | let metroHeader: HTMLDivElement; |
8 | 14 | let metroHeaderItems: HTMLDivElement; |
9 | | - let mobileNavItems: HTMLSpanElement; |
| 15 | + let mobileNavItems: MobileNavButton[] = $state([]); |
10 | 16 | let mobileNavBarItem: HTMLAnchorElement; |
11 | 17 |
|
12 | 18 | let currentPage = $derived(page.url.pathname); |
|
18 | 24 |
|
19 | 25 | $effect(() => { |
20 | 26 | currentPage; |
21 | | -
|
22 | | - metroHeaderItems.innerHTML = mobileNavItems.innerHTML = ""; |
23 | | -
|
24 | | - let categories = document.querySelectorAll(".category-box > *"); |
25 | | - if (!categories[0]) return; |
26 | | -
|
27 | | - let index = 0; |
28 | | - for (const elm of categories) { |
29 | | - let link = document.createElement("a"); |
30 | | - link.innerText = elm.getAttribute("data-category-name") ?? ""; |
31 | | - link.href = "#" + elm.id; |
32 | | - metroHeaderItems.appendChild(link); |
33 | | -
|
34 | | - let btn = document.createElement("a"); |
35 | | - btn.innerText = (index + 1).toLocaleString("en-US"); |
36 | | - btn.classList.add("pop-out-btn") |
37 | | - btn.href = "#" + elm.id; |
38 | | - btn.onclick = (e) => { |
39 | | - e.preventDefault(); |
40 | | - elm.scrollIntoView({inline: "center", behavior: "smooth"}); |
| 27 | + untrack(() => { |
| 28 | + metroHeaderItems.innerHTML = ""; |
| 29 | + mobileNavItems = [] |
| 30 | +
|
| 31 | + let categories = document.querySelectorAll(".category-box > *"); |
| 32 | + if (!categories[0]) return; |
| 33 | +
|
| 34 | + let index = 0; |
| 35 | + for (const elm of categories) { |
| 36 | + let link = document.createElement("a"); |
| 37 | + link.innerText = elm.getAttribute("data-category-name") ?? ""; |
| 38 | + link.href = "#" + elm.id; |
| 39 | + metroHeaderItems.appendChild(link); |
| 40 | +
|
| 41 | + mobileNavItems.push({ |
| 42 | + element: elm, |
| 43 | + icon: elm.getAttribute("data-icon") ?? undefined, |
| 44 | + href: "#" + elm.id, |
| 45 | + highlight: 0, |
| 46 | + }); |
| 47 | +
|
| 48 | + index++; |
41 | 49 | } |
42 | | - mobileNavItems.appendChild(btn); |
43 | | -
|
44 | | - index++; |
45 | | - } |
46 | 50 |
|
47 | | - if (lastPage || currentPage != "/") { |
48 | | - categories[0].scrollIntoView({inline: "center"}); |
49 | | - } |
| 51 | + if (lastPage || currentPage != "/") { |
| 52 | + categories[0].scrollIntoView({inline: "center"}); |
| 53 | + } |
50 | 54 |
|
51 | | - lastPage = currentPage; |
| 55 | + lastPage = currentPage; |
| 56 | + }) |
52 | 57 | }) |
53 | 58 |
|
54 | 59 | onMount(() => { |
|
88 | 93 |
|
89 | 94 | // Update highlight |
90 | 95 | let index = 0; |
91 | | - for (let item of items) { |
92 | | - let highlightValue = Math.min(Math.max(1 - Math.abs(index + 1 - progress), 0), 1) + ""; |
93 | | - item.style.setProperty("--highlight", highlightValue); |
94 | | - (mobileNavItems.childNodes[index] as HTMLElement).style.setProperty("--highlight", highlightValue); |
| 96 | + for (let item of items) { |
| 97 | + let highlightValue = Math.min(Math.max(1 - Math.abs(index + 1 - progress), 0), 1); |
| 98 | + item.style.setProperty("--highlight", highlightValue + ""); |
| 99 | + highlightValue = Math.min(Math.max(index - progress + 2, 0), 2); |
| 100 | + mobileNavItems[index].highlight = highlightValue; |
95 | 101 | index++; |
96 | 102 | } |
97 | | - mobileNavBarItem.style.setProperty("--highlight", Math.max(1 - Math.abs(progress), 0) + ""); |
| 103 | + mobileNavBarItem.style.setProperty("--highlight", Math.max(1 - progress, 0) + ""); |
98 | 104 | } |
99 | 105 |
|
100 | 106 | function scrollToNavBar(e: Event) { |
101 | 107 | e.preventDefault(); |
102 | 108 | navBar.scrollIntoView({inline: "center", behavior: "smooth"}); |
103 | 109 | } |
| 110 | +
|
| 111 | + function scrollToElement(e: Event, elm: Element) { |
| 112 | + e.preventDefault(); |
| 113 | + elm.scrollIntoView({inline: "center", behavior: "smooth"}); |
| 114 | + } |
104 | 115 | </script> |
105 | 116 |
|
106 | 117 |
|
107 | 118 | <div class="mobile-nav" aria-hidden="true"> |
108 | 119 | <a class="pop-out-btn" href="#nav-bar" bind:this={mobileNavBarItem} onclick={scrollToNavBar}> |
109 | 120 | <Icon icon="tabler:menu-2" /> |
110 | 121 | </a> |
111 | | - <span class="mobile-nav-items" bind:this={mobileNavItems}> |
112 | | - |
| 122 | + <span class="mobile-nav-items"> |
| 123 | + {#each mobileNavItems as item, i} |
| 124 | + <a class="pop-out-btn" href={item.href} onclick={(e) => scrollToElement(e, item.element!)} style:--highlight={item.highlight}> |
| 125 | + {#if item.icon} |
| 126 | + <Icon icon={item.icon} /> |
| 127 | + {:else} |
| 128 | + {i + 1} |
| 129 | + {/if} |
| 130 | + </a> |
| 131 | + {/each} |
113 | 132 | </span> |
114 | 133 | </div> |
115 | 134 | <div class="metro-header" aria-hidden="true" bind:this={metroHeader}> |
|
143 | 162 |
|
144 | 163 | .mobile-nav { |
145 | 164 | position: fixed; |
146 | | - inset: auto 2em 2em 2em; |
| 165 | + inset: auto 0 0 0; |
147 | 166 | display: flex; |
| 167 | + padding: 0 4vw 4vw 4vw; |
148 | 168 | justify-content: space-between; |
149 | 169 | z-index: 100; |
150 | 170 | gap: 6px; |
|
162 | 182 | text-decoration: none; |
163 | 183 | color: white; |
164 | 184 | background: black; |
165 | | - width: 2.5em; |
166 | | - height: 2.5em; |
| 185 | + width: 3em; |
| 186 | + height: 3em; |
167 | 187 | } |
168 | 188 | .mobile-nav :global(a)::after { |
169 | 189 | content: ""; |
170 | 190 | position: absolute; |
171 | | - inset: calc(100% * calc(1 - var(--highlight, 0))) 0 0 0; |
172 | | - background: white; |
| 191 | + inset: 0 0 0 0; |
| 192 | + background: linear-gradient(white, white) 0 calc(calc(3em - 4px) * calc(1 - var(--highlight, 0))) / 100% 100% no-repeat; |
173 | 193 | mix-blend-mode: difference; |
174 | 194 | } |
| 195 | + .mobile-nav :global(a > svg) { |
| 196 | + width: 1.6em; |
| 197 | + height: 1.6em; |
| 198 | + } |
175 | 199 |
|
176 | 200 | @media (min-width: 50em) { |
177 | 201 | .metro-header, .mobile-nav { |
|
0 commit comments