From d3454bc39e65cd51a6ca50b5b4564dc9fd00a562 Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 30 Apr 2026 23:52:03 +0300 Subject: [PATCH] Dashboard: UI polish (typography, palette, modal cleanup) - Switch UI font to Inter, keep monospace only for code/logs/paths/ identifiers (loaded via Google Fonts; system fallbacks) - Refine the light/dark palette with softer borders, clearer text hierarchy, and a more nuanced shadow/elevation system; introduce --bg-elevated, --border-strong, --shadow-elevated tokens; retain the orange accent - Introduce a consistent spacing scale (--space-1..6) and apply it across sections, modals, and controls - Extract shared modal CSS classes (.modal-info, .modal-hint, .modal-prompt, .modal-field, .modal-input, .modal-select, .modal-textarea, .modal-actions, .btn-secondary) and strip duplicated inline styles from all seven modals; add an accent-colored focus ring for inputs/textareas and a subtle backdrop blur - Replace the native ` with a filterable combobox — type to filter, keyboard navigation (Up/Down/Enter/Esc), substring highlight, click-outside to close. The typed value is validated against the available languages list before submission. + # v1.3.0 (2026-05-11) * General: diff --git a/src/serena/resources/dashboard/dashboard.css b/src/serena/resources/dashboard/dashboard.css index eb473e9a..ad658a97 100644 --- a/src/serena/resources/dashboard/dashboard.css +++ b/src/serena/resources/dashboard/dashboard.css @@ -3,64 +3,91 @@ html { } :root { + /* Typography */ + --font-sans: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + --font-mono: 'JetBrains Mono', 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; + + /* Spacing scale */ + --space-1: 4px; + --space-2: 8px; + --space-3: 12px; + --space-4: 16px; + --space-5: 24px; + --space-6: 32px; + /* Light theme variables */ --bg-primary: #f5f5f5; --bg-secondary: #ffffff; - --text-primary: #000000; - --text-secondary: #333333; - --text-muted: #666666; - --border-color: #ddd; + --bg-elevated: #ffffff; + --text-primary: #1f2328; + --text-secondary: #3f4754; + --text-muted: #6a737d; + --border-color: #e3e6ea; + --border-strong: #d0d7de; --btn-primary: #eaa45d; --btn-hover: #dca662; - --btn-disabled: #6c757d; - --shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - --tool-highlight: #ffff00; - --tool-highlight-text: #000000; - --log-debug: #808080; - --log-info: #000000; - --log-warning: #FF8C00; - --log-error: #FF0000; - --stats-header: #f8f9fa; + --btn-disabled: #adb5bd; + --btn-secondary-bg: #f0f2f5; + --btn-secondary-hover: #e3e6ea; + --shadow: 0 1px 2px rgba(15, 23, 42, 0.04), 0 1px 3px rgba(15, 23, 42, 0.06); + --shadow-elevated: 0 4px 12px rgba(15, 23, 42, 0.08); + --tool-highlight: #fff3bf; + --tool-highlight-text: #1f2328; + --log-debug: #8b95a1; + --log-info: #1f2328; + --log-warning: #d97706; + --log-error: #dc2626; + --stats-header: #f0f2f5; --header-height: 150px; - --header-padding: 20px; - --header-gap-main: 25px; - --frame-padding: 25px; - --border-radius: 5px; + --header-padding: var(--space-5); + --header-gap-main: var(--space-5); + --frame-padding: var(--space-5); + --border-radius: 6px; + --border-radius-sm: 4px; } [data-theme="dark"] { /* Dark theme variables */ --bg-primary: #1a1a1a; --bg-secondary: #2d2d2d; - --text-primary: #ffffff; - --text-secondary: #e0e0e0; - --text-muted: #b0b0b0; - --border-color: #444; + --bg-elevated: #262b32; + --text-primary: #e6edf3; + --text-secondary: #c9d1d9; + --text-muted: #8b95a1; + --border-color: #2d333b; + --border-strong: #3d444d; --btn-primary: #eaa45d; --btn-hover: #dca662; - --btn-disabled: #6c757d; - --shadow: 0 2px 4px rgba(0, 0, 0, 0.3); - --tool-highlight: #ffd700; - --tool-highlight-text: #000000; - --log-debug: #808080; - --log-info: #ffffff; - --log-warning: #FF8C00; - --log-error: #FF0000; - --stats-header: #3a3a3a; + --btn-disabled: #4a5159; + --btn-secondary-bg: #2d333b; + --btn-secondary-hover: #3d444d; + --shadow: 0 1px 2px rgba(0, 0, 0, 0.25), 0 1px 3px rgba(0, 0, 0, 0.35); + --shadow-elevated: 0 4px 12px rgba(0, 0, 0, 0.45); + --tool-highlight: #f6c948; + --tool-highlight-text: #15181c; + --log-debug: #8b95a1; + --log-info: #e6edf3; + --log-warning: #f59e0b; + --log-error: #f87171; + --stats-header: #262b32; } .news-section { background: var(--bg-secondary); - padding: 20px; + border: 1px solid var(--border-color); + padding: var(--space-5); border-radius: var(--border-radius); box-shadow: var(--shadow); - margin-bottom: 25px; + margin-bottom: var(--space-4); } .news-section h2 { - margin: 0 0 20px 0; - font-size: 18px; - color: var(--text-primary); + margin: 0 0 var(--space-4) 0; + font-size: 15px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--text-muted); } .news-item { @@ -72,6 +99,11 @@ html { position: relative; } +.news-item a { + color: var(--text-muted); + text-decoration: underline; +} + .news-item:last-child { margin-bottom: 0; } @@ -144,13 +176,35 @@ html { } body { - font-family: 'Consolas', 'Monaco', 'Courier New', monospace; + font-family: var(--font-sans); + font-size: 14px; + line-height: 1.5; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; margin: 0; background-color: var(--bg-primary); color: var(--text-primary); transition: background-color 0.3s ease, color 0.3s ease; } +h1, h2, h3, h4, h5, h6 { + font-weight: 600; + letter-spacing: -0.005em; +} + +code, pre, kbd, samp, +.log-container, +.memory-editor, +.project-path, +.tool-item, +.stat-tool-name, +.execution-name, +.execution-meta, +.last-execution-name, +.config-value-mono { + font-family: var(--font-mono); +} + #frame { max-width: 1600px; margin: 0 auto; @@ -260,18 +314,20 @@ body { background-color: var(--bg-secondary); color: var(--text-primary); border: 1px solid var(--border-color); - padding: 8px 16px; - border-radius: 4px; + padding: 8px 14px; + border-radius: var(--border-radius-sm); cursor: pointer; - font-size: 16px; - transition: background-color 0.3s ease, border-color 0.3s ease; + font-size: 14px; + font-family: inherit; + transition: background-color 0.2s ease, border-color 0.2s ease; display: flex; align-items: center; - gap: 8px; + gap: var(--space-2); } .menu-button:hover { - background-color: var(--border-color); + background-color: var(--btn-secondary-hover); + border-color: var(--border-strong); } .menu-dropdown { @@ -412,7 +468,7 @@ body { .overview-container { display: grid; grid-template-columns: 1fr 400px; - gap: 20px; + gap: var(--space-5); } .overview-left { @@ -430,23 +486,30 @@ body { background-color: var(--bg-secondary); border: 1px solid var(--border-color); border-radius: var(--border-radius); - padding: 20px; - margin-bottom: 20px; + padding: var(--space-5); + margin-bottom: var(--space-4); transition: background-color 0.3s ease, border-color 0.3s ease; } .config-section h2, .basic-stats-section h2 { - margin-top: 0; - color: var(--text-secondary); + margin: 0 0 var(--space-4) 0; + font-size: 15px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--text-muted); } /* Collapsible Headers */ .collapsible-header { margin-top: 0; margin-bottom: 0; - font-size: 18px; - color: var(--text-secondary); + font-size: 15px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--text-muted); cursor: pointer; user-select: none; display: flex; @@ -476,13 +539,17 @@ body { .config-grid { display: grid; grid-template-columns: 180px 1fr; - gap: 12px; - margin-bottom: 20px; + gap: var(--space-3); + margin-bottom: var(--space-4); } .config-label { - font-weight: bold; - color: var(--text-secondary); + font-weight: 500; + color: var(--text-muted); + font-size: 13px; + text-transform: uppercase; + letter-spacing: 0.03em; + padding-top: 2px; } .config-value { @@ -587,30 +654,29 @@ body { .basic-stats-section { background-color: var(--bg-secondary); border: 1px solid var(--border-color); - border-radius: 5px; - padding: 20px; - margin-bottom: 20px; + border-radius: var(--border-radius); + padding: var(--space-5); + margin-bottom: var(--space-4); transition: background-color 0.3s ease, border-color 0.3s ease; } -.basic-stats-section h2 { - margin-top: 0; - color: var(--text-secondary); -} - /* Executions Styles */ .executions-section { background-color: var(--bg-secondary); border: 1px solid var(--border-color); - border-radius: 5px; - padding: 20px; - margin-bottom: 20px; + border-radius: var(--border-radius); + padding: var(--space-5); + margin-bottom: var(--space-4); transition: background-color 0.3s ease, border-color 0.3s ease; } .executions-section h2 { - margin-top: 0; - color: var(--text-secondary); + margin: 0 0 var(--space-4) 0; + font-size: 15px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--text-muted); } .execution-list { @@ -864,40 +930,49 @@ body { .btn { background-color: var(--btn-primary); - color: white; + color: #fff; border: none; padding: 8px 16px; - border-radius: 4px; + border-radius: var(--border-radius-sm); cursor: pointer; font-size: 14px; - transition: background-color 0.3s ease; + font-weight: 500; + font-family: inherit; + transition: background-color 0.2s ease, transform 0.05s ease; } .btn:hover { background-color: var(--btn-hover); } +.btn:active { + transform: translateY(1px); +} + .btn:disabled { background-color: var(--btn-disabled); cursor: not-allowed; + transform: none; } .theme-toggle { display: flex; align-items: center; - gap: 5px; + gap: var(--space-2); background-color: var(--bg-secondary); color: var(--text-primary); border: 1px solid var(--border-color); - border-radius: 4px; - padding: 8px 16px; + border-radius: var(--border-radius-sm); + padding: 8px 14px; cursor: pointer; - font-size: 16px; - transition: background-color 0.3s ease, border-color 0.3s ease; + font-size: 14px; + font-family: inherit; + transition: background-color 0.2s ease, border-color 0.2s ease; } .theme-toggle:hover { - background-color: var(--border-color); + background-color: var(--btn-secondary-hover); + border-color: var(--border-strong); } .theme-toggle span { @@ -1084,27 +1159,28 @@ body { width: 100%; height: 100%; overflow: auto; - background-color: rgba(0, 0, 0, 0.5); + background-color: rgba(15, 23, 42, 0.55); + backdrop-filter: blur(2px); } .modal-content { - background-color: var(--bg-primary); - margin: 10% auto; - padding: 25px; + background-color: var(--bg-elevated); + margin: 8vh auto; + padding: var(--space-5); border: 1px solid var(--border-color); - border-radius: 8px; + border-radius: var(--border-radius); width: 90%; max-width: 500px; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); + box-shadow: var(--shadow-elevated); position: relative; } .modal-close { color: var(--text-muted); float: right; - font-size: 28px; - font-weight: bold; - line-height: 20px; + font-size: 24px; + font-weight: 400; + line-height: 1; cursor: pointer; transition: color 0.2s; } @@ -1116,8 +1192,213 @@ body { .modal h3 { margin-top: 0; - margin-bottom: 15px; + margin-bottom: var(--space-3); color: var(--text-primary); + font-size: 17px; + font-weight: 600; +} + +/* Shared modal pieces (replace inline styles) */ +.modal-info { + margin: var(--space-3) 0; + color: var(--text-primary); + line-height: 1.5; +} + +.modal-hint { + margin: var(--space-3) 0; + color: var(--text-muted); + font-size: 13px; + line-height: 1.5; +} + +.modal-field { + margin: var(--space-4) 0; +} + +.modal-field label { + display: block; + margin-bottom: var(--space-2); + font-weight: 500; + color: var(--text-primary); +} + +.modal-input, +.modal-select { + box-sizing: border-box; + width: 100%; + padding: 8px 10px; + border-radius: var(--border-radius-sm); + border: 1px solid var(--border-strong); + background: var(--bg-primary); + color: var(--text-primary); + font-size: 14px; + font-family: var(--font-sans); + transition: border-color 0.15s ease, box-shadow 0.15s ease; +} + +.modal-input:focus, +.modal-select:focus { + outline: none; + border-color: var(--btn-primary); + box-shadow: 0 0 0 3px rgba(234, 164, 93, 0.18); +} + +.modal-textarea { + box-sizing: border-box; + width: 100%; + padding: var(--space-3); + border: 1px solid var(--border-strong); + border-radius: var(--border-radius-sm); + background: var(--bg-primary); + color: var(--text-primary); + font-family: var(--font-mono); + font-size: 13px; + line-height: 1.5; + resize: vertical; + transition: border-color 0.15s ease, box-shadow 0.15s ease; +} + +.modal-textarea:focus { + outline: none; + border-color: var(--btn-primary); + box-shadow: 0 0 0 3px rgba(234, 164, 93, 0.18); +} + +.modal-actions { + display: flex; + gap: var(--space-2); + justify-content: flex-end; + margin-top: var(--space-4); +} + +.modal-actions .btn { + padding: 8px 16px; +} + +.modal-prompt { + margin: var(--space-4) 0; + color: var(--text-primary); + line-height: 1.5; + font-size: 15px; +} + +.btn-secondary { + background-color: var(--btn-secondary-bg); + color: var(--text-primary); +} + +.btn-secondary:hover { + background-color: var(--btn-secondary-hover); +} + +.modal-title-with-meta { + margin-bottom: var(--space-4); + display: flex; + align-items: center; + gap: var(--space-2); + flex-wrap: wrap; +} + +/* Filterable combobox (used in Add Language modal) */ +.combobox { + position: relative; +} + +.combobox-input { + padding-right: 28px; +} + +.combobox-caret { + position: absolute; + top: 50%; + right: var(--space-3); + transform: translateY(-50%); + color: var(--text-muted); + font-size: 12px; + pointer-events: none; + transition: transform 0.15s ease, color 0.15s ease; +} + +.combobox.open .combobox-caret { + color: var(--text-primary); + transform: translateY(-50%) rotate(180deg); +} + +.combobox-options { + position: absolute; + top: calc(100% + 4px); + left: 0; + right: 0; + margin: 0; + padding: var(--space-1) 0; + list-style: none; + background: var(--bg-elevated); + border: 1px solid var(--border-strong); + border-radius: var(--border-radius-sm); + box-shadow: var(--shadow-elevated); + max-height: 240px; + overflow-y: auto; + z-index: 20; +} + +.combobox-options li { + padding: 6px var(--space-3); + color: var(--text-primary); + cursor: pointer; + font-size: 14px; + line-height: 1.4; + transition: background-color 0.1s ease; +} + +.combobox-options li.no-match { + color: var(--text-muted); + font-style: italic; + cursor: default; +} + +.combobox-options li:hover:not(.no-match), +.combobox-options li.highlighted:not(.no-match) { + background-color: var(--btn-secondary-hover); +} + +.combobox-options li.highlighted:not(.no-match) { + color: var(--text-primary); +} + +.combobox-options li mark { + background: transparent; + color: var(--btn-primary); + font-weight: 600; +} + +.combobox-empty { + margin-top: var(--space-2); + color: var(--text-muted); + font-style: italic; + font-size: 13px; +} + +.section-header-row { + display: flex; + justify-content: space-between; + align-items: center; +} + +.section-header-row h2 { + margin: 0 0 var(--space-4) 0; +} + +.stats-summary-block { + margin-bottom: var(--space-5); + text-align: center; +} + +.estimator-name { + text-align: center; + margin-bottom: var(--space-3); + color: var(--text-muted); + font-size: 13px; } /* Language Badge Styles */ diff --git a/src/serena/resources/dashboard/dashboard.js b/src/serena/resources/dashboard/dashboard.js index e48ff3c2..afa71865 100644 --- a/src/serena/resources/dashboard/dashboard.js +++ b/src/serena/resources/dashboard/dashboard.js @@ -296,6 +296,10 @@ class Dashboard { this.$availableContextsDisplay = $('#available-contexts-display'); this.$addLanguageModal = $('#add-language-modal'); this.$modalLanguageSelect = $('#modal-language-select'); + this.$modalLanguageCombobox = $('#modal-language-combobox'); + this.$modalLanguageOptions = $('#modal-language-options'); + this.$modalLanguageEmpty = $('#modal-language-empty'); + this.modalLanguageAvailable = []; this.$modalProjectName = $('#modal-project-name'); this.$modalAddBtn = $('#modal-add-btn'); this.$modalCancelBtn = $('#modal-cancel-btn'); @@ -360,6 +364,7 @@ class Dashboard { this.$modalAddBtn.click(this.addLanguageFromModal.bind(this)); this.$modalCancelBtn.click(this.closeLanguageModal.bind(this)); this.$modalClose.click(this.closeLanguageModal.bind(this)); + this.bindLanguageCombobox(); this.$removeModalOkBtn.click(this.confirmRemoveLanguageOk.bind(this)); this.$removeModalCancelBtn.click(this.closeRemoveLanguageModal.bind(this)); this.$modalCloseRemove.click(this.closeRemoveLanguageModal.bind(this)); @@ -843,7 +848,7 @@ class Dashboard { let html = ''; tools.forEach(function (tool) { - html += '
' + tool.name + '
'; + html += '
' + tool.name + '
'; }); this.$availableToolsDisplay.html(html); @@ -1730,7 +1735,10 @@ class Dashboard { closeLanguageModal() { this.$addLanguageModal.fadeOut(200); - this.$modalLanguageSelect.empty(); + this.closeLanguageDropdown(); + this.$modalLanguageSelect.val(''); + this.$modalLanguageOptions.empty(); + this.modalLanguageAvailable = []; this.$modalAddBtn.prop('disabled', false).text('Add Language'); } @@ -1738,19 +1746,19 @@ class Dashboard { let self = this; $.ajax({ url: '/get_available_languages', type: 'GET', success: function (response) { - const languages = response.languages || []; - // Clear all existing options - self.$modalLanguageSelect.empty(); + const languages = (response.languages || []).slice().sort(); + self.modalLanguageAvailable = languages; + self.$modalLanguageSelect.val(''); if (languages.length === 0) { - // Show message if no languages available - self.$modalLanguageSelect.append($('