main
svelte 181 lines 7.93 KB
Raw
1 <script lang="ts">
2 import { base } from '$app/paths';
3 import { page } from '$app/stores';
4 import { ModeWatcher } from 'mode-watcher';
5 import Sidebar from '$lib/components/Sidebar.svelte';
6 import ThemeToggle from '$lib/components/ThemeToggle.svelte';
7 import MobileNav from '$lib/components/MobileNav.svelte';
8 import TableOfContents from '$lib/components/TableOfContents.svelte';
9 import PrevNextNav from '$lib/components/PrevNextNav.svelte';
10 import { getPrevNext, guidesNavigation, referencesNavigation, isReferencePage } from '$lib/nav';
11 import SearchButton from '$lib/components/SearchButton.svelte';
12 import SearchModal from '$lib/components/SearchModal.svelte';
13 import { copyCode } from '$lib/actions/copy-code';
14 import { useEventListener } from 'runed';
15 import '../app.css';
16
17 let { children } = $props();
18 let mobileNavOpen = $state(false);
19 let searchOpen = $state(false);
20
21 useEventListener(
22 () => (typeof window !== 'undefined' ? window : null),
23 'keydown',
24 (e: Event) => {
25 const ke = e as KeyboardEvent;
26 if ((ke.metaKey || ke.ctrlKey) && ke.key === 'k') {
27 ke.preventDefault();
28 searchOpen = !searchOpen;
29 }
30 }
31 );
32
33 const isLandingPage = $derived(
34 $page.url.pathname === `${base}/` || $page.url.pathname === base || $page.url.pathname === '/'
35 );
36
37 const prevNext = $derived(getPrevNext($page.url.pathname, base));
38 const isReference = $derived(isReferencePage($page.url.pathname, base));
39 const sidebarSections = $derived(isReference ? referencesNavigation : guidesNavigation);
40 </script>
41
42 <ModeWatcher defaultMode="system" />
43 <SearchModal bind:open={searchOpen} />
44
45 <div class="min-h-screen">
46 <!-- Header — relay-style with glassmorphism for docs -->
47 <header
48 class="sticky top-0 z-30 w-full bg-background/95 py-5 backdrop-blur"
49 >
50 <div class="flex w-full items-center gap-4 px-6 py-2 sm:px-8 lg:px-10">
51 {#if !isLandingPage}
52 <!-- Mobile menu button (doc pages only) -->
53 <button
54 class="rounded-lg p-1.5 text-gray-500 hover:bg-gray-100 lg:hidden dark:text-gray-400 dark:hover:bg-white/8"
55 onclick={() => (mobileNavOpen = true)}
56 aria-label="Open navigation"
57 >
58 <svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
59 <path
60 stroke-linecap="round"
61 stroke-linejoin="round"
62 stroke-width="2"
63 d="M4 6h16M4 12h16M4 18h16"
64 />
65 </svg>
66 </button>
67 {/if}
68
69 <!-- Logo -->
70 <a href="{base}/" class="flex items-center gap-2">
71 <div class="flex h-10 w-10 shrink-0 items-center justify-center">
72 <svg
73 xmlns="http://www.w3.org/2000/svg"
74 width="24"
75 height="24"
76 viewBox="0 0 906.26 1457.543"
77 class="h-6 w-6 text-primary"
78 >
79 <path
80 fill="currentColor"
81 d="M254.854 137.158c-34.46 84.407-88.363 149.39-110.934 245.675 90.926-187.569 308.397-483.654 554.729-348.685 135.487 74.216 194.878 270.78 206.058 467.566 21.924 385.996-190.977 853.604-467.585 943.057-174.879 56.543-307.375-86.447-364.527-198.115-176.498-344.82 2.041-910.077 182.259-1109.498zm198.13 7.918C202.61 280.257 4.622 968.542 207.322 1270.414c51.713 77.029 194.535 160.648 285.294 71.318-209.061 31.529-288.389-176.143-301.145-340.765 31.411 147.743 139.396 326.12 309.075 253.588 251.957-107.723 376.778-648.46 269.433-966.817 22.394 134.616 15.572 317.711-47.551 412.087 86.655-230.615 7.903-704.478-269.444-554.749z"
82 />
83 </svg>
84 </div>
85 <span class="font-display text-xl font-extrabold tracking-tight text-foreground sm:text-2xl">
86 PORTAL
87 </span>
88 <span class="inline-flex h-6 items-center rounded-full bg-secondary px-2.5 text-xs font-semibold text-text-muted">v2</span>
89 </a>
90
91 <SearchButton onclick={() => (searchOpen = true)} />
92
93 <div class="flex-1"></div>
94
95 <!-- Nav links (hidden on mobile, visible xl) -->
96 <nav class="hidden items-center gap-6 text-base font-semibold text-gray-500 xl:flex dark:text-gray-400">
97 <a href="{base}/what-is-portal" class="transition-colors {!isLandingPage && !isReference ? 'text-foreground' : 'hover:text-gray-900 dark:hover:text-white'}">
98 Guides
99 </a>
100 <a href="{base}/cli-reference" class="transition-colors {isReference ? 'text-foreground' : 'hover:text-gray-900 dark:hover:text-white'}">
101 References
102 </a>
103 </nav>
104
105 <!-- GitHub icon button -->
106 <a
107 href="https://github.com/gosuda/portal-tunnel"
108 target="_blank"
109 rel="noopener noreferrer"
110 class="inline-flex h-12 w-12 shrink-0 items-center justify-center rounded-full border border-border/70 bg-background/90 text-foreground shadow-sm transition-all hover:-translate-y-0.5 hover:border-primary/40 hover:text-primary"
111 aria-label="View source on GitHub"
112 >
113 <svg height="22" width="22" viewBox="0 0 24 24" fill="currentColor" class="opacity-85 transition-opacity hover:opacity-100">
114 <path
115 d="M12 1C5.923 1 1 5.923 1 12c0 4.867 3.149 8.979 7.521 10.436.55.096.756-.233.756-.522 0-.262-.013-1.128-.013-2.049-2.764.509-3.479-.674-3.699-1.292-.124-.317-.66-1.293-1.127-1.554-.385-.207-.936-.715-.014-.729.866-.014 1.485.797 1.691 1.128.99 1.663 2.571 1.196 3.204.907.096-.715.385-1.196.701-1.471-2.448-.275-5.005-1.224-5.005-5.432 0-1.196.426-2.186 1.128-2.956-.111-.275-.496-1.402.11-2.915 0 0 .921-.288 3.024 1.128a10.193 10.193 0 0 1 2.75-.371c.936 0 1.871.123 2.75.371 2.104-1.43 3.025-1.128 3.025-1.128.605 1.513.221 2.64.111 2.915.701.77 1.127 1.747 1.127 2.956 0 4.222-2.571 5.157-5.019 5.432.399.344.743 1.004.743 2.035 0 1.471-.014 2.654-.014 3.025 0 .289.206.632.756.522C19.851 20.979 23 16.854 23 12c0-6.077-4.922-11-11-11Z"
116 />
117 </svg>
118 </a>
119
120 <ThemeToggle />
121 </div>
122 </header>
123
124 {#if !isLandingPage}
125 <MobileNav bind:open={mobileNavOpen} sections={sidebarSections} />
126 {/if}
127
128 {#if isLandingPage}
129 <!-- Landing page: max-width container with side borders (matches React frontend) -->
130 <div class="mx-auto flex w-full max-w-6xl flex-1 flex-col border-x border-border/80">
131 <main class="px-4 pt-6 pb-8 sm:px-6 sm:pb-10 md:px-8">
132 {@render children()}
133 </main>
134 </div>
135 <footer class="w-full border-t bg-secondary/35">
136 <div class="flex w-full flex-col gap-6 px-6 py-8 sm:px-8 md:flex-row md:items-end md:justify-between lg:px-10">
137 <div class="space-y-1.5">
138 <a href="{base}/" class="inline-block text-lg font-bold tracking-tight text-foreground transition-colors hover:text-primary">
139 PORTAL
140 </a>
141 <p class="text-sm text-text-muted">
142 Public relay index and localhost tunnel launcher.
143 </p>
144 </div>
145 <nav aria-label="Footer" class="flex flex-wrap items-center gap-x-6 gap-y-2 text-sm text-text-muted md:justify-end">
146 <a href="{base}/getting-started" class="transition-colors hover:text-foreground">Docs</a>
147 <a href="https://github.com/gosuda/portal-tunnel" target="_blank" rel="noopener noreferrer" class="transition-colors hover:text-foreground">Source</a>
148 </nav>
149 </div>
150 </footer>
151 {:else}
152 <!-- Doc pages: sidebars + prose -->
153 <div class="mx-auto max-w-[90rem] lg:flex">
154 <aside class="hidden w-64 shrink-0 border-r border-border lg:block">
155 <div class="sticky top-[var(--header-h)] h-[calc(100vh-var(--header-h))] overflow-y-auto p-6">
156 <Sidebar sections={sidebarSections} />
157 </div>
158 </aside>
159
160 <main class="min-w-0 flex-1 px-4 py-8 lg:px-8">
161 <aside role="note" data-disclaimer class="mx-auto mb-6 max-w-3xl rounded-xl border border-primary/20 bg-primary/[0.08] px-4 py-3 text-sm font-medium text-foreground/70 shadow-sm backdrop-blur-sm">
162 This documentation is under active development. Some pages may be incomplete or change without notice.
163 </aside>
164 {#key $page.url.pathname}
165 <article use:copyCode data-pagefind-body class="prose prose-gray dark:prose-invert mx-auto max-w-3xl">
166 {@render children()}
167 </article>
168 {/key}
169 <div class="mx-auto max-w-3xl">
170 <PrevNextNav prev={prevNext.prev} next={prevNext.next} />
171 </div>
172 </main>
173
174 <aside class="hidden w-56 shrink-0 border-l border-border xl:block">
175 <div class="sticky top-[var(--header-h)] h-[calc(100vh-var(--header-h))] overflow-y-auto py-8 px-4">
176 <TableOfContents />
177 </div>
178 </aside>
179 </div>
180 {/if}
181 </div>