{#
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 %}
{% 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 %}
|