:root { --bg: #f6f7f9; --panel: #ffffff; --border: #e3e6ea; --text: #1f2430; --text-muted: #aaa; --accent: #3b5bfd; --accent-hover: #2f4ce0; --danger: #d33d3d; --hover-bg: #eceef2; --accent-tint: #eef1fb; --danger-tint: #fdecec; --danger-border: #f6c6c6; --danger-text: #8a1f1f; --mono: "SF Mono", "Cascadia Code", Consolas, "Courier New", monospace; --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; --radius: 8px; --erd-bg: #fbfdff; --erd-grid: #edf2f7; --erd-node-bg: #ffffff; --erd-node-border: #cbd4e1; --erd-header: #204f66; --erd-text: #1e293b; --erd-muted: #748094; --erd-key: #1f6f78; --erd-line: #687487; --scrollbar-size: 8px; --scrollbar-track: transparent; --scrollbar-thumb: #c8ced8; --scrollbar-thumb-hover: #aeb6c4; } :root[data-theme="dark"] { --bg: #191b21; --panel: #22252c; --border: #34373f; --text: #e7e9ee; --text-muted: #444; --accent: #6c8cff; --accent-hover: #839dff; --danger: #ff6b6b; --hover-bg: #2c2f38; --accent-tint: #232a42; --danger-tint: #3a2323; --danger-border: #5a2e2e; --danger-text: #ff9b9b; --erd-bg: #1a1d24; --erd-grid: #2a2f3a; --erd-node-bg: #22252c; --erd-node-border: #3a404c; --erd-header: #2f6f8f; --erd-text: #e7e9ee; --erd-muted: #8b95a8; --erd-key: #6ec4cf; --erd-line: #8b95a8; --scrollbar-thumb: #4a4f5a; --scrollbar-thumb-hover: #5e6472; } * { box-sizing: border-box; } /* Minimal scrollbars — Firefox */ * { scrollbar-width: thin; scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track); } /* Minimal scrollbars — WebKit (Chrome, Safari, Edge) */ *::-webkit-scrollbar { width: var(--scrollbar-size); height: var(--scrollbar-size); } *::-webkit-scrollbar-track { background: var(--scrollbar-track); } *::-webkit-scrollbar-thumb { background: var(--scrollbar-thumb); border-radius: 999px; border: 2px solid transparent; background-clip: padding-box; } *::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-thumb-hover); border: 2px solid transparent; background-clip: padding-box; } *::-webkit-scrollbar-corner { background: transparent; } html, body { height: 100%; margin: 0; } body { font-family: var(--sans); color: var(--text); background: var(--bg); display: flex; flex-direction: column; overflow: hidden; } /* Top bar */ .topbar { height: 56px; flex: 0 0 auto; display: flex; align-items: center; justify-content: space-between; padding: 0 16px; background: var(--panel); border-bottom: 1px solid var(--border); } .brand { display: flex; align-items: center; gap: 8px; font-weight: 600; font-size: 15px; color: var(--text); } .brand-mark { color: var(--accent); font-size: 18px; } .toolbar { display: flex; align-items: center; gap: 8px; } .btn { display: inline-flex; align-items: center; gap: 6px; font-family: inherit; font-size: 13px; font-weight: 500; padding: 7px 12px; border-radius: var(--radius); border: 1px solid var(--border); background: var(--panel); color: var(--text); cursor: pointer; transition: background-color 0.12s, border-color 0.12s; } .btn:hover { background: var(--hover-bg); } .btn:disabled { opacity: 0.5; cursor: default; } .btn .icon { font-size: 12px; } .btn .spinner-btn { width: 12px; height: 12px; border-color: rgba(255, 255, 255, 0.35); border-top-color: #fff; flex: 0 0 auto; } .btn.is-busy .run-icon { display: none; } .btn-primary { background: var(--accent); border-color: var(--accent); color: #fff; } .btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); } .btn-ghost { background: transparent; border-color: transparent; color: var(--text-muted); } .btn-ghost:hover { background: var(--hover-bg); } .btn-danger { background: var(--danger); border-color: var(--danger); color: #fff; } .btn-danger:hover { filter: brightness(1.08); } .btn-sm { padding: 4px 10px; font-size: 12px; gap: 4px; } .icon-btn { border: none; background: transparent; color: var(--text-muted); cursor: pointer; font-size: 13px; padding: 2px 4px; border-radius: 4px; } .icon-btn:hover { background: var(--hover-bg); color: var(--text); } /* Hamburger menu */ .menu { position: relative; } .icon-btn-lg { font-size: 15px; line-height: 1; padding: 7px 10px; } .menu-panel { position: absolute; top: calc(100% + 6px); right: 0; min-width: 170px; background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); padding: 6px; z-index: 100; } .menu-item { display: flex; align-items: center; gap: 8px; width: 100%; font-family: inherit; font-size: 13px; font-weight: 500; padding: 8px 10px; border: none; border-radius: 6px; background: transparent; color: var(--text); cursor: pointer; text-align: left; } .menu-item:hover { background: var(--accent-tint); color: var(--accent); } .menu-item-danger:hover { background: var(--danger-tint); color: var(--danger); } .menu-separator { height: 1px; background: var(--border); margin: 6px 4px; } /* Layout */ .layout { flex: 1 1 auto; display: flex; min-height: 0; } .sidebar { width: 220px; flex: 0 0 auto; background: var(--panel); border-right: 1px solid var(--border); display: flex; flex-direction: column; min-height: 0; transition: width 0.15s ease; overflow: hidden; } .sidebar.collapsed { width: 40px; } .sidebar-header { display: flex; align-items: center; justify-content: space-between; padding: 12px 14px 8px; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-muted); } .sidebar.collapsed .sidebar-header { justify-content: center; padding: 12px 4px 8px; } .sidebar-header-actions { display: flex; align-items: center; gap: 2px; } .sidebar.collapsed .sidebar-title, .sidebar.collapsed #btn-refresh-tables, .sidebar.collapsed .table-list { display: none; } .table-list { overflow-y: auto; flex: 1 1 auto; padding: 0 8px 8px; white-space: nowrap; } .saved-queries-header { border-top: 1px solid var(--border); flex: 0 0 auto; } .saved-queries-list { flex: 0 1 auto; } .table-group + .table-group { margin-top: 1px; } .table-row { display: flex; align-items: center; gap: 2px; border-radius: 6px; } .table-row:hover { background: var(--accent-tint); } .table-toggle { border: none; background: transparent; color: var(--text-muted); cursor: pointer; font-size: 10px; width: 20px; height: 26px; flex: 0 0 auto; border-radius: 4px; } .table-toggle:hover { color: var(--accent); } .table-name { display: flex; align-items: center; gap: 6px; padding: 6px 8px 6px 0; font-size: 13px; cursor: pointer; color: var(--text); flex: 1 1 auto; min-width: 0; } .table-row:hover .table-name { color: var(--accent); } .table-name .table-icon { color: var(--text-muted); font-size: 12px; } .table-name.is-view { font-style: italic; } .saved-query-delete-btn { visibility: hidden; } .saved-query-row:hover .saved-query-delete-btn { visibility: visible; } .table-columns { padding: 2px 8px 6px 28px; display: flex; flex-direction: column; gap: 2px; } .table-columns[hidden] { display: none; } .column-item { display: flex; align-items: baseline; gap: 8px; font-size: 12px; font-family: var(--mono); color: var(--text-muted); } .column-item .column-name { color: var(--text); } .column-item.empty-hint { padding: 2px 0; font-family: var(--sans); } .empty-hint { color: var(--text-muted); font-size: 13px; padding: 10px 8px; } /* Workspace */ .workspace { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; min-width: 0; } .editor-pane { flex: 0 0 42%; min-height: 80px; } .resizer { height: 6px; flex: 0 0 auto; cursor: row-resize; background: var(--bg); position: relative; } .resizer::after { content: ""; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); width: 36px; height: 3px; border-radius: 2px; background: var(--border); } .resizer:hover::after { background: var(--accent); } .results-pane { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 60px; background: var(--panel); border-top: 1px solid var(--border); } .results-header { display: flex; align-items: center; gap: 10px; padding: 8px 14px; border-bottom: 1px solid var(--border); font-size: 13px; font-weight: 600; } .results-header-actions { margin-left: auto; display: flex; align-items: center; gap: 8px; } .icon-btn-open-tab { display: inline-flex; align-items: center; justify-content: center; padding: 6px; line-height: 0; } .icon-btn-open-tab[hidden] { display: none; } .results-meta { font-weight: 400; color: var(--text-muted); font-size: 12px; } .results-body { flex: 1 1 auto; overflow: auto; padding: 0; } .results-body.has-error { padding: 14px; } /* Results table */ table.result-table { border-collapse: collapse; width: max-content; font-size: 12.5px; font-family: var(--mono); } table.result-table th, table.result-table td { text-align: left; padding: 6px 12px; border-bottom: 1px solid var(--border); border-left: 1px solid var(--border); white-space: pre; } table.result-table th:first-child, table.result-table td:first-child { border-left: 0; } table.result-table td.cell-numeric { text-align: right; font-variant-numeric: tabular-nums; } table.result-table .cell-text { display: inline-block; max-width: 500px; overflow: hidden; text-overflow: ellipsis; white-space: pre; vertical-align: bottom; } table.result-table td.cell-expanded .cell-text { max-width: 500px; overflow: visible; white-space: pre-wrap; word-break: break-word; } table.result-table .cell-expand-btn { display: inline-block; margin-left: 4px; padding: 0 5px; font-size: 10px; line-height: 1.6; border: 1px solid var(--border); border-radius: 3px; background: var(--bg); color: var(--text-muted); cursor: pointer; vertical-align: bottom; } table.result-table .cell-expand-btn:hover { color: var(--accent); border-color: var(--accent); } table.result-table th { position: sticky; top: 0; background: var(--bg); color: var(--text-muted); font-weight: 600; border-bottom: 1px solid var(--border); } table.result-table .row-num-col { position: sticky; left: 0; z-index: 1; background: var(--bg); color: var(--text-muted); text-align: right; font-size: 11px; font-style: italic; border-right: 1px solid var(--border); user-select: none; } table.result-table th.row-num-col { z-index: 2; } table.result-table tbody tr:hover .row-num-col { background: var(--bg); } table.result-table tbody tr:hover { background: var(--accent-tint); } .result-block + .result-block { border-top: 6px solid var(--bg); } .result-block-label { padding: 6px 14px 0; font-size: 11.5px; color: var(--text-muted); font-family: var(--mono); display: flex; align-items: center; justify-content: space-between; gap: 10px; } .result-block-actions { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; } .csv-btn, .result-json-btn, .save-query-btn { font-family: var(--sans); font-size: 11.5px; font-weight: 500; border: 1px solid var(--border); background: var(--panel); color: var(--text-muted); border-radius: 6px; padding: 2px 9px; cursor: pointer; flex: 0 0 auto; } .csv-btn:hover, .result-json-btn:hover, .save-query-btn:hover { color: var(--accent); border-color: var(--accent); } .result-pager { position: sticky; left: 0; bottom: 0; z-index: 3; display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 6px 14px; background: var(--bg); border-top: 1px solid var(--border); font-family: var(--sans); font-size: 11.5px; color: var(--text-muted); } .pager-controls { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; } .pager-page { font-variant-numeric: tabular-nums; } .page-btn { font-family: var(--sans); font-size: 11.5px; font-weight: 500; border: 1px solid var(--border); background: var(--panel); color: var(--text-muted); border-radius: 6px; padding: 2px 9px; cursor: pointer; } .page-btn:hover:not(:disabled) { color: var(--accent); border-color: var(--accent); } .page-btn:disabled { opacity: 0.45; cursor: default; } .cell-null { color: var(--text-muted); font-style: italic; } .error-box { background: var(--danger-tint); border: 1px solid var(--danger-border); color: var(--danger-text); border-radius: var(--radius); padding: 12px 14px; font-family: var(--mono); font-size: 13px; white-space: pre-wrap; } /* Status bar */ .statusbar { flex: 0 0 auto; height: 28px; display: flex; align-items: center; justify-content: space-between; padding: 0 14px; font-size: 12px; color: var(--text-muted); background: var(--panel); border-top: 1px solid var(--border); } .status-left { display: flex; align-items: center; gap: 8px; } .loading-indicator { display: inline-flex; align-items: center; gap: 6px; color: var(--accent); } .spinner { width: 10px; height: 10px; border: 2px solid color-mix(in srgb, var(--accent) 30%, transparent); border-top-color: var(--accent); border-radius: 50%; animation: spin 0.8s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } .db-name { font-family: var(--mono); } /* Toast */ .toast { position: fixed; bottom: 40px; left: 50%; transform: translateX(-50%); background: #1f2430; color: #fff; padding: 9px 16px; border-radius: var(--radius); font-size: 13px; box-shadow: 0 6px 20px rgba(0, 0, 0, 0.18); z-index: 1000; } .toast.error { background: #8a1f1f; } .toast.success { background: #1f7a3f; } /* Startup splash */ #app-shell { display: flex; flex-direction: column; height: 100%; } .splash { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; background: var(--bg); z-index: 2000; } /* Database loading overlay */ .loading-overlay { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; background: rgba(15, 17, 22, 0.45); backdrop-filter: blur(2px); z-index: 2000; } .loading-overlay[hidden] { display: none; } .loading-dialog { display: flex; align-items: center; gap: 18px; min-width: 320px; max-width: 90vw; background: var(--panel); border: 1px solid var(--border); border-radius: 12px; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.35); padding: 28px 32px; } .loading-dialog-text { display: flex; flex-direction: column; gap: 4px; } .loading-dialog-title { font-size: 15px; font-weight: 600; color: var(--text); } .splash-retry-btn { align-self: flex-start; margin-top: 6px; } .splash-retry-btn[hidden] { display: none; } .loading-dialog-subtitle { font-size: 12.5px; color: var(--text-muted); } .spinner-lg { width: 26px; height: 26px; border-width: 3px; flex: 0 0 auto; } /* Dataset picker modal */ .dataset-modal-dialog { display: block; flex-direction: column; min-width: 320px; width: 560px; max-width: 90vw; max-height: 80vh; padding: 0; overflow: hidden; } .dataset-modal-content { display: flex; flex-direction: column; max-height: 80vh; } .dataset-modal-header { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 18px 20px; border-bottom: 1px solid var(--border); } .dataset-modal-close { font-size: 14px; } .dataset-modal-list { overflow-y: auto; padding: 8px 20px 20px; } .dataset-modal-item { padding: 12px 0; border-bottom: 1px solid var(--border); } .dataset-modal-item:last-child { border-bottom: none; } .dataset-modal-title { display: block; width: 100%; font-family: inherit; font-size: 14px; font-weight: 600; color: var(--accent); background: none; border: none; padding: 0; text-align: left; cursor: pointer; } .dataset-modal-title:hover { color: var(--accent-hover); } .dataset-modal-description { margin-top: 8px; font-size: 12.5px; color: var(--text-muted); } .dataset-modal-actions { display: flex; justify-content: flex-end; margin-top: 8px; } /* Styled confirm/prompt dialogs (replace native confirm()/prompt()) */ .app-dialog-overlay { z-index: 2200; } .app-dialog { display: block; min-width: 360px; max-width: 90vw; } .app-dialog-header { margin-bottom: 16px; line-height: 1.4; } .app-dialog-body { margin-bottom: 18px; } .app-dialog-input { width: 100%; padding: 8px 10px; font-family: inherit; font-size: 13px; color: var(--text); background: var(--bg); border: 1px solid var(--border); border-radius: 6px; } .app-dialog-input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-tint); } .app-dialog-actions { display: flex; justify-content: flex-end; gap: 8px; } /* Results / ERD view tabs */ .results-view-tabs { display: inline-flex; align-items: center; gap: 2px; padding: 2px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg); } .results-view-tab { font-family: inherit; font-size: 12.5px; font-weight: 600; padding: 4px 10px; border: none; border-radius: 6px; background: transparent; color: var(--text-muted); cursor: pointer; } .results-view-tab:hover { color: var(--text); background: var(--hover-bg); } .results-view-tab.is-active { background: var(--panel); color: var(--text); box-shadow: 0 0 0 1px var(--border); } /* Query history */ .results-body.history-body { padding: 0; } .history-panel { display: flex; flex-direction: column; min-height: 100%; } .history-toolbar { display: flex; justify-content: flex-end; padding: 8px 12px; border-bottom: 1px solid var(--border); } .history-clear-btn { font-size: 12px; padding: 4px 10px; } .history-list { list-style: none; margin: 0; padding: 0; } .history-item { padding: 10px 14px; border-bottom: 1px solid var(--border); cursor: pointer; } .history-item:hover { background: var(--hover-bg); } .history-item-top { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; } .history-status { flex: 0 0 auto; font-size: 11px; font-weight: 700; letter-spacing: 0.02em; text-transform: uppercase; padding: 1px 6px; border-radius: 4px; background: var(--accent-tint); color: var(--accent); } .history-item.is-error .history-status { background: var(--danger-tint); color: var(--danger-text); } .history-meta { flex: 1 1 auto; min-width: 0; font-size: 12px; font-weight: 400; color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .history-load-btn { flex: 0 0 auto; font-family: inherit; font-size: 12px; font-weight: 600; padding: 3px 8px; border: 1px solid var(--border); border-radius: 6px; background: var(--panel); color: var(--text); cursor: pointer; } .history-load-btn:hover { background: var(--accent-tint); border-color: var(--accent); color: var(--accent); } .history-sql { margin: 0; font-family: var(--mono); font-size: 12.5px; line-height: 1.4; white-space: pre-wrap; word-break: break-word; color: var(--text); } .erd-toolbar { display: inline-flex; align-items: center; gap: 4px; margin-left: 4px; } .erd-toolbar[hidden] { display: none; } .erd-zoom-label { min-width: 42px; text-align: center; font-size: 12px; font-weight: 500; color: var(--text-muted); } .erd-select { height: 26px; margin-right: 4px; padding: 0 6px; border: 1px solid var(--border); border-radius: 6px; background: var(--panel); color: var(--text); font-size: 12px; cursor: pointer; } .results-body.erd-body { padding: 0; overflow: hidden; } .erd-scroll { width: 100%; height: 100%; overflow: auto; overscroll-behavior: contain; /* Allow one-finger pan; custom JS handles two-finger pinch zoom. */ touch-action: pan-x pan-y; background: radial-gradient(circle, var(--erd-grid) 1.25px, transparent 1.3px) 0 0 / 20px 20px, var(--erd-bg); } .erd-scroll.is-dragging { cursor: grabbing; user-select: none; } .erd-svg { display: block; transform-origin: 0 0; max-width: none; max-height: none; } .erd-node { cursor: grab; touch-action: none; } .erd-node.is-dragging { cursor: grabbing; } .erd-scroll.is-sql-mode .erd-node { cursor: pointer; } .erd-node-body { fill: var(--erd-node-bg); stroke: var(--erd-node-border); } .erd-node-header { fill: var(--erd-header); } .erd-node-title { fill: #fff; font-size: 15px; font-weight: 700; pointer-events: none; } .erd-column-text { fill: var(--erd-text); font-size: 13px; pointer-events: none; } .erd-column-hit { fill: transparent; pointer-events: fill; } .erd-column-row.primary-key .erd-column-text { font-weight: 800; } .erd-column-row.foreign-key .erd-column-text { font-style: italic; } .erd-column-type { fill: var(--erd-muted); font-style: normal; font-weight: 600; } .erd-key-primary { stroke: var(--erd-key); } .erd-key-muted { stroke: var(--erd-muted); opacity: 0.85; } .erd-relationship { cursor: pointer; } .erd-line-hit { fill: none; stroke: transparent; stroke-width: 14; stroke-linecap: round; pointer-events: stroke; } .erd-line { stroke: var(--erd-line); stroke-width: 2; stroke-linecap: butt; pointer-events: none; } .erd-crowsfoot { fill: none; stroke: var(--erd-line); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; pointer-events: stroke; } .erd-relationship.is-selected .erd-line, .erd-relationship.is-selected .erd-crowsfoot { stroke: var(--accent); stroke-width: 3; }