/*
 * fincalc renderer.
 *
 * Every color, radius, font, density and control width is a CSS custom
 * property, so an FI is themed by overriding tokens on the .cx-scope wrapper and
 * never by forking a template or shipping bespoke CSS. Tokens in, no layout
 * control out - which is what keeps eighty-one calculators from becoming
 * eighty-one support problems.
 *
 * Breakpoints are container queries, not media queries. A media query measures
 * the viewport, which is only the same thing as the calculator's width when it
 * happens to own the whole page; inside a host's narrow column it is wrong.
 */

.cx-scope {
    --cx-brand: #1f4e79;
    --cx-brand-contrast: #ffffff;
    --cx-accent: #2f9e6f;

    --cx-surface: #ffffff;
    --cx-surface-raised: #f6f8fa;
    --cx-surface-sunken: #eef1f5;
    --cx-border: #ccd4de;
    --cx-hairline: #edf0f4;
    --cx-group-head: #eef2f7;
    --cx-group-head-hover: #e6ecf3;

    --cx-text: #16202b;
    --cx-text-muted: #667283;
    --cx-text-faint: #9aa5b3;
    --cx-danger: #b3261e;

    --cx-radius: 6px;
    --cx-radius-sm: 4px;

    /* HOST-ONLY -- not in forge's renderer.css.
       Caution amber, hue-locked in TokenDerivation for the same reason as --cx-danger: a reader
       brings amber-means-attend-to-this with them, and a brand-derived warning would be decoration.
       These are the fallbacks for a page rendered before any tenant tokens are merged. Used by
       .cx-notice at the end of this file. */
    --cx-warn: #b3701a;
    --cx-warn-surface: #fdf4e5;
    --cx-gap: 0.75rem;
    --cx-row-gap: 0.1rem;
    --cx-control-height: 1.85rem;

    --cx-w-currency: 6.5rem;
    --cx-w-percent: 4.5rem;
    --cx-w-integer: 4rem;
    --cx-w-number: 6rem;
    --cx-w-choice: 11rem;

    --cx-font-body: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    --cx-font-display: var(--cx-font-body);
    --cx-font-size: 0.875rem;

    /*
     * Named, not bare. Once the inputs column becomes a container of its own an
     * unnamed @container query resolves against the nearest containing ancestor,
     * which is the wrong one for anything asking about the calculator's width.
     */
    container: cxroot / inline-size;

    /*
     * The root seal. Every inherited property the scoped reset hardens below is
     * declared HERE as well, and it has to be.
     *
     * The reset works by setting each of these to `inherit` on every descendant,
     * which makes the value chain up to this element. If this element does not
     * declare the property, the chain does not stop here - it carries on into
     * the host page and the reset hands the host exactly what it was written to
     * keep out. So a property added to the descendant rule without a value here
     * is worse than no reset at all: it propagates the host's value to every
     * element in the calculator instead of to some of them.
     *
     * ScopedResetTest asserts the two lists match, because nothing else would.
     */
    color: var(--cx-text);
    font-family: var(--cx-font-body);
    font-size: var(--cx-font-size);
    font-weight: 400;
    font-style: normal;
    font-variant-numeric: normal;
    letter-spacing: normal;
    word-spacing: normal;
    line-height: 1.45;
    text-align: start;
    text-indent: 0;
    text-transform: none;
    white-space: normal;
}

.cx-scope[data-density="compact"] {
    --cx-control-height: 1.65rem;
    --cx-gap: 0.5rem;
    --cx-font-size: 0.82rem;
}

/* ------------------------------------------------------------ host reset */

/*
 * The renderer is always a guest. In the Forge preview it sits inside
 * Pico's classless stylesheet; on an FI page it will sit inside whatever that
 * FI ships. Host stylesheets target bare element names - input, button,
 * summary, p - so they apply inside our markup and win on every property we
 * did not explicitly set.
 *
 * That is not a preview quirk. It is the permanent condition of an embeddable
 * widget, and it has to be handled by an explicit reset rather than by hoping
 * the host is quiet. Pico's `margin-bottom: 1rem` on every form element was the
 * air between rows; its `summary::after` was the second chevron; its button
 * padding was the off-center label. None of those appear anywhere in this file,
 * which is exactly why they were invisible to me.
 *
 * Everything below is scoped to .cx-scope, so the host's own page is untouched.
 */

.cx-scope,
.cx-scope *,
.cx-scope *::before,
.cx-scope *::after { box-sizing: border-box; }

/*
 * ------------------------------------------------ inherited properties
 *
 * Everything above this point defends against host rules that target our
 * elements. This block defends against the other half of the problem, which is
 * not a specificity contest at all.
 *
 * A property this file sets once on .cx-scope - the body font, the text color,
 * the line height - reaches a label or a table cell by INHERITANCE. An inherited
 * value is the weakest thing in the cascade: it applies only where nothing set
 * the property directly on the element. So a host page carrying
 *
 *     * { font-family: Georgia, serif; }
 *
 * sets the font directly on every element inside our markup, and beats us
 * everywhere, on every element, at once. Raising our specificity does nothing,
 * because specificity never enters into it - directly-set beats inherited, and
 * (0,1,0) against (0,0,0) is not the comparison being made.
 *
 * Nor does it take a universal selector. `p, label, div { line-height: 2.4 }` is
 * an ordinary rule an ordinary site ships, and it defeats our inherited 1.45 for
 * the same reason.
 *
 * The fix is to set the property directly on our descendants too, at a value of
 * `inherit`. Directly-set, so the host's rule loses; `inherit`, so the value is
 * still the one .cx-scope resolved - which is what keeps our own chains working.
 * Stamping the token itself here instead would break them: every child of a
 * display-font heading would get the body font pushed onto it directly and the
 * heading's font would stop propagating.
 *
 * `.cx-scope *` and NOT `.cx-scope, .cx-scope *`. `inherit` on the wrapper
 * itself would inherit from the host page - the exact failure this prevents
 * everywhere else, reintroduced at the root and then propagated deliberately.
 *
 * Placement in the file is load-bearing. This rule is (0,1,0), which is the same
 * specificity as every bare `.cx-something` rule below, so the ties are broken by
 * source order and this has to lose all of them. It stays in the reset, above
 * the components. Moving it down would silently flatten the calculator to body
 * type.
 *
 * Not hardened, deliberately: `cursor` (inherit would take the I-beam off every
 * text input), `direction` and `writing-mode` (a host that sets those has an RTL
 * page, and the calculator inside it should be RTL too), and the inherited
 * properties this file already sets directly on the one element that needs them
 * - `list-style` on summary, `border-collapse` on table, `accent-color` on the
 * range.
 */
.cx-scope *,
.cx-scope *::before,
.cx-scope *::after {
    color: inherit;
    font-family: inherit;
    font-size: inherit;
    font-weight: inherit;
    font-style: inherit;
    font-variant-numeric: inherit;
    letter-spacing: inherit;
    word-spacing: inherit;
    line-height: inherit;
    text-align: inherit;
    text-indent: inherit;
    text-transform: inherit;
    white-space: inherit;
}

/*
 * The browser's own defaults, put back.
 *
 * Hardening a property costs the UA stylesheet's value for it: `th` is centered
 * and bold, `em` is italic, `code` is monospace, and each of those is a default
 * the rule above has just overwritten with the body value. None of it shows in
 * today's markup, because every heading and every cell in the four templates
 * carries a cx- class that states its own type - but "the reset only holds while
 * nobody writes plain HTML" is not a property worth shipping, and the next
 * <strong> in an authored paragraph is the one that finds out.
 *
 * :where() is what makes this safe to state by element name. It contributes zero
 * specificity, so these land at (0,1,0) - above any host rule naming the same
 * element, and level with the component rules below, which therefore win on
 * source order. Written as `.cx-scope h1` this block would be (0,1,1) and would
 * outrank .cx-title, which is the trap the note on `.cx-scope details` above
 * describes, sprung a second time.
 */
.cx-scope :where(h1, h2, h3, h4, h5, h6, th, strong, b) { font-weight: bold; }
.cx-scope :where(h1) { font-size: 2em; }
.cx-scope :where(h2) { font-size: 1.5em; }
.cx-scope :where(h3) { font-size: 1.17em; }
.cx-scope :where(h5) { font-size: 0.83em; }
.cx-scope :where(h6) { font-size: 0.67em; }
.cx-scope :where(small, sub, sup) { font-size: 0.83em; }
.cx-scope :where(em, i, cite, dfn, var, address) { font-style: italic; }
.cx-scope :where(code, pre, kbd, samp) { font-family: monospace, monospace; }
.cx-scope :where(pre) { white-space: pre; }
.cx-scope :where(th, caption, button) { text-align: center; }

.cx-scope p,
.cx-scope h1, .cx-scope h2, .cx-scope h3, .cx-scope h4,
.cx-scope label,
.cx-scope input,
.cx-scope select,
.cx-scope textarea,
.cx-scope button,
.cx-scope table,
.cx-scope fieldset,
.cx-scope details,
.cx-scope summary,
.cx-scope aside,
.cx-scope section,
.cx-scope form,
.cx-scope header,
.cx-scope div { margin: 0; }

/*
 * Table internals are reset separately: hosts commonly set padding and borders
 * on th/td, which would otherwise fight the repeating-group layout.
 */
.cx-scope table { border-collapse: collapse; }
.cx-scope th,
.cx-scope td { padding: 0; border: none; background: none; vertical-align: middle; }

/*
 * `details` is deliberately NOT reset here, even though Pico styles it. Every
 * <details> inside .cx-scope is one of ours - .cx-group or .cx-advanced - and
 * `.cx-scope details` (0,1,1) outranks `.cx-group` (0,1,0), so a blanket reset
 * silently erased the group border and the advanced rule no matter what the
 * component asked for. Each component neutralizes the host on its own instead.
 */
.cx-scope fieldset { border: none; padding: 0; background: none; }

/*
 * Disclosure markers in every spelling a host might use. Pico draws its own
 * with summary::after, which is why suppressing ::marker and
 * ::-webkit-details-marker was not enough on its own.
 */
.cx-scope summary { list-style: none; cursor: pointer; }
.cx-scope summary::marker { content: ""; font-size: 0; }
.cx-scope summary::-webkit-details-marker { display: none; }
.cx-scope summary::after,
.cx-scope summary::before { content: none; display: none; }

/*
 * Controls, which the UA gives a font of their own rather than inheriting one.
 * The first two declarations are now also covered by the descendant reset above;
 * they stay because this rule is (0,1,1) and the reset is (0,1,0), so a host
 * stylesheet that grew a `.some-class input` rule is held off here and not
 * there. The rest of the block has no equivalent above at all.
 */
.cx-scope input,
.cx-scope select,
.cx-scope textarea,
.cx-scope button {
    font-family: inherit;
    font-size: inherit;
    line-height: normal;
    min-height: 0;
    box-shadow: none;
    vertical-align: middle;
}

/* Native affordances the host may have stripped, or that we rely on. */
.cx-scope input[type="range"] { padding: 0; border: none; background: none; }
.cx-scope input[type="checkbox"],
.cx-scope input[type="radio"] { width: auto; height: auto; }

/* ------------------------------------------------------------- structure */

.cx-title {
    font-family: var(--cx-font-display);
    font-size: 1.3rem;
    font-weight: 620;
    letter-spacing: -0.012em;
    margin: 0 0 0.3rem;
}

.cx-intro { margin: 0 0 0.8rem; font-size: 0.84rem; color: var(--cx-text-muted); max-width: 42rem; }

.cx-split { display: grid; grid-template-columns: 1fr; gap: var(--cx-gap); }

/*
 * Below the split point the results move ABOVE the form once an answer exists,
 * so a phone user is not asked to scroll back past twelve inputs to read it.
 * Before the first calculation the empty panel stays out of the way at the
 * bottom, where it explains itself without occupying the first screen.
 */
.cx-split__results { order: 2; }
.cx-calc--answered .cx-split__results { order: 0; }

/*
 * The inputs column is its own container so a field row can restack on the
 * column's width rather than the calculator's. The two are no longer the same
 * question once the split is 30/70.
 */
.cx-split__inputs { container: cxinputs / inline-size; }

/*
 * em, not rem, and the distinction is not cosmetic.
 *
 * `rem` resolves against the root element - the HOST page's <html> - so a site
 * carrying `html { font-size: 62.5% }`, which is a common enough pattern in
 * older corporate stylesheets, moves this threshold from 704px to 440px and the
 * calculator goes two-column inside a narrow sidebar. The scoped reset cannot
 * defend against that: every rule in this file is scoped, and `rem` by
 * definition is not.
 *
 * In a container query condition `em` resolves against the QUERY CONTAINER's
 * font-size, which for cxroot is .cx-scope and therefore ours. 50em is 700px at
 * the default --cx-font-size of 0.875rem, four pixels off the 44rem it replaces
 * and immune to the host.
 *
 * One consequence worth knowing: the threshold now tracks density. At
 * data-density="compact" the basis is 0.82rem, so the split arrives at 656px
 * rather than 700px - a denser calculator does fit two columns in less width, so
 * this is the more defensible behavior, but it is a change and not a no-op.
 *
 * The rest of the file is still rem and still host-relative. Fixing that is a
 * unit-basis rewrite of every length here, deferred until a client-hosted JS
 * embed exists to make it reachable.
 */
@container cxroot (min-width: 50em) {
    .cx-split { grid-template-columns: minmax(0, 30fr) minmax(0, 70fr); align-items: start; }
    .cx-split__results, .cx-calc--answered .cx-split__results { order: 0; }
}

/*
 * At 30% of a 1000px canvas the inputs column is about 300px. A label like
 * "Market value of vehicle at end of loan" cannot share a line with a control at
 * that width, so below this the row stacks label above control instead of
 * crushing one or the other.
 *
 * em for the reason above. The query container here is .cx-split__inputs, which
 * sets no font-size of its own and so carries .cx-scope's - still ours, still
 * out of the host's reach. 25em is 350px at the default basis, against the 352px
 * the 22rem meant.
 */
@container cxinputs (max-width: 25em) {
    .cx-row { flex-direction: column; align-items: stretch; gap: 0.1rem; }
    .cx-control { justify-content: flex-start; }
    .cx-labelling { justify-content: flex-start; }
}

.cx-form { display: flex; flex-direction: column; gap: 0.45rem; }

/* ---------------------------------------------------------------- groups */

/*
 * A section needs to read as a section at a glance. A filled header bar against
 * a white body does that in one visual step; a bare uppercase caption inside a
 * plain bordered box - which is what this was - leaves three groups looking like
 * one long list.
 */
.cx-group {
    border: 1px solid var(--cx-border);
    border-radius: var(--cx-radius);
    padding: 0;
    background: var(--cx-surface);
    overflow: hidden;
}

.cx-group__body { padding: 0.15rem 0.7rem 0.45rem; }

/*
 * Suppressing the native disclosure triangle takes all three rules. WebKit uses
 * ::-webkit-details-marker, everything current uses ::marker, and ::marker only
 * applies while the summary is display:list-item - which is the default, and
 * which is why setting only one of the three leaves a stray triangle beside the
 * chevron.
 */
.cx-group__summary,
.cx-advanced__summary { list-style: none; }

.cx-group__summary::-webkit-details-marker,
.cx-advanced__summary::-webkit-details-marker { display: none; }

.cx-group__summary::marker,
.cx-advanced__summary::marker { content: ""; font-size: 0; }

.cx-group__summary {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    cursor: pointer;
    padding: 0.4rem 0.7rem;
    background: var(--cx-group-head);
    border-bottom: 1px solid transparent;
    user-select: none;
}

.cx-group[open] > .cx-group__summary { border-bottom-color: var(--cx-border); }
.cx-group__summary:hover { background: var(--cx-group-head-hover); }

.cx-group__label {
    font-family: var(--cx-font-display);
    font-size: 0.72rem;
    font-weight: 660;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--cx-brand);
}

.cx-chev {
    margin-left: auto;
    width: 0.42rem; height: 0.42rem;
    border-right: 2px solid var(--cx-text-faint);
    border-bottom: 2px solid var(--cx-text-faint);
    transform: rotate(45deg);
    transition: transform 120ms ease;
}

.cx-group[open] > .cx-group__summary .cx-chev { transform: rotate(-135deg); }

.cx-fields { display: flex; flex-direction: column; }

/* ---------------------------------------------------------------- fields */

.cx-field { padding: 0.22rem 0; border-bottom: 1px solid var(--cx-hairline); }
.cx-field:last-child { border-bottom: none; }

/*
 * A conditionally hidden field.
 *
 * Stated rather than left to the `hidden` attribute's UA default: that default
 * is `display: none` at the very bottom of the cascade, and any rule here that
 * gives .cx-field a display would silently outrank it. The !important is aimed
 * at exactly that, not at a specificity fight.
 *
 * :last-child is re-asserted because hiding the final row would otherwise leave
 * the hairline above it drawn under nothing.
 */
.cx-field[hidden] { display: none !important; }
.cx-fields > .cx-field:last-child:not([hidden]) { border-bottom: none; }

.cx-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
}

.cx-labelling { display: flex; align-items: center; gap: 0.3rem; min-width: 0; }

.cx-label { font-size: 0.82rem; line-height: 1.3; }

/*
 * A derived label is a placeholder the converter computed from the field name,
 * not content anyone wrote. The muted-italic treatment is scoped to .cx-preview
 * so the hosted app never shows authoring hints to a client.
 */
.cx-preview .cx-derived { color: var(--cx-text-faint); font-style: italic; }

.cx-control { display: flex; align-items: center; gap: 0.3rem; flex: none; }

.cx-affix { font-size: 0.82rem; color: var(--cx-text-faint); user-select: none; }
.cx-affix--unit { min-width: 2.4rem; }

.cx-input {
    height: var(--cx-control-height);
    padding-block: 0;
    padding-inline: 0.4rem;
    border: 1px solid var(--cx-border);
    border-radius: var(--cx-radius-sm);
    background: var(--cx-surface);
    color: var(--cx-text);
    font: inherit;
    font-size: 0.82rem;
}

/*
 * Numeric controls are sized to their content and right-aligned with tabular
 * figures. A full-width box for a two-digit term is the single clearest tell
 * that a form was generated rather than designed, and proportional digits make
 * a column of currency jitter while it is typed.
 */
.cx-input--num {
    text-align: right;
    font-variant-numeric: tabular-nums;
    width: var(--cx-w-number);
}

.cx-field--currency .cx-input--num { width: var(--cx-w-currency); }
.cx-field--percent  .cx-input--num { width: var(--cx-w-percent); }
.cx-field--integer  .cx-input--num { width: var(--cx-w-integer); }

.cx-input--choice { width: var(--cx-w-choice); }
.cx-input--text { width: 12rem; }

.cx-input:focus {
    outline: none;
    border-color: var(--cx-brand);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--cx-brand) 16%, transparent);
}

.cx-input--bare { height: 1.95rem; border-color: transparent; background: transparent; }
.cx-input--bare:focus { background: var(--cx-surface); border-color: var(--cx-brand); }

.cx-hint { margin: 0.05rem 0 0.1rem; font-size: 0.74rem; color: var(--cx-text-faint); line-height: 1.3; }

.cx-error {
    margin: 0.2rem 0 0;
    font-size: 0.78rem;
    color: var(--cx-danger);
}

.cx-error:empty { display: none; }

.cx-field--invalid .cx-input {
    border-color: var(--cx-danger);
}

.cx-field--invalid .cx-input:focus {
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--cx-danger) 16%, transparent);
}

/* ------------------------------------------------------------------ help */

/*
 * An in-flow disclosure rather than a floating popover: absolute positioning
 * inside an auto-height iframe clips or collapses, and a disclosure needs no
 * script, no focus trap and no placement logic.
 */
/*
 * The glyph is centered by grid rather than by line-height. A circular badge
 * sized in rem with a text baseline sitting inside it is never quite centered,
 * because the baseline is a property of the font and not of the circle - which
 * is why the question mark was riding high.
 */
.cx-help__toggle {
    display: inline-grid;
    place-items: center;
    flex: none;
    width: 0.95rem; height: 0.95rem;
    border: 1px solid var(--cx-border);
    border-radius: 50%;
    font-size: 0.62rem;
    font-weight: 600;
    line-height: 1;
    color: var(--cx-text-faint);
    background: var(--cx-surface);
    cursor: pointer;
    user-select: none;
}

.cx-help__toggle:hover { color: var(--cx-brand); border-color: var(--cx-brand); }

.cx-help__check:checked ~ .cx-row .cx-help__toggle {
    background: var(--cx-brand);
    border-color: var(--cx-brand);
    color: var(--cx-brand-contrast);
}

/*
 * Collapsed by default and revealed as a sibling of the row, so opening help
 * pushes only the fields below it - the label and control stay exactly where
 * they were.
 */
.cx-help__body { display: none; }

.cx-help__check:checked ~ .cx-help__body {
    display: block;
    margin: 0.25rem 0 0.15rem;
    padding: 0.4rem 0.55rem;
    background: var(--cx-surface-raised);
    border-left: 2px solid var(--cx-brand);
    border-radius: 0 var(--cx-radius-sm) var(--cx-radius-sm) 0;
    font-size: 0.76rem;
    line-height: 1.4;
    color: var(--cx-text-muted);
}

.cx-help__body p { margin: 0 0 0.35rem; }
.cx-help__body p:last-child { margin: 0; }
.cx-help__caption { color: var(--cx-text-faint); }

/* ---------------------------------------------------------------- slider */

.cx-range {
    display: block;
    width: 100%;
    height: 0.9rem;
    margin: 0.1rem 0 0 !important;
    accent-color: var(--cx-brand);
}

/* -------------------------------------------------------------- advanced */

.cx-advanced {
    border: none;
    border-top: 1px solid var(--cx-hairline);
    padding: 0;
    background: none;
    margin-top: 0.3rem;
}

.cx-advanced__summary {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    cursor: pointer;
    padding: 0.35rem 0;
    font-size: 0.78rem;
    color: var(--cx-text-muted);
    user-select: none;
}

.cx-advanced__summary:hover { color: var(--cx-brand); }
.cx-advanced[open] { padding-bottom: 0.2rem; }

.cx-count {
    margin-left: auto;
    background: var(--cx-surface-sunken);
    border-radius: 999px;
    padding: 0 0.5rem;
    font-size: 0.72rem;
    color: var(--cx-text-muted);
}

/* --------------------------------------------------------------- toggles */

.cx-toggle { display: inline-flex; align-items: center; cursor: pointer; }
.cx-toggle input { position: absolute; opacity: 0; width: 0; height: 0; }

.cx-toggle__track {
    width: 2.4rem; height: 1.35rem;
    border-radius: 999px;
    background: var(--cx-surface-sunken);
    border: 1px solid var(--cx-border);
    display: inline-flex; align-items: center; padding: 2px;
    transition: background 120ms ease;
}

.cx-toggle__thumb {
    width: 1rem; height: 1rem;
    border-radius: 50%;
    background: var(--cx-surface);
    transition: transform 120ms ease;
}

.cx-toggle input:checked + .cx-toggle__track { background: var(--cx-brand); }
.cx-toggle input:checked + .cx-toggle__track .cx-toggle__thumb { transform: translateX(1.05rem); }

.cx-segmented {
    display: inline-flex;
    border: 1px solid var(--cx-border);
    border-radius: var(--cx-radius-sm);
    overflow: hidden;
    background: var(--cx-surface-sunken);
}

.cx-segmented__option input { position: absolute; opacity: 0; }

.cx-segmented__option span {
    display: block;
    padding: 0.35rem 0.7rem;
    font-size: 0.82rem;
    cursor: pointer;
    white-space: nowrap;
}

.cx-segmented__option input:checked + span {
    background: var(--cx-brand);
    color: var(--cx-brand-contrast);
}

/* --------------------------------------------------------------- family */

/* Fixed layout so the columns share the available width rather than being sized
   by the inputs' intrinsic width — a number input defaults to about 20
   characters, and three of those plus a row-name column overflow a narrow
   container long before the values need the room. */
.cx-family { width: 100%; border-collapse: collapse; font-size: 0.85rem; table-layout: fixed; }

/* Fill the cell rather than claiming a default width. min-width: 0 matters:
   without it an input refuses to shrink below its intrinsic size and the fixed
   layout is overridden anyway. */
.cx-family td .cx-input {
    width: 100%;
    min-width: 0;
    box-sizing: border-box;
}

.cx-family th {
    text-align: left;
    font-weight: 560;
    font-size: 0.74rem;
    color: var(--cx-text-muted);
    padding: 0.25rem 0.3rem;
    border-bottom: 1px solid var(--cx-border);
    /* Headings wrap rather than widening their column: "Monthly payment" over two
       lines costs a few pixels of height, where forcing it onto one costs the
       value columns their width. */
    overflow-wrap: break-word;
}

.cx-family td { padding: 0.1rem; }

/* The leading column names each row — "Credit card 1", "Auto loan 2". It is a
   row header rather than a cell, so a screen reader announces it with every
   input in the row; without it, twelve identical number boxes are the whole
   experience of the table. */
.cx-family__rowhead {
    text-align: left;
    font-weight: 500;
    font-size: 0.8rem;
    color: var(--cx-text);
    /* Width comes from the template, sized to the longest row name — a fixed
       share suits one calculator's column count and starves another's. Wrapping
       lets a name longer than the cap fold rather than overflow. */
    white-space: normal;
    padding-right: 0.5rem;
}
.cx-family thead .cx-family__rowhead { border-bottom: 1px solid var(--cx-border); }
.cx-family tbody .cx-family__rowhead { border-bottom: none; }

/* ----------------------------------------------------------------- grid */

/* Paired inputs (an amount and its growth rate) as one table. Inherits the
   repeating group's table; these rules only seat an affix beside the box. */
.cx-grid { margin: 0.25rem 0 0.5rem; }
.cx-grid td { vertical-align: top; }
.cx-cell__control {
    display: flex;
    align-items: center;
    gap: 0.2rem;
}
.cx-grid .cx-cell__control .cx-input {
    flex: 1 1 auto;
    width: 100%;
    min-width: 0;
    box-sizing: border-box;
}
.cx-grid .cx-family__rowhead[title] { cursor: help; }
.cx-cell--invalid .cx-input { border-color: var(--cx-danger, #b42318); }
.cx-cell .cx-error { margin: 0.15rem 0 0; font-size: 0.72rem; }

/* Visible to assistive technology only: the row-name column's heading has no
   useful wording, but a header cell that is empty reads as a gap. */
.cx-sr-only {
    position: absolute; width: 1px; height: 1px;
    padding: 0; margin: -1px; overflow: hidden;
    clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

.cx-note { margin: 0.5rem 0 0; font-size: 0.76rem; color: var(--cx-text-faint); }

/* ------------------------------------------------------------- calculate */

.cx-calculate {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-top: 0.35rem;
    height: 2.2rem;
    padding: 0 1rem;
    line-height: 1;
    border: none;
    border-radius: var(--cx-radius-sm);
    background: var(--cx-brand);
    color: var(--cx-brand-contrast);
    font: inherit;
    font-size: 0.94rem;
    font-weight: 560;
    cursor: pointer;
}

.cx-calculate:hover { filter: brightness(1.08); }

/*
 * Start over: secondary to Calculate, beside it. Outlined rather than filled so
 * the one action that produces an answer stays the one that looks like it.
 *
 * Carries .cx-scope so it outranks the scoped reset's `.cx-scope button
 * { margin: 0 }` (0,1,1); a bare class would lose and sit flush against
 * Calculate. Drawn only when the caller asks (showStartOver), and hidden until
 * renderer.js binds it, so a page without script never shows a dead button.
 */
.cx-scope .cx-start-over {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin: 0.35rem 0 0 0.5rem;
    height: 2.2rem;
    padding: 0 0.9rem;
    line-height: 1;
    border: 1px solid var(--cx-border);
    border-radius: var(--cx-radius-sm);
    background: var(--cx-surface);
    color: var(--cx-brand);
    font: inherit;
    font-size: 0.88rem;
    font-weight: 520;
    cursor: pointer;
}

.cx-scope .cx-start-over[hidden] { display: none; }
.cx-scope .cx-start-over:hover { border-color: var(--cx-brand); }
.cx-scope .cx-start-over:focus-visible,
.cx-scope .cx-calculate:focus-visible {
    outline: 2px solid var(--cx-brand);
    outline-offset: 2px;
}

/*
 * HOST-ONLY -- not present in fincalc-forge's renderer.css.
 *
 * The visitor's results as a PDF. .cx-calculate's shape and radius, outlined rather than filled:
 * it sits beside the figures as a secondary action, and a second solid brand button would compete
 * with Calculate for the same attention. The brand colour still carries it, so it reads as the
 * licensee's, not ours.
 */
.cx-pdf { margin-top: 0.75rem; }

.cx-pdf__button,
.cx-email__button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: 2.2rem;
    padding: 0 1rem;
    line-height: 1;
    border: 1px solid var(--cx-brand);
    border-radius: var(--cx-radius-sm);
    background: transparent;
    color: var(--cx-brand);
    font: inherit;
    font-size: 0.94rem;
    font-weight: 560;
    cursor: pointer;
}

.cx-pdf__button:hover,
.cx-email__button:hover { background: var(--cx-brand); color: var(--cx-brand-contrast); }

/* The email form: the PDF button's secondary style beside the renderer's own input. */
.cx-email { margin-top: 0.75rem; }
.cx-email__row { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
.cx-email__row .cx-input { flex: 1 1 14rem; min-width: 0; }

/* --------------------------------------------------------------- results */

.cx-results { display: flex; flex-direction: column; gap: 0.6rem; }

/*
 * Cleared by Start over until the next Calculate. The display above would beat
 * the browser's own [hidden] rule, so the hidden state is stated here.
 */
.cx-results[hidden] { display: none !important; }

.cx-card {
    background: var(--cx-surface);
    border: 1px solid var(--cx-border);
    border-radius: var(--cx-radius);
    padding: 0;
    overflow: hidden;
}

/*
 * The results card carries no filled header bar. The input groups on the left do
 * - a filled bar is what makes a collapsible section read as a section - but
 * repeating it on the right would give the two columns the same visual weight,
 * and the answer should outrank the question that produced it.
 */

/*
 * Branches of one answer, side by side. auto-fit rather than a fixed column
 * count because the card has to hold one outcome, two, or occasionally three
 * without the template knowing which; below the minimum they wrap and stack on
 * their own.
 *
 * The outer grid also owns the ROWS every card shares - title, metric label,
 * metric value, note - and each card and each metric inherits them with subgrid.
 * That is what puts the dollar figures on one horizontal line: a label that
 * wraps to two lines in one card reserves the same height in the next, so the
 * values below them stay level instead of one card's answer riding lower than
 * its neighbor's. Centering inside a cell cannot do this - the rows have to be
 * the same rows, not merely the same alignment.
 */
.cx-outcomes {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
    grid-template-rows: auto auto auto auto;
}

.cx-outcome {
    display: grid;
    grid-row: span 4;
    grid-template-rows: subgrid;
    row-gap: 0.15rem;
    padding: 0.8rem 0.7rem 0.9rem;
    min-width: 0;
}

.cx-outcome__label {
    margin: 0 0 0.45rem;
    font-family: var(--cx-font-display);
    font-size: 1.02rem;
    font-weight: 660;
    letter-spacing: -0.01em;
    text-align: center;
    color: var(--cx-brand);
}

/*
 * Metrics within an outcome flow as columns, so a branch with one figure centers
 * it and a branch with two sets them beside each other. Rows are inherited from
 * the outer grid (label above, value below), which is what keeps the values
 * aligned across cards as well as within one.
 */
.cx-metrics {
    grid-row: 2 / span 2;
    display: grid;
    grid-template-rows: subgrid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    column-gap: 0.5rem;
}

.cx-metric {
    display: grid;
    grid-row: span 2;
    grid-template-rows: subgrid;
    min-width: 0;
}

/*
 * The label sits at the bottom of the label row and the value at the top of the
 * value row, so the two are visually paired and hug the boundary between them
 * regardless of how tall a wrapped label made that row.
 */
.cx-metric__label {
    align-self: end;
    text-align: center;
    font-size: 0.74rem;
    font-weight: 600;
    line-height: 1.25;
    color: var(--cx-text-muted);
}

.cx-metric__value {
    align-self: start;
    text-align: center;
    margin-top: 0.15rem;
    font-family: var(--cx-font-display);
    font-size: 1.05rem;
    font-weight: 620;
    letter-spacing: -0.012em;
    font-variant-numeric: tabular-nums;
}

/*
 * Emphasis is carried by size alone. The emphasized member keeps its position in
 * the declared order so the two branches stay column-for-column comparable.
 */
.cx-metric--featured .cx-metric__value { font-size: 1.35rem; }

.cx-outcome__notes { grid-row: 4; }
.cx-card__note { margin: 0.5rem 0 0; font-size: 0.76rem; color: var(--cx-text-faint); }
.cx-outcome .cx-card__note { text-align: center; }

/*
 * Authored layout: two independent axes, both calculator-wide.
 *
 * They are calculator-wide rather than per-card because of the shared rows
 * described above. Cards inherit the outer grid's rows so a label that wraps in
 * one reserves the same height in the next; a single card opting out would leave
 * its neighbors holding a band sized for content that is no longer beside them,
 * and the figures would stop lining up - which is the whole reason the cards sit
 * side by side. So the modifiers live on the container and every card follows.
 */

/*
 * Cards stacked. One column; the auto-fit track is what put them side by side,
 * so replacing it is the entire change. A hairline separates them, since without
 * the horizontal gap between columns two cards would otherwise read as one.
 */
.cx-outcomes--cards-stacked { grid-template-columns: 1fr; }
.cx-outcomes--cards-stacked .cx-outcome + .cx-outcome {
    border-top: 1px solid var(--cx-hairline);
}

/*
 * Figures stacked within each card. The metrics can no longer inherit the
 * label-row/value-row pair - N figures need 2N rows, and a subgrid cannot grow
 * past the span it was given, so the extra rows would spill outside the card.
 * The card therefore drops to three shared rows (title, metrics, notes) and the
 * metrics flow as ordinary rows inside the middle one. Titles and notes still
 * align across cards; the figures inside no longer do, which is inherent to
 * stacking rather than something the rules give up cheaply.
 */
.cx-outcomes--fields-stacked { grid-template-rows: auto auto auto; }
.cx-outcomes--fields-stacked .cx-outcome { grid-row: span 3; }
.cx-outcomes--fields-stacked .cx-outcome__notes { grid-row: 3; }

.cx-outcomes--fields-stacked .cx-metrics {
    grid-row: 2;
    grid-template-rows: none;
    grid-auto-flow: row;
    grid-auto-rows: auto;
    grid-auto-columns: auto;
    row-gap: 0.5rem;
    column-gap: 0;
}

.cx-outcomes--fields-stacked .cx-metric {
    grid-row: auto;
    grid-template-rows: none;
}

/*
 * A stacked figure is read as a pair top to bottom, so the label no longer needs
 * to hug the boundary of a shared row - there is no shared row left to hug.
 */
.cx-outcomes--fields-stacked .cx-metric__label { align-self: auto; }
.cx-outcomes--fields-stacked .cx-metric__value { align-self: auto; }

/*
 * The sentence that answers the question. Boxed like the cards it now sits
 * above, rather than left as a trailing caption: it leads the panel, so it has
 * to read as a peer of the figures and not as a preamble to them.
 */
.cx-narrative {
    margin: 0;
    padding: 0.65rem 0.9rem;
    background: var(--cx-surface);
    border: 1px solid var(--cx-border);
    border-radius: var(--cx-radius);
    font-size: 0.88rem;
    line-height: 1.45;
    text-align: center;
    color: var(--cx-text);
}

/*
 * A blank line between response paragraphs.
 *
 * A calculator can match several response-text rules at once, and each one is
 * its own <p>. The scoped reset zeroes every p margin - it has to, or a host
 * stylesheet's paragraph spacing would reflow the whole calculator - so those
 * paragraphs arrived stacked with no gap at all. Two centred sentences touching
 * line to line read as one wrapped sentence, which is the worst possible outcome
 * for prose whose whole job is to say two separate things.
 *
 * One line box, not a generic paragraph gap: the ask was for a blank line, and
 * that is what an empty line between them would measure. 1lh states it exactly
 * and follows line-height on its own; the em value above it is the same figure
 * computed against the 1.45 declared here, for engines without lh (pre-2023
 * Safari and Firefox). Keep the two in step if that line-height ever moves.
 *
 * Specificity: (0,1,2) against the reset's (0,1,1), so this wins outright rather
 * than on source order - worth stating, because the neighbouring .cx-ws__cell
 * note further down records what happens when a single-class selector does not.
 */
.cx-narrative p + p {
    margin-top: 1.45em;
    margin-top: 1lh;
}

.cx-results--empty .cx-metric__value { color: var(--cx-text-faint); }

/* ----------------------------------------------------------------- charts */

/*
 * A bounded box the canvas fills. Chart.js is told maintainAspectRatio:false, so
 * it sizes to this container rather than imposing a ratio of its own - which is
 * what lets the chart track the results column width instead of overflowing it.
 */
.cx-chart { margin-top: 0.6rem; }

.cx-chart__viz {
    position: relative;
    height: 240px;
    padding: 0.7rem 0.6rem 0.5rem;
    background: var(--cx-surface);
    border: 1px solid var(--cx-border);
    border-radius: var(--cx-radius);
}

.cx-chart__viz canvas { display: block; }

/*
 * A chart and the table of the data behind it, as one unit.
 *
 * The spacing moved here from .cx-chart__viz deliberately. The plot box above
 * has a fixed height - a responsive Chart.js canvas needs one - so it cannot be
 * the element a disclosure grows inside: an opened table would overflow 240px
 * and paint over the next chart rather than pushing it down. This wrapper has no
 * height of its own, so opening the table expands it and everything below moves.
 */
.cx-chart__item + .cx-chart__item { margin-top: 0.6rem; }

.cx-results__empty {
    margin: 0;
    font-size: 0.8rem;
    color: var(--cx-text-faint);
    text-align: center;
    padding: 0.4rem 0;
}

/* ----------------------------------------------------------------- prose */

/*
 * The prose lives inside the results column, so it inherits that column's width
 * and needs no measure of its own; a max-width here would only fight the grid.
 * Tightened a step because it is now adjacent to the result cards rather than
 * separated from the whole calculator by full-width whitespace.
 */
.cx-prose {
    margin-top: 1rem;
    padding-top: 0.85rem;
    border-top: 1px solid var(--cx-border);
    font-size: 0.82rem;
    color: var(--cx-text-muted);
    max-width: none;
}

.cx-prose p { margin: 0 0 0.7rem; line-height: 1.6; }

/*
 * Rich text in the intro, the article and the disclaimer.
 *
 * Institutions may use paragraphs, line breaks, links, bold, italic and lists;
 * the host sanitizes to that set. Bold and italic are already restored by the
 * :where() block near the top of the file. Links and lists are stated here,
 * the same way in all three places.
 *
 * Links: the scoped reset makes every element inherit its color, so without
 * this a link would be the same grey as the text around it and findable only
 * by hovering. Brand-colored and underlined, because color alone is not enough
 * to mark a link for a reader who cannot tell the two greys from the blue.
 * (0,2,1), so a host page's `a { ... }` loses.
 */
.cx-scope .cx-intro a,
.cx-scope .cx-prose a {
    color: var(--cx-brand);
    text-decoration: underline;
    text-underline-offset: 0.15em;
}

.cx-scope .cx-intro a:hover,
.cx-scope .cx-prose a:hover { text-decoration-thickness: 2px; }

.cx-scope .cx-intro a:focus-visible,
.cx-scope .cx-prose a:focus-visible {
    outline: 2px solid var(--cx-brand);
    outline-offset: 2px;
    border-radius: 2px;
}

/*
 * Lists. list-style is not one of the properties the reset hardens, so a host
 * page's `ul { list-style: none; padding: 0 }` would otherwise strip the
 * bullets; these state the markers and the indent outright. :where() leaves them
 * at (0,2,0), above any host rule naming the element.
 */
.cx-scope .cx-intro :where(ul, ol),
.cx-scope .cx-prose :where(ul, ol) { margin: 0 0 0.7rem; padding-left: 1.4em; }
.cx-scope .cx-intro :where(ul),
.cx-scope .cx-prose :where(ul) { list-style: disc outside; }
.cx-scope .cx-intro :where(ol),
.cx-scope .cx-prose :where(ol) { list-style: decimal outside; }
.cx-scope .cx-intro :where(ul ul, ol ul),
.cx-scope .cx-prose :where(ul ul, ol ul) { list-style-type: circle; margin-bottom: 0; }
.cx-scope .cx-intro :where(ol ol, ul ol),
.cx-scope .cx-prose :where(ol ol, ul ol) { margin-bottom: 0; }
.cx-scope .cx-intro :where(li) + :where(li),
.cx-scope .cx-prose :where(li) + :where(li) { margin-top: 0.2rem; }
.cx-scope .cx-prose :where(li) { line-height: 1.6; }

/*
 * The intro is one block of text when it is plain, and paragraphs or a list
 * when an institution wrote it. The reset zeroes p margins inside the scope at
 * (0,1,1), so the space between the intro's own blocks is stated at (0,2,0).
 * The intro sits directly under the title, so it adds no trailing space.
 */
.cx-scope .cx-intro > * + * { margin-top: 0.5rem; }
.cx-scope .cx-intro > :where(ul, ol) { margin-bottom: 0; }

/*
 * The disclaimer.
 *
 * Moved here from fincalc-host, where it lived as a HOST-ONLY block that its
 * own note said belonged in Forge. Forge's earlier rule made the disclaimer
 * 0.78rem and faint against prose at 0.82rem and muted, which on a real page
 * read as one more paragraph of the article. The article is the house
 * explaining the calculation; the disclaimer is the institution limiting what
 * the number means, and a reader who cannot tell where one stops and the other
 * starts has not been given the disclaimer in any meaningful sense.
 *
 * So it is separated structurally rather than tonally: its own rule above it, a
 * sunken panel, and a brand-colored edge that follows a licensee's theme
 * without per-tenant CSS. Nothing here makes it louder than the result cards.
 *
 * It is a div now, holding one paragraph for plain text and possibly several
 * paragraphs or a list for an institution's rich text. The rules below keep a
 * one-paragraph disclaimer drawn exactly as the old <p class="cx-disclaimer">
 * was: the same outer margins, and the paragraph inside taking the panel's
 * line-height with no margin of its own at the panel's edge.
 */
.cx-scope .cx-prose .cx-disclaimer {
    margin: 1rem 0 0.7rem;
    padding: 0.7rem 0.85rem;
    border-top: 1px solid var(--cx-border);
    border-left: 3px solid var(--cx-brand);
    border-radius: 0 var(--cx-radius-sm, 4px) var(--cx-radius-sm, 4px) 0;
    background: var(--cx-surface-sunken);
    font-size: 0.74rem;
    line-height: 1.55;
    color: var(--cx-text-muted);
}

.cx-scope .cx-prose .cx-disclaimer :where(p, li) { line-height: inherit; }
.cx-scope .cx-prose .cx-disclaimer p { margin: 0 0 0.5rem; }
.cx-scope .cx-prose .cx-disclaimer > :last-child { margin-bottom: 0; }

/* The article's last paragraph should not sit flush against the panel: the gap
   is what says these are two different kinds of statement. */
.cx-scope .cx-prose .cx-article + .cx-disclaimer { margin-top: 1.1rem; }

/* ----------------------------------------------------------------- print */

@media print {
    .cx-scope { --cx-surface-raised: #fff; --cx-surface-sunken: #fff; font-size: 10.5pt; }
    .cx-split { grid-template-columns: 1fr; }
    .cx-split__results, .cx-calc--answered .cx-split__results { order: 0; }

    /*
     * Collapsed groups print expanded. A printed calculation that silently
     * omitted the assumptions behind it would be worse than useless - it would
     * be misleading.
     */
    .cx-group, .cx-advanced { display: block !important; }
    .cx-group > *, .cx-advanced > * { display: revert !important; }
    .cx-help__body { display: block !important; border-left: none; padding-left: 0; }
    .cx-chev, .cx-count, .cx-help__toggle, .cx-calculate, .cx-start-over, .cx-range,
    /* HOST-ONLY: .cx-pdf and .cx-email -- buttons have no business on paper. */
    .cx-results__empty, .cx-pdf, .cx-email { display: none !important; }

    /* The disclaimer keeps its separation on paper and drops the fill: a grey
       block behind small type prints as a smear on most office printers. */
    .cx-scope .cx-prose .cx-disclaimer {
        background: none;
        border-left-color: #000;
        color: #000;
    }

    .cx-card { border: 1px solid #000; }
    .cx-input { border: none; padding: 0; }
    .cx-field { break-inside: avoid; }
}
/* ------------------------------------------------------- results data table */

/*
 * The grid behind a chart, disclosed on demand.
 *
 * A calculator that returns an amortization schedule or a year-by-year
 * projection has, in that grid, its most complete answer - and until now no way
 * to show it. It stays closed because the cards and the plot are the answer most
 * readers want; the table is for the one who wants to check a particular year.
 *
 * The scroll box is load-bearing rather than cosmetic. This renderer is drawn
 * inside an iframe that reports its own height to the host page, so an
 * undelimited 360-row table would grow the frame by thousands of pixels the
 * moment someone opened it and shove the host's own content off the screen. The
 * box caps that at a readable window and keeps the scrolling local.
 */
/*
 * The third <details> in the renderer, and the one that forgot to defend itself.
 *
 * The reset deliberately leaves `.cx-scope details` alone - see the note beside
 * the fieldset rule - on the understanding that every disclosure inside the
 * scope neutralizes the host on its own terms. .cx-group and .cx-advanced do.
 * This one arrived with the results data table declaring nothing but a margin,
 * so a host `details { border: 2px solid; padding: 1rem }` drew a box around the
 * schedule and inset it. Found with the preview's hostile toggle, which is what
 * the toggle is for.
 *
 * Stated as three neutralizing declarations rather than folded into the reset,
 * because folding it in is the thing the note above explains does not work:
 * `.cx-scope details` is (0,1,1) and would outrank every one of these classes.
 */
.cx-data {
    margin-top: 0.6rem;
    border: none;
    padding: 0;
    background: none;
}

/*
 * Drawn as a control rather than as a caption. It was neither: muted text at
 * 0.78rem with a small triangle, sitting under a chart, in a renderer whose host
 * reset strips every native disclosure marker. People read it as a figure legend
 * and never discovered that the grid behind it - the most detailed answer the
 * calculator has - was one click away.
 *
 * A bordered pill in the brand color, with the action spelled out in words. The
 * triangle alone was carrying the whole affordance, and a 0.32rem triangle is not
 * enough signal to overcome text that otherwise looks like a caption.
 */
.cx-data__toggle {
    cursor: pointer;
    font-size: 0.78rem;
    color: var(--cx-brand);
    padding: 0.3rem 0.6rem;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    min-height: var(--cx-control-height);
    border: 1px solid var(--cx-border);
    border-radius: var(--cx-radius-sm);
    background: var(--cx-surface-raised);
    user-select: none;
    transition: background 120ms ease, border-color 120ms ease;
}
.cx-data__toggle:hover {
    background: var(--cx-group-head-hover);
    border-color: var(--cx-brand);
}

/*
 * The host reset removes the default outline along with the marker, so without
 * this a keyboard user tabbing through the results has no idea where they are.
 * focus-visible rather than focus, so a mouse click does not leave a ring behind.
 */
.cx-data__toggle:focus-visible {
    outline: 2px solid var(--cx-brand);
    outline-offset: 2px;
}

.cx-data__title { font-weight: 600; }

/*
 * The action word. Which one shows is decided here rather than in the template,
 * because the open state belongs to the browser - see the note in results.html.
 */
.cx-data__action { color: var(--cx-text-muted); }
.cx-data__action--open { display: none; }
.cx-data[open] > .cx-data__toggle .cx-data__action--closed { display: none; }
.cx-data[open] > .cx-data__toggle .cx-data__action--open { display: inline; }

/*
 * The host reset removes every native disclosure marker (see the summary rules
 * above), so the affordance has to be drawn here or there is nothing to say the
 * row is clickable. A CSS triangle rather than a character: an entity would
 * inherit the host's font and land at whatever size and baseline that font
 * happens to give it.
 */
.cx-data__toggle::before {
    content: "";
    width: 0; height: 0;
    flex: 0 0 auto;
    border-left: 0.32rem solid currentColor;
    border-top: 0.26rem solid transparent;
    border-bottom: 0.26rem solid transparent;
    transition: transform 120ms ease;
    align-self: center;
}
.cx-data[open] > .cx-data__toggle::before { transform: rotate(90deg); }

.cx-data__count { color: var(--cx-text-faint); font-weight: 400; }

/* The authored line under an opened table's heading. Sized between the toggle
   and the cells so it reads as prose about the grid rather than as part of it. */
.cx-data__note {
    margin: 0.35rem 0 0;
    font-size: 0.78rem;
    line-height: 1.45;
    color: var(--cx-text-muted);
}

/*
 * A thumb needs more than an input does. The control-height variable is tuned
 * for form fields sitting in a dense column; this is a lone target under a
 * chart, and on a phone it is the one thing on the results panel somebody has
 * to hit deliberately.
 */
@media (pointer: coarse) {
    .cx-data__toggle { min-height: 2.5rem; padding-inline: 0.75rem; }
}

.cx-data__scroll {
    max-height: 22rem;
    overflow: auto;
    margin-top: 0.35rem;
    border: 1px solid var(--cx-border);
    border-radius: var(--cx-radius-sm);
    background: var(--cx-surface);
    /* Reserve the vertical scrollbar's lane up front. Without it the table is
       laid out at the full width, the scrollbar arrives once the schedule is
       long enough, and every column shifts left by its thickness - a reflow the
       reader sees as the grid twitching. Classic scrollbars only; overlay
       scrollbars ignore this by spec, which is why the buffer below exists as
       well and is the part actually keeping digits off the edge. */
    scrollbar-gutter: stable;
}

.cx-data__table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.76rem;
    font-variant-numeric: tabular-nums;
}

/* Present for screen readers; the <summary> already carries the heading visually. */
.cx-data__caption {
    position: absolute;
    width: 1px; height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

.cx-data__table th {
    position: sticky;
    top: 0;
    z-index: 1;
    background: var(--cx-group-head);
    text-align: right;
    font-weight: 600;
    color: var(--cx-text);
    padding: 0.3rem 0.5rem;
    border-bottom: 1px solid var(--cx-border);
    white-space: nowrap;
}

.cx-data__table td {
    text-align: right;
    padding: 0.22rem 0.5rem;
    border-bottom: 1px solid var(--cx-hairline);
    white-space: nowrap;
}

/* The first column is the row's identity - a year, a payment number - so it
   reads as a label rather than as another figure. */
.cx-data__table th:first-child,
.cx-data__table td:first-child { text-align: left; }

/* The last column needs room to the right of it that a scrollbar can sit in.
   Two different scrollbars land there and both used to clip it: an overlay
   vertical scrollbar (macOS, and Chrome's default on touch) paints straight over
   the content box, and on a grid wide enough to scroll sideways the last cell's
   right edge is the end of the scroll extent, where the horizontal bar sits.

   The buffer goes on the CELL, not on .cx-data__scroll. Inline-end padding on a
   scroll container is not included in the scrollable overflow area by most
   engines, so padding-right there is simply lost the moment the table overflows
   - the exact case this is fixing. Cell padding is inside the table box, so it
   survives both axes on every engine.

   Numbers are right-aligned, so this reads as breathing room at the edge of the
   grid rather than as a gap in it. */
.cx-data__table th:last-child,
.cx-data__table td:last-child { padding-right: 1.25rem; }

.cx-data__table tbody tr:last-child td { border-bottom: none; }

/* Banding, because scanning one row across eight columns of similar numbers is
   where a plain grid loses people. */
.cx-data__table tbody tr:nth-child(even) td { background: var(--cx-surface-raised); }

/* Print takes the whole grid: paper has no disclosure to open, and a schedule is
   exactly what someone printing this wants to keep. */
@media print {
    /* No scrollbars on paper, so neither the reserved lane nor the buffer beside
       the last column has anything to hold open. */
    .cx-data__scroll { max-height: none; overflow: visible; scrollbar-gutter: auto; }
    .cx-data__table th:last-child,
    .cx-data__table td:last-child { padding-right: 0.5rem; }
    /* Paper has no disclosure to operate, so the control goes entirely - border,
       action word and all - and the caption below carries the heading instead. */
    .cx-data__toggle { display: none; }
    .cx-data__caption {
        position: static; width: auto; height: auto;
        clip-path: none; text-align: left;
        font-weight: 600; padding: 0.3rem 0;
    }
}

/* --------------------------------------------------------- worksheets */

/*
 * Authored free-form grids, drawn between the results cards and the charts.
 *
 * The stylesheet owns every appearance decision here and the contract owns none
 * of them. A cell declares WHICH edges it draws; what an edge looks like is
 * --cx-border, so the grid re-skins with the rest of the calculator when it is
 * dropped on an FI page. The single exception is a column width, which is a
 * layout fact about this particular grid and carries no color or type.
 *
 * This ships to hosting, unlike the preview-only tool styling in frame.html:
 * a worksheet is product, not scaffolding.
 */
.cx-worksheets { display: flex; flex-direction: column; gap: 0.75rem; }

.cx-ws {
    background: var(--cx-surface);
    border: 1px solid var(--cx-border);
    border-radius: var(--cx-radius);
    padding: 0.85rem 1rem 1rem;
}

/* Prefixed for the margin: the reset zeroes h3 at (0,1,1) and would otherwise
   pull the heading flat against the grid. */
.cx-scope .cx-ws__title {
    margin: 0 0 0.6rem;
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--cx-text);
}

/*
 * Fixed layout, so an authored column width is honored rather than treated as
 * a suggestion the widest cell can overrule. auto layout sizes columns from
 * content, which is precisely what a declared width is asking it not to do.
 */
.cx-ws__grid {
    width: 100%;
    table-layout: fixed;
    border-collapse: collapse;
    font-size: 0.88rem;
}

/*
 * .cx-scope prefixes throughout, and they are load-bearing.
 *
 * The scoped reset at the top of this file carries
 * `.cx-scope td { padding: 0; border: none }` at (0,1,1) so a host stylesheet
 * cannot decorate our tables. A bare `.cx-ws__cell` sits at (0,1,0) and LOSES
 * to it - which is why an authored border never appeared on the page while the
 * editor showed it set, and why the stylesheet's own cell padding did nothing
 * (an authored column padding still worked, because that arrives inline).
 *
 * The same trap the reset's own note about `.cx-scope details` describes: each
 * component has to neutralize the reset on its own terms rather than assume a
 * class beats an element.
 */
.cx-scope .cx-ws__cell {
    padding: 0.3rem 0.5rem;
    vertical-align: baseline;
    font-weight: 400;
    text-align: left;
    /*
     * Tabular figures, for the same reason the numeric inputs use them: a column
     * of currency whose digits do not line up reads as a list rather than as a
     * total being worked towards.
     */
    font-variant-numeric: tabular-nums;
}

/*
 * Heading cells carry no fill of their own. A filled bar would give a section
 * heading inside a worksheet the same weight as the card heading above it, and
 * a heading cannot outrank the thing it is a heading for.
 */
.cx-ws__row--header .cx-ws__cell {
    font-weight: 600;
    text-align: center;
    padding-top: 0.55rem;
}

.cx-scope .cx-ws__cell--strong { font-weight: 600; }
.cx-scope .cx-ws__cell--left { text-align: left; }
.cx-scope .cx-ws__cell--center { text-align: center; }
.cx-scope .cx-ws__cell--right { text-align: right; }

/*
 * Authored edges. Each is a single named rule rather than a shorthand so two
 * edges on one cell compose instead of the second resetting the first.
 */
.cx-scope .cx-ws__cell--rule-top { border-top: 1px solid var(--cx-border); }
.cx-scope .cx-ws__cell--rule-bottom { border-bottom: 1px solid var(--cx-border); }
.cx-scope .cx-ws__cell--rule-left { border-left: 1px solid var(--cx-border); }
.cx-scope .cx-ws__cell--rule-right { border-right: 1px solid var(--cx-border); }

/*
 * Column dividers, applied to the col element so they run the full height of
 * the grid without every cell in the column having to ask for them. This is the
 * pair of lines flanking a ledger's operator column.
 */
.cx-ws__col--rule-left { border-left: 1px solid var(--cx-border); }
.cx-ws__col--rule-right { border-right: 1px solid var(--cx-border); }

/*
 * Placement.
 *
 * A grid defaults to the full width of its card, which is what a ledger wants
 * when it is the only thing on the page. Centering it therefore has to shrink it
 * first: a table already spanning its container has nothing to be centered
 * within, so width:auto comes with the alignment rather than being a second
 * thing to remember. An authored width overrides this inline and wins, which is
 * the case where somebody wants a centered grid wider than its own content.
 *
 * The heading follows the grid. A centered table under a left-aligned title
 * reads as a mistake rather than as a decision.
 */
/*
 * table-layout goes back to auto along with the width, and it has to.
 *
 * The fixed algorithm needs a definite table width to divide among the columns;
 * with width:auto it has nothing to divide, so browsers fall back to sizing from
 * the first row and every later row's content overflows its cell. That is what
 * put "Down payment (capital reduction)" through the operator column and flat
 * against the plus sign, hiding the padding authored on it - the padding was
 * there, the neighbor was sitting on top of it.
 *
 * Auto layout still honors a col width as the column's preferred size, so an
 * authored 9rem amount column keeps its 9rem; the difference is that a label
 * column with no width now sizes to its longest label instead of to its first.
 */
.cx-ws--center .cx-ws__grid,
.cx-ws--right .cx-ws__grid { width: auto; table-layout: auto; }

/* Two classes each, so these clear the reset's `.cx-scope table { margin: 0 }`
   without needing a .cx-scope prefix of their own. */
.cx-ws--center .cx-ws__grid { margin-left: auto; margin-right: auto; }
.cx-ws--right .cx-ws__grid { margin-left: auto; margin-right: 0; }

.cx-ws--center .cx-ws__title { text-align: center; }
.cx-ws--right .cx-ws__title { text-align: right; }

/*
 * A worksheet is the itemized answer, so it prints. The chart controls and the
 * Calculate button do not, but a reader holding a printed page wants the figures
 * that add up to the total more than anything else on it.
 */
@media print {
    .cx-ws { border: 1px solid #000; break-inside: avoid; }
}

/* ============================================================
   HOST-ONLY -- not present in fincalc-forge's renderer.css.

   Layout templates (a class on the frame root re-flows .cx-split)
   - cx-tpl-sidebar : the default responsive side-by-side (no overrides needed)
   - cx-tpl-stacked : single column at all widths, results below inputs

   Host-only because forge previews one calculator in one shape, while host
   lets a licensee's template pick a layout. Kept at the end of the file, after
   everything forge owns, so a re-sync is: copy forge's renderer.css, paste this
   block back on the end.

   Specificity (.cx-tpl-stacked .cx-split = 0,2,0) intentionally beats the
   wide-viewport @media rules (.cx-split = 0,1,0), so no !important is needed.
   ============================================================ */
.cx-tpl-stacked .cx-split { grid-template-columns: 1fr; }
.cx-tpl-stacked .cx-split__results { order: 2; }
.cx-tpl-stacked .cx-calc--answered .cx-split__results { order: 0; }

/* ============================================================
   HOST-ONLY -- not present in fincalc-forge's renderer.css.

   The calculator's title and intro, hidden for a page that carries its own
   heading (cx-head-hidden on the frame root, frame.html;
   docs/embed-page-fit-design.md section 4).

   The intro is not drawn. The title is hidden from sight only: it stays the
   frame's h1, so a screen reader entering the frame still hears what the
   calculator is. The same properties as .cx-sr-only, repeated rather than
   shared, because that class is Forge's and this block must survive a re-sync
   on its own. Print already leaves the whole heading out.
   ============================================================ */
.cx-scope.cx-head-hidden .cx-head .cx-intro { display: none; }
.cx-scope.cx-head-hidden .cx-head .cx-title {
    position: absolute; width: 1px; height: 1px;
    padding: 0; margin: -1px; overflow: hidden;
    clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* ============================================================
   HOST-ONLY -- not present in fincalc-forge's renderer.css.

   The results panel is focused programmatically after Calculate (renderer.js),
   which would otherwise draw the browser's default ring around the entire panel
   and read as a rendering fault to everyone who can see it.

   Suppressing it is safe here and only here: tabindex="-1" keeps the panel out
   of the tab order, so the ONLY way focus reaches it is the script putting it
   there -- nobody can arrive by keyboard and be left without an indicator. The
   panel is not a control, so WCAG 2.4.7's visible focus asks nothing of it;
   every real control keeps its ring.

   Keyed on the attribute, not on :focus-visible. This was
   :focus:not(:focus-visible) until 2026-10-30, and it never matched in practice:
   a browser treats focus moved by script on a freshly loaded page as
   focus-visible (nothing says the visitor is using a mouse, and Calculate by
   Enter says they're on the keyboard), so the ring stayed until the next click
   on every page embedding a calculator. If the panel ever joins the tab order,
   tabindex stops being -1, this stops matching and the ring returns.
   docs/embed-page-fit-design.md section 5.
   ============================================================ */
.cx-results[tabindex="-1"]:focus { outline: none; }

/* ============================================================
   HOST-ONLY -- not present in fincalc-forge's renderer.css.

   The visitor-facing message when a calculation cannot be completed.

   Forge's preview shows failures through its diagnostics panel, which carries URLs and stack traces
   and is gated on previewDiagnostics. A licensee's customer must never see that -- and before this
   existed they saw nothing at all: pressing Calculate produced a flash and an unchanged page, which
   reads as broken rather than as temporarily unavailable.

   Tokens rather than literals, so it inherits the tenant's palette like everything else. Warning
   rather than danger: the calculation is retryable, and alarming somebody about a transient problem
   on a bank's page is a poor trade.
   ============================================================ */
.cx-notice {
  border: 1px solid var(--cx-warn);
  border-radius: var(--cx-radius-sm, 4px);
  background: var(--cx-warn-surface);
  padding: 0.75rem 1rem;
  margin: 0 0 0.75rem;
  text-align: center;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
}

/* A mark, so the message is recognisable as a caution before it is read -- and so it does not depend
   on colour alone, which anybody with a colour vision deficiency would otherwise miss. Drawn with
   CSS rather than an image or an emoji: no request, and it inherits the text colour. */
.cx-notice::before {
  content: "!";
  flex: 0 0 auto;
  width: 1.15em;
  height: 1.15em;
  border-radius: 50%;
  background: var(--cx-warn);
  color: var(--cx-surface);
  font-weight: 700;
  font-size: 0.85em;
  line-height: 1.15em;
  text-align: center;
}

.cx-notice p {
  margin: 0;
  color: var(--cx-text);
  font-size: 0.95em;
  font-weight: 500;
  text-align: left;
}

/* ===================================================================
   HOST-ONLY: the lead capture surface.

   Not in Forge and not to be pasted back into it. Everything below is
   scoped under .cx-scope so it inherits the licensee's tokens and cannot
   reach the host page around the iframe.

   The whole block is styled from --cx-* tokens with literal fallbacks,
   so a tenant whose branding is still the default gets a coherent form
   rather than a browser-default one, and a tenant with branding gets
   theirs. No colour is hard-coded without a token in front of it.
   =================================================================== */

.cx-scope .cx-capture {
    margin-top: 1.1rem;
    padding: 0.95rem 1rem 1.05rem;
    border: 1px solid var(--cx-border, #d9dee6);
    border-radius: var(--cx-radius-sm, 0.35rem);
    /* --cx-surface-sunken, not --cx-surface-muted. There is no such token: TokenDerivation
       produces cx-surface-sunken and cx-surface-raised, the :root block at the top of this file
       defines those two, and nothing anywhere has ever defined a muted one. So this rule always
       fell through to the literal, and the block comment above -- "a tenant with branding gets
       theirs" -- was true in letter and false in effect: a dark-branded tenant got a light grey
       slab. RendererTokenTest now fails the build on a var() naming a token nobody defines. */
    background: var(--cx-surface-sunken, #f7f8fa);
}

/* A gate is the only thing on the panel, so it carries the weight the
   results would have. Not a different component -- the same one, louder. */
.cx-scope .cx-capture--gate {
    margin-top: 0;
    padding: 1.35rem 1.25rem 1.45rem;
    border-color: var(--cx-brand, #1a5f96);
}

/* Inline mode is the same form with its framing removed: one field in the
   results panel, no box, no fill. The difference between the two modes is
   prominence, which is exactly what a border and a background are. */
.cx-scope .cx-capture--inline {
    padding: 0.75rem 0 0;
    border: 0;
    border-top: 1px solid var(--cx-border, #d9dee6);
    border-radius: 0;
    background: none;
}

.cx-scope .cx-capture__prompt {
    margin: 0 0 0.6rem;
    font-size: 0.95rem;
    font-weight: 560;
    color: var(--cx-text, #1d2530);
}

.cx-scope .cx-capture__row {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}

.cx-scope .cx-capture__label {
    font-size: 0.82rem;
    color: var(--cx-text-muted, #667283);
}

.cx-scope .cx-capture__input {
    width: 100%;
    box-sizing: border-box;
    height: 2.4rem;
    padding: 0 0.6rem;
    font: inherit;
    font-size: 0.95rem;
    color: var(--cx-text, #1d2530);
    background: var(--cx-surface, #fff);
    border: 1px solid var(--cx-border, #d9dee6);
    border-radius: var(--cx-radius-sm, 0.3rem);
}

/* Focus is drawn, never removed. An outline:none here would make the form
   unusable by keyboard on a page a bank is legally obliged to keep
   accessible -- and the capture form is the one place on this page where
   somebody types something that matters. */
.cx-scope .cx-capture__input:focus-visible,
.cx-scope .cx-capture__submit:focus-visible,
.cx-scope .cx-capture__consent input:focus-visible {
    outline: 2px solid var(--cx-brand, #1a5f96);
    outline-offset: 2px;
}

/* The consent row.

   align-items:flex-start, not center: the sentence wraps to three or four
   lines on a phone and a centred checkbox would float beside the middle of
   it. The box belongs at the first line, where the reading starts. */
.cx-scope .cx-capture__consent {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
    margin-top: 0.7rem;
}

.cx-scope .cx-capture__consent input {
    /* Nudged onto the first line's baseline rather than its box top. */
    margin-top: 0.2rem;
    flex: 0 0 auto;
}

.cx-scope .cx-capture__consent label {
    font-size: 0.82rem;
    line-height: 1.5;
    color: var(--cx-text-muted, #667283);
}

.cx-scope .cx-capture__submit {
    margin-top: 0.85rem;
    height: 2.4rem;
    padding: 0 1.1rem;
    font: inherit;
    font-size: 0.92rem;
    font-weight: 560;
    /* --cx-brand-contrast, not --cx-on-brand. The second one does not exist either -- same
       class of defect as the background a few rules up, found by the same sweep. This one was
       worse than cosmetic: the fallback is white, so a tenant whose brand colour is light got
       white text on it, which is a contrast failure rather than an off shade. */
    color: var(--cx-brand-contrast, #fff);
    background: var(--cx-brand, #1a5f96);
    border: 1px solid transparent;
    border-radius: var(--cx-radius-sm, 0.3rem);
    cursor: pointer;
}

.cx-scope .cx-capture__submit:hover { filter: brightness(0.94); }

.cx-scope .cx-capture__submit:disabled {
    cursor: default;
    opacity: 0.55;
    filter: none;
}

/* The error uses the derived warn tokens (schema 5) rather than a literal
   red, so a caution on a licensee's page is in their palette. */
.cx-scope .cx-capture__error {
    margin: 0.7rem 0 0;
    padding: 0.5rem 0.65rem;
    font-size: 0.85rem;
    color: var(--cx-warn, #8a6512);
    background: var(--cx-warn-surface, #f4ecd8);
    border-radius: var(--cx-radius-sm, 0.3rem);
}

.cx-scope .cx-capture__note,
.cx-scope .cx-capture__done p {
    margin: 0.6rem 0 0;
    font-size: 0.85rem;
    color: var(--cx-text-muted, #667283);
}

.cx-scope .cx-capture__done p { margin: 0; }

/* A postcode is never long, and a full-width box for six characters reads as
   though something else was expected. Capped rather than sized in ch so it
   holds under a host page that changed the font. */
.cx-scope .cx-capture__input--short { max-width: 11rem; }

/* A printed sheet is a record of the figures. The ask is an interaction,
   and an interaction on paper is noise -- same reasoning that hides
   .cx-calculate when printing.

   Kept last in its section: every other block in this file ends with its
   print rules, and a plain rule sitting below a media query reads as though
   it were meant to be inside one. */
@media print { .cx-scope .cx-capture { display: none !important; } }


/* ===================================================================
   NEXT STEPS -- what a licensee offers once there is an answer.

   Deliberately quiet. These sit under a bank's own result on a bank's
   own page, and a loud stack of buttons reads as an advert rather than
   as the obvious thing to do next -- which is the objection this whole
   feature has to avoid earning. The brand token carries the weight; the
   surface stays the page's.

   Every colour goes through a --cx-* token with a literal fallback, so
   a tenant whose branding is still the default gets a coherent block
   and a tenant with branding gets theirs.
   =================================================================== */

.cx-scope .cx-next {
    display: flex;
    flex-direction: column;
    gap: 0.55rem;
    margin-top: 1.1rem;
}

/* An anchor, not a button element: it navigates. A <button> here would
   be a lie to assistive technology and would lose middle-click, open-in-
   new-tab and every other thing a person expects from a link. */
.cx-scope .cx-next__action {
    display: block;
    padding: 0.7rem 0.9rem;
    border: 1px solid var(--cx-brand, #1f4e79);
    border-radius: var(--cx-radius-sm, 4px);
    background: var(--cx-brand, #1f4e79);
    color: var(--cx-brand-contrast, #ffffff);
    text-decoration: none;
    line-height: 1.3;
}

/* Every offer after the first is an alternative rather than a second
   primary action. Two solid brand-coloured buttons side by side make a
   visitor choose between two things that look equally urgent; one solid
   and the rest outlined says which one the licensee put first, which is
   exactly what authored order means here. */
.cx-scope .cx-next__action ~ .cx-next__action,
.cx-scope .cx-next__ad-terms ~ .cx-next__action {
    background: none;
    color: var(--cx-brand, #1f4e79);
}

.cx-scope .cx-next__action:hover { filter: brightness(0.94); }

.cx-scope .cx-next__action:focus-visible {
    outline: 2px solid var(--cx-brand, #1f4e79);
    outline-offset: 2px;
}

.cx-scope .cx-next__label {
    display: block;
    font-weight: 560;
}

.cx-scope .cx-next__help {
    display: block;
    margin-top: 0.15rem;
    font-size: 0.82rem;
    opacity: 0.85;
}

/* Small, and immediately under the button it belongs to rather than at
   the foot of the page. Reg Z §1026.24 attaches to the thing that
   carried the trigger term, so the distance between the two is the
   whole point of the element. Not muted into invisibility either: a
   disclosure nobody can read is the version of this that fails an
   examination. */
.cx-scope .cx-next__ad-terms {
    margin: -0.15rem 0 0.25rem;
    font-size: 0.78rem;
    line-height: 1.45;
    color: var(--cx-text-muted, #667283);
}


/* ===================================================================
   TEST PLACEMENT BADGE -- host-only, not in fincalc-forge's stylesheet.

   Loud on purpose. Every other thing in this file is designed to sit
   quietly inside a bank's own page; this one is designed to be noticed
   from across a desk, because the failure it guards against is a test
   placement pasted onto a production site, where everything looks
   completely fine.

   --cx-warn and --cx-warn-surface are real derived tokens, so this
   picks up a licensee's own palette rather than shouting in a colour
   that has nothing to do with their site.
   =================================================================== */

.cx-scope .cx-test-badge {
    margin: 0 0 0.9rem;
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--cx-warn, #b3701a);
    border-radius: var(--cx-radius-sm, 4px);
    background: var(--cx-warn-surface, #fdf4e5);
    color: var(--cx-warn, #b3701a);
    font-size: 0.85rem;
    font-weight: 560;
}


/* ===================================================================
   LOGO AND CREDIT LINE -- host-only, not in fincalc-forge's stylesheet.
   docs/logo-and-credit-design.md section 6.

   The institution's logo heads the calculator; the credit line
   ("Calculators by FinCalc") sits at its foot. Both are drawn by
   HOST-ONLY blocks in frame.html, after the test badge and after the
   calculator respectively, so neither touches calculator.html.

   The logo is shown at most 48px tall (40px in a narrow frame) and never
   wider than the frame. The stored PNG is up to four times that, so it is
   sharp on high-density screens; the intrinsic width and height on the
   img keep the frame from jumping while it loads. Literal left, center and
   right, because that is what the institution picked looking at its page.

   The credit line is deliberately quiet: small, muted, end-aligned. It
   says who made the calculator; it must not compete with the bank.
   =================================================================== */

.cx-scope .cx-logo {
    margin: 0 0 0.9em;
    line-height: 0;
    text-align: left;
}

.cx-scope .cx-logo--center { text-align: center; }
.cx-scope .cx-logo--right { text-align: right; }

.cx-scope .cx-logo img {
    display: inline-block;
    width: auto;
    height: auto;
    max-width: 100%;
    max-height: 48px;
}

@container cxroot (max-width: 30em) {
    .cx-scope .cx-logo img { max-height: 40px; }
}

/* Inline, not flex: the words either side of the brand are text whose spaces matter, and flex
   items trim them. */
.cx-scope .cx-credit {
    margin: 1.1em 0 0;
    font-size: 0.75em;
    line-height: 1.4;
    text-align: end;
    color: var(--cx-text-muted, #667283);
}

.cx-scope .cx-credit__mark {
    display: inline-block;
    width: 1.35em;
    height: 1.35em;
    margin-inline-end: 0.35em;
    vertical-align: -0.35em;
}

.cx-scope .cx-credit a {
    color: inherit;
    text-decoration: underline;
    text-underline-offset: 0.15em;
}

.cx-scope .cx-visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

@media print {
    .cx-scope .cx-credit a { text-decoration: none; }
}
