feat(home,sidebar): brand hero + sidebar = footer style + PNG icon
Bundles a handful of UX cleanups: - Findings-card chevron moved to the LEFT side of the head. CSS still rotates it 90° between collapsed/expanded states. - Tool-link buttons in findings rows (``Clean Text →`` etc.) are now left-justified against the icon column with minimal surrounding whitespace. Action column ratio dropped from 1.8 → 1.4 and the button switched from ``width="stretch"`` (centered text) to ``width="content"`` (shrinks to fit, left-aligned within column). - Home-page hero now mirrors the sidebar brand block: 56px ink "D" chip on the left + "UNALOGIX" eyebrow stacked above "DataTools" wordmark, then the "Clean. Normalize. Transform." tagline beneath. New ``.dt-page-brand / -row / -words / -mark / -eyebrow / -wordmark`` rules in ``_DESIGN_TOKENS_CSS``. Streamlit wraps h1 elements in an emotion-cache div with extra padding; a descendant flattener (``.dt-page-brand-words *`` margin:0 / padding:0) keeps the eyebrow + wordmark stack the same height as the chip so they center-align cleanly. - Sidebar nav restyled to match the sticky-footer Help/Close buttons exactly: 13px / 500 / 1.3 line-height, 5×10px padding, 8px gap between icon and label, transparent background. Active item gets the same ``rgba(0,0,0,0.04)`` tint as the hover state (no white pill, no shadow), only the heavier weight + ink text distinguishes it. - OS app icon (page_icon) switched from SVG to a Pillow-rendered ``datatools_icon_256.png`` so Windows / macOS taskbar+dock pick it up reliably (some OS shells fall back to a default icon for SVG favicons). Rounded-square ink ground with cream "D" centered — same mark as the sidebar chip + hero chip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -172,7 +172,7 @@ def _home_page() -> None:
|
||||
from src.i18n import t
|
||||
|
||||
from pathlib import Path as _Path
|
||||
_ICON_PATH = str(_Path(__file__).parent / "assets" / "datatools_icon.svg")
|
||||
_ICON_PATH = str(_Path(__file__).parent / "assets" / "datatools_icon_256.png")
|
||||
st.set_page_config(
|
||||
page_title=t("home.page_title"),
|
||||
page_icon=_ICON_PATH,
|
||||
@@ -182,17 +182,22 @@ def _home_page() -> None:
|
||||
render_sticky_footer()
|
||||
|
||||
import html as _html
|
||||
# Page header — h1 + body subtitle on the left, privacy pill on
|
||||
# the right (mockup §page-header). Rendered as a single HTML block
|
||||
# so the title/subtitle/pill share one flex row; ``st.title`` +
|
||||
# ``st.caption`` + ``st.divider`` would stack vertically and lose
|
||||
# the right-aligned pill. Bottom border replaces the explicit
|
||||
# ``st.divider`` that used to sit below the caption.
|
||||
# Page header — brand block (D icon + "UNALOGIX" eyebrow over
|
||||
# "DataTools" wordmark + tagline) on the left, privacy pill on
|
||||
# the right. Matches the sidebar brand chip scaled up for the
|
||||
# hero. Bottom border replaces the explicit ``st.divider`` that
|
||||
# used to sit below the caption.
|
||||
privacy_label = _html.escape(t("home.privacy_pill"))
|
||||
st.markdown(
|
||||
'<header class="dt-page-header">'
|
||||
'<div>'
|
||||
f'<h1>{_html.escape(t("home.title"))}</h1>'
|
||||
'<div class="dt-page-brand">'
|
||||
'<div class="dt-page-brand-row">'
|
||||
'<div class="dt-page-brand-mark">D</div>'
|
||||
'<div class="dt-page-brand-words">'
|
||||
'<span class="dt-page-eyebrow">UNALOGIX</span>'
|
||||
'<h1 class="dt-page-wordmark">DataTools</h1>'
|
||||
'</div>'
|
||||
'</div>'
|
||||
f'<p class="dt-page-subtitle">{_html.escape(t("home.caption"))}</p>'
|
||||
'</div>'
|
||||
'<span class="dt-privacy-pill">'
|
||||
|
||||
BIN
src/gui/assets/datatools_icon_256.png
Normal file
BIN
src/gui/assets/datatools_icon_256.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.1 KiB |
@@ -267,14 +267,20 @@ body, .stApp {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
/* Nav items — tight padding so the menu lists feel dense and don't
|
||||
waste vertical space. */
|
||||
/* Nav items match the sticky-footer Help/Close button style: ink-
|
||||
secondary text, transparent surface, soft hover tint, no border or
|
||||
active-state pill. Sizes line up with ``.datatools-footer-btn``
|
||||
(13px / 500 / 1.3 line-height, 5px×10px padding, 8px icon gap) so
|
||||
the sidebar and footer feel like the same family. */
|
||||
[data-testid="stSidebarNav"] a[data-testid="stSidebarNavLink"],
|
||||
[data-testid="stSidebarNav"] [data-testid="stSidebarNavLinkContainer"] a {
|
||||
color: var(--ink-secondary) !important;
|
||||
font-size: 13.5px !important;
|
||||
line-height: 1.25 !important;
|
||||
padding: 4px 10px !important;
|
||||
font-size: 13px !important;
|
||||
font-weight: 500 !important;
|
||||
line-height: 1.3 !important;
|
||||
padding: 5px 10px !important;
|
||||
gap: 8px !important;
|
||||
border: none !important;
|
||||
border-radius: var(--r-sm) !important;
|
||||
transition: background 0.12s ease, color 0.12s ease;
|
||||
}
|
||||
@@ -291,13 +297,14 @@ body, .stApp {
|
||||
background: rgba(0,0,0,0.04) !important;
|
||||
color: var(--ink) !important;
|
||||
}
|
||||
/* Active nav item — white pill with subtle shadow. Streamlit marks the
|
||||
active anchor with ``aria-current="page"``. */
|
||||
/* Active item — soft hover-tint background + ink text + heavier
|
||||
weight. No white pill, no shadow. Mirrors the footer buttons,
|
||||
which carry no special "active" treatment. */
|
||||
[data-testid="stSidebarNav"] a[aria-current="page"] {
|
||||
background: var(--surface) !important;
|
||||
background: rgba(0,0,0,0.04) !important;
|
||||
color: var(--ink) !important;
|
||||
font-weight: 500 !important;
|
||||
box-shadow: 0 1px 2px rgba(28,25,23,0.04) !important;
|
||||
font-weight: 600 !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
/* Inline + block code → mono with subtle accent chip. theme.py owns
|
||||
@@ -577,22 +584,89 @@ div[data-testid="stContainer"][data-border="true"] {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
/* ---------- Page header (title + subtitle + privacy pill) ---------- */
|
||||
/* ---------- Page header (brand block + privacy pill) ---------- */
|
||||
.dt-page-header {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
margin: 0 0 24px;
|
||||
padding-bottom: 22px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.dt-page-header h1 { margin: 0 !important; }
|
||||
/* The brand block stacks two pieces vertically: the D-chip + words
|
||||
row up top, then the tagline beneath. The D mark vertically
|
||||
centres with the words column (eyebrow + wordmark), exactly like
|
||||
the sidebar chip. */
|
||||
.dt-page-brand {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
.dt-page-brand-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 18px;
|
||||
}
|
||||
.dt-page-brand-words {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
line-height: 1;
|
||||
}
|
||||
/* Streamlit wraps the h1 in an emotion-cache div that adds ~3px top
|
||||
padding + ~8px bottom margin. Flatten every descendant so the
|
||||
eyebrow + wordmark stack hugs the chip height. */
|
||||
.dt-page-brand-words *,
|
||||
.dt-page-brand-words > div {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
.dt-page-brand-words .dt-page-wordmark {
|
||||
line-height: 1 !important;
|
||||
}
|
||||
/* Same "Letter D (sans)" wordmark as the sidebar chip and favicon
|
||||
— scaled up to hero size. Ink ground, cream D, Geist 700, -0.04em
|
||||
tracking. */
|
||||
.dt-page-brand-mark {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 14px;
|
||||
background: var(--ink);
|
||||
color: var(--accent-fill);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-family: var(--font-sans);
|
||||
font-weight: 700;
|
||||
font-size: 32px;
|
||||
letter-spacing: -0.04em;
|
||||
line-height: 1;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.dt-page-eyebrow {
|
||||
font-family: var(--font-sans) !important;
|
||||
font-size: 11.5px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
color: var(--ink-tertiary);
|
||||
line-height: 1.2;
|
||||
}
|
||||
.dt-page-wordmark {
|
||||
margin: 0 !important;
|
||||
font-family: var(--font-sans) !important;
|
||||
font-weight: 600 !important;
|
||||
font-size: 32px !important;
|
||||
letter-spacing: -0.035em !important;
|
||||
line-height: 1.1 !important;
|
||||
color: var(--ink) !important;
|
||||
}
|
||||
.dt-page-header .dt-page-subtitle {
|
||||
margin: 6px 0 0;
|
||||
margin: 4px 0 0;
|
||||
color: var(--ink-secondary) !important;
|
||||
font-size: 14px;
|
||||
line-height: 1.55;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.dt-privacy-pill {
|
||||
display: inline-flex;
|
||||
@@ -740,9 +814,9 @@ div[data-testid="stContainer"][data-border="true"] {
|
||||
.dt-finding-group-head:hover {
|
||||
background: var(--accent-fill);
|
||||
}
|
||||
/* Chevron lives on the right of the head, rotates to indicate state. */
|
||||
/* Chevron leads the head as the first flex item; rotates 90° to
|
||||
indicate expanded state. */
|
||||
.dt-finding-group-chevron {
|
||||
margin-left: 8px;
|
||||
color: var(--ink-tertiary);
|
||||
font-family: "Material Symbols Outlined" !important;
|
||||
font-size: 20px !important;
|
||||
@@ -750,6 +824,7 @@ div[data-testid="stContainer"][data-border="true"] {
|
||||
line-height: 1 !important;
|
||||
transition: transform 0.15s ease;
|
||||
flex-shrink: 0;
|
||||
margin-right: -2px;
|
||||
}
|
||||
.dt-finding-group-head[data-dt-collapsed="false"] .dt-finding-group-chevron {
|
||||
transform: rotate(90deg);
|
||||
@@ -2843,12 +2918,15 @@ def render_findings_panel(
|
||||
f'<span class="dt-count-pill {sev}">{n} {label}</span>'
|
||||
)
|
||||
|
||||
# Chevron leads the head — clicking the row toggles
|
||||
# ``data-dt-collapsed``. ``chevron_right`` (▶) is the collapsed
|
||||
# rest state; CSS rotates it 90° to point down (▼) when expanded.
|
||||
head_html = (
|
||||
'<div class="dt-finding-group-head" data-dt-collapsed="true">'
|
||||
'<span class="dt-finding-group-chevron">chevron_right</span>'
|
||||
f'<span class="dt-severity-dot {worst}"></span>'
|
||||
f'<span class="dt-group-filename">{_html.escape(header)}</span>'
|
||||
f'<div class="dt-group-counts">{pills_html}</div>'
|
||||
'<span class="dt-finding-group-chevron">chevron_right</span>'
|
||||
'</div>'
|
||||
)
|
||||
st.markdown(head_html, unsafe_allow_html=True)
|
||||
@@ -2911,10 +2989,13 @@ def _render_finding_row_v2(f, *, row_key: str) -> None:
|
||||
meta_html = " · ".join(meta_parts)
|
||||
|
||||
# Action button moved to the LEFT of the description per UX
|
||||
# feedback: ``[icon] [Open <Tool> →] [description]`` — the action
|
||||
# is now the prominent affordance in the row, with the description
|
||||
# taking the wide remaining column.
|
||||
col_icon, col_action, col_body = st.columns([0.4, 1.8, 8])
|
||||
# feedback: ``[icon] [<Tool> →] [description]`` — the action is
|
||||
# the prominent affordance in the row, with the description taking
|
||||
# the wide remaining column. Tight action-column ratio (1.4) plus
|
||||
# ``width="content"`` on the button below keeps the link
|
||||
# left-justified against the icon with minimal surrounding
|
||||
# whitespace.
|
||||
col_icon, col_action, col_body = st.columns([0.4, 1.4, 8])
|
||||
|
||||
col_icon.markdown(
|
||||
f'<div class="dt-finding-icon {f.severity}">'
|
||||
@@ -2930,7 +3011,7 @@ def _render_finding_row_v2(f, *, row_key: str) -> None:
|
||||
f"{tool_label} →",
|
||||
key=f"_finding_open_{row_key}",
|
||||
type="tertiary",
|
||||
width="stretch",
|
||||
width="content",
|
||||
):
|
||||
st.switch_page(page_slug)
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ from src.gui.components import hide_streamlit_chrome, shutdown_app
|
||||
from src.i18n import t
|
||||
|
||||
from pathlib import Path as _Path
|
||||
_ICON_PATH = str(_Path(__file__).parent.parent / "assets" / "datatools_icon.svg")
|
||||
_ICON_PATH = str(_Path(__file__).parent.parent / "assets" / "datatools_icon_256.png")
|
||||
st.set_page_config(
|
||||
page_title=t("close_page.page_title"),
|
||||
page_icon=_ICON_PATH,
|
||||
|
||||
@@ -27,7 +27,7 @@ from src.gui.components import (
|
||||
from src.i18n import t
|
||||
|
||||
from pathlib import Path as _Path
|
||||
_ICON_PATH = str(_Path(__file__).parent.parent / "assets" / "datatools_icon.svg")
|
||||
_ICON_PATH = str(_Path(__file__).parent.parent / "assets" / "datatools_icon_256.png")
|
||||
st.set_page_config(
|
||||
page_title=t("activation.page_title"),
|
||||
page_icon=_ICON_PATH,
|
||||
|
||||
Reference in New Issue
Block a user