From 96a590d2b36ddf89b1ea2ed0e874571285380b30 Mon Sep 17 00:00:00 2001 From: Frank Faulstich Date: Wed, 22 Jul 2026 12:57:12 +0200 Subject: [PATCH] Optimizing Styles Fixes #503 --- sl/SL_Menu.py | 11 +- sl/style_claude.css | 284 ++++++++++++++++++++++++++++++++++++++++++++ sl/style_dark.css | 212 +++++++++++++++++++++++++++++---- 3 files changed, 483 insertions(+), 24 deletions(-) create mode 100644 sl/style_claude.css diff --git a/sl/SL_Menu.py b/sl/SL_Menu.py index 7cdc4da..610eab0 100644 --- a/sl/SL_Menu.py +++ b/sl/SL_Menu.py @@ -70,9 +70,12 @@ def render_icon_button_css(): Keeps icon-only action buttons visually consistent across all Streamlit views. """ button_selectors = [ - '[class*="st-key-toolbar_new_popover"] [data-testid="stPopoverButton"]', - '[class*="st-key-toolbar_mgmt_popover"] [data-testid="stPopoverButton"]', - '[class*="st-key-toolbar_report_popover"] [data-testid="stPopoverButton"]', + # st.popover's `key=` doesn't get reflected as a "st-key-*" class + # anywhere in its rendered DOM (unlike st.button/st.expander), so a + # per-key selector like the ones below can never match it - every + # popover trigger in this app is one of these three toolbar icons, + # so targeting the testid directly covers all of them. + '[data-testid="stPopoverButton"]', '[class*="st-key-toolbar_today_btn"] button', '[class*="st-key-toolbar_planning_btn"] button', '[class*="st-key-toolbar_email_btn"] button', @@ -113,7 +116,7 @@ def render_icon_button_css(): align-items: center !important; justify-content: center !important; gap: 0 !important; - color: #3f3f3f !important; + color: var(--tc-icon-color, #3f3f3f) !important; font-family: Arial, Helvetica, sans-serif !important; font-size: 20px !important; font-weight: 700 !important; diff --git a/sl/style_claude.css b/sl/style_claude.css new file mode 100644 index 0000000..567f263 --- /dev/null +++ b/sl/style_claude.css @@ -0,0 +1,284 @@ +/* ========================================================================== + TimeControl - Claude Style + ========================================================================== + Warm, editorial palette inspired by the Claude Desktop app: a soft cream + page background, a warm charcoal ink for text, and a terracotta/clay + accent for interactive elements, in place of the usual cool blues/grays. + + Custom properties live on :root (not .stApp) because several Streamlit + components (popover panels, select/date dropdowns) render into a portal + appended directly to , outside .stApp's own subtree - a variable + defined only on .stApp would never reach them. + ========================================================================== */ + +:root { + --tc-bg: #F5F4EE; + --tc-bg-elevated: #FFFFFF; + --tc-bg-input: #FFFFFF; + --tc-bg-hover: #ECE9DF; + --tc-text: #3D3929; + --tc-text-muted: #83817D; + --tc-border: #E0DCD0; + --tc-accent: #CC785C; + --tc-accent-hover: #B15E42; + --tc-icon-color: #6B6960; + --tc-code-text: #AF5A3F; +} + +/* Global Font & Background */ +.stApp { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + background-color: var(--tc-bg); + color: var(--tc-text); +} + +/* Native Streamlit chrome: the fixed header bar (Deploy/hamburger menu) and + the toolbar/decoration strip sit outside .stApp's own background, so they + otherwise stay on Streamlit's default white/gray regardless of the rest + of the page. */ +header[data-testid="stHeader"], +[data-testid="stToolbar"], +[data-testid="stDecoration"] { + background-color: var(--tc-bg) !important; + color: var(--tc-text); +} + +header[data-testid="stHeader"] svg { + fill: var(--tc-icon-color) !important; +} + +/* Headings */ +h1, h2, h3 { + font-weight: 700; + color: var(--tc-text); + letter-spacing: -0.02em; +} + +h1 { + font-size: 2.0rem; + padding-bottom: 0.5rem; + border-bottom: 1px solid var(--tc-border); + margin-bottom: 1.5rem; + padding-top: 0.1rem; +} + +h1::before { + content: "⏱️"; + margin-right: 0.5rem; +} + +h2 { + font-size: 1.6rem; +} + +h3 { + font-size: 1.35rem; +} + +h4 { + font-size: 1.15rem; +} + +h5, h6 { + font-size: 1.0rem; +} + +/* Widget labels, captions and help tooltips */ +[data-testid="stWidgetLabel"] p, +[data-testid="stCaptionContainer"], +[data-testid="stCaptionContainer"] p, +[data-testid="stMarkdownContainer"] small { + color: var(--tc-text-muted) !important; +} + +[data-testid="stTooltipIcon"] { + color: var(--tc-text-muted) !important; +} + +/* Buttons - regular, form-submit and popover triggers all need the same + treatment so none of them fall back to Streamlit's own default styling. */ +.stButton button, +[data-testid="stFormSubmitButton"] button, +[data-testid="stPopoverButton"] { + border-radius: 10px; + font-weight: 600; + border: 1px solid var(--tc-border); + background-color: var(--tc-bg-elevated); + color: var(--tc-text); + transition: all 0.2s ease-in-out; + box-shadow: 0 1px 2px rgba(61, 57, 41, 0.06); +} + +.stButton button:hover, +[data-testid="stFormSubmitButton"] button:hover, +[data-testid="stPopoverButton"]:hover { + border-color: var(--tc-accent); + color: var(--tc-accent-hover); + background-color: var(--tc-bg-hover); + transform: translateY(-1px); + box-shadow: 0 4px 10px rgba(61, 57, 41, 0.10); +} + +.stButton button:active, +[data-testid="stFormSubmitButton"] button:active { + transform: translateY(0); + box-shadow: none; +} + +.stButton button:disabled, +.stButton button:disabled:hover { + opacity: 0.5; + color: var(--tc-text-muted) !important; + border-color: var(--tc-border) !important; + background-color: var(--tc-bg-elevated) !important; + transform: none; + box-shadow: none; +} + +/* Primary-type buttons (e.g. "Add Task") get the terracotta treatment to + read as the clear call-to-action, matching Claude's own send/primary + button color. */ +[data-testid="stBaseButton-primary"] { + background-color: var(--tc-accent) !important; + border-color: var(--tc-accent) !important; + color: #FFFFFF !important; +} + +[data-testid="stBaseButton-primary"]:hover { + background-color: var(--tc-accent-hover) !important; + border-color: var(--tc-accent-hover) !important; + color: #FFFFFF !important; +} + +/* The dropdown card a popover opens (toolbar's New/Management/Reporting + menus) - rendered in a body-level portal, stayed on Streamlit's default + white/gray otherwise. */ +[data-testid="stPopoverBody"], +[data-testid="stPopoverBody"] > div { + background-color: var(--tc-bg-elevated) !important; + border: 1px solid var(--tc-border) !important; + color: var(--tc-text); +} + +/* Input Fields */ +.stTextInput input, +.stNumberInput input, +.stTextArea textarea, +.stDateInput input, +.stSelectbox div[data-baseweb="select"] > div { + border-radius: 8px; + border: 1px solid var(--tc-border); + background-color: var(--tc-bg-input); + color: var(--tc-text); +} + +.stTextInput input:focus, +.stNumberInput input:focus, +.stTextArea textarea:focus { + border-color: var(--tc-accent) !important; + box-shadow: 0 0 0 1px var(--tc-accent) !important; +} + +.stTextInput input::placeholder, +.stTextArea textarea::placeholder { + color: var(--tc-text-muted); + opacity: 1; +} + +[data-testid="stNumberInputStepDown"], +[data-testid="stNumberInputStepUp"] { + background-color: var(--tc-bg-hover) !important; + border-color: var(--tc-border) !important; + color: var(--tc-text) !important; +} + +/* Select / date-input dropdown popups also render in a body-level portal + via BaseWeb, so they need the same background rendered explicitly. The + plain [data-baseweb="popover"] wrapper (and the unlabeled div directly + inside it) is what shows a card behind the calendar/menu content - the + inner listbox/menu/calendar elements need their own override too. */ +[data-baseweb="popover"], +[data-baseweb="popover"] > div, +[data-baseweb="popover"] [role="listbox"], +[data-baseweb="menu"], +[data-baseweb="calendar"] { + background-color: var(--tc-bg-elevated) !important; + color: var(--tc-text) !important; +} + +[data-baseweb="popover"] li[role="option"], +[data-baseweb="menu"] li { + color: var(--tc-text) !important; +} + +[data-baseweb="popover"] li[role="option"]:hover, +[data-baseweb="menu"] li:hover { + background-color: var(--tc-bg-hover) !important; +} + +/* The date-input calendar's month/year header bar and weekday-initials row + keep Streamlit's default secondary-background grey regardless of theme; + [role="presentation"] reaches the weekday row, .st-c7 is the shared class + between it and the header bar just above it. Out-of-month filler day + cells render their fill via a ::after pseudo-element rather than their + own background, so they need a separate override. */ +[data-baseweb="calendar"] [role="presentation"], +[data-baseweb="calendar"] .st-c7 { + background-color: transparent !important; +} + +[data-baseweb="calendar"] [role="gridcell"]::after { + background-color: transparent !important; +} + +/* Checkbox */ +[data-testid="stCheckbox"] label p { + color: var(--tc-text) !important; +} + +/* Expander */ +[data-testid="stExpander"] { + border: 1px solid var(--tc-border); + border-radius: 10px; + background-color: var(--tc-bg-elevated); + overflow: hidden; +} + +[data-testid="stExpander"] summary { + color: var(--tc-text) !important; +} + +[data-testid="stExpander"] summary:hover { + background-color: var(--tc-bg-hover); +} + +/* Divider */ +hr { + border-color: var(--tc-border) !important; +} + +/* Alerts (st.info/success/warning/error) */ +[data-testid="stAlert"] { + background-color: var(--tc-bg-elevated); + border-radius: 8px; + border: 1px solid var(--tc-border); +} + +/* Inline code and code blocks */ +code { + background-color: var(--tc-bg-hover) !important; + color: var(--tc-code-text) !important; +} + +/* Report Box */ +.report-box { + background-color: #FFFFFF; + padding: 20px; + border-radius: 12px; + font-family: 'SF Mono', 'Menlo', 'Monaco', 'Courier New', monospace; + font-size: 0.9em; + white-space: pre-wrap; + border: 1px solid var(--tc-border); + box-shadow: inset 0 1px 3px rgba(61, 57, 41, 0.06); + color: var(--tc-text); +} diff --git a/sl/style_dark.css b/sl/style_dark.css index b4a283b..1229943 100644 --- a/sl/style_dark.css +++ b/sl/style_dark.css @@ -1,21 +1,58 @@ +/* ========================================================================== + TimeControl - Dark Style + ========================================================================== + Custom properties live on :root (not .stApp) because several Streamlit + components (popover panels, select/date dropdowns, tooltips) render into + a portal appended directly to , outside .stApp's own subtree - a + variable defined only on .stApp would never reach them. + ========================================================================== */ + +:root { + --tc-bg: #1e1e1e; + --tc-bg-elevated: #2a2a2a; + --tc-bg-input: #2d2d2d; + --tc-bg-hover: #3a3a3a; + --tc-text: #f5f5f7; + --tc-text-muted: #a8a8ad; + --tc-border: #3a3a3a; + --tc-accent: #409cff; + --tc-icon-color: #d5d5da; + --tc-code-text: #7ee787; +} + /* Global Font & Background */ .stApp { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; - background-color: #1e1e1e; - color: #f5f5f7; + background-color: var(--tc-bg); + color: var(--tc-text); +} + +/* Native Streamlit chrome: the fixed header bar (Deploy/hamburger menu) and + the toolbar/decoration strip sit outside .stApp's own background, so they + otherwise stay on Streamlit's default white regardless of the rest of the + page. */ +header[data-testid="stHeader"], +[data-testid="stToolbar"], +[data-testid="stDecoration"] { + background-color: var(--tc-bg) !important; + color: var(--tc-text); +} + +header[data-testid="stHeader"] svg { + fill: var(--tc-text) !important; } /* Headings */ h1, h2, h3 { font-weight: 700; - color: #f5f5f7; + color: var(--tc-text); letter-spacing: -0.02em; } h1 { font-size: 2.0rem; padding-bottom: 0.5rem; - border-bottom: 1px solid #333333; + border-bottom: 1px solid var(--tc-border); margin-bottom: 1.5rem; padding-top: 0.1rem; } @@ -41,36 +78,171 @@ h5, h6 { font-size: 1.0rem; } -/* Buttons */ -.stButton button { +/* Widget labels, captions and help tooltips default to Streamlit's own + near-black text regardless of theme - unreadable on a dark background. */ +[data-testid="stWidgetLabel"] p, +[data-testid="stCaptionContainer"], +[data-testid="stCaptionContainer"] p, +[data-testid="stMarkdownContainer"] small { + color: var(--tc-text-muted) !important; +} + +[data-testid="stTooltipIcon"] { + color: var(--tc-text-muted) !important; +} + +/* Buttons - regular, form-submit and popover triggers all need the same + treatment: form submit buttons and popover triggers use a different + testid than plain st.button() and were previously left on Streamlit's + default white background with inherited near-white text (invisible). */ +.stButton button, +[data-testid="stFormSubmitButton"] button, +[data-testid="stPopoverButton"] { border-radius: 10px; font-weight: 600; - border: 1px solid #333333; - background-color: #2d2d2d; - color: #f5f5f7; + border: 1px solid var(--tc-border); + background-color: var(--tc-bg-elevated); + color: var(--tc-text); transition: all 0.2s ease-in-out; box-shadow: 0 1px 2px rgba(0,0,0,0.2); } -.stButton button:hover { - border-color: #409cff; - color: #409cff; - background-color: #3a3a3a; +.stButton button:hover, +[data-testid="stFormSubmitButton"] button:hover, +[data-testid="stPopoverButton"]:hover { + border-color: var(--tc-accent); + color: var(--tc-accent); + background-color: var(--tc-bg-hover); transform: translateY(-1px); box-shadow: 0 4px 6px rgba(0,0,0,0.3); } -.stButton button:active { +.stButton button:active, +[data-testid="stFormSubmitButton"] button:active { transform: translateY(0); box-shadow: none; } +.stButton button:disabled, +.stButton button:disabled:hover { + opacity: 0.45; + color: var(--tc-text-muted) !important; + border-color: var(--tc-border) !important; + background-color: var(--tc-bg-elevated) !important; + transform: none; + box-shadow: none; +} + +/* The dropdown card a popover opens (toolbar's New/Management/Reporting + menus) - rendered in a body-level portal, stayed white otherwise. */ +[data-testid="stPopoverBody"], +[data-testid="stPopoverBody"] > div { + background-color: var(--tc-bg-elevated) !important; + border: 1px solid var(--tc-border) !important; + color: var(--tc-text); +} + /* Input Fields */ -.stTextInput input, .stNumberInput input, .stSelectbox div[data-baseweb="select"] > div { +.stTextInput input, +.stNumberInput input, +.stTextArea textarea, +.stDateInput input, +.stSelectbox div[data-baseweb="select"] > div { border-radius: 8px; - border: 1px solid #333333; - background-color: #2d2d2d; - color: #f5f5f7; + border: 1px solid var(--tc-border); + background-color: var(--tc-bg-input); + color: var(--tc-text); +} + +.stTextInput input::placeholder, +.stTextArea textarea::placeholder { + color: var(--tc-text-muted); + opacity: 1; +} + +[data-testid="stNumberInputStepDown"], +[data-testid="stNumberInputStepUp"] { + background-color: var(--tc-bg-input) !important; + border-color: var(--tc-border) !important; + color: var(--tc-text) !important; +} + +/* Select / date-input dropdown popups also render in a body-level portal + via BaseWeb, so they need the same background rendered explicitly. The + plain [data-baseweb="popover"] wrapper (and the unlabeled div directly + inside it) is what actually shows a white card behind the calendar/menu + content - the inner listbox/menu/calendar elements are already dark on + their own, but that only covered part of the card. */ +[data-baseweb="popover"], +[data-baseweb="popover"] > div, +[data-baseweb="popover"] [role="listbox"], +[data-baseweb="menu"], +[data-baseweb="calendar"] { + background-color: var(--tc-bg-elevated) !important; + color: var(--tc-text) !important; +} + +[data-baseweb="popover"] li[role="option"], +[data-baseweb="menu"] li { + color: var(--tc-text) !important; +} + +/* The date-input calendar's month/year header bar and weekday-initials row + keep Streamlit's default secondary-background grey regardless of theme; + [role="presentation"] reaches the weekday row, its sibling (same class) + is the header bar just above it. Out-of-month filler day cells render + their fill via a ::after pseudo-element rather than their own background, + so they need a separate override. */ +[data-baseweb="calendar"] [role="presentation"], +[data-baseweb="calendar"] .st-c7 { + background-color: transparent !important; +} + +[data-baseweb="calendar"] [role="gridcell"]::after { + background-color: transparent !important; +} + +[data-baseweb="popover"] li[role="option"]:hover, +[data-baseweb="menu"] li:hover { + background-color: var(--tc-bg-hover) !important; +} + +/* Checkbox */ +[data-testid="stCheckbox"] label p { + color: var(--tc-text) !important; +} + +/* Expander */ +[data-testid="stExpander"] { + border: 1px solid var(--tc-border); + border-radius: 10px; + background-color: var(--tc-bg-elevated); + overflow: hidden; +} + +[data-testid="stExpander"] summary { + color: var(--tc-text) !important; +} + +[data-testid="stExpander"] summary:hover { + background-color: var(--tc-bg-hover); +} + +/* Divider */ +hr { + border-color: var(--tc-border) !important; +} + +/* Alerts (st.info/success/warning/error) */ +[data-testid="stAlert"] { + background-color: var(--tc-bg-elevated); + border-radius: 8px; +} + +/* Inline code and code blocks */ +code { + background-color: var(--tc-bg-elevated) !important; + color: var(--tc-code-text) !important; } /* Report Box */ @@ -81,7 +253,7 @@ h5, h6 { font-family: 'SF Mono', 'Menlo', 'Monaco', 'Courier New', monospace; font-size: 0.9em; white-space: pre-wrap; - border: 1px solid #333333; + border: 1px solid var(--tc-border); box-shadow: inset 0 1px 3px rgba(0,0,0,0.2); color: #d4d4d4; -} \ No newline at end of file +}