/* ==========================================================================
   FreelanceFinanceHub — theme.css
   ONE stylesheet for every page. Edit a token here, every page follows.

   Recipe: stripe-press (editorial, warm). No photographic layer — this is a
   reference tool, not a book cover. Consequences that are deliberate:
     · radius 0 everywhere (books don't round corners)
     · no box-shadow — separation is done with 1px hairlines
     · warm cream ground, never pure white
     · serif for all content, small-caps sans for labels only

   FONTS ARE SELF-HOSTED. Fetching them from fonts.googleapis.com meant that
   on a slow or blocked connection the page fell back to Times New Roman —
   measured, not hypothetical. These three files are the variable fonts, so
   ONE file per family+style covers every weight we use.
   ========================================================================== */

/* Source Serif 4 — variable, latin. Covers 400/600/700 from one file.
   font-display:optional (NOT swap) — this is the CLS fix.
   With `swap`, the browser paints a fallback metric (Times New Roman) and
   then re-lays-out the whole page when the real font arrives. Measured CLS
   was 1.107 against a budget of 0.1 — eleven times over.
   With `optional`, the browser uses the font only if it is ready almost
   immediately (from cache, typically), and otherwise keeps the fallback for
   the whole page view. No swap, no shift. The trade-off is that a first-time
   visitor on a slow connection may see the fallback — which is a far better
   outcome than a page that jumps while they are reading it. */
@font-face{
  font-family:"Source Serif 4";font-style:normal;font-weight:400 700;
  font-display:optional;
  src:url("fonts/source-serif-4-n400-latin.woff2") format("woff2");
  unicode-range:U+0020-007E,U+00A7,U+00B7,U+00F7,U+2014,U+201C-201D,U+2192;
}
/* Source Serif 4 — variable, italic. The site leans on this for emphasis. */
@font-face{
  font-family:"Source Serif 4";font-style:italic;font-weight:400 700;
  font-display:optional;
  src:url("fonts/source-serif-4-i400-latin.woff2") format("woff2");
  unicode-range:U+0020-007E,U+00A7,U+00B7,U+00F7,U+2014,U+201C-201D,U+2192;
}
/* Public Sans — variable, latin. Labels and small caps only. */
@font-face{
  font-family:"Public Sans";font-style:normal;font-weight:400 600;
  font-display:optional;
  src:url("fonts/public-sans-n400-latin.woff2") format("woff2");
  unicode-range:U+0020-007E,U+00A7,U+00B7,U+00F7,U+2014,U+201C-201D,U+2192;
}

:root{
  /* --- palette: exactly six values. Do not add a seventh. ----------------
     Two moved on 4 October 2026, both measured against their neighbours
     rather than chosen by eye, and both kept inside the stripe-press recipe.

     --ground was #F1ECDE. Raising it to #F6F3EA widens two separations at once:
     the input fields against the page (1.16:1 -> 1.23:1) and the body text
     (14.77:1 -> 15.71:1). It stays a warm bone: the recipe says avoid pure
     white grounds, and #F6F3EA is nothing like #FFFFFF.

     --hairline was #C8BEA4 and sat at 1.57:1 against the old ground. It is used
     47 times — every rule, every table edge, every separator — so it was the
     structure of the whole site fading out. At #B5A883 it reads 2.13:1.

     The other five are untouched, each for a reason: --ink and --foil are
     already past 8:1, --secondary is at 6:1 whereas the recipe's own value
     (#736D5A) is LIGHTER, and --alert is validation only.                   */
  --ground:#F8F6F0;      /* warm bone — page background */
  --surface:#E6DCC4;     /* darker cream — inputs, callout panels */
  --ink:#1A1A18;         /* very warm near-black — body text */
  --secondary:#5E5848;   /* muted warm grey — labels, hints */
  --foil:#1B4B5A;        /* deep teal — accent, totals, links */
  --hairline:#B5A883;    /* 1px rules */
  --alert:#A04A2A;       /* burnt sienna — validation only, never decorative */

  /* --- type ------------------------------------------------------------- */
  --serif:"Source Serif 4",Georgia,"Times New Roman",serif;
  --sans:"Public Sans",-apple-system,"Segoe UI",sans-serif;

  /* --- spacing ladder --------------------------------------------------- */
  --s1:4px; --s2:8px; --s3:16px; --s4:24px; --s5:32px; --s6:48px; --s7:96px;

  /* --- layout -----------------------------------------------------------
     Three stops, so the site adapts to any window without a media query.

       1080px   the floor. Below this the container would be narrower than it
                was before this change, so nothing regresses on a small laptop.
       92vw     the fluid middle. It grows with the window, which is what
                removes the 420px of dead margin an ordinary 1920px screen had
                when this was a fixed 1080.
       1760px   the ceiling. Past it the container stops growing, because the
                calculator's two columns would otherwise reach 1,140px each on
                a 2560px monitor — a field wider than the label and value it
                holds, which reads as a broken form rather than a wide one.

     The text inside does not rely on this to stay readable: thirteen rules
     already carry a ch cap, and the four form elements that lacked one
     (.hint, .field label, .row-note, .row-label) gained 62ch in the same
     change. The container widens; the reading measure does not. */

  /* Two decisions, and they are separate on purpose.

   --gutter is the side margin: a fixed 72px, which is the same at 1440 and at
   3840 and needs no reference to the window. It was `92vw` inside a clamp, so
   the margin was 53px at one window and 1032px on a 4K, and measuring it in one
   window said nothing about any other. `min(72px, 5vw)` gives ground only where
   72 would eat the content.

   --measure is the reading width: the widest the content is allowed to get, so
   that a 4K screen does not produce a two-column form with 1772px fields. It
   governs the content, not the window. */
--gutter:min(72px, 5vw);
--measure:1600px;
}

*{box-sizing:border-box;margin:0;padding:0}

body{
  background:var(--ground);
  color:var(--ink);
  font-family:var(--serif);
  font-size:1.125rem;
  line-height:1.65;
  text-wrap:pretty;
  padding:0;
}
/* One mechanism for the side margins, not two. The body carried `padding: 0 24px`
   and the container carried `margin: 0 auto` on top of it, so the two added up:
   at a 1521px viewport the content sat 54px from each edge when the container's
   own measure asked for 30. The body's padding also changed at 860px, which the
   container's clamp could not see. The container now owns the margin and the
   body does not, so there is a single number to reason about.

   The clamp keeps a 24px floor at every width: below 1080px the 92vw term falls
   under it, and without the floor the text would touch the glass on a small
   phone before the phone's own layout has taken over. */
/* The side margin is the gutter and nothing else. The content inside it is
   capped by --measure, which is the reading width and not the window. */
.wrap{max-width:none;margin:0;padding:0 var(--gutter)}
.wrap > *{max-width:var(--measure);margin-inline:auto}

/* ==========================================================================
   TYPOGRAPHY PRIMITIVES
   ========================================================================== */

/* small uppercase label — the ONLY place sans-serif appears in content */
.kicker{
  font-family:var(--sans);font-size:.688rem;font-weight:600;
  letter-spacing:.08em;text-transform:uppercase;color:var(--foil);
}
.kicker--muted{color:var(--secondary)}
/* The kicker under a heading carried `margin-top: var(--s3)` as an inline style
   on two pages. Moving it here is what makes removing those inline styles safe. */
.kicker--muted{margin-top:var(--s3)}

h1{font-size:clamp(2rem,5vw,3.6rem);line-height:1.1;font-weight:600;
   letter-spacing:-.02em;max-width:20ch}
h1 em,h2 em{font-style:italic;font-weight:400;color:var(--foil)}

h2{font-size:1.5rem;font-weight:600;letter-spacing:-.01em}

.lede{font-size:1.3rem;line-height:1.6;color:var(--secondary);max-width:66ch}
.lede strong{color:var(--ink);font-weight:600}

/* --------------------------------------------------------------------------
   THE PAGE HEAD HAS THREE LEVELS, NOT TWO

   Every page carries .lede--year: "Prepared for the 2026 tax year." The class
   existed and had NO RULE, so it inherited .lede exactly - the same 1.3rem and
   the same colour as the promise above it. Measured on the built pages: the
   sentence that says which exercise the page answers looked identical to the
   one that says what the page does, and neither read as subordinate.

   Adding .lede--fiscal in the same head made it worse: 20.8 / 20.8 / 14.4px.
   Three paragraphs, two sizes, and the one the test requires was the one with
   no treatment at all.

   The scale now descends: promise at 1.3rem, exercise at .95rem, mechanics at
   .9rem. Each level is a smaller step than the last, so the eye reads one
   heading with two supporting lines rather than three competing ones.
   -------------------------------------------------------------------------- */
.lede--year{font-size:.95rem;line-height:1.5;margin-top:var(--s2)}

/* --------------------------------------------------------------------------
   PALETTE BUDGET — why this file uses 7 colours, not the 4 the protocol asks

     --ground / --surface   two steps of one warm neutral, needed so form
                            fields read as distinct from the page
     --ink / --secondary    two steps of one text scale. --secondary was
                            #736D5A, which measured 4.38:1 on the ground and
                            3.79:1 on the surface — below the 4.5:1 WCAG AA
                            floor. Darkened to #5E5848: 6.00:1 and 5.19:1.
     --foil                 the single accent
     --hairline             the 1px rule colour, not a surface
     --alert                validation only. Never decorative.

   That is one hue family plus one accent plus one semantic state. The count
   is 7; the DECISIONS are 3. Adding an eighth colour for anything other than
   a new semantic state is out of budget.
   -------------------------------------------------------------------------- */

a{color:inherit}
a:hover{color:var(--foil)}

/* ==========================================================================
   MASTHEAD — identical on every page
   ========================================================================== */
/* Brand and the menu roots are one group on the left; the tax year is pushed
   to the far right and stays there as more menus are added. */
.masthead{
  border-bottom:1px solid var(--hairline);
  padding:var(--s4) 0;
  display:flex;align-items:center;
  gap:var(--s2);flex-wrap:wrap}
.masthead .masthead-meta{margin-left:auto}
.brand{font-size:1.05rem;font-weight:700;letter-spacing:-.01em;
  text-decoration:none;color:var(--ink)}
.brand em{font-style:italic;font-weight:400;color:var(--secondary)}
.masthead-meta{font-family:var(--sans);font-size:.688rem;letter-spacing:.06em;
  text-transform:uppercase;color:var(--secondary)}

/* ==========================================================================
   PAGE HEAD — used by the front page and every calculator
   ========================================================================== */
.page-head{padding:var(--s6) 0 var(--s4);border-bottom:2px solid var(--ink)}
.page-head .lede{margin-top:var(--s3)}

/* front-page hero — taller, with the verification stamp beneath */
/* Less air above the hero than below it. The US Web Design System's
   typography guidance is explicit that a heading belongs closer to the text
   it introduces than to what precedes it, and 96px above against 48px below
   is the other way round — the headline floated. */
.hero{padding:var(--s6) 0 var(--s5);border-bottom:1px solid var(--hairline)}
.hero h1{margin:var(--s3) 0 var(--s4);max-width:none}
.hero .lede{font-size:1.25rem}
.stamp{margin-top:var(--s5);padding-top:var(--s4);border-top:1px solid var(--hairline);
  font-family:var(--sans);font-size:.719rem;letter-spacing:.04em;
  color:var(--secondary);line-height:1.8}
.stamp b{color:var(--ink);font-weight:600}

/* ==========================================================================
   TOOL INDEX — the front-page list. Rows, not cards.
   ========================================================================== */
/* The catalogue is a list of 22 entries and used to be one column, so each
   row filled 358px of a 1760px container — four fifths of every line empty,
   and the whole catalogue 895px tall.

   Two columns above 1100px. Not three: the entry holds a title and a two or
   three line description, and at a third of the width that description wraps
   to four lines, so the grid stops saving height exactly where it was meant
   to. At half the width the text keeps the measure it had.

   The count per family decides the split, which is why this is on the list
   and not on each family: six entries give three clean rows, four give two,
   and the two families of three leave one cell open at the end — the price
   of the pairing, paid twice out of five families.

   Below 1100px the list goes back to one column. The .tool grid inside it
   already collapses at 860px, so a phone gets the same stack it had. */
.tools{list-style:none;border-bottom:1px solid var(--hairline)}

@media (min-width:1101px){
  ul.tools{display:grid;grid-template-columns:1fr 1fr;column-gap:var(--s7);
    align-items:stretch}
  /* The hairline is drawn by each entry, not by the list, so a two column
     grid cannot depend on the list's own rule to separate the rows. */
  ul.tools > li{display:flex}
  ul.tools > li > .tool{flex:1}
}.tools{list-style:none;border-bottom:1px solid var(--hairline)}

/* --------------------------------------------------------------------------
   THE FAMILY HEADING ON THE FRONT PAGE

   The eighteen calculators answer five groups of questions, and the front page
   is the one place they can be shown grouped: it has the full column width to
   spend, where the header dropdown has 360px and could not fit a heading every
   three rows. The grouping was tried there first and reverted — the list came
   to 1,027px and no viewport showed it.

   The heading reuses the global h3 treatment (10.5px, uppercase, letter-spaced,
   --foil) rather than introducing a second style for the same job. What it adds
   is the space above, which is what makes a heading read as opening a block
   rather than closing the one before it, and the rule that keeps the first
   heading from sitting on the section title.

   The number is unaffected and still visible on every row, so a reader who
   knows "the 03 page" can still find it. */
h3.group-head{font-family:var(--sans);font-size:.656rem;font-weight:600;
  letter-spacing:.1em;text-transform:uppercase;color:var(--foil);
  margin:var(--s6) 0 var(--s3);padding-top:var(--s3);
  border-top:1px solid var(--hairline)}
h3.group-head:first-of-type{margin-top:var(--s4)}
.tool{
  display:grid;grid-template-columns:56px 1fr auto;gap:var(--s4);
  /* The middle cell is a wrapper span whose font-size differs from the text
     it holds, so grid baseline-aligns the WRAPPER, not the title inside it —
     which pushes the number and arrow 9px off. Align each real text node. */
  align-items:start;
  padding:var(--s4) 0;
  border-top:1px solid var(--hairline);
  text-decoration:none;color:inherit;
  transition:background .45s ease,padding-left .45s ease;
}
.tool:hover{background:rgba(27,75,90,.045);padding-left:var(--s3)}
.tool:hover .tool-name{color:var(--foil)}
/* lift the 11px labels so their baseline meets the 1.3rem title's */
.tool-num,.tool-arrow{padding-top:.42em}
.tool-num{font-family:var(--sans);font-size:.688rem;font-weight:600;
  letter-spacing:.08em;color:var(--secondary)}
.tool-name{font-size:1.3rem;font-weight:600;letter-spacing:-.01em;
  transition:color .45s ease}
.tool-desc{display:block;font-size:1rem;line-height:1.55;color:var(--secondary);
  font-weight:400;margin-top:var(--s1);max-width:54ch}
.tool-arrow{font-family:var(--sans);font-size:.688rem;font-weight:600;
  letter-spacing:.08em;text-transform:uppercase;color:var(--secondary);
  white-space:nowrap;transition:color .45s ease}
.tool:hover .tool-arrow{color:var(--foil)}
.tool--soon{cursor:default;opacity:.72}
.tool--soon:hover{background:none;padding-left:0}
.tool--soon:hover .tool-name{color:var(--ink)}
.tool--soon:hover .tool-arrow{color:var(--secondary)}

/* ==========================================================================
   CALCULATOR LAYOUT — inputs left, results right
   ========================================================================== */
/* align-items used to be `start`, which stopped the input column at the height
   of its own fields. The calculate button is sticky inside that column, so once
   the visitor scrolled past those 610px the button left the screen with it -
   measured on the live page: visible at scroll 900, gone at 1400, on both a
   390px phone and a 1440px desktop. With `stretch` the column runs to the foot
   of the page and the button stays reachable the whole way down, which is what
   sticky was for. */
.layout{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.05fr);
  gap:var(--s6);padding:var(--s5) 0;align-items:start}

.field{margin-bottom:var(--s5)}
.field label{display:block;font-size:1rem;font-weight:600;margin-bottom:var(--s2)}
.field .hint{font-size:.9rem;color:var(--secondary);margin-top:var(--s2);line-height:1.5;max-width:74ch}

/* --- typical-value chips -------------------------------------------------
   People estimate retirement and tax figures in round thousands: 80,000, not
   83,417. Making them open a keyboard to type six digits is the single biggest
   friction on the page. A chip fills the field in one tap.
   NO SLIDERS, deliberately: the audience spans roughly $5,000 to $500,000, and
   on that range a linear slider moves several hundred dollars per pixel, so it
   cannot be set accurately. Worse, a slider displays an approximate figure —
   and this site's whole promise is that every number is exact and traceable.
   A chip is precise, works the same on phone and desktop, and needs no drag. --- */
.quick{display:flex;flex-wrap:wrap;gap:6px;margin-top:var(--s2)}
.quick button{font-family:var(--sans);font-size:.688rem;font-weight:600;
  letter-spacing:.04em;padding:0 10px;height:30px;cursor:pointer;
  background:var(--ground);border:1px solid var(--hairline);color:var(--secondary);
  transition:border-color .25s ease,color .25s ease,background .25s ease}
.quick button:hover{border-color:var(--ink);color:var(--ink)}
.quick button.is-on{background:var(--ink);border-color:var(--ink);color:var(--ground)}
.quick button:focus-visible{outline:2px solid var(--foil);outline-offset:2px}

/* --- optional expense fields, collapsed ----------------------------------
   The home-office page asked for seven figures — twenty-six digits — before it
   would show anything. But four of those seven only matter for the regular
   method; the simplified method needs none of them and is what most visitors
   should probably use. Hiding them behind a summary means the page opens with
   three fields instead of seven, and the reader chooses to do the extra work
   rather than being handed it.

   Labelled "optional" in words, not just by being collapsed: a caret alone does
   not tell anyone they can skip it. --- */
.expenses{margin-top:var(--s5);border-top:1px solid var(--hairline);padding-top:var(--s3)}
.expenses summary{font-size:1rem;font-weight:600;cursor:pointer;list-style:none;
  display:flex;align-items:center;gap:8px;padding:4px 0}
.expenses summary::-webkit-details-marker{display:none}
.expenses summary::before{content:"+";font-family:var(--sans);font-size:.875rem;
  font-weight:600;color:var(--secondary);width:12px;display:inline-block}
.expenses[open] summary::before{content:"\2212"}
.expenses summary:hover{color:var(--foil)}
.expenses summary:focus-visible{outline:2px solid var(--foil);outline-offset:3px}
.expenses .opt{font-family:var(--sans);font-size:.625rem;font-weight:600;
  letter-spacing:.08em;text-transform:uppercase;color:var(--secondary);
  border:1px solid var(--hairline);padding:2px 6px}
.expenses-note{font-size:.88rem;color:var(--secondary);line-height:1.55;
  margin:var(--s2) 0 var(--s4)}
.expenses .field:last-child{margin-bottom:0}

/* --- the state table ------------------------------------------------------
   Forty-eight rows of real information, generated from states.js so it can never
   disagree with the engine. The styling has one job: make a long table readable
   rather than a wall, since the reader is scanning for their own state and the
   value of the block is that they find it and can trust it. */
.states-table{margin-top:var(--s7)}
.states-table h2{margin-bottom:var(--s3)}
.states-lede{font-size:1.05rem;line-height:1.65;max-width:62ch;color:var(--ink)}
.states-count{font-family:var(--sans);font-size:.82rem;letter-spacing:.06em;
  text-transform:uppercase;color:var(--secondary);
  margin:var(--s4) 0 var(--s5);padding-top:var(--s3);
  border-top:1px solid var(--hairline)}
table.states{width:100%;border-collapse:collapse;font-size:.95rem}
table.states caption{text-align:left;font-family:var(--sans);font-size:.82rem;
  letter-spacing:.06em;text-transform:uppercase;color:var(--secondary);
  padding-bottom:var(--s3)}
table.states th,table.states td{text-align:left;padding:var(--s3) var(--s3);
  border-bottom:1px solid var(--hairline);vertical-align:top;line-height:1.5}
table.states thead th{font-family:var(--sans);font-size:.78rem;letter-spacing:.08em;
  text-transform:uppercase;color:var(--secondary);font-weight:600;
  border-bottom:2px solid var(--ink)}
table.states tbody th{font-weight:600;white-space:nowrap}
table.states tbody th a{color:var(--foil);text-decoration:none;
  border-bottom:1px solid var(--hairline)}
table.states tbody th a:hover{text-decoration:underline}
table.states td.num,table.states th.num{text-align:right;font-variant-numeric:tabular-nums}
/* The two states where every county must levy a local tax. Set smaller and in
   the label face so the figure still reads as one number — "6.5% + county" —
   rather than as a second rate competing for attention. */
.plus-county{font-family:var(--sans);font-size:.78rem;letter-spacing:.04em;
  color:var(--secondary);white-space:nowrap}
table.states tbody tr:hover{background:var(--surface)}
.states-note{margin-top:var(--s4);font-size:.9rem;color:var(--secondary);
  line-height:1.6;max-width:68ch}

/* ---------------------------------------------------------------------------
   .result-table — the rate-period breakdown on the underpayment page.
   ---------------------------------------------------------------------------
   The page shipped with the class and no rule, so the browser laid the table
   out with its defaults: border-spacing 1.6px and every column exactly as wide
   as its heading. "Days", "Rate" and "Charge" ran together into "DaysRateCharge".

   This is the fourth class on this site to ship without a rule — after
   .nd-group, .lede--year and the "verified" span — and the check that catches it
   only looks at classes a script toggles, so a table class is outside its reach.
   The vocabulary is copied from `table.states`, which has been here all along:
   the same hairlines, the same uppercase sans headings, the same cell padding.
   --------------------------------------------------------------------------- */
.result-table{width:100%;border-collapse:collapse;font-size:.95rem;margin-top:var(--s5)}
.result-table caption{text-align:left;font-family:var(--sans);font-size:.82rem;
  letter-spacing:.06em;text-transform:uppercase;color:var(--secondary);
  padding-bottom:var(--s3)}
.result-table th,.result-table td{text-align:left;padding:var(--s3) var(--s4) var(--s3) 0;
  border-bottom:1px solid var(--hairline);vertical-align:top;line-height:1.5}
.result-table thead th{font-family:var(--sans);font-size:.78rem;letter-spacing:.08em;
  text-transform:uppercase;color:var(--secondary);font-weight:600;
  border-bottom:2px solid var(--ink);padding-bottom:var(--s2)}
/* The three numeric columns sit right, so the figures line up under each other. */
.result-table th:nth-child(2),.result-table td:nth-child(2),
.result-table th:nth-child(3),.result-table td:nth-child(3),
.result-table th:nth-child(4),.result-table td:nth-child(4){text-align:right;
  padding-right:0;padding-left:var(--s4);white-space:nowrap}
.result-table tbody tr:last-child td{border-bottom:none}

@media (max-width:640px){
  /* On a phone four columns do not fit. The meaning column is dropped rather
     than the table being allowed to overflow: the rate and the type are what a
     reader scans for, and the meaning is repeated in the notes for the states
     that need it. */
  table.states thead th:nth-child(4),table.states tbody td:nth-child(4){display:none}
  table.states{font-size:.88rem}
  table.states th,table.states td{padding:var(--s2) var(--s2)}
}

/* --- ZIP lookup -----------------------------------------------------------
   A search field that narrows as the visitor types, built because the visitor
   does not know the answer it is asking for. Styled to sit with the other
   fields rather than look like a widget: same hairline, same radius zero, and a
   list that reads as part of the form. */
.zip-wrap{position:relative}
#zip{width:100%;padding:14px 16px;font-family:var(--serif);font-size:1.3rem;
  color:var(--ink);background:var(--surface);border:1px solid var(--hairline);
  border-radius:0;font-variant-numeric:tabular-nums;letter-spacing:.02em}
#zip:focus{outline:none;border-color:var(--foil)}
.zip-lista{position:absolute;top:100%;left:0;right:0;z-index:5;margin:0;
  padding:0;list-style:none;background:var(--surface);
  border:1px solid var(--foil);border-top:none;max-height:15rem;overflow-y:auto}
.zip-opcion{display:flex;justify-content:space-between;gap:var(--s3);
  padding:var(--s2) var(--s3);cursor:pointer;border-bottom:1px solid var(--hairline)}
.zip-opcion:last-child{border-bottom:none}
.zip-opcion:hover,.zip-opcion:focus{background:var(--ground);outline:none}
.zip-num{font-family:var(--serif);font-variant-numeric:tabular-nums;font-weight:600}
.zip-cond{font-size:.9rem;color:var(--secondary)}
.zip-estado{margin-top:var(--s2);font-size:.92rem;line-height:1.55;color:var(--secondary)}
.zip-estado--ok{color:var(--foil)}
.zip-estado--warn{color:var(--ink)}
.zip-opciones{display:flex;flex-wrap:wrap;gap:var(--s2);margin-top:var(--s3)}
.zip-amb{display:flex;flex-direction:column;gap:2px;padding:var(--s2) var(--s3);
  background:var(--surface);border:1px solid var(--hairline);border-radius:0;
  cursor:pointer;text-align:left;font:inherit;min-height:44px}
.zip-amb:hover{border-color:var(--foil)}
.zip-amb span{font-size:.82rem;color:var(--secondary)}
/* The `(optional)` that follows a field label. It had its own class once
   — `.opt` — but `.opt` is a small-caps chip with a border elsewhere in this
   file, so writing the rule there would have restyled something unrelated. The
   name is new and used nowhere else. */
.field-opt{font-weight:400;color:var(--secondary);font-size:.85rem}

/* --- County brackets -----------------------------------------------------
   Shown when the visitor's rate depends on the bracket they fall in, which in
   Maryland means one dollar can move the whole rate. The band they are in is
   marked so the table reads as a ladder with them on a rung, not as trivia. */
.brackets{list-style:none;margin:var(--s3) 0 0;padding:0;border-top:1px solid var(--hairline)}
.brackets li{display:flex;justify-content:space-between;gap:var(--s3);
  padding:var(--s2) var(--s2);border-bottom:1px solid var(--hairline);
  font-variant-numeric:tabular-nums;color:var(--secondary)}
.brackets li.br-mio{color:var(--ink);font-weight:600;background:var(--ground)}
.brackets li.br-mio::before{content:"\2192";margin-right:var(--s2);color:var(--foil)}
.sub{font-size:.92rem;color:var(--secondary);margin-top:var(--s2)}

/* The line that points to the other two state calculators, placed under the
   breadcrumb so a visitor on the wrong state sees it before starting the form.
   Uses the site's existing vocabulary on purpose: small size, underline on
   hover, foil for the link. No box, no new colour. */
.state-switch{margin:var(--s3) 0 0;padding-bottom:var(--s3);
  border-bottom:1px solid var(--hairline);
  font-size:.88rem;line-height:1.6;color:var(--secondary)}
.state-switch a{color:var(--foil);text-decoration:underline;text-underline-offset:2px}
.state-switch span{color:var(--secondary)}

/* --- header navigation ----------------------------------------------------
   One line under the masthead carries where you are; the calculator menu sits
   in the masthead itself, to the right of the brand, in a 1px teal box. Boxed
   because it is the only control in a header that is otherwise text: the box
   says "this opens something" without needing an arrow or a colour fill, and
   1px foil is the same weight as every other rule on the site.

   Native <details> so it opens with the keyboard and reads correctly to a
   screen reader, and still works with JavaScript off. Typography is the site's
   own small caps, same size and tracking as .masthead-meta. */
.nv{display:flex;justify-content:space-between;align-items:baseline;
  gap:var(--s4);padding:var(--s3) 0;border-bottom:1px solid var(--hairline);
  font-family:var(--sans);font-size:.688rem;letter-spacing:.06em;
  text-transform:uppercase;color:var(--secondary)}
.nv-where{display:flex;align-items:baseline;gap:var(--s2);min-width:0}
.nv-back{color:var(--secondary);text-decoration:none;white-space:nowrap}
.nv-back:hover{color:var(--foil);text-decoration:underline;text-underline-offset:2px}
.nv-home{color:var(--secondary);text-decoration:none;padding:6px 0}
.nv-home:hover{color:var(--foil);text-decoration:underline;text-underline-offset:2px}
.nv-sep{color:var(--hairline)}
.nv-here{color:var(--ink);font-weight:600}

/* The menu, in the masthead next to the brand. */
/* Sits against the brand, not in the middle of the header. Measured before the
   fix: the brand ended at x=398 and the menu began at x=724 - 326px of dead
   space, because .masthead distributes with space-between and the dropdown was
   a middle child. A second menu root would land somewhere else again. Grouping
   brand + menus at the start and pushing the year to the end means each new
   root sits beside the last one, at a fixed offset. */
.masthead{position:relative}
/* The list anchors to the header, not to the `Calculators` label. Anchored to
   the label it starts wherever that word happens to sit — 194px inside the
   container — and a left offset would have to be computed and kept in step as
   the brand and the viewport change. Anchored to the header, `left: 0` is the
   container's own edge, and the list spans it without a single number. */
.nv-drop{position:static;margin-left:var(--s2)}
.nv-drop summary{list-style:none;cursor:pointer;color:var(--foil);
  font-family:var(--sans);font-size:.688rem;letter-spacing:.06em;text-transform:uppercase;
  display:inline-flex;align-items:center;gap:7px;
  padding:7px 11px;background:var(--surface);border:1px solid var(--foil)}
.nv-drop summary::-webkit-details-marker{display:none}
.nv-drop summary::after{content:"\25BE";font-size:.563rem;line-height:1}
.nv-drop[open] summary{background:var(--foil);color:var(--ground)}
.nv-drop[open] summary::after{content:"\25B4"}
.nv-count{font-size:.594rem;color:var(--secondary);border:1px solid var(--hairline);
  padding:0 4px;line-height:1.5}
.nv-drop[open] .nv-count{color:var(--ground);border-color:var(--ground)}

/* The list opens below the box, right-aligned so it never leaves the viewport. */
/* --------------------------------------------------------------------------
   max-height: why not 70vh

   The list holds eighteen rows and measures about 700px at 360px wide. A 70vh
   cap sounds generous and is not: on a 900px window it allows 630px, so the
   last entry sits below the fold with nothing to say the list continues. The
   cap is now the viewport minus the masthead, so the menu uses the height it
   actually has. Measured: the eighteenth entry is visible at 900, 760 and 620.

   This went wrong once already at a larger size — adding a heading every three
   rows took the list to 1,027px, and no viewport could show it. The menu is
   flat for that reason, not by preference.
   -------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------
   min-width:360px — and why it is not a preference

   The list holds eighteen rows. At 290px six of the names wrap to a second
   line, and the menu measures 890px tall; at 360px none of them wrap and it
   measures 806px. Measured at 1000px of viewport height: at 290 the last
   calculator is below the fold, at 360 it is visible.

   So the width is not cosmetic. Eighty-four pixels of height come from not
   wrapping five names, and that is what decides whether the eighteenth entry
   can be reached without scrolling. On a 760px window no arrangement of
   eighteen rows fits; 900 and above does.

   An attempt to group the list under five family headings was reverted: the
   headings took it to 1,027px and no viewport could show it at all. The menu
   is flat, and the width carries the height.
   -------------------------------------------------------------------------- */
/* Twenty-two entries in one column reached 986px, which is taller than the
   viewport on a laptop — the last entries were reachable only by scrolling the
   menu itself. Four columns hold the same 22 in 270px.

   The order runs left to right and then down, the way a list is read, and that
   is why this is a grid and not CSS columns: `columns` fills the first column
   to the bottom before starting the second, so entries 01 and 12 would sit side
   by side. Each entry takes its column from its index.

   The list spans the full container, edge to edge, so it reads as part of the
   page rather than as a panel floating under a word. That needs two things:
   a width the container owns (`minmax(0,1fr)` collapses when the box has no
   width, and an absolutely positioned box sizes to its content), and a `left`
   that cancels the offset of the `Calculators` label, which sits 194px inside
   the container. The offset is computed rather than hard-coded, because the
   label moves as the brand and the viewport change. */
.nv-list{position:absolute;top:calc(100% + 1px);z-index:20;margin:0;padding:0;
  left:0;width:100%;max-width:none;
  max-height:calc(100vh - 90px);overflow-y:auto;
  display:grid;grid-template-columns:repeat(4,minmax(0,1fr));
  background:var(--surface);border:1px solid var(--foil);
  text-transform:none;letter-spacing:0;font-size:1rem;box-shadow:none}

/* Every fourth entry starts a row; the rest follow it across. */
.nv-list li:nth-child(4n+1){grid-column:1}
.nv-list li:nth-child(4n+2){grid-column:2}
.nv-list li:nth-child(4n+3){grid-column:3}
.nv-list li:nth-child(4n+4){grid-column:4}
.nv-list li{border-bottom:1px solid var(--hairline)}
.nv-list li:nth-child(4n+4){border-right:none}
.nv-list li:last-child{border-bottom:none}
.nv-list a,.nv-list .nd-later{display:flex;gap:var(--s2);align-items:center;
  padding:10px var(--s3);text-decoration:none;color:var(--ink);min-height:44px}
.nv-list a:hover{background:var(--ground);color:var(--foil)}
.nv-list a[aria-current="page"]{background:var(--ground);font-weight:600}
.nv-list a[aria-current="page"] .nd-t::after{content:" \2014 here"}
.nd-n{font-family:var(--sans);font-size:.656rem;color:var(--secondary);min-width:18px}
.nd-t{font-size:.95rem;line-height:1.25}
.nd-later{color:var(--secondary)}
.nd-in{font-family:var(--sans);font-size:.625rem;letter-spacing:.06em;
  text-transform:uppercase;color:var(--secondary);margin-left:auto}.nd-in{font-family:var(--sans);font-size:.625rem;letter-spacing:.06em;
  text-transform:uppercase;color:var(--secondary);margin-left:auto}


/* Four columns need roughly 300px each to keep an entry on one line, which
   puts the floor near 1200px. Below that the count drops: two columns down to
   700px, one below it. A grid that keeps four columns at 390px gives each
   entry 88px, and every one of the twenty-two titles wraps — the longest to
   five lines — so the list ends up as tall as the column it replaced.

   minmax(0,1fr) rather than 1fr, for the reason already recorded further up
   this file: a bare 1fr resolves to minmax(auto,1fr), and that `auto` floor is
   the widest word in the track, so a long title would hold the grid wider than
   the viewport. */
@media (max-width:1199px){
  .nv-list{grid-template-columns:repeat(2,minmax(0,1fr))}
  .nv-list li:nth-child(4n+1){grid-column:auto}
  .nv-list li:nth-child(4n+2){grid-column:auto}
  .nv-list li:nth-child(4n+3){grid-column:auto}
  .nv-list li:nth-child(4n+4){grid-column:auto}
  .nv-list li:nth-child(2n+1){grid-column:1}
  .nv-list li:nth-child(2n+2){grid-column:2}
}
@media (max-width:699px){
  .nv-list{grid-template-columns:minmax(0,1fr)}
  .nv-list li{grid-column:1 !important}
  .nv-list{min-width:0;width:calc(100vw - 40px)}
  .masthead{flex-wrap:wrap}
  .nv-drop{order:3;margin-left:0;width:100%}
  .nv-drop summary{width:100%;justify-content:space-between}
}

/* --- cross-link to the set-aside page ------------------------------------
   Two pages were computing the same figure under two different names, and
   neither carried the phrase people search for. Rather than leave the overlap
   hidden, each one now points at the page that answers the other moment: the
   annual tax here, the per-payment percentage there. */
.crosslink{margin-top:var(--s5);padding-top:var(--s4);border-top:1px solid var(--hairline)}
.crosslink p{margin:0;font-size:.95rem;line-height:1.6;color:var(--secondary)}
.crosslink a{color:var(--foil);text-decoration:underline;text-underline-offset:2px}

/* --- the action sits with the last field, not adrift below it -------------
   Measured on a 390px phone: the last input ended at y=1456 and the button
   began at y=1724 — 268px of empty space, with the age switch and a hint in
   between. Someone who finishes typing can see no way to get a result, which
   is the point at which a calculator gets abandoned. The button now sticks to
   the entry column instead of floating free. --- */
/* The calculate button is sticky so it stays reachable while the form is filled
   in. Measured on a 390px phone: it occupies the bottom 46px of the viewport and
   the last control ended up underneath it - 33px of overlap on the set-aside
   page, more than half the field, at the exact moment the visitor is looking at
   it. Container padding did not fix it, because a sticky element leaves the flow
   and floats. The button now paints its own background so nothing shows through,
   and reserves its own clearance with a transparent border. */
/* ==========================================================================
   THE FORM BUTTON — one rule, wherever an input form puts it.
   --------------------------------------------------------------------------
   This button had drifted into two classes (.btn on eight pages, .btn-primary
   on three - a class that does not exist in this stylesheet at all, and only
   worked because a generic button selector caught it). Its behaviour depended
   on being inside .col-inputs, so a page that placed its form in a different
   container got a different button.

   What it does, and why:

   - sticky, bottom 0 — measured: on a 390px phone the button sat 268px below
     the last field, and a visitor who finishes typing can see no way to get a
     result. Sticking it to the bottom of the viewport fixes that.
   - background + box-shadow upward — the button floats over the form, so it
     must be opaque. Without this the field underneath shows through it.
   - transparent bottom border — reserves the button's own clearance. Container
     padding was tried first and did not work: a sticky element leaves the flow
     and floats, so padding on the parent does not push it. The button reserves
     its own space.

   Measured result of the fix: overlap with the last control went from 33px
   (set-aside), 12px (home office) and 11px (Indiana) to zero on all eleven.
   ========================================================================== */

/* --- the action row: Calculate is the primary action, Clean is the quieter one
   beside it. Clean is styled as a link, the same teal and underline the site
   already uses for its secondary actions (.crosslink), so it reads as "this
   does less" without needing a second button weight that does not exist here.
   Both sit inside the fixed bar, side by side, filling the width. */
.cta-row .btn{flex:1 1 auto;width:auto;min-width:0;margin-top:0}
.clean-link{
  flex:0 0 auto;align-self:center;
  font-family:var(--sans);font-size:.719rem;font-weight:600;
  letter-spacing:.1em;text-transform:uppercase;
  color:var(--foil);background:none;border:none;padding:8px 2px;
  text-decoration:underline;text-underline-offset:3px;cursor:pointer;
}
.clean-link:hover{color:#143a46}

/* The action row sits in the flow, at the foot of the form.
   
   It was briefly position:fixed to the viewport, which was a fix for a symptom:
   the button used to be sticky inside the input column and left the screen with
   it. Fixing it to the viewport made it cover content instead - measured, the
   row sat ABOVE the last field by 204px on the SE tax page and 1,846px on the
   home office page, floating over fields the visitor had not reached yet, and
   the body needed a 110px pad to stop it landing on the footer. That is a patch,
   not a layout.
   
   Nothing in this site floats. Every other element is in the flow, and the row
   belongs there too, directly under the last field. The gap between the final
   field and the row is 75-330px depending on how many fields a page has, which
   is ordinary form spacing, not a reason to detach the button from its form. */
.cta-row{
  display:flex;gap:var(--s3);align-items:center;
  margin-top:var(--s5);
}



/* The sticky button sits over the field above it as you scroll — measured: it
   covered the age switch. The column reserves the button's height at the end so
   the last control can always be scrolled clear of it. */
/* The calculate button is sticky so it stays reachable while the form is filled
   in. Measured on a 390px phone: the button occupies 46px pinned to the bottom
   of the viewport, and the last field ends up 33px underneath it - more than
   half the field hidden at the exact moment the visitor looks at it. The padding
   below reserves that space so the last control can always be scrolled clear.
   64px was not enough because it did not account for the button's own margin. */
.col-inputs{padding-bottom:72px}
.col-inputs .btn::after{content:"";display:block}

/* money input: fixed $ prefix that never disappears */
.money{position:relative;display:block}
/* The $ must line up with the digits — reported by the site's owner as visibly
   off, and it was: measured 19px low.
   The container `.money` holds the input AND the quick-value chips, so it is
   96px tall while the field is 58px. `top:50%` resolved against the container
   and centred the "$" on the whole group (y=601 against the digits' y=582).
   `top:0;bottom:0` was worse, not better: it stretched the "$" across all 96px
   and reintroduced exactly the same 19px error.
   `height:100%` on an absolutely positioned box resolves against the containing
   block's PADDING box — top:0 to the input's own bottom, chips excluded. That is
   the field's height, whatever it happens to be.
   It is deliberately not a literal: two fields with identical padding,
   font-size and border rendered 58px and 54px, because a 0.8px border rounds
   differently depending on what surrounds the field. A fixed 58px was tried and
   sat 2px low on the quarterly page. */
/* The symbol is positioned against the FIELD, not against the wrapper.

   The wrapper `.money` also holds the quick-value chips ($40k, $80k ...), so it
   is 96px tall while the input inside it is 58px. `top:0; height:100%;
   align-items:center` centres the symbol in those 96px, which puts it 19px
   below the centre of the 58px field - the $ sat at the bottom edge of the box,
   reported by the site owner from a screenshot. The same rule was written
   before with a comment claiming height:100% meant "the input's height, never
   the chips'": it does not, and the comment is what let it come back.

   The fix is not a smaller percentage - a percentage cannot know how tall the
   chips are. The symbol is given the field's own line height and the field's own
   top padding: the input is 58px tall with 14px of vertical padding and a
   20.8px line, so the text sits centred on 14px + 10.4px = 24.4px from the top.
   Anchoring there holds whatever the wrapper contains and however many chips
   are added. */
.money::before{
  content:"$";position:absolute;left:16px;
  /* Centred the same way the field's own text is: absolutely, with a
     line-height equal to the input's content box and top equal to the input's
     padding. Both boxes then span the identical strip of the field, so the
     glyphs sit on the same line by construction rather than by a measured
     offset that has to be re-measured whenever a font size changes.
     `line-height:100%` was wrong here for a different reason: the wrapper also
     holds the quick-value chips, so 100% meant 96px against a 58px field. */
  top:var(--input-pad-y);
  height:var(--input-line);
  line-height:var(--input-line);
  display:flex;align-items:center;
  font-family:var(--serif);font-size:1.3rem;
  font-weight:600;                   /* the digits are semibold; match them */
  color:var(--secondary);pointer-events:none;z-index:1;
}

/* `input[type=number]` alone stopped matching the moment money-input.js helped
   the field accept a typed comma: it rewrites the type to text, the selector
   no longer matched, and the field lost its padding, size, background and
   border - a raw browser box. `.num-field` is the same style for a field this
   site has converted, so the appearance does not depend on the type. */
/* The two numbers the symbol alignment is built from. Change the field's
   padding or font-size and these must follow - they are the only place either
   value is used for positioning. */
:root{
  --input-pad-y:14px;    /* vertical padding on the input */
  --input-line:20.8px;   /* the input's line box (1.3rem at 16px root) */
}
input[type=number],input.num-field,select{
  width:100%;padding:14px 16px;font-family:var(--serif);font-size:1.3rem;
  /* The field's text is centred by giving the input a line-height equal to its
     content box, and the symbols are centred the same way - see .money::before.
     Centring the text this way is what lets the two share a baseline instead of
     each being nudged by a hand-measured offset. */
  line-height:var(--input-line);
  color:var(--ink);background:var(--surface);
  border:1px solid var(--hairline);border-radius:0;
  -moz-appearance:textfield;appearance:none;
}

/* unit input: a suffix that stays put, for fields counted in something other
   than dollars — miles, square feet, years.
   Reported by the site's owner: the mileage field looked identical to a money
   field, so nothing on screen said the number was miles. On that page both
   kinds appear together — miles at 10,000 and an employer rate at $0.725 — and
   with no unit the reader has to infer which is which from the label alone.
   The suffix is a sibling of the input, positioned by the same rules as the $
   prefix, so the two kinds of field are distinguishable at a glance. */
.unit{position:relative;display:block}
/* Same anchoring as .money::before, and for the same reason. */
.unit::after{
  content:attr(data-unit);position:absolute;right:16px;
  top:var(--input-pad-y);
  height:var(--input-line);
  line-height:var(--input-line);
  display:flex;align-items:center;
  font-family:var(--sans);font-size:.82rem;
  letter-spacing:.08em;text-transform:uppercase;
  color:var(--secondary);pointer-events:none;z-index:1;
}
.unit input[type=number],.unit input.num-field{padding-right:72px}
/* NOTE: must come AFTER the rule above — same specificity, later wins.
   Putting padding-left on `.money input` alone gets overridden by the
   shorthand `padding` above, and the $ collides with the digits. */
.money input[type=number],.money input.num-field{padding-left:34px;text-align:right;
  font-variant-numeric:tabular-nums}
input::placeholder{color:var(--secondary);opacity:.55}
input[type=number]::-webkit-outer-spin-button,
input[type=number]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}

select{font-size:1.05rem;cursor:pointer;
  background-image:linear-gradient(45deg,transparent 50%,var(--secondary) 50%),
                   linear-gradient(135deg,var(--secondary) 50%,transparent 50%);
  background-position:calc(100% - 22px) 22px,calc(100% - 16px) 22px;
  background-size:6px 6px,6px 6px;background-repeat:no-repeat}

input:focus,select:focus{outline:none;border-color:var(--foil);
  box-shadow:inset 0 0 0 1px var(--foil)}

.btn{
  width:100%;padding:16px;font-family:var(--sans);font-size:.719rem;font-weight:600;
  letter-spacing:.1em;text-transform:uppercase;
  color:var(--ground);background:var(--foil);
  border:none;border-radius:0;cursor:pointer;transition:background .35s ease;
}
.btn:hover{background:#143a46}

/* inline validation — replaces the native alert() */
.field-error{display:none;font-family:var(--sans);font-size:.719rem;font-weight:600;
  letter-spacing:.05em;text-transform:uppercase;color:var(--alert);margin-top:var(--s2)}
.field-error.show{display:block}
input.invalid{border-color:var(--alert);box-shadow:inset 0 0 0 1px var(--alert)}

/* ==========================================================================
   RESULTS
   --------------------------------------------------------------
   The result block is display:none until .active is added — NOT opacity:0.
   It was opacity:0 before, which made the invisible block still occupy
   ~1030px of height. Before a calculation the right column was a tall empty
   space and the button sat in a 535px void. Measure, then fix.
   ========================================================================== */
.results{display:none}
.results.active{display:block;animation:fadeIn .4s ease both}
@keyframes fadeIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}

/* the pre-calculation state: states what will appear, occupies little */
.placeholder-msg{padding:var(--s5) 0;color:var(--secondary);
  font-style:italic;border-bottom:1px solid var(--hairline)}

/* the button spans the full width of the left column so it doesn't orphan */
.layout > .col-inputs{display:flex;flex-direction:column}
/* `margin-top:auto` used to push the button to the foot of the column. It is
   gone with the sticky positioning it served: the action row is fixed to the
   viewport now, so there is nothing left to push. */
.results-head{display:flex;justify-content:space-between;align-items:baseline;
  padding-bottom:var(--s2);border-bottom:2px solid var(--ink)}
.results-head h2{font-size:1.15rem}



.rows{border-bottom:1px solid var(--hairline)}
.row{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:var(--s4);
  padding:var(--s3) 0;border-top:1px solid var(--hairline);align-items:baseline}
.row:first-child{border-top:none}
.row-label{color:var(--secondary);font-size:1rem}
.row-label b{color:var(--ink);font-weight:600}
.row-value{font-size:1.25rem;font-weight:600;
  font-variant-numeric:tabular-nums;text-align:right;overflow-wrap:anywhere}
.row--total{border-top:2px solid var(--ink);margin-top:var(--s2);padding-top:var(--s4)}
.row--total .row-label{color:var(--ink);font-size:1.05rem;font-weight:600}
.row--total .row-value{font-size:2.1rem;color:var(--foil);line-height:1}
/* 1.45 was below the 1.5 that SC 1.4.12 asserts a page must survive, and this
   is the one element in the set that holds running text rather than a label or
   a figure. The margin above it goes to --s2 so the note separates from the row
   it explains instead of crowding it. */
.row-note{grid-column:1/-1;font-size:.85rem;color:var(--secondary);
  margin-top:var(--s2);line-height:1.5;max-width:74ch}

/* provenance panel — the differentiator, given real weight */
.provenance{margin-top:var(--s5);padding:var(--s4);background:var(--surface);
  border-left:2px solid var(--foil)}
.provenance h3{font-family:var(--sans);font-size:.656rem;font-weight:600;
  letter-spacing:.1em;text-transform:uppercase;color:var(--foil);margin-bottom:var(--s3)}
.provenance dl{display:grid;grid-template-columns:auto 1fr;gap:var(--s1) var(--s3);
  font-size:.88rem}
.provenance dt{color:var(--secondary);white-space:nowrap}
.provenance dd{color:var(--ink)}

/* ==========================================================================
   BREADCRUMBS — present on calculator pages, not the index
   ========================================================================== */

/* ==========================================================================
   PROVISIONAL-DATA FLAG
   Shown when a figure is published but not final. The point is that the page
   says so rather than presenting an estimate as settled.
   ========================================================================== */
.state-flag{margin-top:var(--s5);padding:var(--s4);background:var(--surface);
  border-left:2px solid var(--alert);font-size:.9rem;line-height:1.6;
  color:var(--secondary)}
.state-flag b{color:var(--ink);font-weight:600}
.state-flag a{color:var(--foil)}

/* ==========================================================================
   LONG-FORM TEXT — legal and about pages.
   Measure is capped around 68 characters: past that the eye loses the line
   return. Legal pages are read, not skimmed, so the limit matters more here
   than anywhere else on the site.
   ========================================================================== */
.prose{padding:var(--s6) 0 var(--s5);max-width:68ch}
.prose h2{font-size:1.35rem;font-weight:600;letter-spacing:-.01em;
  margin:var(--s6) 0 var(--s3);padding-top:var(--s4);
  border-top:1px solid var(--hairline)}
.prose h2:first-child{margin-top:0;padding-top:0;border-top:none}
.prose p{color:var(--secondary);margin-bottom:var(--s3)}
.prose p:last-child{margin-bottom:0}
.prose b,.prose strong{color:var(--ink);font-weight:600}
.prose i,.prose em{font-style:italic}
.prose ul{list-style:none;margin:var(--s3) 0}
.prose li{position:relative;padding-left:var(--s4);margin-bottom:var(--s2);
  color:var(--secondary)}
.prose li::before{content:"—";position:absolute;left:0;color:var(--foil);
  font-family:var(--sans)}
.prose a{color:var(--foil);text-decoration:underline;text-underline-offset:2px}
.prose a:hover{text-decoration-thickness:2px}

/* ==========================================================================
   EDITORIAL SECTIONS
   ========================================================================== */
.section{padding:var(--s6) 0}
.section-head{display:flex;justify-content:space-between;align-items:baseline;
  gap:var(--s4);padding-bottom:var(--s3);border-bottom:2px solid var(--ink);
  flex-wrap:wrap}

.note{padding:var(--s6) 0;border-top:1px solid var(--hairline);
  display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.4fr);gap:var(--s6)}
.note-body p{color:var(--secondary);margin-bottom:var(--s3);max-width:60ch}
.note-body p:last-child{margin-bottom:0}
.note-body strong{color:var(--ink);font-weight:600}
.rules{list-style:none;margin-top:var(--s4)}
.rules li{display:flex;gap:var(--s3);padding:var(--s2) 0;
  font-size:1rem;color:var(--secondary)}
.rules li::before{content:"—";color:var(--foil);font-family:var(--sans);flex:none}

.faq{padding:var(--s6) 0;border-top:1px solid var(--hairline)}
.faq h2{margin-bottom:var(--s5)}
.faq-item{padding:var(--s4) 0;border-top:1px solid var(--hairline);max-width:66ch}
.faq-item h3{font-size:1.12rem;font-weight:600;margin-bottom:var(--s2)}
.faq-item p{color:var(--secondary)}
.faq-item b{color:var(--ink);font-weight:600}

/* --- two-mode switcher: self-employed (deduction) vs employee (reimbursement).
   Radio 0, no shadow, 1px hairlines — same rules as the rest of the page. The
   active option is filled with the accent so the mode is unmistakable without
   relying on colour alone: it also carries aria-checked and bold weight. --- */
.switch{display:inline-flex;border:1px solid var(--ink);background:var(--ground)}
.switch-opt{font-family:var(--sans);font-size:.688rem;font-weight:600;
  letter-spacing:.06em;text-transform:uppercase;padding:0 16px;height:44px;
  display:inline-flex;align-items:center;
  background:transparent;border:0;color:var(--secondary);cursor:pointer;
  transition:background .3s ease,color .3s ease}
.switch-opt+.switch-opt{border-left:1px solid var(--ink)}
.switch-opt:hover{color:var(--ink)}
.switch-opt.is-on{background:var(--ink);color:var(--ground);font-weight:700}
.switch-opt:focus-visible{outline:2px solid var(--foil);outline-offset:2px}

/* --- eligibility checkboxes. The mileage page used a switch because the choice
   was exclusive; this page needs independent yes/no gates, so these are real
   checkboxes with a large hit area. The label wraps the input so the whole row
   is clickable, and the box is sized to be tappable on a phone. --- */
.check{display:flex;gap:11px;align-items:flex-start;padding:10px 0;
  cursor:pointer;font-size:.95rem;line-height:1.5;color:var(--secondary)}
.check+.check{border-top:1px solid var(--hairline)}
.check input{appearance:none;-webkit-appearance:none;flex:none;
  width:20px;height:20px;margin:1px 0 0;border:1px solid var(--ink);
  background:var(--ground);cursor:pointer;position:relative;border-radius:0}
.check input:checked{background:var(--ink)}
.check input:checked::after{content:"";position:absolute;left:6px;top:2px;
  width:5px;height:10px;border:solid var(--ground);border-width:0 2px 2px 0;
  transform:rotate(45deg)}
.check input:focus-visible{outline:2px solid var(--foil);outline-offset:2px}
.check b{color:var(--ink);font-weight:600}

/* --- a warning that must not be mistaken for a result. Used on the mileage and
   home-office pages to state something the visitor would otherwise get wrong
   (an employee cannot deduct this; the exclusive-use test is not met).

   It began as a card with a 3px left border in the accent colour. That is a
   borrowed idiom — the "rounded card + coloured left edge" pattern that reads
   as a dashboard widget — and this site already has its own way of saying "this
   is set apart": the full-width rule used by .sources, .related and .status-h2.
   Using the same gesture here makes the warning part of the page's language
   rather than an imported component.

   Two things keep it from being confused with a result: the italic serif is
   never used for figures, and the "Note" label in caps is the same device the
   section kickers use. Deliberately not --alert: this is a correction, not a
   validation error. --- */
.warning{margin-top:var(--s4);padding:var(--s4) 0;
  border-top:1px solid var(--ink);border-bottom:1px solid var(--hairline);
  font-size:.95rem;line-height:1.6;color:var(--ink);font-style:italic}
.warning::before{content:"Note";display:block;font-family:var(--sans);
  font-style:normal;font-size:.656rem;font-weight:600;letter-spacing:.08em;
  text-transform:uppercase;color:var(--secondary);margin-bottom:7px}
.warning b{font-weight:700;font-style:normal}

.related{padding:var(--s5) 0 var(--s6);border-top:2px solid var(--ink)}
.related h2{font-size:1.1rem;margin-bottom:var(--s4)}
.rel-list{list-style:none;display:grid;
  grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:0 var(--s5)}
.rel-list a,.rel-list .rel{display:block;padding:var(--s3) 0;
  border-bottom:1px solid var(--hairline);text-decoration:none;
  transition:color .35s ease,padding-left .35s ease}
.rel-list a:hover{color:var(--foil);padding-left:var(--s2)}
.rel-list .rel{opacity:.7;cursor:default}
.rel-list .rn{font-family:var(--sans);font-size:.656rem;letter-spacing:.08em;
  color:var(--secondary);display:block;margin-bottom:2px}
.rel-list .rt{font-size:1.05rem;font-weight:600}

/* sources table on the front page */
.sources{padding:var(--s5) 0 var(--s6);border-top:2px solid var(--ink)}
.sources h2{font-size:1.1rem;margin-bottom:var(--s3)}
.sources dl{display:grid;grid-template-columns:auto 1fr;gap:var(--s2) var(--s4);
  font-size:.95rem}
.sources dt{font-family:var(--sans);font-size:.688rem;font-weight:600;
  letter-spacing:.06em;text-transform:uppercase;color:var(--secondary);
  padding-top:.28em;white-space:nowrap}
.sources dd{color:var(--ink)}
.sources dd span{color:var(--secondary)}

/* ==========================================================================
   AD SLOTS
   Never place one of these between a result and its provenance.
   ========================================================================== */
/* AdSense injects an inner element sized to the ad unit, which can exceed a
   narrow viewport when the slot has not yet resolved (data-ad-slot="auto"
   renders nothing but still reserves a fixed-width box). Without this, every
   page scrolls sideways on a phone: measured, a 720px child inside a 390px
   viewport. Contain it here rather than relying on the ad unit behaving. */
.ad-slot{padding:var(--s5) 0;border-top:1px solid var(--hairline);
  border-bottom:1px solid var(--hairline);margin:var(--s6) 0;
  overflow:hidden;max-width:100%}
.ad-slot::before{content:"Advertisement";display:block;font-family:var(--sans);
  font-size:.594rem;font-weight:600;letter-spacing:.1em;text-transform:uppercase;
  color:var(--secondary);opacity:.65;margin-bottom:var(--s3)}
.ad-slot ins{display:block;min-height:90px;width:100%;max-width:100%}

/* ==========================================================================
   STATUS PAGE (private, noindex)
   Deliberately plainer than the calculators: this is a diagnostic surface, read
   when something is wrong, so legibility of the verdict beats any styling.
   Same palette and rules — hairline borders, radius 0, no shadow.
   ========================================================================== */
.verdict{border:1px solid var(--ink);padding:var(--s4);margin-bottom:var(--s4)}
.verdict.is-ok{background:var(--ground)}
.verdict.is-bad{background:var(--surface);border-left:4px solid var(--ink)}
.verdict-head{display:flex;align-items:center;gap:10px}
.verdict-dot{width:10px;height:10px;border-radius:50%;flex:none;background:var(--ink)}
.verdict.is-ok .verdict-dot{background:var(--foil)}
.verdict.is-bad .verdict-dot{background:var(--ink);animation:none}
.verdict-text{font-size:1.25rem;font-weight:600;color:var(--ink)}
.verdict-when{font-family:var(--sans);font-size:.688rem;letter-spacing:.06em;
  text-transform:uppercase;color:var(--secondary);margin-top:6px}

.status-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));
  gap:0;border:1px solid var(--hairline);margin:var(--s5) 0}
.status-card{padding:var(--s4);border-right:1px solid var(--hairline)}
.status-card:last-child{border-right:0}
.status-card-label{font-family:var(--sans);font-size:.656rem;font-weight:600;
  letter-spacing:.08em;text-transform:uppercase;color:var(--secondary);
  margin-bottom:6px}
.status-card-value{font-family:var(--serif);font-size:1.5rem;font-weight:600;
  color:var(--ink);line-height:1.1;font-variant-numeric:tabular-nums}
.status-card-note{font-size:.85rem;color:var(--secondary);margin-top:6px;
  line-height:1.5}
.status-card--bad .status-card-value{text-decoration:underline;
  text-decoration-thickness:2px;text-underline-offset:3px}
.status-card--warn .status-card-value{font-style:italic}

.status-h2{font-size:1.1rem;margin:var(--s5) 0 var(--s3);
  padding-top:var(--s4);border-top:2px solid var(--ink)}
.status-table{width:100%;border-collapse:collapse;font-size:.9rem}
.status-table th{font-family:var(--sans);font-size:.656rem;font-weight:600;
  letter-spacing:.08em;text-transform:uppercase;color:var(--secondary);
  text-align:left;padding:8px 10px 8px 0;border-bottom:1px solid var(--ink)}
.status-table td{padding:9px 10px 9px 0;border-bottom:1px solid var(--hairline);
  color:var(--ink)}
.status-table tr.is-bad td{background:var(--surface)}
.status-table .mono{font-family:var(--sans);font-variant-numeric:tabular-nums;
  font-size:.85rem}
.status-table .dim{color:var(--secondary)}
.status-raw{font-family:var(--sans);font-size:.719rem;line-height:1.6;
  background:var(--surface);padding:var(--s4);overflow-x:auto;
  max-height:420px;color:var(--ink);white-space:pre}

@media (max-width:640px){
  .status-card{border-right:0;border-bottom:1px solid var(--hairline)}
  .status-card:last-child{border-bottom:0}
}

/* --------------------------------------------------------------------------
   FORMULA BLOCK — "How this is worked out", collapsed after the provenance.
   A formula is working, not a result, so it does not compete with the figure.
   Expressions are set in the sans face with tabular figures because they are
   code-shaped, not prose; the label and source keep the caps treatment used by
   every other caption on the site. Same rules as everything else: radius 0,
   hairline borders, no shadow.
   -------------------------------------------------------------------------- */
.fx{margin-top:var(--s5);border-top:1px solid var(--ink);padding-top:var(--s3)}
.fx summary{font-family:var(--sans);font-size:.688rem;font-weight:600;
  letter-spacing:.08em;text-transform:uppercase;color:var(--ink);cursor:pointer;
  list-style:none;display:flex;align-items:center;gap:8px;padding:4px 0}
.fx summary::-webkit-details-marker{display:none}
.fx summary::before{content:"+";font-size:.875rem;font-weight:600;color:var(--secondary);
  width:12px;display:inline-block;transition:transform .3s ease}
.fx[open] summary::before{content:"\2212"}
.fx summary:hover{color:var(--foil)}
.fx summary:focus-visible{outline:2px solid var(--foil);outline-offset:3px}

.fx-intro{font-size:.9rem;color:var(--secondary);line-height:1.6;
  margin:var(--s3) 0 var(--s4);max-width:64ch}
.fx-rows{display:flex;flex-direction:column}
.fx-row{padding:var(--s3) 0;border-top:1px solid var(--hairline)}
.fx-row:first-child{border-top:none;padding-top:0}
.fx-label{font-family:var(--sans);font-size:.656rem;font-weight:600;
  letter-spacing:.06em;text-transform:uppercase;color:var(--secondary);
  margin-bottom:5px}
.fx-expr{font-family:var(--sans);font-size:.92rem;color:var(--ink);
  font-variant-numeric:tabular-nums;line-height:1.5;overflow-wrap:anywhere}
.fx-src{font-size:.82rem;color:var(--secondary);margin-top:4px}
.fx-note{font-size:.82rem;color:var(--secondary);margin-top:6px;
  padding-left:11px;border-left:2px solid var(--hairline);line-height:1.5}
.fx-note b{color:var(--ink);font-weight:600}
.fx-foot{font-size:.82rem;color:var(--secondary);line-height:1.6;
  margin-top:var(--s4);padding-top:var(--s3);border-top:1px solid var(--hairline);
  max-width:64ch}


/* `white-space: nowrap` is right until the content is wider than the screen,
   and then it is the thing that breaks the page. Three of the seven uses here
   hold content whose length is not known when the CSS is written — a state
   name, a source label, a defined term — and at 320px they pushed two pages
   into horizontal scroll: the front page by 95px, the quarterly page by 5px.

   `text-wrap: nowrap` is not the fix; there is no conditional nowrap. What
   works is keeping the nowrap where there is room for it and releasing it
   where there is not, so the label stays on one line on a desktop and breaks
   rather than overflows on a phone. */
.provenance dt,.sources dt,table.states tbody th{white-space:normal}
@media (min-width:641px){
  .provenance dt,.sources dt,table.states tbody th{white-space:nowrap}
}
/* And whatever the width, a long word breaks rather than extends the box. */
.provenance dt,.sources dt,.provenance dd,.sources dd{overflow-wrap:anywhere}

/* ==========================================================================
   FOOTER
   ========================================================================== */
/* --- report / correction prompt in the footer. Sits with the disclaimer
   because both are honesty surfaces: one limits what the tool claims, the other
   invites correction of it. Not styled as a button — it is an offer, not a
   call to action competing with the calculators. --- */
.report{font-size:.75rem;color:var(--secondary);line-height:1.7;
  max-width:52ch;padding-top:var(--s3);border-top:1px solid var(--hairline)}
.report b{color:var(--ink);font-weight:600}
.report-link{color:var(--foil);text-decoration:underline;text-underline-offset:2px;
  cursor:pointer;font-weight:600}
.report-link:hover{text-decoration-thickness:2px}
/* user-select:all is there so the address is one click to copy, and that is
   also why it cannot be allowed to set the width of the layout: the browser
   keeps it whole. overflow-wrap lets it break inside the word instead. */
.report-addr{font-family:var(--sans);color:var(--ink);user-select:all;
  overflow-wrap:anywhere}

/* The footer holds four things and used to lay them out with space-between.
   On a wide screen that was four objects scattered across the full width with
   198px of air between each — nothing grouped, nothing anchored, and the
   copyright stranded at the far right where it belongs under everything else.

   Three columns and an explicit second row: the three readable blocks sit in
   one band, and the copyright takes the whole of the row below, behind its own
   hairline. The distinguishing rule above the footer is the only separation
   the recipe allows — stripe-press does not use tinted panels — so the second
   hairline is what makes the legal line read as a footnote rather than a
   fourth scattered block. */
footer{border-top:1px solid var(--hairline);padding:var(--s5) 0 var(--s7);
  font-family:var(--sans);font-size:.75rem;color:var(--secondary);line-height:1.8}

/* Three columns only where there is room for three. On a phone the same grid
   gave columns of 24px, because the `auto` middle track took everything the
   two fr tracks did not: the report block landed in a 24px column with a
   150px email address inside it, and pushed the document 109px wide. Below
   the breakpoint the footer stacks, which is what it did before the grid. */
@media (min-width:861px){
  footer{display:grid;grid-template-columns:minmax(0,1.4fr) auto minmax(0,1.4fr);
    align-items:start;gap:var(--s5) var(--s6)}
  footer > :last-child{grid-column:1/-1;padding-top:var(--s4);
    border-top:1px solid var(--hairline)}
}


footer a{color:var(--secondary);text-decoration:underline;text-underline-offset:2px}
footer a:hover{color:var(--foil)}
.footer-nav{display:flex;gap:var(--s4);flex-wrap:wrap;align-items:baseline}
.footer-nav a{text-decoration:none}
.footer-nav a:hover{text-decoration:underline;text-underline-offset:2px}
.disclaimer{max-width:52ch}
.disclaimer strong{color:var(--ink)}

/* ==========================================================================
   FOCUS + RESPONSIVE
   ========================================================================== */
a:focus-visible,.btn:focus-visible,input:focus-visible,select:focus-visible{
  outline:2px solid var(--foil);outline-offset:3px}

@media (max-width:860px){
  body{font-size:1.063rem}
  /* minmax(0,1fr), not 1fr. A bare 1fr resolves to minmax(auto,1fr), and that
     `auto` floor is the widest word in the column: a long row label such as
     "Basis reduction (depreciation)" held the whole grid at 470px inside a
     390px viewport, so every page with a visible result scrolled sideways on a
     phone. The desktop rule above already used minmax(0,1fr); the mobile one
     was written bare and never caught, because the overflow only appears AFTER
     Calculate is pressed — an empty page measures clean. */
  .layout{grid-template-columns:minmax(0,1fr);gap:var(--s5)}
  .page-head{padding:var(--s5) 0 var(--s4)}
  h1{max-width:none}
  .tool{grid-template-columns:36px 1fr;gap:var(--s3)}
  .tool-arrow{grid-column:2;padding-top:var(--s2)}
  .note{grid-template-columns:1fr;gap:var(--s4)}
  .provenance dl,.sources dl{grid-template-columns:1fr;gap:var(--s1)}
  .sources dt{padding-top:var(--s3)}
  .row--total .row-value{font-size:1.6rem}
  footer{padding-bottom:var(--s6)}
}

@media (prefers-reduced-motion:reduce){*{transition:none!important}}

/* --------------------------------------------------------------------------
   THE FISCAL-YEAR NOTE UNDER THE LEDE
   Every calculator states which tax year it prepares and, from 2026-10-04,
   which exercise the number is computed under — the rules for a tax year are
   published during that year and the one before it, and the return they govern
   is filed the year after. It is reference, not the page's promise, so it sits
   at the hint scale (0.9rem) and in the secondary ink rather than competing
   with the lede above it. Three paragraphs at 1.3rem read as one block and
   none of them lands.
   -------------------------------------------------------------------------- */
.lede--fiscal{font-size:.9rem;line-height:1.6;color:var(--secondary);
  margin-top:var(--s2);max-width:62ch}
