Spaces:
Sleeping
Sleeping
NeonClary commited on
Commit Β·
d57958f
1
Parent(s): 8297932
Replace orchestrator modal with sub-menu to fix backdrop-filter containment bug
Browse filesThe modal used position:fixed inside a header with backdrop-filter:blur(),
which creates a new containing block per CSS spec, constraining the modal
to the 61px header instead of the viewport. Replaced with a sub-menu panel
using position:absolute off the Developer dropdown.
Made-with: Cursor
frontend/src/components/DevMenu.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
| 1 |
-
import React, { useState } from 'react';
|
| 2 |
-
import { ChevronDown, Download, Settings2 } from 'lucide-react';
|
| 3 |
-
import OrchestratorPicker from './OrchestratorPicker';
|
| 4 |
|
| 5 |
export default function DevMenu({
|
| 6 |
allModels,
|
|
@@ -16,9 +15,43 @@ export default function DevMenu({
|
|
| 16 |
}) {
|
| 17 |
const [open, setOpen] = useState(false);
|
| 18 |
const [orchOpen, setOrchOpen] = useState(false);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
return (
|
| 21 |
-
<div className="dev-wrap">
|
| 22 |
<div className="dev-download-btns">
|
| 23 |
<button className="btn-sm btn-outline" disabled={!hasChat} onClick={onDownloadChatTxt}>
|
| 24 |
<Download size={14} /> .txt
|
|
@@ -29,13 +62,13 @@ export default function DevMenu({
|
|
| 29 |
</div>
|
| 30 |
|
| 31 |
<div className="dev-dropdown-header">
|
| 32 |
-
<button className="btn-sm btn-ghost" onClick={() => setOpen(o => !o)}>
|
| 33 |
<Settings2 size={14} /> Developer <ChevronDown size={12} />
|
| 34 |
</button>
|
| 35 |
{open && (
|
| 36 |
<div className="dev-panel">
|
| 37 |
-
<button onClick={() => { setOrchOpen(
|
| 38 |
-
Orchestrator modelβ¦
|
| 39 |
</button>
|
| 40 |
<button
|
| 41 |
disabled={personaMode === 'structured'}
|
|
@@ -54,15 +87,48 @@ export default function DevMenu({
|
|
| 54 |
</button>
|
| 55 |
</div>
|
| 56 |
)}
|
| 57 |
-
</div>
|
| 58 |
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
</div>
|
| 67 |
);
|
| 68 |
}
|
|
|
|
| 1 |
+
import React, { useState, useMemo, useRef, useEffect } from 'react';
|
| 2 |
+
import { ChevronDown, ChevronRight, Download, Settings2, Search } from 'lucide-react';
|
|
|
|
| 3 |
|
| 4 |
export default function DevMenu({
|
| 5 |
allModels,
|
|
|
|
| 15 |
}) {
|
| 16 |
const [open, setOpen] = useState(false);
|
| 17 |
const [orchOpen, setOrchOpen] = useState(false);
|
| 18 |
+
const [q, setQ] = useState('');
|
| 19 |
+
const wrapRef = useRef(null);
|
| 20 |
+
const searchRef = useRef(null);
|
| 21 |
+
|
| 22 |
+
useEffect(() => {
|
| 23 |
+
if (orchOpen && searchRef.current) searchRef.current.focus();
|
| 24 |
+
}, [orchOpen]);
|
| 25 |
+
|
| 26 |
+
useEffect(() => {
|
| 27 |
+
function handleClickOutside(e) {
|
| 28 |
+
if (wrapRef.current && !wrapRef.current.contains(e.target)) {
|
| 29 |
+
setOpen(false);
|
| 30 |
+
setOrchOpen(false);
|
| 31 |
+
setQ('');
|
| 32 |
+
}
|
| 33 |
+
}
|
| 34 |
+
document.addEventListener('mousedown', handleClickOutside);
|
| 35 |
+
return () => document.removeEventListener('mousedown', handleClickOutside);
|
| 36 |
+
}, []);
|
| 37 |
+
|
| 38 |
+
const filtered = useMemo(() => {
|
| 39 |
+
const s = q.trim().toLowerCase();
|
| 40 |
+
if (!s) return allModels;
|
| 41 |
+
return allModels.filter(row => {
|
| 42 |
+
const hay = `${row.name} ${row.id} ${row.provider || ''}`.toLowerCase();
|
| 43 |
+
return hay.includes(s);
|
| 44 |
+
});
|
| 45 |
+
}, [allModels, q]);
|
| 46 |
+
|
| 47 |
+
const currentName = useMemo(() => {
|
| 48 |
+
if (!orchestratorModel) return 'Default (backend)';
|
| 49 |
+
const m = allModels.find(m => m.id === orchestratorModel);
|
| 50 |
+
return m ? m.name : orchestratorModel;
|
| 51 |
+
}, [orchestratorModel, allModels]);
|
| 52 |
|
| 53 |
return (
|
| 54 |
+
<div className="dev-wrap" ref={wrapRef}>
|
| 55 |
<div className="dev-download-btns">
|
| 56 |
<button className="btn-sm btn-outline" disabled={!hasChat} onClick={onDownloadChatTxt}>
|
| 57 |
<Download size={14} /> .txt
|
|
|
|
| 62 |
</div>
|
| 63 |
|
| 64 |
<div className="dev-dropdown-header">
|
| 65 |
+
<button className="btn-sm btn-ghost" onClick={() => { setOpen(o => !o); setOrchOpen(false); setQ(''); }}>
|
| 66 |
<Settings2 size={14} /> Developer <ChevronDown size={12} />
|
| 67 |
</button>
|
| 68 |
{open && (
|
| 69 |
<div className="dev-panel">
|
| 70 |
+
<button onClick={() => { setOrchOpen(o => !o); setQ(''); }}>
|
| 71 |
+
Orchestrator model⦠<ChevronRight size={12} style={{ marginLeft: 'auto', opacity: 0.5 }} />
|
| 72 |
</button>
|
| 73 |
<button
|
| 74 |
disabled={personaMode === 'structured'}
|
|
|
|
| 87 |
</button>
|
| 88 |
</div>
|
| 89 |
)}
|
|
|
|
| 90 |
|
| 91 |
+
{open && orchOpen && (
|
| 92 |
+
<div className="dev-sub-panel">
|
| 93 |
+
<div className="dev-sub-header">
|
| 94 |
+
<span className="dev-sub-title">Orchestrator</span>
|
| 95 |
+
<span className="dev-sub-current">{currentName}</span>
|
| 96 |
+
</div>
|
| 97 |
+
<div className="dev-sub-search">
|
| 98 |
+
<Search size={14} className="dev-sub-search-icon" />
|
| 99 |
+
<input
|
| 100 |
+
ref={searchRef}
|
| 101 |
+
type="search"
|
| 102 |
+
placeholder="Search modelsβ¦"
|
| 103 |
+
value={q}
|
| 104 |
+
onChange={e => setQ(e.target.value)}
|
| 105 |
+
/>
|
| 106 |
+
</div>
|
| 107 |
+
<ul className="dev-sub-list">
|
| 108 |
+
<li>
|
| 109 |
+
<button
|
| 110 |
+
className={`dev-sub-item ${!orchestratorModel ? 'dev-sub-item-active' : ''}`}
|
| 111 |
+
onClick={() => { onOrchestratorChange(null); setOrchOpen(false); setOpen(false); setQ(''); }}
|
| 112 |
+
>
|
| 113 |
+
<strong>Default (backend)</strong>
|
| 114 |
+
<span className="dev-sub-provider">Use server default</span>
|
| 115 |
+
</button>
|
| 116 |
+
</li>
|
| 117 |
+
{filtered.map(m => (
|
| 118 |
+
<li key={m.id}>
|
| 119 |
+
<button
|
| 120 |
+
className={`dev-sub-item ${orchestratorModel === m.id ? 'dev-sub-item-active' : ''}`}
|
| 121 |
+
onClick={() => { onOrchestratorChange(m.id); setOrchOpen(false); setOpen(false); setQ(''); }}
|
| 122 |
+
>
|
| 123 |
+
<strong>{m.name}</strong>
|
| 124 |
+
<span className="dev-sub-provider">{m.provider}</span>
|
| 125 |
+
</button>
|
| 126 |
+
</li>
|
| 127 |
+
))}
|
| 128 |
+
</ul>
|
| 129 |
+
</div>
|
| 130 |
+
)}
|
| 131 |
+
</div>
|
| 132 |
</div>
|
| 133 |
);
|
| 134 |
}
|
frontend/src/components/OrchestratorPicker.js
DELETED
|
@@ -1,71 +0,0 @@
|
|
| 1 |
-
import React, { useState, useMemo } from 'react';
|
| 2 |
-
import { Search, X } from 'lucide-react';
|
| 3 |
-
|
| 4 |
-
export default function OrchestratorPicker({ open, onClose, models, currentModel, onSelect }) {
|
| 5 |
-
const [q, setQ] = useState('');
|
| 6 |
-
|
| 7 |
-
const filtered = useMemo(() => {
|
| 8 |
-
const s = q.trim().toLowerCase();
|
| 9 |
-
if (!s) return models;
|
| 10 |
-
return models.filter(row => {
|
| 11 |
-
const hay = `${row.name} ${row.id} ${row.provider || ''}`.toLowerCase();
|
| 12 |
-
return hay.includes(s);
|
| 13 |
-
});
|
| 14 |
-
}, [models, q]);
|
| 15 |
-
|
| 16 |
-
if (!open) return null;
|
| 17 |
-
|
| 18 |
-
return (
|
| 19 |
-
<div className="modal-backdrop" role="dialog" aria-modal="true" aria-label="Orchestrator model" onClick={e => { if (e.target === e.currentTarget) onClose(); }}>
|
| 20 |
-
<div className="modal-panel orchestrator-picker">
|
| 21 |
-
<div className="modal-header">
|
| 22 |
-
<h3>Orchestrator model</h3>
|
| 23 |
-
<button className="modal-close-btn" onClick={onClose} aria-label="Close">
|
| 24 |
-
<X size={18} />
|
| 25 |
-
</button>
|
| 26 |
-
</div>
|
| 27 |
-
<p className="modal-hint">
|
| 28 |
-
Choose any model from the app registry to use as the conversation orchestrator.
|
| 29 |
-
</p>
|
| 30 |
-
|
| 31 |
-
<div className="picker-search">
|
| 32 |
-
<Search size={16} className="picker-search-icon" />
|
| 33 |
-
<input
|
| 34 |
-
type="search"
|
| 35 |
-
placeholder="Search by any part of name or idβ¦"
|
| 36 |
-
value={q}
|
| 37 |
-
onChange={e => setQ(e.target.value)}
|
| 38 |
-
autoFocus
|
| 39 |
-
/>
|
| 40 |
-
</div>
|
| 41 |
-
|
| 42 |
-
<ul className="picker-list">
|
| 43 |
-
<li>
|
| 44 |
-
<button
|
| 45 |
-
className={`picker-item ${!currentModel ? 'picker-item-active' : ''}`}
|
| 46 |
-
onClick={() => onSelect(null)}
|
| 47 |
-
>
|
| 48 |
-
<span className="picker-item-info">
|
| 49 |
-
<strong className="picker-item-name">Default (backend)</strong>
|
| 50 |
-
<span className="picker-item-provider">Use server default orchestrator</span>
|
| 51 |
-
</span>
|
| 52 |
-
</button>
|
| 53 |
-
</li>
|
| 54 |
-
{filtered.map(m => (
|
| 55 |
-
<li key={m.id}>
|
| 56 |
-
<button
|
| 57 |
-
className={`picker-item ${currentModel === m.id ? 'picker-item-active' : ''}`}
|
| 58 |
-
onClick={() => onSelect(m.id)}
|
| 59 |
-
>
|
| 60 |
-
<span className="picker-item-info">
|
| 61 |
-
<strong className="picker-item-name">{m.name}</strong>
|
| 62 |
-
<span className="picker-item-provider">{m.provider}</span>
|
| 63 |
-
</span>
|
| 64 |
-
</button>
|
| 65 |
-
</li>
|
| 66 |
-
))}
|
| 67 |
-
</ul>
|
| 68 |
-
</div>
|
| 69 |
-
</div>
|
| 70 |
-
);
|
| 71 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/styles/components.css
CHANGED
|
@@ -648,77 +648,56 @@
|
|
| 648 |
}
|
| 649 |
|
| 650 |
|
| 651 |
-
/* ββ
|
| 652 |
|
| 653 |
-
.
|
| 654 |
-
position:
|
| 655 |
-
|
| 656 |
-
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
|
| 660 |
-
z-index: 200;
|
| 661 |
-
padding: 20px;
|
| 662 |
-
}
|
| 663 |
-
|
| 664 |
-
.modal-panel {
|
| 665 |
-
width: 100%;
|
| 666 |
-
max-width: 480px;
|
| 667 |
-
max-height: 80vh;
|
| 668 |
-
overflow: hidden;
|
| 669 |
display: flex;
|
| 670 |
flex-direction: column;
|
| 671 |
background: var(--card-bg);
|
| 672 |
border: 1px solid var(--border-primary);
|
| 673 |
-
border-radius:
|
| 674 |
-
padding:
|
| 675 |
-
|
|
|
|
| 676 |
}
|
| 677 |
|
| 678 |
-
.
|
| 679 |
display: flex;
|
| 680 |
-
|
| 681 |
-
|
| 682 |
-
|
|
|
|
|
|
|
| 683 |
}
|
| 684 |
|
| 685 |
-
.
|
| 686 |
-
font-size:
|
| 687 |
font-weight: 600;
|
| 688 |
color: var(--text-primary);
|
| 689 |
-
|
| 690 |
-
|
| 691 |
-
|
| 692 |
-
.modal-close-btn {
|
| 693 |
-
border: none;
|
| 694 |
-
background: transparent;
|
| 695 |
-
color: var(--text-secondary);
|
| 696 |
-
cursor: pointer;
|
| 697 |
-
padding: 4px;
|
| 698 |
-
border-radius: 6px;
|
| 699 |
-
display: inline-flex;
|
| 700 |
-
align-items: center;
|
| 701 |
-
}
|
| 702 |
-
|
| 703 |
-
.modal-close-btn:hover {
|
| 704 |
-
background: var(--bg-tertiary);
|
| 705 |
-
color: var(--text-primary);
|
| 706 |
}
|
| 707 |
|
| 708 |
-
.
|
| 709 |
-
font-size:
|
| 710 |
color: var(--text-muted);
|
| 711 |
-
|
| 712 |
}
|
| 713 |
|
| 714 |
-
.
|
| 715 |
position: relative;
|
| 716 |
-
margin-bottom:
|
| 717 |
}
|
| 718 |
|
| 719 |
-
.
|
| 720 |
position: absolute;
|
| 721 |
-
left:
|
| 722 |
top: 50%;
|
| 723 |
transform: translateY(-50%);
|
| 724 |
color: var(--text-muted);
|
|
@@ -726,83 +705,87 @@
|
|
| 726 |
pointer-events: none;
|
| 727 |
}
|
| 728 |
|
| 729 |
-
.
|
| 730 |
width: 100%;
|
| 731 |
box-sizing: border-box;
|
| 732 |
-
padding:
|
| 733 |
border: 1px solid var(--border-primary);
|
| 734 |
-
border-radius:
|
| 735 |
background: var(--bg-primary);
|
| 736 |
color: var(--text-primary);
|
| 737 |
-
font-size:
|
| 738 |
}
|
| 739 |
|
| 740 |
-
.
|
| 741 |
outline: none;
|
| 742 |
border-color: var(--accent-primary);
|
| 743 |
}
|
| 744 |
|
| 745 |
-
.
|
| 746 |
color: var(--text-muted);
|
| 747 |
}
|
| 748 |
|
| 749 |
-
.
|
| 750 |
list-style: none;
|
| 751 |
margin: 0;
|
| 752 |
padding: 0;
|
| 753 |
overflow-y: auto;
|
| 754 |
flex: 1;
|
| 755 |
-
max-height: 50vh;
|
| 756 |
}
|
| 757 |
|
| 758 |
-
.
|
| 759 |
-
margin-bottom:
|
| 760 |
}
|
| 761 |
|
| 762 |
-
.
|
| 763 |
width: 100%;
|
| 764 |
text-align: left;
|
| 765 |
-
padding:
|
| 766 |
border: 1px solid transparent;
|
| 767 |
-
border-radius:
|
| 768 |
background: transparent;
|
| 769 |
color: var(--text-primary);
|
| 770 |
cursor: pointer;
|
| 771 |
display: flex;
|
| 772 |
flex-direction: column;
|
| 773 |
-
gap:
|
| 774 |
-
font-size:
|
| 775 |
transition: background 0.1s;
|
| 776 |
}
|
| 777 |
|
| 778 |
-
.
|
| 779 |
background: var(--bg-tertiary);
|
| 780 |
}
|
| 781 |
|
| 782 |
-
.
|
| 783 |
border-color: var(--accent-primary);
|
| 784 |
background: var(--accent-light);
|
| 785 |
}
|
| 786 |
|
| 787 |
-
.
|
| 788 |
background: var(--accent-light);
|
| 789 |
}
|
| 790 |
|
| 791 |
-
.
|
| 792 |
-
display: flex;
|
| 793 |
-
flex-direction: column;
|
| 794 |
-
gap: 1px;
|
| 795 |
-
}
|
| 796 |
-
|
| 797 |
-
.picker-item-name {
|
| 798 |
font-weight: 500;
|
|
|
|
| 799 |
}
|
| 800 |
|
| 801 |
-
.
|
| 802 |
-
font-size:
|
| 803 |
color: var(--text-muted);
|
| 804 |
}
|
| 805 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 806 |
/* ββ Status / Loading ββββββββββββββββββββββββββββββββββββββββββββ */
|
| 807 |
|
| 808 |
.status-bar {
|
|
|
|
| 648 |
}
|
| 649 |
|
| 650 |
|
| 651 |
+
/* ββ Orchestrator sub-menu βββββββββββββββββββββββββββββββββββββββ */
|
| 652 |
|
| 653 |
+
.dev-sub-panel {
|
| 654 |
+
position: absolute;
|
| 655 |
+
right: 100%;
|
| 656 |
+
top: 0;
|
| 657 |
+
margin-right: 4px;
|
| 658 |
+
width: 280px;
|
| 659 |
+
max-height: 70vh;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 660 |
display: flex;
|
| 661 |
flex-direction: column;
|
| 662 |
background: var(--card-bg);
|
| 663 |
border: 1px solid var(--border-primary);
|
| 664 |
+
border-radius: 10px;
|
| 665 |
+
padding: 8px;
|
| 666 |
+
z-index: 51;
|
| 667 |
+
box-shadow: var(--shadow-md);
|
| 668 |
}
|
| 669 |
|
| 670 |
+
.dev-sub-header {
|
| 671 |
display: flex;
|
| 672 |
+
flex-direction: column;
|
| 673 |
+
gap: 2px;
|
| 674 |
+
padding: 4px 6px 8px;
|
| 675 |
+
border-bottom: 1px solid var(--border-primary);
|
| 676 |
+
margin-bottom: 6px;
|
| 677 |
}
|
| 678 |
|
| 679 |
+
.dev-sub-title {
|
| 680 |
+
font-size: 12px;
|
| 681 |
font-weight: 600;
|
| 682 |
color: var(--text-primary);
|
| 683 |
+
text-transform: uppercase;
|
| 684 |
+
letter-spacing: 0.5px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 685 |
}
|
| 686 |
|
| 687 |
+
.dev-sub-current {
|
| 688 |
+
font-size: 11px;
|
| 689 |
color: var(--text-muted);
|
| 690 |
+
font-style: italic;
|
| 691 |
}
|
| 692 |
|
| 693 |
+
.dev-sub-search {
|
| 694 |
position: relative;
|
| 695 |
+
margin-bottom: 6px;
|
| 696 |
}
|
| 697 |
|
| 698 |
+
.dev-sub-search-icon {
|
| 699 |
position: absolute;
|
| 700 |
+
left: 8px;
|
| 701 |
top: 50%;
|
| 702 |
transform: translateY(-50%);
|
| 703 |
color: var(--text-muted);
|
|
|
|
| 705 |
pointer-events: none;
|
| 706 |
}
|
| 707 |
|
| 708 |
+
.dev-sub-search input {
|
| 709 |
width: 100%;
|
| 710 |
box-sizing: border-box;
|
| 711 |
+
padding: 7px 8px 7px 30px;
|
| 712 |
border: 1px solid var(--border-primary);
|
| 713 |
+
border-radius: 8px;
|
| 714 |
background: var(--bg-primary);
|
| 715 |
color: var(--text-primary);
|
| 716 |
+
font-size: 12px;
|
| 717 |
}
|
| 718 |
|
| 719 |
+
.dev-sub-search input:focus {
|
| 720 |
outline: none;
|
| 721 |
border-color: var(--accent-primary);
|
| 722 |
}
|
| 723 |
|
| 724 |
+
.dev-sub-search input::placeholder {
|
| 725 |
color: var(--text-muted);
|
| 726 |
}
|
| 727 |
|
| 728 |
+
.dev-sub-list {
|
| 729 |
list-style: none;
|
| 730 |
margin: 0;
|
| 731 |
padding: 0;
|
| 732 |
overflow-y: auto;
|
| 733 |
flex: 1;
|
|
|
|
| 734 |
}
|
| 735 |
|
| 736 |
+
.dev-sub-list li {
|
| 737 |
+
margin-bottom: 2px;
|
| 738 |
}
|
| 739 |
|
| 740 |
+
.dev-sub-item {
|
| 741 |
width: 100%;
|
| 742 |
text-align: left;
|
| 743 |
+
padding: 6px 8px;
|
| 744 |
border: 1px solid transparent;
|
| 745 |
+
border-radius: 6px;
|
| 746 |
background: transparent;
|
| 747 |
color: var(--text-primary);
|
| 748 |
cursor: pointer;
|
| 749 |
display: flex;
|
| 750 |
flex-direction: column;
|
| 751 |
+
gap: 1px;
|
| 752 |
+
font-size: 12px;
|
| 753 |
transition: background 0.1s;
|
| 754 |
}
|
| 755 |
|
| 756 |
+
.dev-sub-item:hover {
|
| 757 |
background: var(--bg-tertiary);
|
| 758 |
}
|
| 759 |
|
| 760 |
+
.dev-sub-item-active {
|
| 761 |
border-color: var(--accent-primary);
|
| 762 |
background: var(--accent-light);
|
| 763 |
}
|
| 764 |
|
| 765 |
+
.dev-sub-item-active:hover {
|
| 766 |
background: var(--accent-light);
|
| 767 |
}
|
| 768 |
|
| 769 |
+
.dev-sub-item strong {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 770 |
font-weight: 500;
|
| 771 |
+
font-size: 12px;
|
| 772 |
}
|
| 773 |
|
| 774 |
+
.dev-sub-provider {
|
| 775 |
+
font-size: 10px;
|
| 776 |
color: var(--text-muted);
|
| 777 |
}
|
| 778 |
|
| 779 |
+
@media (max-width: 600px) {
|
| 780 |
+
.dev-sub-panel {
|
| 781 |
+
right: 0;
|
| 782 |
+
top: 100%;
|
| 783 |
+
margin-right: 0;
|
| 784 |
+
margin-top: 4px;
|
| 785 |
+
width: 260px;
|
| 786 |
+
}
|
| 787 |
+
}
|
| 788 |
+
|
| 789 |
/* ββ Status / Loading ββββββββββββββββββββββββββββββββββββββββββββ */
|
| 790 |
|
| 791 |
.status-bar {
|