ConfigShell.svelte RawHistoryFolder
1<script lang="ts">
2 import { onMount } from 'svelte';
3 import { Home } from '@lucide/svelte';
4
5 interface Props {
6 children?: import('svelte').Snippet;
7 }
8
9 let { children }: Props = $props();
10
11 function navigateWithViewTransition(url: string) {
12 const doc = document as any;
13
14 if (typeof doc?.startViewTransition !== 'function') {
15 window.location.href = url;
16 return;
17 }
18
19 const transition = doc.startViewTransition(() => {
20 document.documentElement.dataset.vt = 'leaving';
21 });
22
23 transition.finished.finally(() => {
24 window.location.href = url;
25 });
26 }
27
28 onMount(() => {
29 // Avoid default WebKit focus rings.
30 document.documentElement.dataset.vt = 'ready';
31 });
32</script>
33
34<svelte:head>
35 <title>dumb://config</title>
36 <meta name="description" content="Dumber Browser - Settings" />
37 {@html `<style>
38 html, body { margin: 0; padding: 0; }
39 html { background: var(--background, #0a0a0a); }
40 body { background: var(--background, #0a0a0a); color: var(--foreground, #e5e5e5); }
41 *:focus { outline: none !important; }
42 *:focus-visible { outline: none !important; }
43 button:focus, input:focus, a:focus { outline: none !important; box-shadow: none !important; }
44 * { -webkit-overflow-scrolling: touch; }
45 </style>`}
46</svelte:head>
47
48<div class="config-shell browser-ui">
49 <div class="terminal-frame">
50 <header class="terminal-header">
51 <div class="header-left">
52 <span class="terminal-path">dumb://config</span>
53 </div>
54 <div class="header-right">
55 <button
56 class="action-button"
57 type="button"
58 onclick={() => navigateWithViewTransition('dumb://home')}
59 title="Back to Home"
60 >
61 <Home size={14} strokeWidth={2} />
62 <span class="button-text">HOME</span>
63 </button>
64 </div>
65 </header>
66
67 <main class="terminal-body scrollable">
68 {#if children}
69 {@render children()}
70 {/if}
71 </main>
72
73 <footer class="terminal-footer">
74 <span class="footer-text">Dumber Browser</span>
75 </footer>
76 </div>
77</div>
78
79<style>
80 :global(html),
81 :global(body) {
82 height: 100%;
83 }
84
85 :global(body) {
86 overflow: hidden;
87 overscroll-behavior: contain;
88 }
89
90 .config-shell {
91 height: 100vh;
92 min-height: 100vh;
93 display: flex;
94 flex-direction: column;
95 font-family: ui-monospace, 'Fira Code', 'Cascadia Code', Menlo, Monaco, Consolas, monospace;
96 line-height: 1.5;
97 color: var(--foreground);
98 background: var(--background);
99 overflow: hidden;
100 }
101
102 .terminal-frame {
103 flex: 1;
104 display: grid;
105 grid-template-rows: auto 1fr auto;
106 width: 100%;
107 height: 100%;
108 background: color-mix(in srgb, var(--card) 60%, var(--background) 40%);
109 }
110
111 :global(.dark) .terminal-frame {
112 box-shadow: 0 24px 48px -12px rgb(0 0 0 / 0.5);
113 }
114
115 .terminal-header {
116 display: flex;
117 align-items: center;
118 justify-content: space-between;
119 gap: 1rem;
120 padding: 0.75rem 1rem;
121 min-height: 3.5rem;
122 border-bottom: 1px solid var(--border);
123 background: color-mix(in srgb, var(--background) 90%, var(--card) 10%);
124 position: sticky;
125 top: 0;
126 z-index: 5;
127 }
128
129 .header-left {
130 display: flex;
131 align-items: center;
132 gap: 1.5rem;
133 }
134
135 .terminal-path {
136 font-size: 0.8rem;
137 font-weight: 600;
138 color: var(--primary, #4ade80);
139 letter-spacing: 0.05em;
140 }
141
142 .header-right {
143 display: flex;
144 align-items: center;
145 gap: 0.5rem;
146 }
147
148 .action-button {
149 display: flex;
150 align-items: center;
151 gap: 0.5rem;
152 padding: 0.4rem 0.75rem;
153 font-size: 0.7rem;
154 font-weight: 500;
155 letter-spacing: 0.08em;
156 color: var(--muted-foreground);
157 background: transparent;
158 border-width: 1px;
159 border-style: solid;
160 border-color: var(--border);
161 cursor: pointer;
162 transition: all 120ms ease;
163 }
164
165 .action-button:hover,
166 .action-button:focus-visible {
167 color: var(--foreground);
168 border-color: color-mix(in srgb, var(--border) 50%, var(--foreground) 50%);
169 background: color-mix(in srgb, var(--card) 30%, transparent);
170 outline: none;
171 }
172
173 .terminal-body {
174 display: flex;
175 flex-direction: column;
176 min-height: 0;
177 overflow: hidden;
178 background: var(--background);
179 }
180
181 .terminal-body.scrollable {
182 overflow-y: auto;
183 -webkit-overflow-scrolling: touch;
184 touch-action: pan-y;
185 }
186
187 .terminal-footer {
188 display: flex;
189 align-items: center;
190 justify-content: center;
191 padding: 0.75rem 1rem;
192 border-top: 1px solid var(--border);
193 background: color-mix(in srgb, var(--background) 92%, var(--card) 8%);
194 }
195
196 .footer-text {
197 font-size: 0.7rem;
198 letter-spacing: 0.08em;
199 color: var(--muted-foreground);
200 }
201
202 /* View Transition helpers */
203 :global(html[data-vt='leaving']) {
204 view-transition-name: config-root;
205 }
206
207 :global(::view-transition-old(config-root)) {
208 animation: vt-fade-out 180ms ease both;
209 }
210
211 :global(::view-transition-new(config-root)) {
212 animation: vt-fade-in 220ms ease both;
213 }
214
215 @keyframes vt-fade-out {
216 to {
217 opacity: 0;
218 transform: translateY(6px);
219 }
220 }
221
222 @keyframes vt-fade-in {
223 from {
224 opacity: 0;
225 transform: translateY(-6px);
226 }
227 }
228</style>