An audit of 1,980 text/surface pairs (every steward.css rule and every inline template style, tints composited over page, card and hovered row) found: - Red, yellow and orange status text on its own 20% tint over a hovered row at 3.97-4.06:1 (badges, count badge, alert-error, btn-warn). --red and --yellow are lifted 15% toward the primary text, --orange 18%, as --blue already was: worst case now 4.54:1. - Inputs, selects, ghost and range buttons outlined in --border-mid at 1.4-1.9:1, on surfaces 1.1:1 apart, so the outline is what marks them. New --border-control (45% toward secondary text): 3.13:1 worst. The dashboard switcher and widget-drawer fields drop their inline styles for the shared .input class. - The breadcrumb separator painted in a border colour (1.4-1.9:1): now --text-dim (4.64:1) and aria-hidden, since it is decoration. tests/test_contrast.py resolves the tokens (color-mix and tints included) and holds every drawn pair to 4.5:1, status text on its tint to 4.5:1 and control outlines to 3:1, with a test that the guard catches the old pairs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
87 lines
4.1 KiB
HTML
87 lines
4.1 KiB
HTML
{# Shared template macros. Import with: {% from "_macros.html" import crumbs %} #}
|
||
|
||
{# Breadcrumb trail. `items` is a list of (label, href) pairs, ordered root→current.
|
||
Intermediate items with a truthy href render as links; the last item (or any
|
||
item with an empty href) renders as plain current text. #}
|
||
{% macro crumbs(items) -%}
|
||
<nav class="breadcrumb" aria-label="Breadcrumb">
|
||
{%- for label, href in items -%}
|
||
{%- if loop.last -%}
|
||
<span class="breadcrumb-cur" aria-current="page">{{ label }}</span>
|
||
{%- elif href -%}
|
||
<a href="{{ href }}">{{ label }}</a><span class="breadcrumb-sep" aria-hidden="true">›</span>
|
||
{%- else -%}
|
||
<span>{{ label }}</span><span class="breadcrumb-sep" aria-hidden="true">›</span>
|
||
{%- endif -%}
|
||
{%- endfor -%}
|
||
</nav>
|
||
{%- endmacro %}
|
||
|
||
{# A state as a labelled badge: up, down, stale, firing, pending, acknowledged,
|
||
no_data, ok, warn, crit, resolved, inactive (formatting.STATES). `state` may
|
||
be a string or an enum; `label` replaces the standard wording. #}
|
||
{% macro status_badge(state, label=none, title=none) -%}
|
||
{%- set tone, text = status_info(state) -%}
|
||
<span class="badge badge-{{ tone }}"{% if title %} title="{{ title }}"{% endif %}>{{ label or text }}</span>
|
||
{%- endmacro %}
|
||
|
||
{# A state as a coloured dot. The dot is never the only signal: its label is
|
||
read out to a screen reader, and shown beside it when `show_label`. #}
|
||
{% macro status_dot(state, label=none, show_label=false) -%}
|
||
{%- set tone, text = status_info(state) -%}
|
||
<span class="dot dot-{{ tone }}" aria-hidden="true" title="{{ label or text }}"></span>
|
||
{%- if show_label %}<span class="dot-label">{{ label or text }}</span>{% else %}<span class="sr-only">{{ label or text }}</span>{% endif %}
|
||
{%- endmacro %}
|
||
|
||
{# Threshold colouring: `threshold_style(value, kind)` (an inline style) and
|
||
`status_class(value, kind)` ('ok' / 'warn' / 'crit' / 'unknown') are jinja
|
||
globals reading the cutoffs from Settings → Thresholds. #}
|
||
|
||
{# One headline number. `state` (a STATES name, or a status_class result)
|
||
colours the value; `href` makes the whole tile a link. #}
|
||
{% macro stat_tile(label, value, state=none, hint=none, href=none) -%}
|
||
{%- set tone = status_info(state)[0] if state else none -%}
|
||
{%- set body -%}
|
||
<span class="stat-tile-label">{{ label }}</span>
|
||
<span class="stat-tile-value">{{ value }}</span>
|
||
{%- if hint %}<span class="stat-tile-hint">{{ hint }}</span>{% endif %}
|
||
{%- endset -%}
|
||
{%- if href -%}
|
||
<a href="{{ href }}" class="stat-tile{% if tone %} stat-{{ tone }}{% endif %}">{{ body }}</a>
|
||
{%- else -%}
|
||
<div class="stat-tile{% if tone %} stat-{{ tone }}{% endif %}">{{ body }}</div>
|
||
{%- endif -%}
|
||
{%- endmacro %}
|
||
|
||
{# What a list shows when it has nothing in it, with an optional next step. #}
|
||
{% macro empty_state(message, action_label=none, action_href=none) -%}
|
||
<div class="empty-state">
|
||
<p>{{ message }}</p>
|
||
{%- if action_label and action_href %}<a class="btn btn-ghost btn-sm" href="{{ action_href }}">{{ action_label }}</a>{% endif %}
|
||
</div>
|
||
{%- endmacro %}
|
||
|
||
{# The page's <h1>, with its actions to the right:
|
||
{% call page_header("Alerts") %}<a class="btn" href="…">New rule</a>{% endcall %} #}
|
||
{% macro page_header(title, subtitle=none) -%}
|
||
<div class="page-header">
|
||
<div>
|
||
<h1 class="page-title">{{ title }}</h1>
|
||
{%- if subtitle %}<p class="page-subtitle">{{ subtitle }}</p>{% endif %}
|
||
</div>
|
||
{%- if caller is defined %}<div class="page-actions">{{ caller() }}</div>{% endif %}
|
||
</div>
|
||
{%- endmacro %}
|
||
|
||
{# A button that shows only an icon. The label is required: it is the button's
|
||
accessible name and its tooltip, and rendering one without it raises. #}
|
||
{% macro icon_button(icon, label, href=none, cls="btn-ghost") -%}
|
||
{%- set name = required(label, "icon_button label") -%}
|
||
{%- if href -%}
|
||
<a class="btn btn-sm btn-icon {{ cls }}" href="{{ href }}" aria-label="{{ name }}" title="{{ name }}">{{ icon }}</a>
|
||
{%- else -%}
|
||
<button type="{{ kwargs.get('type', 'button') }}" class="btn btn-sm btn-icon {{ cls }}" aria-label="{{ name }}" title="{{ name }}"
|
||
{%- if kwargs.get('confirm') %} data-confirm="{{ kwargs.confirm }}"{% endif %}>{{ icon }}</button>
|
||
{%- endif -%}
|
||
{%- endmacro %}
|