Navbar.svelte 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. <script>
  2. // Svelte 5 runes - navbar owns its mobile + transparency state
  3. import favicon from '$lib/assets/favicon.png';
  4. let scrollY = $state(0);
  5. let mobileMenuOpen = $state(false);
  6. // Receive scroll handler from parent
  7. let { scrollTo } = $props();
  8. function handleScroll() {
  9. scrollY = window.scrollY;
  10. }
  11. function toggleMobileMenu() {
  12. mobileMenuOpen = !mobileMenuOpen;
  13. }
  14. // Keyboard escape for mobile menu
  15. function handleKeydown(e) {
  16. if (e.key === 'Escape') {
  17. if (mobileMenuOpen) mobileMenuOpen = false;
  18. }
  19. }
  20. // Attach listeners (scoped to this component)
  21. $effect(() => {
  22. window.addEventListener('scroll', handleScroll, { passive: true });
  23. window.addEventListener('keydown', handleKeydown);
  24. return () => {
  25. window.removeEventListener('scroll', handleScroll);
  26. window.removeEventListener('keydown', handleKeydown);
  27. };
  28. });
  29. const navSolid = $derived(scrollY > 80);
  30. function handleNavClick(id) {
  31. mobileMenuOpen = false;
  32. scrollTo(id);
  33. }
  34. </script>
  35. <nav class:nav-solid={navSolid}>
  36. <div class="nav-inner">
  37. <button
  38. class="logo logo-btn"
  39. onclick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}
  40. aria-label="ChatRPG home"
  41. >
  42. <div class="logo-icon">
  43. <img src={favicon} alt="ChatRPG logo" width="28" height="28" />
  44. </div>
  45. <span class="logo-text">ChatRPG</span>
  46. </button>
  47. <!-- Desktop Nav -->
  48. <div class="nav-links">
  49. <button onclick={() => handleNavClick('how')} class="nav-link">How it Works</button>
  50. <button onclick={() => handleNavClick('features')} class="nav-link">Features</button>
  51. <button onclick={() => handleNavClick('pricing')} class="nav-link">Pricing</button>
  52. <button onclick={() => handleNavClick('faq')} class="nav-link">FAQ</button>
  53. </div>
  54. <div class="nav-actions">
  55. <a href="/login" class="btn btn-secondary">Log In</a>
  56. <a href="/register" class="btn btn-primary">Get Started</a>
  57. </div>
  58. <!-- Mobile hamburger -->
  59. <button class="mobile-toggle" onclick={toggleMobileMenu} aria-label="Toggle menu">
  60. <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
  61. {#if mobileMenuOpen}
  62. <path d="M18 6L6 18M6 6l12 12"/>
  63. {:else}
  64. <path d="M3 12h18M3 6h18M3 18h18"/>
  65. {/if}
  66. </svg>
  67. </button>
  68. </div>
  69. <!-- Mobile Menu -->
  70. {#if mobileMenuOpen}
  71. <div class="mobile-menu">
  72. <button onclick={() => handleNavClick('how')} class="mobile-link">How it Works</button>
  73. <button onclick={() => handleNavClick('features')} class="mobile-link">Features</button>
  74. <button onclick={() => handleNavClick('pricing')} class="mobile-link">Pricing</button>
  75. <button onclick={() => handleNavClick('faq')} class="mobile-link">FAQ</button>
  76. <div class="mobile-ctas">
  77. <button class="btn btn-primary full" onclick={() => handleNavClick('pricing')}>See Pricing</button>
  78. </div>
  79. </div>
  80. {/if}
  81. </nav>
  82. <style>
  83. /* Navbar specific styles */
  84. nav {
  85. position: fixed;
  86. top: 0;
  87. left: 0;
  88. right: 0;
  89. z-index: 100;
  90. background: rgba(11, 11, 16, 0.7);
  91. backdrop-filter: blur(12px);
  92. border-bottom: 1px solid transparent;
  93. transition: all 0.2s ease;
  94. }
  95. .nav-solid {
  96. background: #0b0b10;
  97. border-bottom-color: #2a2a35;
  98. }
  99. .nav-inner {
  100. max-width: 1080px;
  101. margin: 0 auto;
  102. padding: 0 24px;
  103. height: 68px;
  104. display: flex;
  105. align-items: center;
  106. justify-content: space-between;
  107. }
  108. .logo {
  109. font-size: 21px;
  110. }
  111. .logo-icon {
  112. width: 28px;
  113. height: 28px;
  114. display: flex;
  115. align-items: center;
  116. justify-content: center;
  117. }
  118. .logo-btn {
  119. background: none;
  120. border: none;
  121. padding: 0;
  122. color: inherit;
  123. font: inherit;
  124. }
  125. .nav-links {
  126. display: flex;
  127. gap: 6px;
  128. }
  129. .nav-link {
  130. background: none;
  131. border: none;
  132. color: #a1a1aa;
  133. padding: 8px 16px;
  134. font-size: 14.5px;
  135. font-weight: 500;
  136. cursor: pointer;
  137. border-radius: 8px;
  138. transition: all 0.1s ease;
  139. }
  140. .nav-link:hover {
  141. color: #e5e5ea;
  142. background: #1f1f27;
  143. }
  144. .nav-actions {
  145. display: flex;
  146. gap: 10px;
  147. align-items: center;
  148. }
  149. .mobile-toggle {
  150. display: none;
  151. background: none;
  152. border: 1px solid #2a2a35;
  153. color: #e5e5ea;
  154. padding: 6px 10px;
  155. border-radius: 8px;
  156. cursor: pointer;
  157. }
  158. .mobile-menu {
  159. display: none;
  160. padding: 12px 24px 24px;
  161. background: #0b0b10;
  162. border-top: 1px solid #2a2a35;
  163. }
  164. .mobile-link {
  165. display: block;
  166. width: 100%;
  167. text-align: left;
  168. background: none;
  169. border: none;
  170. color: #e5e5ea;
  171. padding: 14px 0;
  172. font-size: 16px;
  173. border-bottom: 1px solid #1f1f27;
  174. cursor: pointer;
  175. }
  176. .mobile-link:last-of-type {
  177. border-bottom: none;
  178. }
  179. .mobile-ctas {
  180. display: flex;
  181. flex-direction: column;
  182. gap: 10px;
  183. margin-top: 16px;
  184. }
  185. @media (max-width: 700px) {
  186. .nav-links,
  187. .nav-actions {
  188. display: none;
  189. }
  190. .mobile-toggle {
  191. display: block;
  192. }
  193. .mobile-menu {
  194. display: block;
  195. }
  196. }
  197. </style>