{# MULTI-COLUMN CONTAINER One template for every split, two columns or three. The eight per-ratio files this replaced were byte-identical apart from a Jinja comment and the numbers — which is what made the frame an assumption spread across eight files rather than a value the builder owns. Widths arrive computed: inkfold.builder.sizing.column_layout() splits the frame's content width by the ratio's own weights, floors each column and distributes the remainder outside-in, then gives each column the padding its width earns. The MSO ghost table and the inline-block table read the *same* number, because there is now only one number to read. The band is wrapped in a cell that insets it by frame.pad_x, so its text starts where a full-width section's text starts and where this section's own heading starts. That cell also zeroes font-size and line-height: the columns are inline-blocks, so the newline between two of them would otherwise render as a space, and 616px of columns plus that space no longer fits in 616px of content width. The column cells set both properties themselves, so nothing inherits the zero. Context variables: columns — One dict per column: width, pad_left, pad_right, content section_align — Optional alignment for this section's copy; empty means the template's own behaviour, which is what keeps an email that states none byte-identical (#126) section_title — Optional heading spanning every column section_anchor — The heading's `id`, empty when untitled (#183) section_badge — The title's badge, {label, tone}, or none (#325) section_kicker — The label above the title, {text, grounded}, or none (#333) background_color — Optional hex override; defaults to the theme's surface, or its highlight tint when highlighted highlight — When true, tints the band and adds hairline rules top and bottom section_border — The frame's colour when the section is bordered (#267), else empty section_frame — "solid" or "dashed", how that frame is drawn (#390) section_break — The row's break declarations on paper (#364), empty elsewhere section_kept — The row's `id` when kept together on paper, for the tall-section check section_source — The section's source line, cut at its markers (#338); empty for none. It is a table of its own under the columns, at the band's inset, and carries the `fine-print` hook so paper keeps it with them section_as_of — Its date, or empty section_bleed — On paper, how far the ground runs past the frame, {left, right, top} (#396) section_pin — On paper, how the section reaches its sheet's foot (#397); see placed.html stack — natural; reverse, where dir=rtl lays the phone order out right to left on a desktop (#362); or fixed, one fluid row that never stacks (#363) gutter_share — The gutter as a percentage of the band, for a fixed row #} {# `inline-table`, NOT `inline-block`, and the difference is #129. With `inline-block` the element stops being a table box: the tr and td get an anonymous table around them which shrink-wraps to its content, so the width specified here applied to the outer box and the cell inside stayed as narrow as its copy — 76 of the gallery's 88 column cells did. Anything that then depended on the cell's width had no room to show: #126's alignment reached every cell correctly and was invisible. Measured, not reasoned about, in #76's tradition — the obvious fix does NOT work. `width="100%"` on the cell (attribute, style, or both) changes nothing at all, because it resolves against an anonymous table that is itself shrink-wrapping; a fixed px width does fill on desktop but then constrains the copy at the mobile breakpoint, where `.stack-column` overrides the table's width and the cell's px value goes stale. Outlook never sees this: its Word engine ignores both display values and lays the columns out from the MSO ghost table above. The residual risk is a client that implements `inline-block` but not `inline-table`, which would stack the columns — the same graceful degradation a client supporting neither already gets. #} {# A highlighted band spends 1px a side on its hairline frame, so every horizontal inset inside it gives that pixel back. Content then starts at frame.pad_x from the frame edge whether the band is highlighted or not — which is the whole of #85: a heading and the text under it must not disagree about where the left margin is. A border (#267) spends one more. #} {# `section_align` lands on TWO cells, and that is not redundancy: the title and the content are sibling tables, not parent and child, so a declaration on the content cell alone leaves the heading where it was. Both spellings travel together per #125 — the attribute for Outlook's Word engine, the style because an attribute is not inherited by the prose inside the cell and inheritance is #126's whole mechanism. #} {# On paper a column paints no fill of its own. An inline table is painted after every block background, so its fill would cover the half-pixel row the next section owns at the band's edge, under the tallest column only. The band's cell already paints that ground. #} {% from "common/badge.html" import badge with context %} {% from "common/kicker.html" import kicker with context %} {% from "common/source-line.html" import source_line with context %} {% from "common/placed.html" import open_placed, close_placed %} {% set edge_pad = size.frame.pad_x - (1 if highlight else 0) - (1 if section_border else 0) %} {# The band also has to *fit*: 2x pad_x plus the computed content width is the whole frame, so without the pixel back the last column wraps. #} {% if section_bleed or section_pin %} {{ open_placed(section_bleed, section_pin, background_color | default(theme.palette.highlight_tint if highlight else theme.palette.surface)) }} {% endif %} {% if section_title %} {% if section_kicker %} {{ kicker(section_kicker) }} {% endif %} {{ section_title | escape_html }} {% if section_badge %} {{ badge(section_badge) }} {% endif %} {% endif %} {% set rtl = stack == "reverse" %} {% if stack == "fixed" %}
{% for col in columns %} {% if not loop.last %} {% endif %} {% endfor %}
{{ col.content }}
{% else %} {% for col in columns %} {% if not loop.last %} {% endif %} {% endfor %} {% endif %} {% if section_source %} {{ source_line(section_source, section_as_of) }} {% endif %} {% if section_bleed or section_pin %} {{ close_placed(section_bleed, section_pin) }} {% endif %}