Keep the status pills on exactly one row at every width
All checks were successful
Deploy / deploy (push) Successful in 21s

The strip wrapped to two or three rows on narrow screens. It's now
nowrap with flex: 1 1 0 pills, so all six share the row equally and
shrink together instead of spilling onto a second line.

Six pills can't hold full detail in 375px, so detail degrades inside
the pill rather than the strip breaking: the stat drops at 1024px (the
dot already carries up/down), then below 560px the name swaps to a
short label — Son, Rad, Prow, SAB, Taut, Ombi. Without that swap the
names ellipsised to ~23px, about two characters, which identifies
nothing. The full name stays in the DOM at font-size: 0 so screen
readers still announce it, and it's on the pill's title attribute.

The dot and the pill itself never go, so no service is ever hidden —
which was the point of the old wrapping rule.

Verified one row with nothing clipped and no page overflow at 320, 375,
430, 560, 620, 700, 820, 1024, 1100 and 1400px.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Barely Removable 2026-08-27 08:06:31 -07:00
parent 41205be83c
commit 3c931873b1
2 changed files with 59 additions and 16 deletions

View file

@ -30,20 +30,26 @@ header {
} }
/* Service status pills — the Status section's only content, left-aligned /* Service status pills — the Status section's only content, left-aligned
with every other section's content and wrapping among themselves so with every other section's content. All six share the row equally and
every service stays visible at any width. Never scroll or clip: a shrink together, so the strip is always exactly one row: never two,
hidden pill is a service whose status you can't see. */ never a scroller. Every service stays visible at every width; what
gives way as space tightens is detail *within* a pill, in this order:
the stat drops below 1024px, then the name shortens below 560px. The
dot and the pill itself never go, so no service is ever hidden. */
.svc-strip { .svc-strip {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.5rem; gap: 0.5rem;
flex-wrap: wrap; flex-wrap: nowrap;
min-width: 0; min-width: 0;
} }
/* Full --tap height now they're content rather than header furniture. */ /* Full --tap height now they're content rather than header furniture. */
.svc-pill { .svc-pill {
flex: 1 1 0; /* equal share of the row, all shrink together */
min-width: 0; /* ...which flex items won't do without this */
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center;
gap: 0.45rem; gap: 0.45rem;
padding: 0.35rem 0.9rem; padding: 0.35rem 0.9rem;
min-height: var(--tap); min-height: var(--tap);
@ -54,13 +60,47 @@ header {
color: var(--text-2); color: var(--text-2);
text-decoration: none; text-decoration: none;
white-space: nowrap; white-space: nowrap;
overflow: hidden;
transition: border-color 0.2s, color 0.2s; transition: border-color 0.2s, color 0.2s;
} }
.svc-pill:hover { border-color: var(--border-lt); color: var(--text); } .svc-pill:hover { border-color: var(--border-lt); color: var(--text); }
.svc-pill.down { color: var(--danger); border-color: var(--danger-border); } .svc-pill.down { color: var(--danger); border-color: var(--danger-border); }
.svc-name { font-weight: 600; } .svc-name {
.svc-stat { color: var(--text-3); } font-weight: 600;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
.svc-stat {
color: var(--text-3);
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
.svc-pill.down .svc-stat { color: var(--danger); } .svc-pill.down .svc-stat { color: var(--danger); }
@media (max-width: 1024px) {
/* Six stats stop fitting here. The dot already carries up/down, so the
stat is the first thing that can go without losing any status. */
.svc-stat { display: none; }
}
@media (max-width: 620px) {
/* Tighten the furniture so the names keep their characters longer. */
.svc-strip { gap: 0.3rem; }
.svc-pill { padding: 0.35rem 0.5rem; gap: 0.3rem; }
}
@media (max-width: 560px) {
/* Six full names in one row leaves ~23px each — two characters and an
ellipsis, which identifies nothing. Swap in the short label instead.
The full name stays in the DOM (font-size: 0 — invisible but still
announced) and on the pill's title attribute. */
.svc-strip { gap: 0.25rem; }
.svc-pill { padding: 0.35rem 0.35rem; gap: 0.25rem; }
.svc-name { font-size: 0; }
.svc-name::after {
content: attr(data-short);
font-size: var(--fs-xs);
}
}
.status-light { .status-light {
width: 7px; height: 7px; border-radius: 50%; width: 7px; height: 7px; border-radius: 50%;

View file

@ -38,12 +38,15 @@
{%- endif -%} {%- endif -%}
{% endmacro %} {% endmacro %}
{#- One service status pill in the header strip. -#} {#- One service status pill in the Status strip. `short` is swapped in for
{% macro svc_pill(key, label, url, up, stat) %} `label` on phones, where six full names can't share one row without
ellipsising down to two characters — see the .svc-name rules. The full
name stays in the DOM for screen readers either way. -#}
{% macro svc_pill(key, label, short, url, up, stat) %}
<a class="svc-pill {{ 'up' if up else 'down' }}" id="svc-{{ key }}" <a class="svc-pill {{ 'up' if up else 'down' }}" id="svc-{{ key }}"
href="{{ url }}" target="_blank" rel="noopener"> href="{{ url }}" target="_blank" rel="noopener" title="{{ label }}">
<span class="status-light {{ 'up' if up else 'down' }}" id="svc-light-{{ key }}"></span> <span class="status-light {{ 'up' if up else 'down' }}" id="svc-light-{{ key }}"></span>
<span class="svc-name">{{ label }}</span> <span class="svc-name" data-short="{{ short }}">{{ label }}</span>
<span class="svc-stat" id="svc-stat-{{ key }}">{{ stat }}</span> <span class="svc-stat" id="svc-stat-{{ key }}">{{ stat }}</span>
</a> </a>
{% endmacro %} {% endmacro %}
@ -75,17 +78,17 @@
rows rather than scrolling or truncating. A hidden pill is a service rows rather than scrolling or truncating. A hidden pill is a service
whose status you can't see. #} whose status you can't see. #}
<nav class="svc-strip" aria-label="Service status"> <nav class="svc-strip" aria-label="Service status">
{{ svc_pill('sonarr', 'Sonarr', service_urls.sonarr, sonarr.status == 'ok', {{ svc_pill('sonarr', 'Sonarr', 'Son', service_urls.sonarr, sonarr.status == 'ok',
(sonarr.total_series ~ ' series') if sonarr.status == 'ok' else 'offline') }} (sonarr.total_series ~ ' series') if sonarr.status == 'ok' else 'offline') }}
{{ svc_pill('radarr', 'Radarr', service_urls.radarr, radarr.status == 'ok', {{ svc_pill('radarr', 'Radarr', 'Rad', service_urls.radarr, radarr.status == 'ok',
(radarr.total_movies ~ ' movies') if radarr.status == 'ok' else 'offline') }} (radarr.total_movies ~ ' movies') if radarr.status == 'ok' else 'offline') }}
{{ svc_pill('prowlarr', 'Prowlarr', service_urls.prowlarr, prowlarr.status == 'ok', {{ svc_pill('prowlarr', 'Prowlarr', 'Prow', service_urls.prowlarr, prowlarr.status == 'ok',
(prowlarr.up_count ~ '/' ~ prowlarr.total ~ ' up') if prowlarr.status == 'ok' else 'offline') }} (prowlarr.up_count ~ '/' ~ prowlarr.total ~ ' up') if prowlarr.status == 'ok' else 'offline') }}
{{ svc_pill('sabnzbd', 'SABnzbd', service_urls.sabnzbd, sabnzbd.status == 'ok', {{ svc_pill('sabnzbd', 'SABnzbd', 'SAB', service_urls.sabnzbd, sabnzbd.status == 'ok',
sabnzbd.speed if sabnzbd.status == 'ok' else 'offline') }} sabnzbd.speed if sabnzbd.status == 'ok' else 'offline') }}
{{ svc_pill('tautulli', 'Tautulli', service_urls.tautulli, tautulli.status == 'ok', {{ svc_pill('tautulli', 'Tautulli', 'Taut', service_urls.tautulli, tautulli.status == 'ok',
((tautulli.active_streams ~ ' streaming') if tautulli.active_streams > 0 else '0 streams') if tautulli.status == 'ok' else 'offline') }} ((tautulli.active_streams ~ ' streaming') if tautulli.active_streams > 0 else '0 streams') if tautulli.status == 'ok' else 'offline') }}
{{ svc_pill('ombi', 'Ombi', service_urls.ombi, ombi.status == 'ok', {{ svc_pill('ombi', 'Ombi', 'Ombi', service_urls.ombi, ombi.status == 'ok',
(ombi.total_pending ~ ' pending') if ombi.status == 'ok' else 'offline') }} (ombi.total_pending ~ ' pending') if ombi.status == 'ok' else 'offline') }}
</nav> </nav>
</section> </section>