{# DATA TABLE — Financial table with headers, alternating rows, colour-coded values. Context: subtitle — Optional sub-heading rendered above the table caption — Optional table name (#120). Rendered as a `caption` element, the table's accessible name — what a screen reader announces on reaching it. Distinct from `subtitle`, which is copy that merely sits above. groups — List of dicts with keys: label, span. Empty for no header tier. units — One unit per column, or empty for no units row. columns — List of dicts with keys: header, parts, align, kind, width — already resolved (#117), so this template decides neither. A column's position means nothing here; its `kind` carries the face and its `align` the alignment. `width` is a percentage, or empty when the table sets no weights (#271). `rule_after` draws a rule in `rule_dark` down the column's right edge (#390). rows — List of dicts with keys: kind, cells (list), alt (bool). A `total` is ruled off above and bold across; a `subhead` is a tinted label band. Both draw from theme tokens only — a row's kind is chrome, unlike a cell's colour, which is data. Each cell carries text, parts, pad, heat, bar, bar_color, reverse, arrow, spark, align, color, background, is_text — all resolved (#118), so this template chooses none of them. An empty color or background means "the theme's". `row_header` marks the cell a screen reader navigates the row by — the first column, when its resolved kind is text. frame — "solid" or "dashed" for a frame round the table, else empty (#390). It is a wrapping cell's border, inset by a cell's padding: on the table itself, a collapsed border loses to the solid rules on its first and last rows, and the dashed edge would show on two sides. source — Attribution string (optional) as_of — Date string (optional) anchor — The table's `id`, empty when it has none (#181). The caption arrives already numbered: the document assigned the number #} {% from "common/notes.html" import marked with context %} {% from "analysis/trend-arrow.html" import arrow with context %} {% from "analysis/sparkline-bars.html" import sparkline with context %} {% from "common/badge.html" import badge, dot with context %} {% if subtitle %}

{{ subtitle | escape_html }}

{% endif %} {% include "common/qualifier.html" %} {% if caption and medium.email %} {# Outlook's copy of the caption (#403). The Word engine moves a nested table's `caption` out of place: when the table sits in any row but the first of an enclosing layout table, the caption is drawn at the top of that layout table, above the copy before it and outside any frame, where on a dark band it reads dark-on-dark. Measured in classic Outlook, not argued. So the `caption` below is hidden from Outlook with `mso-hide:all`, which the engine honours by dropping it, and Outlook reads this paragraph instead: same face and spacing, in place, directly above the table. `ids=false` keeps each marker's id written once. Email only, since no other medium has a Word engine to read it. #} {% endif %} {% if frame %}
{% endif %} {% if caption %} {# The table's accessible NAME, not a standfirst — see the component's docstring on why this is a field of its own. `caption-side` and the explicit alignment are here because a bare `caption` is centred by default, and Outlook's Word engine is unreliable about both. The style carries the alignment ALONE, deliberately: #125 paired every other alignment with an `align` attribute, but on a `caption` that attribute means PLACEMENT — top/bottom/left/right — not the alignment of the text inside it. Adding it here would move the caption to the side of the table in a legacy renderer, which is the opposite of a no-op. The two spellings are equivalent on td/th/p/div and nowhere else. #} {% endif %} {# Header row #} {% if groups %} {# The header tier (#223): the one place a span is admitted. #} {% for group in groups %} {% endfor %} {% endif %} {% for column in columns %} {% endfor %} {% if units %} {# The units row (#226): each unit once, beneath its head. #} {% for column in columns %} {% endfor %} {% endif %} {# Data rows #} {% for row in rows %} {% for cell in row.cells %} {% set subhead = row.kind == "subhead" %}{% set bold = subhead or row.kind == "total" or not cell.is_text %} <{% if cell.row_header %}th scope="row"{% else %}td{% endif %} align="{{ cell.align | escape_html }}" style="font-family: {% if subhead or cell.is_text %}{{ font.label }}{% else %}{{ font.numeric }}{% endif %}; font-size: {{ size.type.secondary }}px; font-weight: {% if bold %}bold{% else %}normal{% endif %}; line-height: {{ size.type.body_line | percent }}; color: {% if subhead %}{{ theme.text.heading }}{% elif cell.color %}{{ cell.color | validate_hex_color }}{% elif cell.heat %}{{ cell.heat.position | heat_color(theme.palette.surface, theme.tone[cell.heat.toward]) | readable_on(theme.text.primary, theme.text.on_accent) }}{% elif cell.tone %}{{ theme.tone[cell.tone] }}{% elif cell.is_text %}{{ theme.text.primary }}{% else %}{{ theme.semantic.neutral }}{% endif %}; text-align: {{ cell.align }}; padding: {{ size.component.table_cell_pad }}px; background-color: {% if subhead %}{{ theme.palette.highlight_tint }}{% elif cell.background %}{{ cell.background | validate_hex_color }}{% elif cell.heat %}{{ cell.heat.position | heat_color(theme.palette.surface, theme.tone[cell.heat.toward]) }}{% elif row.alt %}{{ theme.palette.row_alt }}{% else %}{{ theme.palette.surface }}{% endif %};{% if columns[loop.index0].rule_after %} border-right: 1px solid {{ theme.palette.rule_dark }};{% endif %}">{% if cell.spark %}{{ sparkline(cell.spark, cell.align if cell.align != "left" else "") }}{% else %}{% if cell.arrow %}{{ arrow(cell.arrow, cell.color | validate_hex_color if cell.color else theme.tone[cell.tone or 'neutral']) }}{% endif %}{% if cell.status %}{{ dot(cell.status) }}{% endif %}{{ marked(cell.parts) }}{{ " " * cell.pad }}{% if cell.badge %} {{ badge(cell.badge) }}{% endif %}{% if cell.bar is not none %}{% include "analysis/cell-bar.html" %}{% endif %}{% endif %} {% endfor %} {% endfor %}
{{ marked(caption_parts) }}
{{ group.label | escape_html }}
{{ marked(column.parts) }}
{{ units[loop.index0] | escape_html }}
{% if frame %}
{% endif %} {% if source %}

{{ marked(source_parts) }}{% if as_of %} as of {{ as_of | escape_html }}{% endif %}

{% endif %} {% include "common/disclosure.html" %}