|
1 | 1 | <script> |
2 | | - import { t } from "$lib/stores/language.js"; |
3 | | - import ThemeSwitch from "./ThemeSwitch.svelte"; |
| 2 | + import { t } from '$lib/stores/language.js'; |
| 3 | + import { onMount } from 'svelte'; |
4 | 4 |
|
5 | | - export let quests = [ |
6 | | - { href: "#about", text: "nav.about" }, |
7 | | - { href: "#skills", text: "nav.skills" }, |
8 | | - { href: "#experience", text: "nav.experience" }, |
9 | | - { href: "#projects", text: "nav.projects" }, |
10 | | - { href: "#education", text: "nav.education" }, |
11 | | - ]; |
| 5 | + export let quests = [ |
| 6 | + { href: '#about', text: 'nav.about', icon: 'M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z', style: '' }, |
| 7 | + { href: '#skills', text: 'nav.skills', icon: 'M5 13.18v4L12 21l7-3.82v-4L12 17l-7-3.82zM12 3L1 9l11 6 9-4.91V17h2V9L12 3z', style: '' }, |
| 8 | + { href: '#experience', text: 'nav.experience', icon: 'M20 6h-4V4c0-1.11-.89-2-2-2h-4c-1.11 0-2 .89-2 2v2H4c-1.11 0-1.99.89-1.99 2L2 19c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V8c0-1.11-.89-2-2-2zm-6 0h-4V4h4v2z', style: '' }, |
| 9 | + { href: '#projects', text: 'nav.projects', icon: 'M4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm16-4H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-1 9h-4v4h-2v-4H9V9h4V5h2v4h4v2z', style: '' }, |
| 10 | + { href: '#education', text: 'nav.education', icon: 'M5 13.18v4L12 21l7-3.82v-4L12 17l-7-3.82zM12 3L1 9l11 6 9-4.91V17h2V9L12 3z', style: '' } |
| 11 | + ]; |
12 | 12 |
|
13 | | - let selectedQuest = quests[0].href; |
| 13 | + let selectedQuest = quests[0].href; |
| 14 | + /** |
| 15 | + * @type {IntersectionObserver} |
| 16 | + */ |
| 17 | + let observer; |
14 | 18 |
|
15 | | - /** |
16 | | - * @param {string} href |
17 | | - */ |
18 | | - function selectQuest(href) { |
19 | | - selectedQuest = href; |
20 | | - } |
| 19 | + onMount(() => { |
| 20 | + quests = quests.map(q => ({ |
| 21 | + ...q, |
| 22 | + style: `transform: rotate(${(Math.random() - 0.5) * 5}deg); margin-left: ${Math.random() * 20}px;` |
| 23 | + })); |
| 24 | +
|
| 25 | + const sections = document.querySelectorAll('section[id]'); |
| 26 | + observer = new IntersectionObserver( |
| 27 | + (entries) => { |
| 28 | + entries.forEach((entry) => { |
| 29 | + if (entry.isIntersecting) { |
| 30 | + selectedQuest = `#${entry.target.id}`; |
| 31 | + } |
| 32 | + }); |
| 33 | + }, |
| 34 | + { rootMargin: '-50% 0px -50% 0px' } |
| 35 | + ); |
| 36 | +
|
| 37 | + sections.forEach((section) => { |
| 38 | + observer.observe(section); |
| 39 | + }); |
| 40 | +
|
| 41 | + return () => { |
| 42 | + sections.forEach((section) => { |
| 43 | + observer.unobserve(section); |
| 44 | + }); |
| 45 | + }; |
| 46 | + }); |
| 47 | +
|
| 48 | + /** |
| 49 | + * @param {HTMLAnchorElement} node |
| 50 | + */ |
| 51 | + function smoothScroll(node) { |
| 52 | + /** |
| 53 | + * @param {MouseEvent} event |
| 54 | + */ |
| 55 | + function handleClick(event) { |
| 56 | + event.preventDefault(); |
| 57 | + const href = node.href; |
| 58 | + const id = href.substring(href.indexOf('#')); |
| 59 | + const target = document.querySelector(id); |
| 60 | + if (target) { |
| 61 | + target.scrollIntoView({ behavior: 'smooth' }); |
| 62 | + } |
| 63 | + } |
| 64 | +
|
| 65 | + node.addEventListener('click', handleClick); |
| 66 | +
|
| 67 | + return { |
| 68 | + destroy() { |
| 69 | + node.removeEventListener('click', handleClick); |
| 70 | + } |
| 71 | + }; |
| 72 | + } |
21 | 73 | </script> |
22 | 74 |
|
23 | 75 | <aside |
24 | | - class="bg-ctp-latte border-2 border-ctp-mocha rounded-lg p-6 shadow-xl m-5 w-96 flex-shrink-0 overflow-y-auto max-h-[calc(100vh-40px)] sticky top-5 font-serif text-ctp-text dark:bg-ctp-crust dark:border-ctp-mocha dark:text-ctp-text md:w-auto md:m-2.5" |
| 76 | + class="bg-ctp-crust border-2 border-ctp-surface0 rounded-lg p-6 shadow-xl m-5 sticky top-24 h-[calc(100vh-12rem)] overflow-y-auto font-serif text-ctp-text" |
25 | 77 | > |
26 | | - <div class="flex justify-between items-center mb-4"> |
27 | | - <h1 class="text-3xl font-bold text-ctp-text drop-shadow-md"> |
28 | | - {$t("name")} |
29 | | - </h1> |
30 | | - <ThemeSwitch /> |
31 | | - </div> |
32 | | - <div class="text-center mb-6 border-b border-dashed border-ctp-mocha pb-3"> |
33 | | - <h2 class="text-2xl font-bold text-ctp-text drop-shadow-md"> |
34 | | - {$t("journal.title")} |
35 | | - </h2> |
36 | | - </div> |
37 | | - <ul class="list-none p-0 m-0"> |
38 | | - {#each quests as quest, i} |
39 | | - <li |
40 | | - class="mb-3 p-3 bg-ctp-base/30 rounded-md transition-colors hover:bg-ctp-base/50" |
41 | | - class:completed={selectedQuest === quest.href} |
42 | | - > |
43 | | - <a |
44 | | - href={quest.href} |
45 | | - on:click={() => selectQuest(quest.href)} |
46 | | - class="flex items-center no-underline text-ctp-text text-xl" |
47 | | - > |
48 | | - <span |
49 | | - class="w-5 h-5 border-2 rounded-full mr-3 bg-transparent transition-all" |
50 | | - class:border-ctp-green={selectedQuest === quest.href} |
51 | | - class:bg-ctp-green={selectedQuest === quest.href} |
52 | | - class:border-ctp-mocha={selectedQuest !== quest.href} |
53 | | - ></span> |
54 | | - <span |
55 | | - class="flex-grow" |
56 | | - class:line-through={selectedQuest === quest.href} |
57 | | - class:text-ctp-subtext0={selectedQuest === quest.href} |
58 | | - >{$t(quest.text)}</span |
59 | | - > |
60 | | - </a> |
61 | | - </li> |
62 | | - {/each} |
63 | | - </ul> |
| 78 | + <div class="text-center mb-6"> |
| 79 | + <h1 class="text-4xl font-bold text-ctp-text drop-shadow-md font-fantasy">{$t('name')}</h1> |
| 80 | + <h2 class="text-2xl text-ctp-subtext0 font-fantasy">{$t('journal.title')}</h2> |
| 81 | + </div> |
| 82 | + |
| 83 | + <div class="relative py-4"> |
| 84 | + <div class="absolute top-0 left-1/2 -translate-x-1/2 w-full h-px bg-ctp-surface2"></div> |
| 85 | + <svg width="100%" height="20" class="absolute top-0 left-0 right-0"> |
| 86 | + <path d="M0 10 Q 5 0, 10 10 T 20 10 T 30 10 T 40 10 T 50 10 T 60 10 T 70 10 T 80 10 T 90 10 T 100 10" stroke="var(--ctp-surface2)" stroke-width="2" fill="none" /> |
| 87 | + </svg> |
| 88 | + </div> |
| 89 | + |
| 90 | + <ul class="list-none p-0 m-0 space-y-4"> |
| 91 | + {#each quests as quest} |
| 92 | + <li style={quest.style}> |
| 93 | + <a |
| 94 | + href={quest.href} |
| 95 | + use:smoothScroll |
| 96 | + class="flex items-center no-underline text-ctp-text text-lg p-3 rounded-lg transition-all duration-300" |
| 97 | + class:bg-ctp-surface0={selectedQuest === quest.href} |
| 98 | + class:text-ctp-mauve={selectedQuest === quest.href} |
| 99 | + class:hover:bg-ctp-surface1={selectedQuest !== quest.href} |
| 100 | + > |
| 101 | + <svg class="w-6 h-6 mr-4 fill-current" viewBox="0 0 24 24"> |
| 102 | + <path d={quest.icon}></path> |
| 103 | + </svg> |
| 104 | + <span class="flex-grow font-semibold">{$t(quest.text)}</span> |
| 105 | + <div class="w-3 h-3 rounded-full border-2 transition-all" |
| 106 | + class:border-ctp-green={selectedQuest === quest.href} |
| 107 | + class:bg-ctp-green={selectedQuest === quest.href} |
| 108 | + class:border-ctp-surface2={selectedQuest !== quest.href}> |
| 109 | + </div> |
| 110 | + </a> |
| 111 | + </li> |
| 112 | + {/each} |
| 113 | + </ul> |
| 114 | + |
| 115 | + <div class="relative py-4 mt-4"> |
| 116 | + <div class="absolute bottom-0 left-1/2 -translate-x-1/2 w-full h-px bg-ctp-surface2"></div> |
| 117 | + <svg width="100%" height="20" class="absolute bottom-0 left-0 right-0"> |
| 118 | + <path d="M0 10 Q 5 20, 10 10 T 20 10 T 30 10 T 40 10 T 50 10 T 60 10 T 70 10 T 80 10 T 90 10 T 100 10" stroke="var(--ctp-surface2)" stroke-width="2" fill="none" /> |
| 119 | + </svg> |
| 120 | + </div> |
64 | 121 | </aside> |
0 commit comments