/*
 * Page-scoped styles for the calculator. Loaded only by
 * /tools/mortgage-calculator.html, so nothing here can cost the other pages a
 * byte. Declared into @layer components alongside components.css -- the layer
 * order is fixed by the document's @layer statement, so this sheet can arrive in
 * any order without changing what wins.
 */
@layer components {

  /* ---------- the formula ---------- */

  /*
   * The equation is set as MathML, not as an image and not as positioned spans.
   * An image cannot be selected, searched, resized, or read; a span lattice
   * cannot be read either, and breaks the moment the reader changes font size.
   */
  .formula {
    margin-block: var(--s-5);
    margin-inline: 0;
    padding: var(--s-5) var(--s-4);
    max-width: var(--measure);
    border-block: 1px solid var(--rule);
    text-align: center;
    /* A wide equation scrolls inside its own box rather than widening the page. */
    overflow-x: auto;
  }
  .formula math { font-size: var(--t-2); }

  /*
   * The table that turns the equation from decoration into something the reader
   * can check: symbol, meaning, and the value currently in the calculator.
   */
  .params {
    max-width: 52rem;
    margin-block: var(--s-5);
    /* Four columns now, and they fit at 320px: the prose column wraps and
       hyphenates rather than widening. The guard is here anyway, so that a
       longer value can never take the page with it -- the same reason .formula
       carries one. */
    overflow-x: auto;
  }
  .params .table th:first-child {
    font-family: var(--font-mono);
    font-weight: 600;
    color: var(--text);
    inline-size: 4rem;
  }
  /* A prose column reads from its start edge, and so must the heading above it.
     The .table default ends every cell but the first, which is right for money
     and wrong for sentences. */
  .params .table :is(th, td):nth-child(2),
  .params .table :is(th, td):nth-child(4) { text-align: start; }
  .params .table td:nth-child(2),
  .params .table td:nth-child(4) {
    color: var(--text-dim);
    /*
     * A prose column beside two numeric ones. On a phone the table's minimum
     * width is set by its longest single word, and letting that word break is
     * what keeps four columns on the screen without turning the table into one
     * more thing to scroll. The document is lang="en", so the break lands where
     * English hyphenates. Switched off again above 48rem, where there is room
     * and a hyphen would be noise -- see the breakpoint at the foot of the sheet.
     */
    hyphens: auto;
  }
  /* Still no nowrap, and no width below 48rem: "$1,918.56 a month" held on one
     line is exactly what pushes a four-column table past a narrow viewport, and
     the value reads perfectly well over two lines. Where there is room, the
     width comes back -- see the breakpoint at the foot of this sheet. */
  .params__value { font-weight: 600; }

  /* ---------- the calculator ---------- */

  .calc { margin-block: var(--s-5); }

  .calc__layout { display: grid; gap: var(--s-6); align-items: start; }

  .calc__section { margin-block-start: var(--s-6); }

  .calc__privacy {
    margin-block-start: var(--s-4);
    padding-block-start: var(--s-4);
    border-block-start: 1px solid var(--rule);
    font-size: var(--t--1);
    color: var(--text-dim);
  }
  .calc__privacy a {
    color: var(--accent);
    text-decoration: underline;
    text-underline-offset: 0.15em;
    text-decoration-thickness: from-font;
  }
  .calc__privacy a:hover { color: var(--text); }

  /* The read-out that sits beside the down payment, because loan-to-value is what
     actually governs whether mortgage insurance applies. */
  .field__readout {
    margin-block-start: var(--s-1);
    font-size: var(--t--1);
    color: var(--text-dim);
    font-variant-numeric: tabular-nums;
  }

  /*
   * The note that appears when the loan amount has been typed directly and the
   * home price has become the derived figure. Deliberately quiet: this is a
   * consequence of what the reader just did, not a problem with it.
   */
  .field__pinned {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s-2);
    margin-block-start: var(--s-2);
    font-size: var(--t--1);
    color: var(--text-dim);
  }
  .field__unpin {
    min-height: 44px;
    padding: var(--s-1) var(--s-3);
    border: 1px solid var(--rule);
    border-radius: var(--radius-pill);
    background: var(--surface);
    color: var(--text);
    font: inherit;
    font-size: var(--t--1);
    cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
  }
  .field__unpin:hover { border-color: var(--accent); color: var(--accent); }

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

  .result {
    padding: var(--s-5);
    border: 1px solid var(--rule);
    border-radius: var(--radius-md);
    background: var(--surface);
    box-shadow: var(--shadow-sm);
  }

  .result__label {
    display: block;
    font-family: var(--font-mono);
    font-size: var(--t--1);
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--text-dim);
  }

  /*
   * The one number the page exists to produce, so it is the one piece of type set
   * at display size. <output> rather than <span>: it is a calculated result and
   * announces itself as one.
   */
  .result__amount {
    display: block;
    margin-block: var(--s-2);
    font-family: var(--font-display);
    font-size: var(--t-3);
    font-weight: 600;
    line-height: var(--leading-tight);
    letter-spacing: var(--track-tight);
    font-variant-numeric: tabular-nums;
    color: var(--text);
  }
  .result__unit { font-size: var(--t-0); font-weight: 400; color: var(--text-dim); }

  /*
   * Dimmed, never blanked, and never showing $NaN. When an input goes invalid the
   * last good figures stay on screen with this class on them and a sentence
   * saying they are waiting -- a stale number that still looks current is the one
   * failure mode worse than no number.
   */
  .result--stale { opacity: 0.45; }
  .result__stale-note {
    display: flex;
    gap: var(--s-2);
    margin-block-start: var(--s-3);
    padding-inline-start: var(--s-3);
    border-inline-start: 3px solid var(--danger);
    font-size: var(--t--1);
    color: var(--text);
  }
  .result__stale-note::before { content: '!'; font-weight: 700; color: var(--danger); }

  /*
   * Two columns at every width, not auto-fit. auto-fit gives three tiles and an
   * orphan on a panel this wide, and the ragged fourth cell reads as a mistake;
   * a fixed 2x2 is stable from 320px up.
   */
  .result__grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--s-4) var(--s-5);
    margin-block-start: var(--s-5);
    padding-block-start: var(--s-5);
    border-block-start: 1px solid var(--rule);
  }
  .result__tile { display: flex; flex-direction: column; gap: var(--s-1); }
  .result__tile-label { font-size: var(--t--1); color: var(--text-dim); }
  .result__tile-value { font-size: var(--t-1); font-weight: 600; font-variant-numeric: tabular-nums; }

  /*
   * The housing total is deliberately NOT a peer of the headline. Lighter type, a
   * dashed rule, and a label that always says "estimated monthly housing payment"
   * rather than "monthly payment" -- because it is a different quantity, and the
   * whole point of the section above it is that the formula never produced it.
   */
  .result__housing {
    margin-block-start: var(--s-5);
    padding-block-start: var(--s-5);
    border-block-start: 1px dashed var(--edge);
  }
  .result__housing-amount {
    display: block;
    margin-block-start: var(--s-1);
    font-size: var(--t-2);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--text-dim);
  }
  .result__housing-note { margin-block-start: var(--s-2); font-size: var(--t--1); color: var(--text-dim); }

  /* ---------- the explanation ---------- */

  /*
   * Assembled sentence by sentence in JS, so it is styled as running prose and
   * nothing else. If a paragraph's sentences all filter out, the <p> is hidden
   * rather than left standing empty.
   */
  .explain { max-width: var(--measure); }
  .explain p { margin-block-end: var(--s-4); }

  /* ---------- amortization ---------- */

  .amort { margin-block: var(--s-5); }
  .amort .table { inline-size: 100%; }
  /* The year column is a single small integer; it should not claim a share of a
     grid that four currency columns are already competing for. */
  .amort .table :is(th, td):first-child { inline-size: 3rem; }
  .amort__wrap {
    /* Wider than the reading measure, because four money columns need the room,
       but not the full 72rem: a four-column table stretched across a desktop puts
       so much space between a label and its figure that the eye loses the row. */
    max-width: 52rem;
    border: 1px solid var(--rule);
    border-radius: var(--radius-md);
    background: var(--surface);
    padding: var(--s-2) var(--s-4);
  }
  /* 5 columns is the ceiling: at 320px the monthly table is then only mildly
     wider than the viewport, and it scrolls inside its own focusable region. */
  .amort--monthly .table { min-inline-size: 34rem; }

  /* ---------- sensitivity ---------- */

  /*
   * Same frame as the amortization table, because it is the same kind of object:
   * rows of figures produced by the same engine. Sharing .amort__wrap outright
   * was the alternative, and it would have made one of the two names a lie.
   */
  .sens { margin-block: var(--s-5); }
  .sens .table { inline-size: 100%; }
  .sens__wrap {
    max-width: 52rem;
    border: 1px solid var(--rule);
    border-radius: var(--radius-md);
    background: var(--surface);
    padding: var(--s-2) var(--s-4);
    /*
     * Four columns, three of them currency, and two of the four carry cents.
     * Below about 26rem they stop fitting, and the table scrolls inside this box
     * rather than taking the page with it. The markup carries the tabindex that
     * makes the box reachable from a keyboard -- see the note on .table-scroll,
     * which solves the same problem for the monthly schedule.
     */
    overflow-x: auto;
  }

  /*
   * The reader's own row, which every other row's delta is measured against.
   * Marked three ways -- a word in the cell, a rule down the edge, and weight --
   * because a tint alone would not survive forced-colors, a monochrome print, or
   * a reader who cannot see it.
   */
  .sens__row--current :is(th, td) {
    font-weight: 600;
    color: var(--text);
    background: color-mix(in srgb, var(--accent) 7%, transparent);
  }
  .sens__row--current th:first-child { box-shadow: inset 3px 0 0 0 var(--accent); }
  .sens__tag {
    display: inline-block;
    margin-inline-start: var(--s-2);
    /* The tracking below adds a space after the last letter that the pill would
       otherwise carry as an off-centre gap. Trimmed from the end padding. */
    padding-inline: var(--s-2) calc(var(--s-2) - 0.08em);
    border: 1px solid var(--accent);
    border-radius: var(--radius-pill);
    font-family: var(--font-mono);
    font-size: var(--t--1);
    font-weight: 400;
    letter-spacing: var(--track-wide);
    color: var(--accent);
  }

  /* ---------- factors ---------- */

  /*
   * Three prose columns, so this one overrides the .table default of ending
   * every cell: figures align on the right, sentences do not.
   *
   * Same scrollport as the sensitivity tables and no height cap: fourteen rows
   * of prose is a table to be read down, not a box to be scrolled inside. The
   * markup carries the tabindex that makes it reachable from a keyboard.
   */
  .factors { margin-block: var(--s-5); overflow-x: auto; }
  .factors .table { inline-size: 100%; }
  .factors .table :is(th, td) { text-align: start; }
  .factors .table td:nth-child(2) { color: var(--text-dim); }
  .factors .table th[scope='row'] { white-space: normal; color: var(--text); font-weight: 600; }
  .factors__wrap { max-width: 52rem; }

  /*
   * Where a factor stands relative to the model. The word is the content and the
   * colour is decoration -- read the tag with every colour stripped out and it
   * still says which of the four buckets the row is in.
   */
  .tag {
    display: inline-block;
    /* Same trailing-space trim as .sens__tag, for the same tracking. */
    padding-inline: var(--s-2) calc(var(--s-2) - 0.08em);
    border: 1px solid var(--edge);
    border-radius: var(--radius-pill);
    font-family: var(--font-mono);
    font-size: var(--t--1);
    letter-spacing: var(--track-wide);
    white-space: nowrap;
    color: var(--text-dim);
  }
  .tag--in { border-color: var(--accent); color: var(--accent); }
  .tag--side { border-color: var(--edge); color: var(--text); }
  .tag--out,
  .tag--never { border-style: dashed; }

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

  .charts { display: grid; gap: var(--s-6); margin-block: var(--s-6); }

  .chart { margin: 0; }
  .chart__caption {
    font-size: var(--t--1);
    font-weight: 600;
    margin-block-end: var(--s-3);
  }
  .chart__frame {
    position: relative;
    border: 1px solid var(--rule);
    border-radius: var(--radius-md);
    background: var(--surface);
    padding: var(--s-4) var(--s-4) var(--s-3);
  }
  /*
   * preserveAspectRatio="none" lets the plot fill whatever width it is given at
   * a fixed height, with no resize listener and no JS measuring anything. It
   * distorts, which is exactly why every label lives in HTML outside the <svg>
   * and every stroke carries vector-effect="non-scaling-stroke".
   */
  .chart__svg { display: block; inline-size: 100%; block-size: 180px; overflow: visible; }
  .chart__grid line { stroke: var(--rule); stroke-width: 1; vector-effect: non-scaling-stroke; }
  .chart__area { fill: var(--accent); fill-opacity: 0.18; }
  .chart__line { fill: none; stroke: var(--accent); stroke-width: 2; vector-effect: non-scaling-stroke; }
  /*
   * The interest series is a hatch, not just a second colour. Two areas
   * distinguished by hue alone would be unreadable to a colour-blind reader and
   * would vanish entirely in forced-colors mode.
   */
  .chart__band--interest { fill: var(--text-dim); fill-opacity: 0.22; }
  .chart__band--principal { fill: var(--accent); fill-opacity: 0.28; }
  .chart__hatch { stroke: var(--text-dim); stroke-width: 1; }

  .chart__scale {
    display: flex;
    justify-content: space-between;
    margin-block-start: var(--s-2);
    font-size: var(--t--1);
    color: var(--text-dim);
    font-variant-numeric: tabular-nums;
  }
  .chart__ymax {
    position: absolute;
    inset-block-start: var(--s-2);
    inset-inline-start: var(--s-4);
    font-size: var(--t--1);
    color: var(--text-dim);
    background: color-mix(in srgb, var(--surface) 85%, transparent);
    padding-inline: var(--s-1);
    font-variant-numeric: tabular-nums;
  }

  .chart__legend {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-2) var(--s-4);
    margin-block-start: var(--s-3);
    font-size: var(--t--1);
    color: var(--text-dim);
  }
  .chart__key { display: flex; align-items: center; gap: var(--s-2); }
  .chart__swatch { inline-size: 1.4em; block-size: 0.8em; border: 1px solid var(--edge); }
  /* The legend swatches repeat the plot's own treatment, hatch included, so the
     key is readable as a key rather than as two similar grey rectangles. */
  .chart__swatch--interest {
    background:
      repeating-linear-gradient(45deg, var(--text-dim) 0 1px, transparent 1px 5px),
      color-mix(in srgb, var(--text-dim) 22%, transparent);
  }
  .chart__swatch--principal { background: color-mix(in srgb, var(--accent) 28%, transparent); }

  /* ---------- the live region ---------- */

  /*
   * One region for the whole page, and it is visually hidden: the numbers are
   * already on screen for anyone who can see them, and two live regions on one
   * page reliably talk over each other.
   */
  .status {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }

  /* ---------- the one new breakpoint ---------- */

  /*
   * The site had exactly one (40rem, for the motto's line breaks). This is the
   * second, and it buys the single highest-value behaviour on the page: the
   * payment stays on screen while the inputs that produce it are being changed.
   */
  @media (min-width: 48rem) {
    /* The value column is what a reader checks against the calculator, so where
       there is room it gets a stable share rather than whatever the prose column
       leaves it. Narrower than this it would force the table to scroll. */
    .params .table :is(th, td):nth-child(3) { inline-size: 10.5rem; }
    /* And with room, no hyphens: the break above is for phones only. */
    .params .table td:nth-child(2) { hyphens: manual; }

    .calc__layout { grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); gap: var(--s-6); }
    .calc__results { position: sticky; inset-block-start: calc(var(--header-h) + var(--s-4)); }
    .charts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .chart__svg { block-size: 200px; }
  }

  @media print {
    /* Sticky positioning has no meaning on paper and can strand the panel. */
    .calc__results { position: static; }
    .result, .chart__frame, .amort__wrap, .sens__wrap { box-shadow: none; }
  }
}
