Skip to content

Commit 53000de

Browse files
committed
Add theme-aware surface color, iframe theming, and UI polish
- Add --bg-surface CSS variable for brighter response header and side panel - Refactor JSON tree CSS to use custom properties for dynamic theming - Add MutationObserver to auto-theme new iframes (light theme support) - Move Re-auth SSO and Connect buttons side by side - Add mini UI mockup previews to theme selector cards - Improve curl box contrast with theme-aware colors Co-authored-by: Isaac
1 parent a6cd83c commit 53000de

3 files changed

Lines changed: 241 additions & 76 deletions

File tree

app.py

Lines changed: 154 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -230,55 +230,62 @@ def _find_list_key(data: dict) -> Optional[str]:
230230
# ── JSON Tree Viewer ──────────────────────────────────────────────────────────
231231
_TREE_CSS = (
232232
"@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&display=swap');"
233-
"html{margin:0;padding:0;background:#050810;height:100%;overflow:hidden;}"
234-
"body{margin:0;padding:0;background:#050810;height:100%;overflow:auto;}"
233+
":root{"
234+
"--t-bg:#050810;--t-text:#94a3b8;--t-text-hi:#e2e8f0;--t-muted:#475569;"
235+
"--t-border:rgba(255,255,255,.07);--t-accent:#00d4ff;"
236+
"--t-key:#60a5fa;--t-str:#86efac;--t-num:#fbbf24;--t-bool:#c084fc;--t-null:#94a3b8;"
237+
"--t-punc:#64748b;--t-btn-bg:rgba(0,212,255,.07);--t-btn-border:rgba(0,212,255,.2);"
238+
"--t-input-bg:rgba(255,255,255,.06);--t-input-border:rgba(255,255,255,.12);"
239+
"--t-scroll:rgba(255,255,255,.12);--t-tip-bg:#1e293b;--t-mark:#7c3aed;--t-mark-cur:#f59e0b;"
240+
"}"
241+
"html{margin:0;padding:0;background:var(--t-bg);height:100%;overflow:hidden;}"
242+
"body{margin:0;padding:0;background:var(--t-bg);height:100%;overflow:auto;}"
235243
"body{font-family:'JetBrains Mono','Fira Code',ui-monospace,monospace;"
236-
"font-size:12px;line-height:1.7;color:#94a3b8;}"
237-
"#toolbar{position:sticky;top:0;background:#050810;border-bottom:1px solid rgba(255,255,255,.07);"
244+
"font-size:12px;line-height:1.7;color:var(--t-text);}"
245+
"#toolbar{position:sticky;top:0;background:var(--t-bg);border-bottom:1px solid var(--t-border);"
238246
"padding:5px 14px;display:flex;align-items:center;gap:6px;z-index:10;}"
239-
"#depth-label{color:#475569;font-size:11px;font-style:italic;flex:1;}"
240-
".depth-btn{background:rgba(0,212,255,.07);border:1px solid rgba(0,212,255,.2);color:#00d4ff;"
247+
"#depth-label{color:var(--t-muted);font-size:11px;font-style:italic;flex:1;}"
248+
".depth-btn{background:var(--t-btn-bg);border:1px solid var(--t-btn-border);color:var(--t-accent);"
241249
"border-radius:4px;width:22px;height:22px;font-size:15px;line-height:1;cursor:pointer;"
242250
"display:inline-flex;align-items:center;justify-content:center;padding:0;flex-shrink:0;}"
243-
".depth-btn:hover{background:rgba(0,212,255,.18);border-color:rgba(0,212,255,.45);}"
251+
".depth-btn:hover{opacity:.85;}"
244252
".depth-btn:disabled{opacity:.3;cursor:default;}"
245253
"#root{padding:10px 14px;}"
246254
".tree-node{display:block;}"
247255
".node-header{display:block;}"
248256
".toggle{display:inline-block;width:14px;text-align:center;cursor:pointer;"
249-
"user-select:none;color:#475569;transition:color .1s;}"
250-
".toggle:hover{color:#00d4ff;}"
251-
".preview{color:#475569;font-style:italic;}"
257+
"user-select:none;color:var(--t-muted);transition:color .1s;}"
258+
".toggle:hover{color:var(--t-accent);}"
259+
".preview{color:var(--t-muted);font-style:italic;}"
252260
".tree-node:not(.collapsed)>.node-header>.preview{display:none;}"
253261
".children{margin-left:20px;display:block;}"
254262
".close-line{display:block;}"
255263
".tree-node.collapsed>.children,.tree-node.collapsed>.close-line{display:none;}"
256264
".kv,.item{display:block;}"
257-
".jk{color:#60a5fa}.jv{color:#86efac}.jn{color:#fbbf24}"
258-
".jb{color:#c084fc}.jbn{color:#94a3b8}.jp{color:#64748b}"
265+
".jk{color:var(--t-key)}.jv{color:var(--t-str)}.jn{color:var(--t-num)}"
266+
".jb{color:var(--t-bool)}.jbn{color:var(--t-null)}.jp{color:var(--t-punc)}"
259267
".id-link{cursor:pointer;border-bottom:1px dashed currentColor;}"
260-
".id-link:hover{color:#00d4ff!important;border-bottom-color:#00d4ff;"
261-
"text-shadow:0 0 6px rgba(0,212,255,.5);}"
262-
".jts{position:relative;cursor:default;border-bottom:1px dotted #fbbf24;}"
268+
".id-link:hover{color:var(--t-accent)!important;border-bottom-color:var(--t-accent);}"
269+
".jts{position:relative;cursor:default;border-bottom:1px dotted var(--t-num);}"
263270
".jts:hover .ts-tip{visibility:visible;opacity:1;}"
264271
".ts-tip{visibility:hidden;opacity:0;position:absolute;bottom:calc(100% + 4px);left:50%;"
265-
"transform:translateX(-50%);background:#1e293b;color:#e2e8f0;font-size:11px;"
272+
"transform:translateX(-50%);background:var(--t-tip-bg);color:var(--t-text-hi);font-size:11px;"
266273
"padding:3px 8px;border-radius:4px;white-space:nowrap;pointer-events:none;"
267-
"border:1px solid rgba(0,212,255,.25);z-index:100;"
274+
"border:1px solid var(--t-btn-border);z-index:100;"
268275
"transition:opacity .15s;}"
269276
"::-webkit-scrollbar{width:6px;height:6px}"
270-
"::-webkit-scrollbar-thumb{background:rgba(255,255,255,.12);border-radius:3px}"
277+
"::-webkit-scrollbar-thumb{background:var(--t-scroll);border-radius:3px}"
271278
"::-webkit-scrollbar-track{background:transparent}"
272-
"*{scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.12) transparent}"
273-
"#search-box{flex:1;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.12);"
274-
"color:#e2e8f0;border-radius:4px;padding:2px 8px;font-family:inherit;font-size:11px;"
279+
"*{scrollbar-width:thin;scrollbar-color:var(--t-scroll) transparent}"
280+
"#search-box{flex:1;background:var(--t-input-bg);border:1px solid var(--t-input-border);"
281+
"color:var(--t-text-hi);border-radius:4px;padding:2px 8px;font-family:inherit;font-size:11px;"
275282
"outline:none;min-width:80px;}"
276-
"#search-box:focus{border-color:rgba(0,212,255,.4);background:rgba(0,212,255,.05);}"
277-
"#search-box::placeholder{color:#475569;}"
278-
"#sc{color:#94a3b8;font-size:11px;white-space:nowrap;flex-shrink:0;min-width:44px;text-align:center;}"
279-
"mark.sh{background:#7c3aed;color:#fff;border-radius:2px;padding:0 1px;}"
280-
"mark.sh.cur{background:#f59e0b;color:#050810;}"
281-
".sep{width:1px;height:16px;background:rgba(255,255,255,.12);flex-shrink:0;}"
283+
"#search-box:focus{border-color:var(--t-btn-border);}"
284+
"#search-box::placeholder{color:var(--t-muted);}"
285+
"#sc{color:var(--t-text);font-size:11px;white-space:nowrap;flex-shrink:0;min-width:44px;text-align:center;}"
286+
"mark.sh{background:var(--t-mark);color:#fff;border-radius:2px;padding:0 1px;}"
287+
"mark.sh.cur{background:var(--t-mark-cur);color:#050810;}"
288+
".sep{width:1px;height:16px;background:var(--t-scroll);flex-shrink:0;}"
282289
)
283290

284291
# Raw string — no Python escape processing; JS unicode escapes work as-is in browser.
@@ -1027,48 +1034,55 @@ def build_sidebar() -> html.Div:
10271034
_THEMES = {
10281035
"midnight": {"label": "Midnight", "dark": True, "icon": "bi-moon-stars-fill",
10291036
"bg-void": "#050810", "bg-dark": "#080c18", "bg-panel": "#0d1225",
1037+
"bg-surface": "#141a2e",
10301038
"bg-card": "rgba(255,255,255,0.028)", "bg-hover": "rgba(255,255,255,0.055)",
10311039
"bg-active": "rgba(0,212,255,0.09)", "border": "rgba(255,255,255,0.07)",
10321040
"border-hi": "rgba(0,212,255,0.45)", "text-hi": "#f8fafc",
10331041
"text-1": "#e2e8f0", "text-2": "#94a3b8", "text-3": "#4b5563",
10341042
"accent": "#00d4ff", "card-bg": "#1a1d23"},
10351043
"obsidian": {"label": "Obsidian", "dark": True, "icon": "bi-gem",
10361044
"bg-void": "#0a0a0a", "bg-dark": "#111111", "bg-panel": "#181818",
1045+
"bg-surface": "#222222",
10371046
"bg-card": "rgba(255,255,255,0.035)", "bg-hover": "rgba(255,255,255,0.06)",
10381047
"bg-active": "rgba(168,85,247,0.1)", "border": "rgba(255,255,255,0.08)",
10391048
"border-hi": "rgba(168,85,247,0.5)", "text-hi": "#fafafa",
10401049
"text-1": "#e0e0e0", "text-2": "#9e9e9e", "text-3": "#555555",
10411050
"accent": "#a855f7", "card-bg": "#1e1e1e"},
10421051
"deep-ocean": {"label": "Deep Ocean", "dark": True, "icon": "bi-water",
10431052
"bg-void": "#020617", "bg-dark": "#0f172a", "bg-panel": "#1e293b",
1053+
"bg-surface": "#263448",
10441054
"bg-card": "rgba(255,255,255,0.03)", "bg-hover": "rgba(255,255,255,0.05)",
10451055
"bg-active": "rgba(56,189,248,0.1)", "border": "rgba(255,255,255,0.06)",
10461056
"border-hi": "rgba(56,189,248,0.5)", "text-hi": "#f8fafc",
10471057
"text-1": "#cbd5e1", "text-2": "#64748b", "text-3": "#475569",
10481058
"accent": "#38bdf8", "card-bg": "#1e293b"},
10491059
"aurora": {"label": "Aurora", "dark": True, "icon": "bi-stars",
10501060
"bg-void": "#030712", "bg-dark": "#0c1427", "bg-panel": "#162032",
1061+
"bg-surface": "#1e2a3e",
10511062
"bg-card": "rgba(255,255,255,0.03)", "bg-hover": "rgba(255,255,255,0.055)",
10521063
"bg-active": "rgba(16,185,129,0.1)", "border": "rgba(255,255,255,0.07)",
10531064
"border-hi": "rgba(16,185,129,0.5)", "text-hi": "#ecfdf5",
10541065
"text-1": "#d1fae5", "text-2": "#6ee7b7", "text-3": "#34d399",
10551066
"accent": "#10b981", "card-bg": "#1a2332"},
10561067
"snowlight": {"label": "Snowlight", "dark": False, "icon": "bi-sun-fill",
10571068
"bg-void": "#f8fafc", "bg-dark": "#f1f5f9", "bg-panel": "#ffffff",
1069+
"bg-surface": "#eef2f7",
10581070
"bg-card": "rgba(0,0,0,0.03)", "bg-hover": "rgba(0,0,0,0.05)",
10591071
"bg-active": "rgba(99,102,241,0.08)", "border": "rgba(0,0,0,0.1)",
10601072
"border-hi": "rgba(99,102,241,0.5)", "text-hi": "#0f172a",
10611073
"text-1": "#1e293b", "text-2": "#64748b", "text-3": "#94a3b8",
10621074
"accent": "#6366f1", "card-bg": "#ffffff"},
10631075
"paper": {"label": "Paper", "dark": False, "icon": "bi-file-earmark-text",
10641076
"bg-void": "#fafaf9", "bg-dark": "#f5f5f4", "bg-panel": "#ffffff",
1077+
"bg-surface": "#edeceb",
10651078
"bg-card": "rgba(0,0,0,0.025)", "bg-hover": "rgba(0,0,0,0.04)",
10661079
"bg-active": "rgba(234,88,12,0.08)", "border": "rgba(0,0,0,0.08)",
10671080
"border-hi": "rgba(234,88,12,0.45)", "text-hi": "#1c1917",
10681081
"text-1": "#292524", "text-2": "#78716c", "text-3": "#a8a29e",
10691082
"accent": "#ea580c", "card-bg": "#ffffff"},
10701083
"cloud": {"label": "Cloud", "dark": False, "icon": "bi-cloud-sun-fill",
10711084
"bg-void": "#f0f9ff", "bg-dark": "#e0f2fe", "bg-panel": "#ffffff",
1085+
"bg-surface": "#daeaf8",
10721086
"bg-card": "rgba(0,0,0,0.02)", "bg-hover": "rgba(0,0,0,0.04)",
10731087
"bg-active": "rgba(14,165,233,0.08)", "border": "rgba(0,0,0,0.08)",
10741088
"border-hi": "rgba(14,165,233,0.45)", "text-hi": "#0c4a6e",
@@ -1094,12 +1108,42 @@ def build_sidebar() -> html.Div:
10941108
_light_themes = [t for t in _THEMES.values() if not t["dark"]]
10951109

10961110
def _theme_card(tid: str, t: dict) -> html.Div:
1097-
"""Build a clickable theme preview card."""
1111+
"""Build a clickable theme preview card with a mini UI mockup."""
1112+
bg = t["bg-void"]
1113+
panel = t["bg-panel"]
1114+
accent = t["accent"]
1115+
text1 = t["text-1"]
1116+
text3 = t["text-3"]
1117+
border = t["border"]
10981118
return html.Button([
1119+
# Mini UI preview
10991120
html.Div([
1100-
html.Div(className="theme-swatch-bar", style={"background": t["accent"]}),
1101-
html.Div(className="theme-swatch-bg", style={"background": t["bg-void"]}),
1102-
], className="theme-swatch"),
1121+
# Topbar
1122+
html.Div([
1123+
html.Div(className="tm-logo", style={"background": accent}),
1124+
html.Div(className="tm-topbar-spacer"),
1125+
html.Div(className="tm-avatar", style={"background": text3}),
1126+
], className="tm-topbar", style={"background": panel, "borderBottom": f"1px solid {border}"}),
1127+
# Body
1128+
html.Div([
1129+
# Sidebar
1130+
html.Div([
1131+
html.Div(className="tm-nav-item", style={"background": accent, "opacity": "0.7"}),
1132+
html.Div(className="tm-nav-item", style={"background": text3, "opacity": "0.4"}),
1133+
html.Div(className="tm-nav-item", style={"background": text3, "opacity": "0.4"}),
1134+
html.Div(className="tm-nav-item", style={"background": text3, "opacity": "0.3"}),
1135+
], className="tm-sidebar", style={"background": panel, "borderRight": f"1px solid {border}"}),
1136+
# Content
1137+
html.Div([
1138+
html.Div(className="tm-line tm-line-title", style={"background": text1, "opacity": "0.6"}),
1139+
html.Div(className="tm-line", style={"background": text3, "opacity": "0.3"}),
1140+
html.Div(className="tm-line tm-line-short", style={"background": text3, "opacity": "0.2"}),
1141+
html.Div(className="tm-btn", style={"background": accent, "opacity": "0.5"}),
1142+
html.Div(className="tm-code-block", style={"background": panel, "border": f"1px solid {border}"}),
1143+
], className="tm-content"),
1144+
], className="tm-body"),
1145+
], className="tm-preview", style={"background": bg}),
1146+
# Label
11031147
html.Div([
11041148
html.I(className=f"bi {t['icon']} me-1"),
11051149
t["label"],
@@ -1375,12 +1419,6 @@ def _profile_section() -> html.Div:
13751419
),
13761420
], className="profile-select-row"),
13771421
html.Div(id="profile-auth-type", className="conn-hint mt-1"),
1378-
html.Button(
1379-
[html.I(className="bi bi-arrow-repeat me-2"), "Re-authenticate with SSO"],
1380-
id="reauth-btn",
1381-
n_clicks=0,
1382-
className="reauth-btn mt-2",
1383-
),
13841422
html.Div(id="reauth-status", className="mt-2 small"),
13851423
], id="profile-section")
13861424

@@ -1453,12 +1491,20 @@ def _custom_section() -> html.Div:
14531491
_profile_section(),
14541492
_sso_section(),
14551493
_custom_section(),
1456-
html.Button(
1457-
[html.I(className="bi bi-plug-fill me-2"), "Connect"],
1458-
id="apply-conn-btn",
1459-
n_clicks=0,
1460-
className="apply-btn mt-3",
1461-
),
1494+
html.Div([
1495+
html.Button(
1496+
[html.I(className="bi bi-arrow-repeat me-2"), "Re-auth SSO"],
1497+
id="reauth-btn",
1498+
n_clicks=0,
1499+
className="reauth-btn",
1500+
),
1501+
html.Button(
1502+
[html.I(className="bi bi-plug-fill me-2"), "Connect"],
1503+
id="apply-conn-btn",
1504+
n_clicks=0,
1505+
className="apply-btn",
1506+
),
1507+
], className="conn-btn-row mt-3"),
14621508
html.Div(id="conn-status", className="mt-2 small"),
14631509
], className="px-4 pb-3"),
14641510

@@ -1741,12 +1787,14 @@ def update_settings(tz, lang, theme_clicks, current):
17411787
function(settings) {
17421788
if (!settings) return window.dash_clientside.no_update;
17431789
1744-
var themes = """ + json.dumps({k: {kk: vv for kk, vv in v.items() if kk not in ("label", "icon", "dark")} for k, v in _THEMES.items()}) + """;
1790+
var themes = """ + json.dumps({k: {kk: vv for kk, vv in v.items() if kk not in ("label", "icon")} for k, v in _THEMES.items()}) + """;
17451791
var theme = themes[settings.theme || "midnight"] || themes["midnight"];
1792+
var isDark = theme["dark"];
17461793
var root = document.documentElement;
17471794
root.style.setProperty('--bg-void', theme['bg-void']);
17481795
root.style.setProperty('--bg-dark', theme['bg-dark']);
17491796
root.style.setProperty('--bg-panel', theme['bg-panel']);
1797+
root.style.setProperty('--bg-surface', theme['bg-surface']);
17501798
root.style.setProperty('--bg-card', theme['bg-card']);
17511799
root.style.setProperty('--bg-hover', theme['bg-hover']);
17521800
root.style.setProperty('--bg-active', theme['bg-active']);
@@ -1768,6 +1816,68 @@ def update_settings(tz, lang, theme_clicks, current):
17681816
}
17691817
});
17701818
1819+
// Push theme into JSON tree iframes
1820+
var iframeVars = isDark ? {
1821+
'--t-bg': theme['bg-void'], '--t-text': theme['text-2'], '--t-text-hi': theme['text-1'],
1822+
'--t-muted': theme['text-3'], '--t-border': theme['border'], '--t-accent': theme['accent'],
1823+
'--t-key': '#60a5fa', '--t-str': '#86efac', '--t-num': '#fbbf24',
1824+
'--t-bool': '#c084fc', '--t-null': theme['text-2'], '--t-punc': theme['text-3'],
1825+
'--t-btn-bg': 'rgba(0,212,255,.07)', '--t-btn-border': 'rgba(0,212,255,.2)',
1826+
'--t-input-bg': 'rgba(255,255,255,.06)', '--t-input-border': 'rgba(255,255,255,.12)',
1827+
'--t-scroll': 'rgba(255,255,255,.12)', '--t-tip-bg': theme['bg-panel'],
1828+
'--t-mark': '#7c3aed', '--t-mark-cur': '#f59e0b'
1829+
} : {
1830+
'--t-bg': theme['bg-void'], '--t-text': '#374151', '--t-text-hi': '#111827',
1831+
'--t-muted': '#9ca3af', '--t-border': theme['border'], '--t-accent': theme['accent'],
1832+
'--t-key': '#2563eb', '--t-str': '#059669', '--t-num': '#d97706',
1833+
'--t-bool': '#7c3aed', '--t-null': '#9ca3af', '--t-punc': '#6b7280',
1834+
'--t-btn-bg': 'rgba(99,102,241,.08)', '--t-btn-border': 'rgba(99,102,241,.25)',
1835+
'--t-input-bg': 'rgba(0,0,0,.04)', '--t-input-border': 'rgba(0,0,0,.12)',
1836+
'--t-scroll': 'rgba(0,0,0,.15)', '--t-tip-bg': '#ffffff',
1837+
'--t-mark': '#7c3aed', '--t-mark-cur': '#f59e0b'
1838+
};
1839+
// Store current iframe vars globally so new iframes can pick them up
1840+
window.__iframeThemeVars = iframeVars;
1841+
1842+
// Apply to all existing iframes
1843+
function applyIframeTheme(iframe) {
1844+
try {
1845+
var iDoc = iframe.contentDocument || (iframe.contentWindow && iframe.contentWindow.document);
1846+
if (iDoc && iDoc.documentElement) {
1847+
var r = iDoc.documentElement;
1848+
var vars = window.__iframeThemeVars;
1849+
if (vars) { for (var k in vars) { r.style.setProperty(k, vars[k]); } }
1850+
}
1851+
} catch(e) {}
1852+
}
1853+
document.querySelectorAll('iframe').forEach(function(iframe) {
1854+
applyIframeTheme(iframe);
1855+
// Also apply when iframe finishes loading (for newly created ones)
1856+
iframe.addEventListener('load', function() { applyIframeTheme(iframe); });
1857+
});
1858+
1859+
// Watch for new iframes added to the DOM and theme them automatically
1860+
if (!window.__iframeThemeObserver) {
1861+
window.__iframeThemeObserver = new MutationObserver(function(mutations) {
1862+
for (var i = 0; i < mutations.length; i++) {
1863+
var nodes = mutations[i].addedNodes;
1864+
for (var j = 0; j < nodes.length; j++) {
1865+
var nd = nodes[j];
1866+
if (nd.tagName === 'IFRAME') {
1867+
nd.addEventListener('load', function() { applyIframeTheme(nd); });
1868+
applyIframeTheme(nd);
1869+
} else if (nd.querySelectorAll) {
1870+
nd.querySelectorAll('iframe').forEach(function(f) {
1871+
f.addEventListener('load', function() { applyIframeTheme(f); });
1872+
applyIframeTheme(f);
1873+
});
1874+
}
1875+
}
1876+
}
1877+
});
1878+
window.__iframeThemeObserver.observe(document.body, { childList: true, subtree: true });
1879+
}
1880+
17711881
return window.dash_clientside.no_update;
17721882
}
17731883
""",

0 commit comments

Comments
 (0)