From b2e14b212af0366f84af3d244a643d3be0cc0943 Mon Sep 17 00:00:00 2001 From: Matthew Spah Date: Thu, 24 Sep 2026 09:48:34 -0700 Subject: [PATCH 1/2] feat: group team analytics navigation under Teams --- app/web/navigation.py | 16 ++- app/web/static/css/app.css | 127 ++++++++++++++++++--- app/web/templates/_team_navigation.html | 18 +++ app/web/templates/base.html | 22 ++-- app/web/templates/baserunners.html | 2 +- app/web/templates/comparison.html | 2 +- app/web/templates/hits_allowed.html | 2 +- app/web/templates/index.html | 2 +- app/web/templates/pitching.html | 2 +- app/web/templates/run_differential.html | 2 +- app/web/templates/runs.html | 2 +- app/web/templates/strikeouts.html | 2 +- app/web/templates/team_base.html | 10 ++ docs/ui-information-architecture.md | 31 +++++ tests/test_navigation.py | 24 +++- tests/test_web.py | 11 +- tests/test_web_baserunners.py | 4 +- tests/test_web_page_scaffolding.py | 146 ++++++++++++++++++------ tests/test_web_runs.py | 2 +- 19 files changed, 343 insertions(+), 84 deletions(-) create mode 100644 app/web/templates/_team_navigation.html create mode 100644 app/web/templates/team_base.html create mode 100644 docs/ui-information-architecture.md diff --git a/app/web/navigation.py b/app/web/navigation.py index 486a7cb..4fc0b33 100644 --- a/app/web/navigation.py +++ b/app/web/navigation.py @@ -9,6 +9,7 @@ """ from dataclasses import dataclass +from typing import Literal from urllib.parse import urlencode HITS_PATH = "/" @@ -22,12 +23,12 @@ HITS_LABEL = "Hits" STRIKEOUTS_LABEL = "Batting Strikeouts" -RUNS_LABEL = "Runs" +RUNS_LABEL = "Runs Scored" BASERUNNERS_LABEL = "Baserunners" RUN_DIFFERENTIAL_LABEL = "Run Differential" -PITCHING_LABEL = "Pitching" +PITCHING_LABEL = "Pitching Trends" HITS_ALLOWED_LABEL = "Hits Allowed" -COMPARISON_LABEL = "Comparison" +COMPARISON_LABEL = "Hits vs Batting Strikeouts" @dataclass(frozen=True) @@ -37,6 +38,7 @@ class NavLink: label: str href: str is_current: bool + group: Literal["Offense", "Pitching", "Results"] def build_nav_links( @@ -60,41 +62,49 @@ def build_nav_links( return [ NavLink( label=HITS_LABEL, + group="Offense", href=f"{HITS_PATH}{suffix}", is_current=current_path == HITS_PATH, ), NavLink( label=STRIKEOUTS_LABEL, + group="Offense", href=f"{STRIKEOUTS_PATH}{suffix}", is_current=current_path == STRIKEOUTS_PATH, ), NavLink( label=RUNS_LABEL, + group="Offense", href=f"{RUNS_PATH}{suffix}", is_current=current_path == RUNS_PATH, ), NavLink( label=BASERUNNERS_LABEL, + group="Offense", href=f"{BASERUNNERS_PATH}{suffix}", is_current=current_path == BASERUNNERS_PATH, ), NavLink( label=RUN_DIFFERENTIAL_LABEL, + group="Results", href=f"{RUN_DIFFERENTIAL_PATH}{suffix}", is_current=current_path == RUN_DIFFERENTIAL_PATH, ), NavLink( label=PITCHING_LABEL, + group="Pitching", href=f"{PITCHING_PATH}{suffix}", is_current=current_path == PITCHING_PATH, ), NavLink( label=HITS_ALLOWED_LABEL, + group="Pitching", href=f"{HITS_ALLOWED_PATH}{suffix}", is_current=current_path == HITS_ALLOWED_PATH, ), NavLink( label=COMPARISON_LABEL, + group="Offense", href=f"{COMPARISON_PATH}{suffix}", is_current=current_path == COMPARISON_PATH, ), diff --git a/app/web/static/css/app.css b/app/web/static/css/app.css index c0430f1..8702eee 100644 --- a/app/web/static/css/app.css +++ b/app/web/static/css/app.css @@ -86,41 +86,131 @@ body { color: #d8e6f2; } -/* Navigation */ +/* Primary/domain navigation */ -.site-nav { +.primary-nav { display: flex; flex-wrap: wrap; gap: 0.35rem; - max-width: 100%; } -.site-nav__link { +.primary-nav__link { display: inline-block; - padding: 0.4rem 0.85rem; - border-radius: 999px; - color: #c8d6e5; - font-size: 0.95rem; + padding: 0.5rem 0.85rem; + border-radius: 8px; + color: #ffffff; font-weight: 600; text-decoration: none; } -.site-nav__link:hover { - color: #ffffff; - background: rgba(255, 255, 255, 0.08); +.primary-nav__link:hover, +.primary-nav__link[aria-current="location"] { + background: rgba(255, 255, 255, 0.12); } -.site-nav__link--current { - color: #ffffff; - background: rgba(255, 255, 255, 0.12); +.primary-nav__link[aria-current="location"] { + text-decoration: underline; + text-underline-offset: 0.3em; } .brand:focus-visible, -.site-nav__link:focus-visible { +.primary-nav__link:focus-visible { outline: 2px solid #7fd3d4; outline-offset: 2px; } +/* Team analytics navigation */ + +.team-nav { + background: var(--card-bg); + border-bottom: 1px solid var(--border); +} + +.team-nav__groups { + display: flex; + gap: 1rem 2rem; + padding-top: 1rem; + padding-bottom: 1rem; +} + +.team-nav__group { + min-width: 0; + flex: 1 1 0; +} + +.team-nav__group:first-child { + flex-grow: 2; +} + +.team-nav__heading { + margin: 0 0 0.4rem; + color: var(--muted); + font-size: 0.8rem; + font-weight: 700; +} + +.team-nav__links { + display: flex; + flex-wrap: wrap; + gap: 0.25rem; + margin: 0; + padding: 0; + list-style: none; +} + +.team-nav__links li { + max-width: 100%; +} + +.team-nav__link { + display: block; + padding: 0.55rem 0.75rem; + border-radius: 6px; + color: var(--navy); + font-size: 0.95rem; + font-weight: 600; + text-decoration: none; +} + +.team-nav__link:hover { + background: var(--page-bg); +} + +.team-nav__link--current { + background: var(--navy); + color: #ffffff; + text-decoration: underline; + text-underline-offset: 0.25em; +} + +.team-nav__link--current:hover { + background: var(--navy-deep); +} + +.team-nav__link:focus-visible, +.skip-link:focus-visible, +#main-content:focus-visible { + outline: 3px solid var(--teal-dark); + outline-offset: 3px; +} + +/* Keyboard shortcut past both navigation landmarks. */ +.skip-link { + position: absolute; + top: 0.5rem; + left: 0.5rem; + z-index: 10; + transform: translateY(-200%); + padding: 0.75rem 1rem; + background: #ffffff; + color: var(--navy); + font-weight: 700; +} + +.skip-link:focus { + transform: translateY(0); +} + /* Page */ .page { @@ -488,6 +578,11 @@ body { } @media (max-width: 48rem) { + .team-nav__groups { + flex-direction: column; + gap: 0.75rem; + } + /* Only the inline padding changes with width. A `padding` shorthand here would also reset the vertical padding `.page` sets. */ .shell { @@ -524,7 +619,7 @@ body { gap: 0.5rem; } - .site-nav { + .primary-nav { margin-left: -0.85rem; } diff --git a/app/web/templates/_team_navigation.html b/app/web/templates/_team_navigation.html new file mode 100644 index 0000000..1369946 --- /dev/null +++ b/app/web/templates/_team_navigation.html @@ -0,0 +1,18 @@ + diff --git a/app/web/templates/base.html b/app/web/templates/base.html index 95d1195..ab4bcd9 100644 --- a/app/web/templates/base.html +++ b/app/web/templates/base.html @@ -9,9 +9,7 @@ {% block head %}{% endblock %} - {# One header bar: branding on the left, the real metric navigation on the - right. The navigation is the only site-wide control the application - actually has, so it fills the space rather than a second stacked bar. #} + -
{% block content %}{% endblock %}
+ {% block domain_navigation %}{% endblock %} + +
{% block content %}{% endblock %}