detached 27396fb

feat(ui): implement terminal-style theme for omnibox components

Omnibox Container:
- Switch to monospace font (JetBrains Mono, Fira Code)
- Remove border-radius (sharp corners throughout)
- Adjust margins (20vh top instead of 8vh)
- Update backdrop blur (1.5px instead of 2px)
- Refine color mixing ratios for better contrast
- Add layered box-shadow for depth
- Only apply fading effect in find mode, not omnibox mode
- Remove text-shadow from fade effects

Suggestions List:
- Add proper CSS classes instead of inline Tailwind
- Use dashed borders for items
- Update hover/selection states
- Adjust spacing and typography (uppercase, letter-spacing)
- Update favicon styling (square borders, proper sizing)
- Add gradient mask for text overflow

Find List:
- Match suggestions styling with proper CSS classes
- Use dashed borders and consistent spacing
- Update header styling (uppercase, letter-spacing)

Highlight Marks:
- Remove border-radius from find highlights for consistency
commit
27396fb72bc823ca91cc677c335a05c2d38fc01e
author
bamen · · 2025-10-06 20:11 UTC
committer
bamen · · 2025-10-06 16:13 UTC
parent
babf2f5
signature
unsigned
4 files · +194 -73
M gui/src/components/omnibox/Omnibox.svelte +59 -35
@@ -177,18 +177,20 @@
177177 // defensive: ignore if setting styles fails for some reason
178178 }
179179
180- const surfaceMix = isDarkMode ? '65%' : '92%';
181- const inputMix = isDarkMode ? '60%' : '94%';
182- const borderMix = isDarkMode ? '70%' : '90%';
183- const blurMix = isDarkMode ? '28%' : '16%';
180+ const surfaceMix = isDarkMode ? '55%' : '88%';
181+ const inputMix = isDarkMode ? '52%' : '90%';
182+ const borderMix = isDarkMode ? '62%' : '85%';
183+ const blurMix = isDarkMode ? '22%' : '12%';
184184
185- const containerBackground = faded
185+ const applyFade = faded && mode === 'find';
186+
187+ const containerBackground = applyFade
186188 ? `color-mix(in srgb, var(--dynamic-surface) ${surfaceMix}, transparent)`
187189 : 'var(--dynamic-surface)';
188- const inputBackground = faded
190+ const inputBackground = applyFade
189191 ? `color-mix(in srgb, var(--dynamic-bg) ${inputMix}, transparent)`
190192 : 'var(--dynamic-bg)';
191- const borderColor = faded
193+ const borderColor = applyFade
192194 ? `color-mix(in srgb, var(--dynamic-border) ${borderMix}, transparent)`
193195 : 'var(--dynamic-border)';
194196 const blurTint = `color-mix(in srgb, var(--dynamic-bg) ${blurMix}, transparent)`;
@@ -196,7 +198,8 @@
196198 boxElement.style.background = containerBackground;
197199 boxElement.style.color = 'var(--dynamic-text)';
198200 boxElement.style.setProperty('--dumber-border-color', borderColor);
199- boxElement.style.setProperty('--dumber-omnibox-surface', containerBackground);
201+ // Force the omnibox to use our neutral surfaces so it stays in sync with the shell palette
202+ boxElement.style.setProperty('--dumber-omnibox-surface', 'color-mix(in srgb, var(--dynamic-bg) 88%, var(--dynamic-surface) 12%)');
200203
201204 inputElement.style.background = inputBackground;
202205 inputElement.style.color = 'var(--dynamic-text)';
@@ -217,24 +220,17 @@
217220 // If computed styles fail, keep fallback accent color
218221 }
219222 inputElement.style.setProperty('--dumber-input-accent', accentColor);
220- boxElement.style.setProperty('--dumber-input-accent', accentColor);
223+ boxElement.style.setProperty('--dumber-input-accent', 'var(--dynamic-muted)');
221224
222225 const isFocused = document.activeElement === inputElement;
223226
224227 if (!isFocused) {
225- inputElement.style.setProperty('--dumber-input-border-color', borderColor);
228+ inputElement.style.setProperty('--dumber-input-border-color', 'var(--dynamic-border)');
226229 }
227230
228- if (faded) {
229- const textShadow = isDarkMode
230- ? '1px 1px 1px rgba(0,0,0,0.65)'
231- : '1px 1px 1px rgba(255,255,255,0.65)';
232- inputElement.style.textShadow = textShadow;
233- boxElement.style.textShadow = textShadow;
231+ if (applyFade) {
234232 blurLayerElement.style.opacity = '1';
235233 } else {
236- inputElement.style.textShadow = '';
237- boxElement.style.textShadow = '';
238234 blurLayerElement.style.opacity = '0';
239235 }
240236 });
@@ -388,21 +384,26 @@
388384 <!-- Main omnibox container -->
389385 <div
390386 bind:this={boxElement}
391- class="dumber-omnibox-container omnibox-base rounded shadow-[0_10px_30px_rgba(0,0,0,0.6)]"
387+ class="dumber-omnibox-container omnibox-base terminal-omnibox"
392388 style="position: relative !important;
393389 left: 50% !important;
394390 transform: translateX(-50%) !important;
395- margin-top: 8vh !important;
391+ margin-top: 20vh !important;
396392 margin-left: 0 !important;
397393 margin-right: 0 !important;
398394 width: {responsiveStyles.width};
399395 padding: {responsiveStyles.padding};
400- font-family: 'Fira sans', system-ui, -apple-system, 'Segoe UI', Roboto, Ubuntu, 'Helvetica Neue', Arial, sans-serif;
396+ font-family: 'JetBrains Mono', 'Fira Code', 'SFMono-Regular', Menlo, monospace;
401397 pointer-events: auto !important;
402398 box-sizing: border-box !important;
403- background: var(--dumber-omnibox-surface, var(--dynamic-surface));
399+ background: var(--dumber-omnibox-surface, color-mix(in srgb, var(--dynamic-bg) 88%, var(--dynamic-surface) 12%));
404400 color: var(--dynamic-text);
405- border: 2px solid var(--dumber-border-color, var(--dynamic-border));
401+ border: 1px solid var(--dumber-border-color, var(--dynamic-border));
402+ box-shadow:
403+ 0 20px 60px rgba(0, 0, 0, 0.45),
404+ 0 10px 30px rgba(0, 0, 0, 0.35),
405+ 0 5px 15px rgba(0, 0, 0, 0.25),
406+ inset 0 0 0 1px color-mix(in srgb, var(--dynamic-border) 22%, transparent);
406407 --dumber-border-color: var(--dynamic-border);"
407408 onmousedown={handleBoxClick}
408409 onmouseenter={handleMouseEnter}
@@ -418,7 +419,7 @@
418419 bind:this={blurLayerElement}
419420 class="dumber-omnibox-blur-layer"
420421 aria-hidden="true"
421- style="position: absolute; inset: 0; pointer-events: none; border-radius: inherit; opacity: 0;"
422+ style="position: absolute; inset: 0; pointer-events: none; border-radius: 0; opacity: 0;"
422423 ></div>
423424 <div class="dumber-omnibox-content">
424425 <!-- Input component -->
@@ -436,36 +437,59 @@
436437
437438 <style>
438439 .dumber-omnibox-container {
439- background: var(--dumber-omnibox-surface, var(--dynamic-surface));
440+ background: var(--dumber-omnibox-surface, color-mix(in srgb, var(--dynamic-bg) 88%, var(--dynamic-surface) 12%));
440441 color: var(--dynamic-text);
441- border: 2px solid var(--dumber-border-color, var(--dynamic-border));
442- transition: background-color 160ms ease, border-color 160ms ease;
442+ border: 1px solid var(--dumber-border-color, var(--dynamic-border));
443+ transition: background-color 140ms ease, border-color 140ms ease, box-shadow 140ms ease;
444+ border-radius: 0;
445+ box-shadow:
446+ 0 20px 60px rgba(0, 0, 0, 0.45),
447+ 0 10px 30px rgba(0, 0, 0, 0.35),
448+ 0 5px 15px rgba(0, 0, 0, 0.25),
449+ inset 0 0 0 1px color-mix(in srgb, var(--dynamic-border) 22%, transparent);
443450 }
444451
445452 .dumber-omnibox-container:focus-within {
446- border-color: var(--dumber-input-accent, var(--dynamic-accent));
453+ border-color: color-mix(in srgb, var(--dumber-border-color, var(--dynamic-border)) 55%, var(--dynamic-text) 45%);
454+ box-shadow:
455+ 0 25px 75px rgba(0, 0, 0, 0.5),
456+ 0 15px 40px rgba(0, 0, 0, 0.4),
457+ 0 8px 20px rgba(0, 0, 0, 0.3),
458+ inset 0 0 0 1px color-mix(in srgb, var(--dynamic-border) 28%, transparent);
447459 }
448460
449461 :global(.dumber-omnibox-container input) {
450- background: var(--dumber-input-bg, var(--dynamic-bg));
462+ background: var(--dumber-input-bg, color-mix(in srgb, var(--dynamic-bg) 92%, var(--dynamic-surface) 8%));
451463 color: var(--dynamic-text);
452- border-color: var(--dumber-input-border-color, var(--dynamic-border));
453- transition: border-color 120ms ease, background-color 120ms ease;
464+ border: 1px solid var(--dumber-input-border-color, var(--dynamic-border));
465+ transition: border-color 120ms ease, background-color 120ms ease, color 120ms ease;
466+ font-family:
467+ "JetBrains Mono",
468+ "Fira Code",
469+ "SFMono-Regular",
470+ Menlo,
471+ monospace;
472+ font-size: inherit;
473+ letter-spacing: 0.05em;
474+ text-transform: none;
454475 }
455476
456477 :global(.dumber-omnibox-container input::placeholder) {
457478 color: var(--dynamic-muted);
479+ letter-spacing: 0.08em;
480+ text-transform: uppercase;
458481 }
459482
460483 /* Blur layer styles: use `backdrop-filter` but animate opacity only. */
461484 .dumber-omnibox-blur-layer {
462- backdrop-filter: blur(2px) saturate(110%);
463- -webkit-backdrop-filter: blur(2px) saturate(110%);
464- background: var(--dumber-blur-color, color-mix(in srgb, var(--dynamic-bg) 16%, transparent));
465- transition: opacity 160ms ease;
485+ backdrop-filter: blur(1.5px) saturate(105%);
486+ -webkit-backdrop-filter: blur(1.5px) saturate(105%);
487+ background: var(--dumber-blur-color, color-mix(in srgb, var(--dynamic-bg) 12%, transparent));
488+ transition: opacity 140ms ease;
466489 will-change: opacity;
467490 z-index: 0;
468491 mix-blend-mode: normal;
492+ border-radius: 0;
469493 }
470494
471495 /* Ensure the actual content (input, suggestions) sits above the blur layer */
@@ -177,18 +177,20 @@
177 // defensive: ignore if setting styles fails for some reason177 // defensive: ignore if setting styles fails for some reason
178 }178 }
179 179
180- const surfaceMix = isDarkMode ? '65%' : '92%';180+ const surfaceMix = isDarkMode ? '55%' : '88%';
181- const inputMix = isDarkMode ? '60%' : '94%';181+ const inputMix = isDarkMode ? '52%' : '90%';
182- const borderMix = isDarkMode ? '70%' : '90%';182+ const borderMix = isDarkMode ? '62%' : '85%';
183- const blurMix = isDarkMode ? '28%' : '16%';183+ const blurMix = isDarkMode ? '22%' : '12%';
184 184
185- const containerBackground = faded185+ const applyFade = faded && mode === 'find';
186+
187+ const containerBackground = applyFade
186 ? `color-mix(in srgb, var(--dynamic-surface) ${surfaceMix}, transparent)`188 ? `color-mix(in srgb, var(--dynamic-surface) ${surfaceMix}, transparent)`
187 : 'var(--dynamic-surface)';189 : 'var(--dynamic-surface)';
188- const inputBackground = faded190+ const inputBackground = applyFade
189 ? `color-mix(in srgb, var(--dynamic-bg) ${inputMix}, transparent)`191 ? `color-mix(in srgb, var(--dynamic-bg) ${inputMix}, transparent)`
190 : 'var(--dynamic-bg)';192 : 'var(--dynamic-bg)';
191- const borderColor = faded193+ const borderColor = applyFade
192 ? `color-mix(in srgb, var(--dynamic-border) ${borderMix}, transparent)`194 ? `color-mix(in srgb, var(--dynamic-border) ${borderMix}, transparent)`
193 : 'var(--dynamic-border)';195 : 'var(--dynamic-border)';
194 const blurTint = `color-mix(in srgb, var(--dynamic-bg) ${blurMix}, transparent)`;196 const blurTint = `color-mix(in srgb, var(--dynamic-bg) ${blurMix}, transparent)`;
@@ -196,7 +198,8 @@
196 boxElement.style.background = containerBackground;198 boxElement.style.background = containerBackground;
197 boxElement.style.color = 'var(--dynamic-text)';199 boxElement.style.color = 'var(--dynamic-text)';
198 boxElement.style.setProperty('--dumber-border-color', borderColor);200 boxElement.style.setProperty('--dumber-border-color', borderColor);
199- boxElement.style.setProperty('--dumber-omnibox-surface', containerBackground);201+ // Force the omnibox to use our neutral surfaces so it stays in sync with the shell palette
202+ boxElement.style.setProperty('--dumber-omnibox-surface', 'color-mix(in srgb, var(--dynamic-bg) 88%, var(--dynamic-surface) 12%)');
200 203
201 inputElement.style.background = inputBackground;204 inputElement.style.background = inputBackground;
202 inputElement.style.color = 'var(--dynamic-text)';205 inputElement.style.color = 'var(--dynamic-text)';
@@ -217,24 +220,17 @@
217 // If computed styles fail, keep fallback accent color220 // If computed styles fail, keep fallback accent color
218 }221 }
219 inputElement.style.setProperty('--dumber-input-accent', accentColor);222 inputElement.style.setProperty('--dumber-input-accent', accentColor);
220- boxElement.style.setProperty('--dumber-input-accent', accentColor);223+ boxElement.style.setProperty('--dumber-input-accent', 'var(--dynamic-muted)');
221 224
222 const isFocused = document.activeElement === inputElement;225 const isFocused = document.activeElement === inputElement;
223 226
224 if (!isFocused) {227 if (!isFocused) {
225- inputElement.style.setProperty('--dumber-input-border-color', borderColor);228+ inputElement.style.setProperty('--dumber-input-border-color', 'var(--dynamic-border)');
226 }229 }
227 230
228- if (faded) {231+ if (applyFade) {
229- const textShadow = isDarkMode
230- ? '1px 1px 1px rgba(0,0,0,0.65)'
231- : '1px 1px 1px rgba(255,255,255,0.65)';
232- inputElement.style.textShadow = textShadow;
233- boxElement.style.textShadow = textShadow;
234 blurLayerElement.style.opacity = '1';232 blurLayerElement.style.opacity = '1';
235 } else {233 } else {
236- inputElement.style.textShadow = '';
237- boxElement.style.textShadow = '';
238 blurLayerElement.style.opacity = '0';234 blurLayerElement.style.opacity = '0';
239 }235 }
240 });236 });
@@ -388,21 +384,26 @@
388 <!-- Main omnibox container -->384 <!-- Main omnibox container -->
389 <div385 <div
390 bind:this={boxElement}386 bind:this={boxElement}
391- class="dumber-omnibox-container omnibox-base rounded shadow-[0_10px_30px_rgba(0,0,0,0.6)]"387+ class="dumber-omnibox-container omnibox-base terminal-omnibox"
392 style="position: relative !important;388 style="position: relative !important;
393 left: 50% !important;389 left: 50% !important;
394 transform: translateX(-50%) !important;390 transform: translateX(-50%) !important;
395- margin-top: 8vh !important;391+ margin-top: 20vh !important;
396 margin-left: 0 !important;392 margin-left: 0 !important;
397 margin-right: 0 !important;393 margin-right: 0 !important;
398 width: {responsiveStyles.width};394 width: {responsiveStyles.width};
399 padding: {responsiveStyles.padding};395 padding: {responsiveStyles.padding};
400- font-family: 'Fira sans', system-ui, -apple-system, 'Segoe UI', Roboto, Ubuntu, 'Helvetica Neue', Arial, sans-serif;396+ font-family: 'JetBrains Mono', 'Fira Code', 'SFMono-Regular', Menlo, monospace;
401 pointer-events: auto !important;397 pointer-events: auto !important;
402 box-sizing: border-box !important;398 box-sizing: border-box !important;
403- background: var(--dumber-omnibox-surface, var(--dynamic-surface));399+ background: var(--dumber-omnibox-surface, color-mix(in srgb, var(--dynamic-bg) 88%, var(--dynamic-surface) 12%));
404 color: var(--dynamic-text);400 color: var(--dynamic-text);
405- border: 2px solid var(--dumber-border-color, var(--dynamic-border));401+ border: 1px solid var(--dumber-border-color, var(--dynamic-border));
402+ box-shadow:
403+ 0 20px 60px rgba(0, 0, 0, 0.45),
404+ 0 10px 30px rgba(0, 0, 0, 0.35),
405+ 0 5px 15px rgba(0, 0, 0, 0.25),
406+ inset 0 0 0 1px color-mix(in srgb, var(--dynamic-border) 22%, transparent);
406 --dumber-border-color: var(--dynamic-border);"407 --dumber-border-color: var(--dynamic-border);"
407 onmousedown={handleBoxClick}408 onmousedown={handleBoxClick}
408 onmouseenter={handleMouseEnter}409 onmouseenter={handleMouseEnter}
@@ -418,7 +419,7 @@
418 bind:this={blurLayerElement}419 bind:this={blurLayerElement}
419 class="dumber-omnibox-blur-layer"420 class="dumber-omnibox-blur-layer"
420 aria-hidden="true"421 aria-hidden="true"
421- style="position: absolute; inset: 0; pointer-events: none; border-radius: inherit; opacity: 0;"422+ style="position: absolute; inset: 0; pointer-events: none; border-radius: 0; opacity: 0;"
422 ></div>423 ></div>
423 <div class="dumber-omnibox-content">424 <div class="dumber-omnibox-content">
424 <!-- Input component -->425 <!-- Input component -->
@@ -436,36 +437,59 @@
436 437
437 <style>438 <style>
438 .dumber-omnibox-container {439 .dumber-omnibox-container {
439- background: var(--dumber-omnibox-surface, var(--dynamic-surface));440+ background: var(--dumber-omnibox-surface, color-mix(in srgb, var(--dynamic-bg) 88%, var(--dynamic-surface) 12%));
440 color: var(--dynamic-text);441 color: var(--dynamic-text);
441- border: 2px solid var(--dumber-border-color, var(--dynamic-border));442+ border: 1px solid var(--dumber-border-color, var(--dynamic-border));
442- transition: background-color 160ms ease, border-color 160ms ease;443+ transition: background-color 140ms ease, border-color 140ms ease, box-shadow 140ms ease;
444+ border-radius: 0;
445+ box-shadow:
446+ 0 20px 60px rgba(0, 0, 0, 0.45),
447+ 0 10px 30px rgba(0, 0, 0, 0.35),
448+ 0 5px 15px rgba(0, 0, 0, 0.25),
449+ inset 0 0 0 1px color-mix(in srgb, var(--dynamic-border) 22%, transparent);
443 }450 }
444 451
445 .dumber-omnibox-container:focus-within {452 .dumber-omnibox-container:focus-within {
446- border-color: var(--dumber-input-accent, var(--dynamic-accent));453+ border-color: color-mix(in srgb, var(--dumber-border-color, var(--dynamic-border)) 55%, var(--dynamic-text) 45%);
454+ box-shadow:
455+ 0 25px 75px rgba(0, 0, 0, 0.5),
456+ 0 15px 40px rgba(0, 0, 0, 0.4),
457+ 0 8px 20px rgba(0, 0, 0, 0.3),
458+ inset 0 0 0 1px color-mix(in srgb, var(--dynamic-border) 28%, transparent);
447 }459 }
448 460
449 :global(.dumber-omnibox-container input) {461 :global(.dumber-omnibox-container input) {
450- background: var(--dumber-input-bg, var(--dynamic-bg));462+ background: var(--dumber-input-bg, color-mix(in srgb, var(--dynamic-bg) 92%, var(--dynamic-surface) 8%));
451 color: var(--dynamic-text);463 color: var(--dynamic-text);
452- border-color: var(--dumber-input-border-color, var(--dynamic-border));464+ border: 1px solid var(--dumber-input-border-color, var(--dynamic-border));
453- transition: border-color 120ms ease, background-color 120ms ease;465+ transition: border-color 120ms ease, background-color 120ms ease, color 120ms ease;
466+ font-family:
467+ "JetBrains Mono",
468+ "Fira Code",
469+ "SFMono-Regular",
470+ Menlo,
471+ monospace;
472+ font-size: inherit;
473+ letter-spacing: 0.05em;
474+ text-transform: none;
454 }475 }
455 476
456 :global(.dumber-omnibox-container input::placeholder) {477 :global(.dumber-omnibox-container input::placeholder) {
457 color: var(--dynamic-muted);478 color: var(--dynamic-muted);
479+ letter-spacing: 0.08em;
480+ text-transform: uppercase;
458 }481 }
459 482
460 /* Blur layer styles: use `backdrop-filter` but animate opacity only. */483 /* Blur layer styles: use `backdrop-filter` but animate opacity only. */
461 .dumber-omnibox-blur-layer {484 .dumber-omnibox-blur-layer {
462- backdrop-filter: blur(2px) saturate(110%);485+ backdrop-filter: blur(1.5px) saturate(105%);
463- -webkit-backdrop-filter: blur(2px) saturate(110%);486+ -webkit-backdrop-filter: blur(1.5px) saturate(105%);
464- background: var(--dumber-blur-color, color-mix(in srgb, var(--dynamic-bg) 16%, transparent));487+ background: var(--dumber-blur-color, color-mix(in srgb, var(--dynamic-bg) 12%, transparent));
465- transition: opacity 160ms ease;488+ transition: opacity 140ms ease;
466 will-change: opacity;489 will-change: opacity;
467 z-index: 0;490 z-index: 0;
468 mix-blend-mode: normal;491 mix-blend-mode: normal;
492+ border-radius: 0;
469 }493 }
470 494
471 /* Ensure the actual content (input, suggestions) sits above the blur layer */495 /* Ensure the actual content (input, suggestions) sits above the blur layer */
M gui/src/components/omnibox/OmniboxFind.svelte +49 -17
@@ -54,17 +54,9 @@
5454 });
5555 </script>
5656
57-<div
58- id="find-list"
59- class="mt-2 max-h-[50vh] overflow-auto border-t border-[#333]"
60- role="listbox"
61- aria-label="Find results"
62->
57+<div id="find-list" class="find-list" role="listbox" aria-label="Find results">
6358 <!-- Header with match count -->
64- <div
65- class="px-2.5 py-1.5 text-xs text-[#bbb]
66- border-b border-[#2a2a2a]"
67- >
59+ <div class="find-header">
6860 {#if totalMatches > 0}
6961 {totalMatches} match{totalMatches === 1 ? '' : 'es'}
7062 {:else}
@@ -79,9 +71,7 @@
7971
8072 <div
8173 id="find-item-{index}"
82- class="px-2.5 py-2 cursor-pointer
83- border-b border-[#2a2a2a] last:border-b-0
84- {isSelected ? 'bg-[#0a0a0a]' : ''}"
74+ class={isSelected ? 'find-item selected' : 'find-item'}
8575 role="option"
8676 tabindex="-1"
8777 aria-selected={isSelected}
@@ -90,10 +80,7 @@
9080 onkeydown={(e) => e.key === 'Enter' && handleItemClick(index)}
9181 >
9282 <!-- Match context -->
93- <div
94- class="text-[#ddd] text-sm
95- whitespace-nowrap overflow-hidden text-ellipsis"
96- >
83+ <div class="find-context">
9784 {match.context || ''}
9885 </div>
9986 </div>
@@ -101,3 +88,48 @@
10188 {/if}
10289 </div>
10390
91+<style>
92+ .find-list {
93+ margin-top: 0.75rem;
94+ max-height: 50vh;
95+ overflow-y: auto;
96+ border-top: 1px solid var(--dynamic-border);
97+ background: color-mix(in srgb, var(--dynamic-bg) 94%, var(--dynamic-surface) 6%);
98+ }
99+
100+ .find-header {
101+ padding: 0.65rem 0.85rem;
102+ font-size: 0.7rem;
103+ text-transform: uppercase;
104+ letter-spacing: 0.08em;
105+ color: var(--dynamic-muted);
106+ border-bottom: 1px dashed var(--dynamic-border);
107+ }
108+
109+ .find-item {
110+ padding: 0.65rem 0.85rem;
111+ border-bottom: 1px dashed var(--dynamic-border);
112+ cursor: pointer;
113+ transition: background-color 120ms ease, color 120ms ease;
114+ }
115+
116+ .find-item:last-child {
117+ border-bottom: none;
118+ }
119+
120+ .find-item.selected,
121+ .find-item:hover,
122+ .find-item:focus-visible {
123+ background: color-mix(in srgb, var(--dynamic-bg) 75%, var(--dynamic-surface) 25%);
124+ color: var(--dynamic-text);
125+ outline: none;
126+ }
127+
128+ .find-context {
129+ color: var(--dynamic-text);
130+ font-size: 0.75rem;
131+ white-space: nowrap;
132+ overflow: hidden;
133+ text-overflow: ellipsis;
134+ }
135+</style>
@@ -54,17 +54,9 @@
54 });54 });
55 </script>55 </script>
56 56
57-<div57+<div id="find-list" class="find-list" role="listbox" aria-label="Find results">
58- id="find-list"
59- class="mt-2 max-h-[50vh] overflow-auto border-t border-[#333]"
60- role="listbox"
61- aria-label="Find results"
62->
63 <!-- Header with match count -->58 <!-- Header with match count -->
64- <div59+ <div class="find-header">
65- class="px-2.5 py-1.5 text-xs text-[#bbb]
66- border-b border-[#2a2a2a]"
67- >
68 {#if totalMatches > 0}60 {#if totalMatches > 0}
69 {totalMatches} match{totalMatches === 1 ? '' : 'es'}61 {totalMatches} match{totalMatches === 1 ? '' : 'es'}
70 {:else}62 {:else}
@@ -79,9 +71,7 @@
79 71
80 <div72 <div
81 id="find-item-{index}"73 id="find-item-{index}"
82- class="px-2.5 py-2 cursor-pointer74+ class={isSelected ? 'find-item selected' : 'find-item'}
83- border-b border-[#2a2a2a] last:border-b-0
84- {isSelected ? 'bg-[#0a0a0a]' : ''}"
85 role="option"75 role="option"
86 tabindex="-1"76 tabindex="-1"
87 aria-selected={isSelected}77 aria-selected={isSelected}
@@ -90,10 +80,7 @@
90 onkeydown={(e) => e.key === 'Enter' && handleItemClick(index)}80 onkeydown={(e) => e.key === 'Enter' && handleItemClick(index)}
91 >81 >
92 <!-- Match context -->82 <!-- Match context -->
93- <div83+ <div class="find-context">
94- class="text-[#ddd] text-sm
95- whitespace-nowrap overflow-hidden text-ellipsis"
96- >
97 {match.context || ''}84 {match.context || ''}
98 </div>85 </div>
99 </div>86 </div>
@@ -101,3 +88,48 @@
101 {/if}88 {/if}
102 </div>89 </div>
103 90
91+<style>
92+ .find-list {
93+ margin-top: 0.75rem;
94+ max-height: 50vh;
95+ overflow-y: auto;
96+ border-top: 1px solid var(--dynamic-border);
97+ background: color-mix(in srgb, var(--dynamic-bg) 94%, var(--dynamic-surface) 6%);
98+ }
99+
100+ .find-header {
101+ padding: 0.65rem 0.85rem;
102+ font-size: 0.7rem;
103+ text-transform: uppercase;
104+ letter-spacing: 0.08em;
105+ color: var(--dynamic-muted);
106+ border-bottom: 1px dashed var(--dynamic-border);
107+ }
108+
109+ .find-item {
110+ padding: 0.65rem 0.85rem;
111+ border-bottom: 1px dashed var(--dynamic-border);
112+ cursor: pointer;
113+ transition: background-color 120ms ease, color 120ms ease;
114+ }
115+
116+ .find-item:last-child {
117+ border-bottom: none;
118+ }
119+
120+ .find-item.selected,
121+ .find-item:hover,
122+ .find-item:focus-visible {
123+ background: color-mix(in srgb, var(--dynamic-bg) 75%, var(--dynamic-surface) 25%);
124+ color: var(--dynamic-text);
125+ outline: none;
126+ }
127+
128+ .find-context {
129+ color: var(--dynamic-text);
130+ font-size: 0.75rem;
131+ white-space: nowrap;
132+ overflow: hidden;
133+ text-overflow: ellipsis;
134+ }
135+</style>
M gui/src/components/omnibox/OmniboxSuggestions.svelte +85 -20
@@ -20,7 +20,7 @@
2020 // Handle suggestion item mouse enter
2121 function handleItemMouseEnter(index: number) {
2222 omniboxStore.setSelectedIndex(index);
23- omniboxStore.setFaded(true);
23+ omniboxStore.setFaded(false);
2424 scrollToSelection();
2525 }
2626
@@ -86,7 +86,7 @@
8686 {#if hasContent}
8787 <div
8888 id="omnibox-list"
89- class="mt-2 max-h-[50vh] overflow-auto border-t border-[#333]"
89+ class="suggestion-list"
9090 role="listbox"
9191 aria-label="Search suggestions"
9292 >
@@ -96,9 +96,7 @@
9696
9797 <div
9898 id="omnibox-item-{index}"
99- class="px-2.5 py-2 flex gap-2.5 items-center cursor-pointer
100- border-b border-[#2a2a2a] last:border-b-0
101- {isSelected ? 'bg-[#0a0a0a]' : ''}"
99+ class={isSelected ? 'suggestion-item selected' : 'suggestion-item'}
102100 role="option"
103101 tabindex={isSelected ? 0 : -1}
104102 aria-selected={isSelected}
@@ -108,15 +106,11 @@
108106 >
109107 <!-- Favicon chip -->
110108 {#if suggestion.favicon}
111- <div
112- class="flex-shrink-0 w-5 h-5 rounded-full
113- bg-[#ccc] border border-black/12
114- shadow-sm flex items-center justify-center"
115- >
109+ <div class="suggestion-favicon">
116110 <img
117111 src={suggestion.favicon}
118112 alt=""
119- class="w-4 h-4 filter brightness-105 contrast-103"
113+ class="suggestion-favicon-img"
120114 loading="lazy"
121115 onerror={handleFaviconError}
122116 style="image-rendering: -webkit-optimize-contrast;"
@@ -125,24 +119,19 @@
125119 {/if}
126120
127121 <!-- URL text with gradient fade -->
128- <div
129- class="flex-1 min-w-0 flex gap-2 items-center
130- whitespace-nowrap overflow-hidden
131- [mask-image:linear-gradient(90deg,black_85%,transparent_100%)]
132- [-webkit-mask-image:linear-gradient(90deg,black_85%,transparent_100%)]"
133- >
122+ <div class="suggestion-text">
134123 <!-- Domain -->
135- <span class="text-[#e6e6e6] opacity-95">
124+ <span class="suggestion-domain">
136125 {domain}
137126 </span>
138127
139128 <!-- Separator -->
140- <span class="text-[#777]">
129+ <span class="suggestion-separator">
141130 {' | '}
142131 </span>
143132
144133 <!-- Path -->
145- <span class="text-[#99aadd]">
134+ <span class="suggestion-path">
146135 {path || '/'}
147136 </span>
148137 </div>
@@ -151,3 +140,79 @@
151140 </div>
152141 {/if}
153142
143+
144+
145+<style>
146+ .suggestion-list {
147+ margin-top: 0.75rem;
148+ max-height: 50vh;
149+ overflow-y: auto;
150+ border-top: 1px solid var(--dynamic-border);
151+ background: color-mix(in srgb, var(--dynamic-bg) 94%, var(--dynamic-surface) 6%);
152+ }
153+
154+ .suggestion-item {
155+ display: flex;
156+ gap: 0.75rem;
157+ align-items: center;
158+ padding: 0.65rem 0.85rem;
159+ border-bottom: 1px dashed var(--dynamic-border);
160+ cursor: pointer;
161+ transition: background-color 120ms ease, color 120ms ease;
162+ letter-spacing: 0.04em;
163+ }
164+
165+ .suggestion-item:last-child {
166+ border-bottom: none;
167+ }
168+
169+ .suggestion-item.selected,
170+ .suggestion-item:hover,
171+ .suggestion-item:focus-visible {
172+ background: color-mix(in srgb, var(--dynamic-bg) 60%, var(--dynamic-surface) 40%);
173+ color: var(--dynamic-text);
174+ outline: none;
175+ }
176+
177+ .suggestion-item .suggestion-domain {
178+ color: var(--dynamic-text);
179+ opacity: 0.95;
180+ }
181+
182+ .suggestion-item .suggestion-separator {
183+ color: var(--dynamic-muted);
184+ }
185+
186+ .suggestion-item .suggestion-path {
187+ color: var(--dynamic-muted);
188+ }
189+
190+ .suggestion-favicon {
191+ width: 20px;
192+ height: 20px;
193+ border: 1px solid var(--dynamic-border);
194+ display: flex;
195+ align-items: center;
196+ justify-content: center;
197+ background: var(--dynamic-bg);
198+ }
199+
200+ .suggestion-favicon-img {
201+ width: 16px;
202+ height: 16px;
203+ object-fit: contain;
204+ }
205+
206+ .suggestion-text {
207+ flex: 1;
208+ min-width: 0;
209+ display: flex;
210+ gap: 0.75rem;
211+ align-items: center;
212+ white-space: nowrap;
213+ overflow: hidden;
214+ text-overflow: ellipsis;
215+ mask-image: linear-gradient(90deg, black 85%, transparent 100%);
216+ -webkit-mask-image: linear-gradient(90deg, black 85%, transparent 100%);
217+ }
218+</style>
@@ -20,7 +20,7 @@
20 // Handle suggestion item mouse enter20 // Handle suggestion item mouse enter
21 function handleItemMouseEnter(index: number) {21 function handleItemMouseEnter(index: number) {
22 omniboxStore.setSelectedIndex(index);22 omniboxStore.setSelectedIndex(index);
23- omniboxStore.setFaded(true);23+ omniboxStore.setFaded(false);
24 scrollToSelection();24 scrollToSelection();
25 }25 }
26 26
@@ -86,7 +86,7 @@
86 {#if hasContent}86 {#if hasContent}
87 <div87 <div
88 id="omnibox-list"88 id="omnibox-list"
89- class="mt-2 max-h-[50vh] overflow-auto border-t border-[#333]"89+ class="suggestion-list"
90 role="listbox"90 role="listbox"
91 aria-label="Search suggestions"91 aria-label="Search suggestions"
92 >92 >
@@ -96,9 +96,7 @@
96 96
97 <div97 <div
98 id="omnibox-item-{index}"98 id="omnibox-item-{index}"
99- class="px-2.5 py-2 flex gap-2.5 items-center cursor-pointer99+ class={isSelected ? 'suggestion-item selected' : 'suggestion-item'}
100- border-b border-[#2a2a2a] last:border-b-0
101- {isSelected ? 'bg-[#0a0a0a]' : ''}"
102 role="option"100 role="option"
103 tabindex={isSelected ? 0 : -1}101 tabindex={isSelected ? 0 : -1}
104 aria-selected={isSelected}102 aria-selected={isSelected}
@@ -108,15 +106,11 @@
108 >106 >
109 <!-- Favicon chip -->107 <!-- Favicon chip -->
110 {#if suggestion.favicon}108 {#if suggestion.favicon}
111- <div109+ <div class="suggestion-favicon">
112- class="flex-shrink-0 w-5 h-5 rounded-full
113- bg-[#ccc] border border-black/12
114- shadow-sm flex items-center justify-center"
115- >
116 <img110 <img
117 src={suggestion.favicon}111 src={suggestion.favicon}
118 alt=""112 alt=""
119- class="w-4 h-4 filter brightness-105 contrast-103"113+ class="suggestion-favicon-img"
120 loading="lazy"114 loading="lazy"
121 onerror={handleFaviconError}115 onerror={handleFaviconError}
122 style="image-rendering: -webkit-optimize-contrast;"116 style="image-rendering: -webkit-optimize-contrast;"
@@ -125,24 +119,19 @@
125 {/if}119 {/if}
126 120
127 <!-- URL text with gradient fade -->121 <!-- URL text with gradient fade -->
128- <div122+ <div class="suggestion-text">
129- class="flex-1 min-w-0 flex gap-2 items-center
130- whitespace-nowrap overflow-hidden
131- [mask-image:linear-gradient(90deg,black_85%,transparent_100%)]
132- [-webkit-mask-image:linear-gradient(90deg,black_85%,transparent_100%)]"
133- >
134 <!-- Domain -->123 <!-- Domain -->
135- <span class="text-[#e6e6e6] opacity-95">124+ <span class="suggestion-domain">
136 {domain}125 {domain}
137 </span>126 </span>
138 127
139 <!-- Separator -->128 <!-- Separator -->
140- <span class="text-[#777]">129+ <span class="suggestion-separator">
141 {' | '}130 {' | '}
142 </span>131 </span>
143 132
144 <!-- Path -->133 <!-- Path -->
145- <span class="text-[#99aadd]">134+ <span class="suggestion-path">
146 {path || '/'}135 {path || '/'}
147 </span>136 </span>
148 </div>137 </div>
@@ -151,3 +140,79 @@
151 </div>140 </div>
152 {/if}141 {/if}
153 142
143+
144+
145+<style>
146+ .suggestion-list {
147+ margin-top: 0.75rem;
148+ max-height: 50vh;
149+ overflow-y: auto;
150+ border-top: 1px solid var(--dynamic-border);
151+ background: color-mix(in srgb, var(--dynamic-bg) 94%, var(--dynamic-surface) 6%);
152+ }
153+
154+ .suggestion-item {
155+ display: flex;
156+ gap: 0.75rem;
157+ align-items: center;
158+ padding: 0.65rem 0.85rem;
159+ border-bottom: 1px dashed var(--dynamic-border);
160+ cursor: pointer;
161+ transition: background-color 120ms ease, color 120ms ease;
162+ letter-spacing: 0.04em;
163+ }
164+
165+ .suggestion-item:last-child {
166+ border-bottom: none;
167+ }
168+
169+ .suggestion-item.selected,
170+ .suggestion-item:hover,
171+ .suggestion-item:focus-visible {
172+ background: color-mix(in srgb, var(--dynamic-bg) 60%, var(--dynamic-surface) 40%);
173+ color: var(--dynamic-text);
174+ outline: none;
175+ }
176+
177+ .suggestion-item .suggestion-domain {
178+ color: var(--dynamic-text);
179+ opacity: 0.95;
180+ }
181+
182+ .suggestion-item .suggestion-separator {
183+ color: var(--dynamic-muted);
184+ }
185+
186+ .suggestion-item .suggestion-path {
187+ color: var(--dynamic-muted);
188+ }
189+
190+ .suggestion-favicon {
191+ width: 20px;
192+ height: 20px;
193+ border: 1px solid var(--dynamic-border);
194+ display: flex;
195+ align-items: center;
196+ justify-content: center;
197+ background: var(--dynamic-bg);
198+ }
199+
200+ .suggestion-favicon-img {
201+ width: 16px;
202+ height: 16px;
203+ object-fit: contain;
204+ }
205+
206+ .suggestion-text {
207+ flex: 1;
208+ min-width: 0;
209+ display: flex;
210+ gap: 0.75rem;
211+ align-items: center;
212+ white-space: nowrap;
213+ overflow: hidden;
214+ text-overflow: ellipsis;
215+ mask-image: linear-gradient(90deg, black 85%, transparent 100%);
216+ -webkit-mask-image: linear-gradient(90deg, black 85%, transparent 100%);
217+ }
218+</style>
M gui/src/components/omnibox/find.ts +1 -1
@@ -23,7 +23,7 @@ function ensureFindStyles(): void {
2323 background-color: rgb(255 235 59);
2424 color: rgb(0 0 0);
2525 padding: 0 0.125rem;
26- border-radius: 0.125rem;
26+ border-radius: 0;
2727 box-shadow: 0 0 0 1px rgb(200 185 0) inset;
2828 }
2929
@@ -23,7 +23,7 @@ function ensureFindStyles(): void {
23 background-color: rgb(255 235 59);23 background-color: rgb(255 235 59);
24 color: rgb(0 0 0);24 color: rgb(0 0 0);
25 padding: 0 0.125rem;25 padding: 0 0.125rem;
26- border-radius: 0.125rem;26+ border-radius: 0;
27 box-shadow: 0 0 0 1px rgb(200 185 0) inset;27 box-shadow: 0 0 0 1px rgb(200 185 0) inset;
28 }28 }
29 29

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close