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:
2026-05-19 02:04:53 +00:00
parent 6c3939d21b
commit 1016a4d2c4
5 changed files with 120 additions and 34 deletions

View File

@@ -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)