/*
 * Salt offer-application portal — /offer-form/. Job 2648, lane F1.
 *
 * ⛔ NAMESPACE `.sapply-`, AND IT IS A HARD RULE, NOT A PREFERENCE.
 *
 * The site's existing shell component and its pop-up own their own two class prefixes, and a
 * collision between this file and one of them already cost this job a gate run that timed out
 * silently and hid two real findings for hours. Nothing in this stylesheet may match an element
 * the older components render, and nothing they ship may reach this one. Every selector below
 * begins inside `.sapply`.
 *
 * TOKENS. The values are the Salt design system's, restated here rather than inherited. They HAVE
 * to be restated: the older stylesheet declares them on its own root class, which this component
 * deliberately does not carry, so `var(--sf-marine)` would resolve to nothing on this page. Same
 * hexes, same names, one source of truth in the design system and two declarations of it.
 *
 *   Marine #022938 · Silica #F5EFE4 · Sea Spray #BFF6E5 · Scrub #65654B
 *   Line rgba(2,41,56,.22) · Error #a4262c
 *
 * TYPE. Kodchasan for everything; Ruthfully for the display heading. Both already load on every
 * page — Kodchasan from the Google Fonts link in the layout, Ruthfully from the captured
 * @font-face in the theme's inline head styles. Neither is re-declared here.
 *
 * ⚠ ONE BREAKPOINT, at 900px. Both sides of it are driven in tests/offer-form-portal.test.ts, at 899
 * and at 900. Do not add a second without driving it the same way: a pass at one width says
 * nothing about the other.
 *
 * ⛔ IT USED TO BE 640px AND THAT NUMBER WAS MEASURABLY WRONG. The design system's own breakpoint
 * is 640, so the rail was given its 260px column the moment the viewport passed it — and the panel
 * that had 548px of content at 639px had 226px at 641px, NARROWER THAN A 320px PHONE. A surname
 * rendered as "Whitcon" and an email clipped at "…@bigpon", on the page where a buyer types the
 * two things the sales team has to contact them with. The rail now waits until there is genuinely
 * room for it beside a usable panel, and the fields that used to be laid out by viewport width are
 * laid out by the width they actually have (see .sapply-names and .sapply-grid).
 */

/*
 * ⚠ THE ONE EXCEPTION TO "every selector begins inside .sapply" — 4a spec §9. This targets the
 * <div> that WRAPS #salt-apply on src/pages/offer-form.astro, an ancestor no descendant selector
 * can reach, so it cannot be written any other way. Still `.sapply-`-prefixed for the same
 * collision-avoidance reason as everything else here, just not nested.
 *
 * Every other Salt page's own container caps at 1080px, and this one still does everywhere below
 * 1180px. Only once the third grid column opens does it need more: the panel was measuring ~360px
 * at 1440px viewport with the 1080px cap still in force — the .sapply-names row gave back exactly
 * ONE column instead of three, a worse repeat of the 226px "Whitcon" incident the 900px breakpoint
 * exists to prevent, one column later. Was an inline max-width on the element itself, which no
 * external rule — however specific — can override; moved here so a breakpoint can reach it at all.
 * `width: 100%` is required alongside max-width, not decorative: the theme's own
 * `.brxe-container{width:1100px}` is a fixed width, not auto, so raising max-width alone would
 * never let the box grow past 1100px to use it.
 */
/*
 * ⛔⛔ CRITICAL — THE PANEL STOPPED GROWING PAST 1440px AND WASTED THE REST OF THE VIEWPORT.
 * Measured: the middle grid column read 460px at 1180, 671.484px at 1440 AND at 1920 — identical,
 * despite .sapply-page-container itself correctly growing from 1180px to its 1500px cap over that
 * range (confirmed with getBoundingClientRect: 1180 / 1440 / 1500). The container was never the
 * ceiling. The real cause: `.brxe-container` (the theme class this element also carries) sets
 * `display:flex; flex-direction:column; align-items:flex-start`, which SHRINK-WRAPS every flex
 * child — including #salt-apply.sapply — to its own content width instead of stretching it to fill
 * the container. A CSS grid sized by shrink-to-fit resolves its `1fr` tracks against their own
 * content's max-content size, not the container's actual width, which is why the number was
 * IDENTICAL whether the grid had two tracks (900–1179px) or three (≥1180px): both were being sized
 * by content, not by the space .sapply-page-container had genuinely grown to. `align-items: stretch`
 * restores the normal flex behaviour so the grid fills whatever width the container below already
 * earns it. Verified live: with this rule, 1440px → 720px middle column, 1920px → 780px (was
 * 671.484px at both, unchanged).
 */
.sapply-page-container { max-width: 1080px; align-items: stretch; }
@media (min-width: 1180px) {
  .sapply-page-container { width: 100%; max-width: 1500px; }
}

.sapply {
  --sf-marine: #022938;
  --sf-silica: #F5EFE4;
  --sf-spray: #BFF6E5;
  --sf-scrub: #65654B;
  --sf-line: rgba(2, 41, 56, .22);
  /* ⛔ The FIELD underline is not decoration — it is the only thing identifying a text field on
     this component, so it is held to 3:1 as a control boundary rather than to the hairline the
     rest of the panel uses. rgba(2,41,56,.35) measured 2.09:1 on Silica. */
  --sf-line-field: rgba(2, 41, 56, .62);
  --sf-err: #a4262c;
  --sf-ok: #0C5064;

  /* Added for the 4a build spec, §1. Values only — the existing --sf-* names stay, a deliberate
     deviation from the spec's literal :root block (renaming ~630 lines of usages is pure churn). */
  --sf-label: #4A4A38;                          /* field labels. Scrub darkened. 7.9:1 on Silica */
  --sf-marine-deep: #04384c;                    /* busy button only */
  --sf-error-bg: #fdf3f3;
  --sf-on-marine-muted: rgba(245, 239, 228, .28); /* meter track, not text */

  font-family: 'Kodchasan', system-ui, -apple-system, sans-serif;
  color: var(--sf-marine);
  display: grid;
  /* Stacked BY DEFAULT. The two-column rail is the enhancement, added at 900px where it fits. */
  grid-template-columns: minmax(0, 1fr);
  gap: 18px;
  align-items: start;
}
.sapply * { box-sizing: border-box; }

/* ============================================================================================
 * THE STEP RAIL — the first of the three things that make this a portal rather than a form.
 *
 * It is a real <nav><ol> of real <button>s, so every step is keyboard reachable and a screen
 * reader is told which one is current (aria-current) and which are not yet open (aria-disabled).
 * State is never carried by colour alone: complete, current and locked each change the marker's
 * SHAPE and each carry a word.
 * ============================================================================================ */
.sapply-rail {
  position: static;
  min-width: 0;
}
.sapply-rail__heading {
  display: none;
  font-size: 13px; font-weight: 600; letter-spacing: .14em; text-transform: uppercase;
  /* ⛔ WAS --sf-scrub — 4a moved the page shell to Marine (spec §4) and this heading sits on it
     with no surface of its own. Scrub-on-Marine measured 2.4:1 (spec §1), same defect as the
     rail label/state fix below. Sea Spray is 12.9:1 on Marine. */
  color: var(--sf-spray); margin: 0 0 12px;
}
/* The compact bar. Shown until the rail has room to be a sidebar, where the list is always open. */
.sapply-rail__toggle {
  display: flex; align-items: center; gap: 10px; width: 100%; min-height: 48px;
  font-family: inherit; font-size: 13.5px; font-weight: 600; line-height: 1.3; text-align: left;
  letter-spacing: normal; text-transform: none;
  background: #fff; color: var(--sf-marine);
  border: 1px solid var(--sf-line); border-radius: 8px; padding: 11px 14px; cursor: pointer;
}
.sapply .sapply-rail__toggle:focus-visible {
  outline: 2px solid var(--sf-spray); outline-offset: 1px; box-shadow: 0 0 0 4px var(--sf-marine);
}
.sapply-rail__count { font-weight: 600; }
.sapply-rail__now { color: var(--sf-scrub); font-weight: 400; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sapply-rail__chev { margin-left: auto; flex: none; width: 10px; height: 10px; border-right: 2px solid var(--sf-marine); border-bottom: 2px solid var(--sf-marine); transform: rotate(45deg) translate(-2px, -2px); }
.sapply-rail__toggle[aria-expanded="true"] .sapply-rail__chev { transform: rotate(-135deg) translate(-2px, -2px); }

.sapply-rail__list { list-style: none; margin: 8px 0 0; padding: 0; display: grid; gap: 2px; }
.sapply-rail__list[hidden] { display: none; }
.sapply-rail__item, .sapply-rail__end { margin: 0; }
/* The endcap is where the buyer is on the review and confirmation screens, and it is deliberately
   NOT a .sapply-rail__item: there are six steps and there go on being six. */
.sapply-rail__end { margin-top: 6px; padding-top: 6px; border-top: 1px solid var(--sf-line); }
/*
 * ⛔⛔ CRITICAL — THE RAIL SITS ON TWO DIFFERENT GROUNDS, AND ONE COLOUR NEVER COVERS BOTH.
 *
 * Measured on the review screen (desktop readability fix, job 2648): `.sapply-rail__link{color:
 * var(--sf-marine)}` on rows with no ground of their own — every row except is-current/is-sent —
 * sits directly on the Marine page shell 4a's ground-inversion put under it (spec §4). Marine text
 * on a Marine ground measured rgb(2,41,56) on rgb(2,41,56) = 1.00:1. Every step name between the
 * ticks rendered invisible; Keith's screenshot showed ticks and "COMPLETE" with nothing readable
 * between them.
 *
 * The fix is NOT a single blanket colour, because is-current/is-sent — and any row on hover — get
 * a white/Silica pill background from the rules below, and Sea Spray on that ground is ~1.05:1
 * (documented elsewhere in this file). So: Sea Spray is the DEFAULT (Marine ground), overridden to
 * Marine wherever a rule below gives the row a white ground. State is still never colour alone —
 * the tick glyph, the dashed marker and the state word all stay.
 */
.sapply-rail__link {
  font-family: inherit; font-size: 14px; font-weight: 500; line-height: 1.35; text-align: left;
  letter-spacing: normal; text-transform: none;
  display: grid; grid-template-columns: 26px minmax(0, 1fr); gap: 10px; align-items: center;
  width: 100%; min-height: 44px; padding: 9px 10px;
  background: transparent; color: var(--sf-spray);
  border: 1px solid transparent; border-radius: 8px; cursor: pointer;
}
/* Hover always gives the row a white ground (below), except the disabled case further down, whose
   own :hover rule keeps the ground transparent — its aria-disabled selector is more specific than
   this one so it wins regardless of source order, keeping locked-row text Sea Spray on hover too. */
.sapply-rail__link:hover { background: #fff; border-color: var(--sf-line); color: var(--sf-marine); }
.sapply .sapply-rail__link:focus-visible { outline: 2px solid var(--sf-spray); outline-offset: 1px; box-shadow: 0 0 0 3px var(--sf-marine); }
/*
 * ⛔ NO OPACITY ON TEXT THAT HAS TO BE READ. `opacity:.72` over Scrub composited to #90907D on
 * white — 3.24:1 at 14px/500 and at 11px/600, where AA needs 4.5 — and it was the state FIVE of the
 * six rows are in on first load, so most of the rail failed on the screen a buyer opens first.
 * ⚠ UPDATED FOR 4a — this row never gets a white ground (its own :hover rule below keeps it
 * transparent), so it stays on Marine and needs Sea Spray, not Scrub: Scrub-on-Marine is 2.4:1
 * (spec §1), Sea Spray is 12.9:1. The state is carried by the word and the dashed marker besides,
 * never by colour alone.
 */
.sapply-rail__link[aria-disabled="true"] { cursor: not-allowed; color: var(--sf-spray); opacity: 1; }
.sapply-rail__link[aria-disabled="true"]:hover { background: transparent; border-color: transparent; }

.sapply-rail__num {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 26px;
  font-size: 12.5px; font-weight: 600; line-height: 1;
  border: 1px solid var(--sf-line); background: #fff; color: var(--sf-marine);
}
.sapply-rail__label { min-width: 0; }
/*
 * ⛔⛔ CRITICAL — SAME MARINE-GROUND DEFECT AS THE LABEL ABOVE, MEASURED SEPARATELY. rgb(101,101,75)
 * on rgb(2,41,56) = 2.55:1, where AA needs 4.5:1 — spec §1 names this pairing explicitly: "Never
 * put --sf-scrub on Marine — 2.4:1, fails." Sea Spray is the default for the same reason as the
 * label; the is-current/is-sent override below returns it to Scrub once the ground is white, which
 * is where Scrub already passes (5.97:1) and is how this rail read before the 4a ground inversion.
 */
.sapply-rail__state {
  grid-column: 2; font-size: 12.5px; font-weight: 600; letter-spacing: .1em;
  text-transform: uppercase; color: var(--sf-spray);
}
/* The disabled row never gets a white ground either (see the :hover rule above), so its state text
   stays Sea Spray on hover too — same reasoning as the label colour above, just a higher-specificity
   selector than a plain :hover rule would need, since it must beat any future generic hover-state
   rule regardless of source order. */
.sapply-rail__link[aria-disabled="true"]:hover .sapply-rail__state { color: var(--sf-spray); }

/* Complete: a filled marker carrying a tick GLYPH, plus the word "Complete". */
.sapply-rail__item.is-complete .sapply-rail__num,
.sapply-rail__end.is-sent .sapply-rail__num {
  background: var(--sf-marine); border-color: var(--sf-marine); color: var(--sf-spray);
}
/*
 * Current: a ring, and the row itself is the only one with a ground.
 * ⛔ CRITICAL — label + state text are stated here too, not inherited. Without this, the base
 * Sea Spray colours above (correct on Marine) would sit on THIS row's white/Silica background at
 * ~1.05:1 — the exact inverse of the defect this whole block fixes. Marine matches the pre-4a rail
 * (15.25:1 measured); Scrub for the state word matches the pre-4a rail too (5.97:1 on white).
 */
.sapply-rail__item.is-current .sapply-rail__link,
.sapply-rail__end.is-current .sapply-rail__link,
.sapply-rail__end.is-sent .sapply-rail__link {
  background: #fff; border-color: var(--sf-marine); color: var(--sf-marine);
}
.sapply-rail__item.is-current .sapply-rail__state,
.sapply-rail__end.is-current .sapply-rail__state,
.sapply-rail__end.is-sent .sapply-rail__state {
  color: var(--sf-scrub);
}
.sapply-rail__item.is-current .sapply-rail__num,
.sapply-rail__end.is-current .sapply-rail__num {
  background: var(--sf-spray); border-color: var(--sf-marine); border-width: 2px;
}
/*
 * Locked: a dashed marker, so the state survives a greyscale print and a colour-blind reader.
 * ⛔ The border colour is stated. Inherited it was --sf-line under opacity .72, compositing to
 * #D7DDDF on white = 1.37:1, where a control boundary needs 3:1. Scrub is 5.97:1 on the white
 * .sapply-rail__num circle background — a different ground to the row's own text (see above), so
 * Scrub is correct HERE even though the row's own label/state text needs Sea Spray on Marine.
 */
.sapply-rail__item.is-locked .sapply-rail__num,
.sapply-rail__end.is-locked .sapply-rail__num {
  border-style: dashed; border-color: var(--sf-scrub); color: var(--sf-scrub);
}

/* ============================================================================================
 * THE PROGRESS METER — 4a spec §8. Replaces the rail below 900px; the two never coexist in the
 * DOM (public/js/salt-offer-form.js decides which markup to build, keyed to the same 900px line
 * the rail's own sidebar breakpoint below uses), so there is no rail-vs-meter display rule to
 * write here — only a defensive one at the bottom of this block.
 *
 * ⚠ CONTRAST — this sits on the Marine page shell, outside the Silica panel. Spec §1: Scrub on
 * Marine is 2.4:1 and fails. Every line here is Sea Spray, never Scrub.
 *
 * ⚠ THE TEXT IS THE ACCESSIBLE POSITION ANNOUNCEMENT. "Step 3 of 6" and the step name are real
 * content, read in normal document order ahead of the panel — not a caption on a decoration. The
 * track below is aria-hidden because the six coloured bars carry no fact those two lines do not
 * already say in words.
 * ============================================================================================ */
.sapply-meter { margin: 0 0 20px; }
.sapply-meter__count {
  font-size: 12.5px; font-weight: 600; letter-spacing: .1em; text-transform: uppercase;
  color: var(--sf-spray); margin: 0 0 4px;
}
.sapply-meter__name { font-size: 15px; font-weight: 600; color: var(--sf-spray); margin: 0 0 10px; }
.sapply-meter__track { display: flex; gap: 4px; }
.sapply-meter__seg {
  flex: 1 1 0; min-width: 0; height: 6px; border-radius: 3px; background: var(--sf-on-marine-muted);
}
.sapply-meter__seg.is-done { background: var(--sf-spray); }
.sapply-meter__seg.is-current {
  background: var(--sf-spray);
  box-shadow: 0 0 0 2px var(--sf-marine), 0 0 0 3px var(--sf-silica);
}
.sapply-meter__next { font-size: 13px; color: var(--sf-spray); margin: 10px 0 0; }
.sapply-meter__next[hidden] { display: none; }
@media (min-width: 900px) {
  /* Defence in depth: the JS never builds this markup at 900px and up, but a display rule costs
     nothing and fails towards "nothing shown" rather than a stray bar if it ever did. */
  .sapply-meter { display: none; }
}

/* ============================================================================================
 * THE SAVE STATE — the second of the three. There is no save button anywhere in this component,
 * on purpose, so this line is the only thing that tells a buyer their work is held.
 *
 * aria-live="polite" is set in the markup, not here: a state a sighted person watches change has
 * to be announced to a person who cannot.
 * ============================================================================================ */
.sapply-save {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 13px; line-height: 1.4; color: var(--sf-scrub); margin: 0 0 16px; min-height: 20px;
}
.sapply-save__dot {
  width: 8px; height: 8px; border-radius: 8px; flex: none;
  background: var(--sf-line); border: 1px solid var(--sf-line);
}
.sapply[data-save-state="saving"] .sapply-save__dot { background: var(--sf-spray); }
.sapply[data-save-state="saved"] .sapply-save__dot { background: var(--sf-spray); border-color: var(--sf-marine); }
.sapply[data-save-state="saved"] .sapply-save { color: var(--sf-ok); }
.sapply[data-save-state="error"] .sapply-save__dot { background: var(--sf-err); border-color: var(--sf-err); }
.sapply[data-save-state="error"] .sapply-save { color: var(--sf-err); }

/* ============================================================================================
 * THE PANEL
 * ============================================================================================ */
/* ⚠ THE SHADOW IS LOAD-BEARING NOW — 4a spec §4. The panel used to read against a white shell, so
   its own Silica ground was enough of an edge. The shell went Marine in this phase and a Silica
   panel on a Marine ground with no separation reads as flat, not lifted — the shadow is what says
   "this sits above the page" once colour contrast alone cannot carry that job. */
.sapply-panel {
  background: var(--sf-silica); border-radius: 8px; padding: 24px 20px; min-width: 0;
  box-sizing: border-box; box-shadow: 0 8px 28px rgba(0, 0, 0, .28);
}
.sapply-panel:focus { outline: none; }
/* ⛔ A 2px Sea Spray ring on Silica is 1.05:1 — invisible, and it was the focus state of the
   region every step move lands on. Marine outside it, as the chips and block cards already do. */
.sapply .sapply-panel:focus-visible {
  outline: 2px solid var(--sf-spray); outline-offset: 2px; box-shadow: 0 0 0 5px var(--sf-marine);
}

/* ⚠ THE BAND IS GONE — 4a spec §4. It used to be a Sea Spray strip bled to the panel edge with a
   negative margin (`-24px -20px`); the eyebrow, title, blurb and required-note now sit as plain
   content on the Silica panel, in the panel's own padding, keeping only the bottom margin that
   separates them from the save line beneath. Marine and Ocean text (below) are already fine on
   Silica — this removal changes ground, not colour. */
.sapply-panel__head { margin: 0 0 22px; }
.sapply-panel__eyebrow {
  font-size: 12.5px; font-weight: 600; letter-spacing: .12em; text-transform: uppercase;
  color: var(--sf-ok); margin: 0 0 8px;
}
.sapply-panel__title {
  font-family: 'Ruthfully', 'Kodchasan', cursive; font-weight: 400;
  font-size: 34px; line-height: 1.1; margin: 0 0 6px; color: var(--sf-marine);
}
.sapply-panel__blurb { font-size: 16px; line-height: 1.6; color: var(--sf-marine); margin: 0; }
/* The required-field convention, stated rather than inferred from the absence of "(optional)". */
.sapply-panel__req { font-size: 12.5px; line-height: 1.5; color: var(--sf-ok); margin: 8px 0 0; font-weight: 600; }

/* 4a spec §9 — "Panel holds a 56ch measure on prose and 560px on field rows, so extra width
   becomes air, not line length." Width the grid earns above 900px goes to the panel's own
   padding, not to longer lines. The two named exceptions — the block grid (.sapply-blocks) and
   the review cards (.sapply-review, given its own two-column rule in the 900px breakpoint above)
   — are why .sapply-fieldset is NOT in the selector list below even though .sapply-row and
   .sapply-names are: block_slug's chip-style choice renders as a <fieldset class="sapply-fieldset">
   wrapping .sapply-blocks, so capping every fieldset would cap the one grid the spec names as
   exempt. */
.sapply-panel__blurb, .sapply-legal, .sapply-hint, .sapply-busy-note, .sapply-panel__req {
  max-width: 56ch;
}
/*
 * ⛔ SENIOR UI/UX PASS — `.sapply-grid` WAS MISSING FROM THIS LIST, MEASURED.
 *
 * Without a cap, .sapply-grid's own content width (up to the panel's full 560px field measure
 * plus whatever the panel itself has) left `repeat(auto-fit, minmax(180px, 1fr))` genuinely
 * enough room for a THIRD track at ordinary panel widths, which auto-fit then collapses to 0px
 * because there are only ever two fields (email + mobile, or similar) to put in it. Measured at
 * 768/899/1024/1179/1440px against the built page: `grid-template-columns` computed
 * "294.734px 294.75px 0px" — a real, if invisible, third grid track nobody asked for. Capping
 * this row at the same 560px every other field row already respects removes the spare room the
 * 3rd track needs to exist at all: two 180px-minimum tracks plus one 18px gap never leaves 180px
 * over inside 560px, so auto-fit can only ever resolve two tracks here now.
 */
.sapply-row, .sapply-names, .sapply-grid {
  max-width: 560px;
}

/* ---------- fields ---------- */
.sapply-row { margin: 0 0 20px; min-width: 0; }
/*
 * ⛔ THESE ROWS ARE LAID OUT BY THE WIDTH THEY HAVE, NOT BY THE WIDTH OF THE WINDOW.
 *
 * `1fr 1fr 1fr` on the name row was three columns at every viewport above the breakpoint, so when
 * the rail took its 260px column the three name fields were sharing 226px and a surname rendered
 * as "Whitcon". auto-fit + minmax gives back exactly as many columns as actually fit, at any panel
 * width, with no breakpoint to keep in step.
 */
.sapply-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 18px; }
.sapply-names { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 16px; margin: 0 0 18px; }

/*
 * ⛔ SENIOR UI/UX PASS (job 2648) — "Middle name (optional)" WRAPPING MISALIGNED ITS INPUT,
 * MEASURED. Three name fields packed to two columns at 402px and again at 600px: First name's
 * one-line label left its input top at one Y; Middle name's "Middle name (optional)" wrapped to
 * two lines in the SAME visual row and pushed its input 19.5–20px lower — a ragged, obviously
 * broken baseline on the form's own first screen. Not a Middle-name-specific bug: ANY label that
 * wraps while its row neighbour does not will misalign the same way (each field's input follows
 * directly after its OWN label in normal block flow, so a taller label alone pushes its input
 * down regardless of the sibling), so the fix is structural rather than a label rewrite.
 *
 * CSS SUBGRID, not a min-height reservation on the label. A min-height tall enough to always
 * cover a two-line label would sit as permanent dead space at every width where nothing wraps
 * (500px+, 700px+), which this pass's brief rules out explicitly. Subgrid costs nothing when
 * nothing wraps: each field below (.sapply-row) adopts two of ITS ROW's own row tracks — one for
 * the label, one for everything after it — so the label track's height is the tallest label
 * actually sharing that physical line of columns, and every input on that line starts on the
 * same grid line regardless of how many lines its own label took. When only one column fits
 * (narrow enough that fields stack, one per line) each field is alone in its own row pair, so
 * there is nothing to align against and no dead space either way. Supported since Safari
 * 16/Chrome 117/Firefox 71 — nothing else on this estate needs to fall back further than that.
 */
.sapply-names > .sapply-row,
.sapply-grid > .sapply-row {
  display: grid;
  grid-template-rows: subgrid;
  grid-row: span 2;
  /*
   * The label→input gap stays the label's own 8px bottom margin (below, unchanged) — that is
   * spec §3's "label → input: 8" and this subgrid must not touch it. This row's OWN row-gap is
   * zeroed so the CONTAINER's 16/18px gap (declared two rules up) is spent once, between
   * physical rows of wrapped fields, rather than doubled a second time inside a single field
   * between its own label and input. Subgrid items are explicitly allowed their own gap distinct
   * from the parent grid's, which is what this relies on.
   */
  row-gap: 0;
  /* Overrides the base .sapply-row 20px bottom margin (spec's field-to-field spacing) — the
     vertical space between wrapped physical rows here is the container's own gap above, not a
     second margin stacked underneath it, which would otherwise both apply. */
  margin-bottom: 0;
}

/* ⛔ 12px UPPERCASE .12em SCRUB WAS THE CLIENT'S READABILITY COMPLAINT. 4a spec §2: 15px, sentence
   case, normal tracking, on --sf-label (Scrub darkened, 7.9:1 on Silica) — a legible label rather
   than a decorative one. */
.sapply-label {
  display: block; font-size: 15px; line-height: 1.3; font-weight: 600; letter-spacing: normal;
  text-transform: none; color: var(--sf-label); margin: 0 0 8px;
}
.sapply-label .opt { font-weight: 400; letter-spacing: .04em; text-transform: none; }
.sapply-hint { font-size: 14px; line-height: 1.55; color: var(--sf-scrub); margin: 8px 0 0; }

/*
 * ⛔ 16px IS A FLOOR, NOT THE TARGET. iOS Safari zooms the whole page when a focused input computes
 * under 16px, which throws the layout mid-typing and reads as the form breaking. 17px here — one
 * step above that floor, per 4a spec §2 — reads more comfortably without going anywhere near the
 * zoom trigger. Flat px here rather than max(16px,1em): this component sets its own type scale and
 * inherits no fluid body ramp, so the two expressions would resolve to the same number and the
 * plain one says what it means.
 */
.sapply-input, .sapply-select, .sapply-textarea {
  font-family: inherit; font-size: 17px; line-height: 1.4; color: var(--sf-marine);
  width: 100%; min-height: 46px; padding: 11px 2px;
  background: transparent; border: 0; border-bottom: 1px solid var(--sf-line-field);
  border-radius: 0; appearance: none;
}
.sapply-textarea { min-height: 92px; resize: vertical; padding-top: 12px; }
.sapply-input:hover, .sapply-select:hover, .sapply-textarea:hover { border-bottom-color: var(--sf-marine); }
/*
 * ⛔ THE FOCUS RING IS MARINE ON THE OUTSIDE, and that is the whole point. Sea Spray alone measured
 * 1.05:1 on Silica and 1.20:1 on white — a focus state nobody can see is a focus state that is not
 * there, on every text field of a half-million-dollar offer. Same compound treatment the chips and
 * the block cards have carried since the file was written.
 */
.sapply .sapply-input:focus, .sapply .sapply-select:focus, .sapply .sapply-textarea:focus {
  border-bottom: 2px solid var(--sf-marine);
  outline: 2px solid var(--sf-spray); outline-offset: 2px;
  box-shadow: 0 0 0 5px var(--sf-marine);
}
/*
 * ⚠ AFTER :focus DELIBERATELY. Both rules score the same, so source order decides, and a field
 * that is focused AND invalid must stay Error — the error is the thing the person has to act on,
 * and focus is still shown by the Sea Spray outline, which this does not touch.
 */
.sapply-input[aria-invalid="true"], .sapply-select[aria-invalid="true"], .sapply-textarea[aria-invalid="true"] {
  border-bottom-color: var(--sf-err); border-bottom-width: 2px;
}
/* A read-only amount is a STATEMENT, not a field. It must not look typeable. */
.sapply-input[readonly] {
  color: var(--sf-marine); border-bottom-style: dotted; background: rgba(255, 255, 255, .55);
  padding-left: 10px; padding-right: 10px; cursor: default;
}
.sapply-input[readonly]:hover { border-bottom-color: var(--sf-line-field); }
/* 4a spec §11 — the postcode fallback is the one text field on this form with a fixed, narrow
   width: it holds four digits, and stretching it to the row's full width would ask a buyer to
   read an empty field the size of an address for a four-character answer. */
.sapply-input--narrow { width: 120px; max-width: 100%; }

.sapply-select {
  background-image:
    linear-gradient(45deg, transparent 50%, var(--sf-marine) 50%),
    linear-gradient(135deg, var(--sf-marine) 50%, transparent 50%);
  background-repeat: no-repeat; background-size: 5px 5px, 5px 5px;
  background-position: calc(100% - 12px) 21px, calc(100% - 7px) 21px;
  padding-right: 28px;
}

/*
 * 4a spec §6 — 14px/500 Error, plus an 18px round "!" glyph. flex rather than block so the glyph
 * sits beside the first line rather than above it; ::before rather than an extra DOM node, since
 * the badge is decorative and the message text alone is what a screen reader needs.
 */
.sapply-err { display: none; align-items: flex-start; gap: 6px; font-size: 14px; font-weight: 500; line-height: 1.5; color: var(--sf-err); margin-top: 6px; }
.sapply-err.is-shown { display: flex; }
.sapply-err.is-shown::before {
  content: '!'; flex: none; margin-top: 1px;
  width: 18px; height: 18px; border-radius: 50%;
  background: var(--sf-err); color: var(--sf-silica);
  font-size: 12px; font-weight: 700; line-height: 18px; text-align: center;
}

/* The honeypot. Off-screen rather than display:none — some bots skip hidden inputs. */
.sapply-hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* ---------- tap-chips: any choice of four or fewer ---------- */
.sapply-fieldset { border: 0; padding: 0; margin: 0 0 22px; min-width: 0; }
.sapply-legend {
  font-size: 18px; line-height: 1.35; font-weight: 600; padding: 0; margin: 0 0 8px; color: var(--sf-marine);
}

/* ⛔ .sapply-condgroup / .sapply-condgroup__intro REMOVED, job 2648 4a spec §13.3. They styled the
   old "at least one of these three" fieldset — one shared error above the set. Step 4's three
   condition fields are now independent yes/no gates, each its own chipField()/textField() with its
   own error, so nothing renders this class any more. */
/* No fixed widths anywhere: one line where there is room, never past the viewport, never crushed,
   and the ROW wraps. Three earlier attempts at fixed widths on this estate each broke something. */
.sapply-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.sapply-chip {
  font-family: inherit; font-size: 14.5px; font-weight: 500; line-height: 1.2;
  letter-spacing: normal; text-transform: none; text-align: left;
  background: #fff; color: var(--sf-marine); border: 1px solid var(--sf-line);
  border-radius: 24px; cursor: pointer;
  width: max-content; max-width: 100%; min-width: min-content; min-height: 44px; padding: 10px 18px;
}
.sapply-chip:hover { border-color: var(--sf-scrub); background: var(--sf-silica); }
.sapply .sapply-chip:focus-visible { outline: 2px solid var(--sf-spray); outline-offset: 1px; box-shadow: 0 0 0 3px var(--sf-marine); }
/* Selected INVERTS as well as recolouring, so the state never depends on colour alone. */
.sapply-chip.is-selected, .sapply-chip[aria-pressed="true"] {
  background: var(--sf-marine); border-color: var(--sf-marine); color: var(--sf-silica);
}
.sapply-chip.is-selected:hover, .sapply-chip[aria-pressed="true"]:hover {
  background: var(--sf-marine); border-color: var(--sf-marine); color: var(--sf-silica);
}

/* ---------- the block grid ---------- */
/* auto-fill off the panel's real width, so the card grid needs no breakpoint either. */
.sapply-blocks { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; }
/* The whole card is the tap target, so it is a <button>, not a card with a link inside it. */
.sapply-blockcard {
  font-family: inherit; font-size: 14px; font-weight: 400; line-height: 1.4;
  letter-spacing: normal; text-transform: none; text-align: left;
  background: #fff; color: var(--sf-marine); border: 1px solid var(--sf-line);
  border-radius: 8px; padding: 14px 16px; cursor: pointer;
  display: grid; gap: 4px; min-height: 44px; width: auto; max-width: 100%; min-width: 0;
}
.sapply-blockcard:hover { border-color: var(--sf-marine); background: var(--sf-silica); }
.sapply .sapply-blockcard:focus-visible { outline: 2px solid var(--sf-spray); outline-offset: 1px; box-shadow: 0 0 0 4px var(--sf-marine); }
/* ⛔ CHOSEN INVERTS — 4a spec §4. Marine ground, 2px Marine border (the border keeps the same
   footprint as the unselected 1px card so nothing reflows; it does not need to contrast against
   its own fill). All three lines of card text are Marine in the base rules above, which is
   correct on the white/Silica-hover unselected card and INVISIBLE at 1:1 on this ground, so every
   one of them is restated in Sea Spray here rather than only the figure the spec calls out by
   name — the block number and the price render real text too (public/js/salt-offer-form.js), and
   a card that goes silently blank on the one state a buyer has actually chosen is worse than a
   contrast miss on a state nobody is looking at. */
.sapply-blockcard[aria-pressed="true"] {
  border-color: var(--sf-marine); border-width: 2px; background: var(--sf-marine);
}
.sapply-blockcard[aria-pressed="true"] .no { color: var(--sf-spray); }
.sapply-blockcard[aria-pressed="true"] .sz { color: var(--sf-spray); }
.sapply-blockcard[aria-pressed="true"] .pr { color: var(--sf-spray); }
.sapply-blockcard .no { font-size: 12px; font-weight: 600; letter-spacing: .12em; text-transform: uppercase; color: var(--sf-scrub); }
.sapply-blockcard .sz { font-size: 20px; font-weight: 600; color: var(--sf-marine); }
.sapply-blockcard .pr { font-size: 14px; color: var(--sf-marine); }

/* ---------- the acknowledgement panel: room to be read ---------- */
.sapply-ack { background: #fff; border: 1px solid var(--sf-line); border-radius: 8px; padding: 20px 22px; margin: 0 0 18px; }
.sapply-ack__body { font-size: 14.5px; line-height: 1.75; color: var(--sf-marine); margin: 0 0 16px; max-width: 62ch; }
.sapply-ack__docs { display: flex; flex-wrap: wrap; gap: 10px 20px; margin: 0 0 16px; }
/*
 * ⚠ THE UNDERLINE IS THE AFFORDANCE, and it has to be stated. The ported theme strips underlines
 * from anchors, so these two rendered as bold text that happened to be clickable — on the one
 * screen where a buyer is being asked to confirm they have READ two documents. Weight alone is not
 * a link.
 */
.sapply-ack__docs a {
  font-size: 13.5px; font-weight: 600; color: var(--sf-marine);
  text-decoration: underline; text-underline-offset: 3px;
  min-height: 44px; display: inline-flex; align-items: center;
}
.sapply-ack__docs a:hover { color: #04384c; }
.sapply .sapply-ack__docs a:focus-visible { outline: 2px solid var(--sf-spray); outline-offset: 2px; box-shadow: 0 0 0 3px var(--sf-marine); }
/*
 * ⛔ A CAVEAT ABOUT OUR MISSING DOCUMENT IS A NOTICE, NOT AN ERROR.
 *
 * This block was --sf-err text on #fdf3f3 inside a red border — byte for byte the treatment of
 * .sapply-alert.err, which is what a buyer sees when something they did was REJECTED. On step 6 it
 * announced a gap on our side and read as a fault on theirs, next to the box they are being asked
 * to tick. Silica ground, Marine text, an Ocean rule down the left: informative, and not alarming.
 */
.sapply-ack__notice {
  font-size: 12.5px; line-height: 1.6; color: var(--sf-marine); background: var(--sf-silica);
  border: 1px solid var(--sf-line); border-left: 3px solid var(--sf-ok);
  border-radius: 6px; padding: 10px 12px; margin: 0 0 16px;
}
.sapply-ack__notice a { color: var(--sf-marine); text-decoration: underline; text-underline-offset: 2px; }
/* A checkbox is a real checkbox. Its label is the tap target and clears 44px on its own. */
.sapply-check { display: grid; grid-template-columns: 22px minmax(0, 1fr); gap: 12px; align-items: start; cursor: pointer; min-height: 44px; padding: 6px 0; }
.sapply-check input { width: 22px; height: 22px; margin: 1px 0 0; accent-color: #022938; cursor: pointer; }
/* ⚠ colour AND weight restated: the theme paints <label> in a lighter Scrub, and an
 * acknowledgement a buyer is legally confirming must not be the palest text on its own panel. */
.sapply-check span {
  font-family: inherit; font-size: 14.5px; font-weight: 400; line-height: 1.55;
  letter-spacing: normal; text-transform: none; color: var(--sf-marine);
}
.sapply .sapply-check input:focus-visible { outline: 2px solid var(--sf-spray); outline-offset: 2px; box-shadow: 0 0 0 3px var(--sf-marine); }

/* ---------- the review screen ---------- */
.sapply-review { display: grid; gap: 18px; margin: 0 0 22px; }
/*
 * ⛔⛔ CRITICAL — THE OFFER AMOUNT WRAPPED MID-NUMBER. Measured on this exact card: at 900/1024/
 * 1280/1440/1920px the two-across review grid below (900px block, .sapply-review) gives each card
 * ~239–296px outer width. The old side-by-side dt/dd rule gave the label column a fixed 120px
 * minimum plus a 16px gap before the value ever got a pixel, so the VALUE column measured 44–117px
 * wide — "$550,000" wrapped onto 2–3 lines. 1180px alone read as one line, by accident: the auto-fit
 * review grid collapses to a single column exactly at that width band, which happened to hand the
 * whole card back to one dt/dd pair — proof the defect was the CARD's width, not the number.
 *
 * FIX: `container-type` here makes this card the query context for its own dt/dd layout below,
 * instead of the page viewport. A card gets the side-by-side treatment only once IT is wide enough
 * to give the value real room — never mind what breakpoint the page itself is sitting at.
 * Registering the container is inert on its own (nothing queries it yet); the @container rule
 * that actually changes the layout lives inside the ≥900px block further down, so phones stay on
 * the plain stacked .sapply-review__rows rule below and are untouched by this fix.
 */
.sapply-review__sec { background: #fff; border: 1px solid var(--sf-line); border-radius: 8px; padding: 18px 20px; container-type: inline-size; }
.sapply-review__head { display: flex; flex-wrap: wrap; gap: 10px; align-items: baseline; justify-content: space-between; margin: 0 0 12px; }
/*
 * ⚠ font-family IS LOAD-BEARING HERE, and it was found by looking at a screenshot rather than by
 * reading the CSS. This is an <h3>, and the ported theme sets every heading to the Ruthfully
 * display script — so "1. Your details" rendered as 15px handwriting and was, in practice,
 * unreadable. The two headings that are MEANT to be Ruthfully say so explicitly; every other
 * heading in this component takes the body face back.
 */
.sapply-review__title { font-family: inherit; font-size: 15px; font-weight: 600; margin: 0; color: var(--sf-marine); }
/* Edit in place: a real button that returns to the step, not a link to a page. */
.sapply-review__edit {
  font-family: inherit; font-size: 13.5px; font-weight: 600; line-height: 1.4;
  letter-spacing: normal; text-transform: none;
  color: var(--sf-marine); background: none; border: 0;
  border-bottom: 1px solid rgba(2, 41, 56, .35); border-radius: 0;
  padding: 12px 0; margin: -12px 0; min-height: 44px; cursor: pointer; width: auto;
}
.sapply-review__edit:hover { border-bottom-color: var(--sf-marine); }
.sapply .sapply-review__edit:focus-visible {
  outline: 2px solid var(--sf-spray); outline-offset: 2px; box-shadow: 0 0 0 5px var(--sf-marine);
}
.sapply-review__rows { margin: 0; display: grid; grid-template-columns: minmax(0, 1fr); gap: 2px 0; }
.sapply-review__rows dt { font-size: 12px; font-weight: 600; letter-spacing: .1em; text-transform: uppercase; color: var(--sf-scrub); }
.sapply-review__rows dd { margin: 0 0 10px; font-size: 14.5px; line-height: 1.5; overflow-wrap: anywhere; }
/*
 * ⛔ THE OFFER AMOUNT IS THE NUMBER THIS ENTIRE DOCUMENT IS ABOUT. It was set identically to MIDDLE
 * NAME — same size, same weight, same row — on the screen where a buyer confirms half a million
 * dollars. Nothing else on the review is allowed to be this loud.
 */
.sapply-review__rows dd[data-review-field="offer_amount"] {
  font-size: 26px; font-weight: 600; line-height: 1.25; letter-spacing: .01em;
}
.sapply-review__rows dd[data-review-field="deposit_amount"] { font-size: 17px; font-weight: 600; }

/* ---------- actions ---------- */
.sapply-actions { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; margin: 22px 0 0; }
/*
 * ⛔ THE ONE CONTROL ON THE REVIEW THAT MATTERS. Keith, 22/08: "its not clear where the button to
 * 'submit' the offer sitting over to the right i wasnt aware it was there."
 *
 * 4a §9 had moved Send into the summary column. That instruction is withdrawn — see the long note
 * on renderSummary() in salt-offer-form.js for why it failed in BOTH directions, invisible below
 * 1180px and unnoticed above it. Returning it to the action row fixes the first half; this rule
 * fixes the second.
 *
 * ⚠ NOT just bigger. `flex-basis:100%` forces its own ROW, so it never sits beside Back competing
 * for the same glance — on every other step Continue and Back are peers, and on this one screen
 * they are not. It ends up directly under the last answer, which is where reading finishes and
 * where the hand goes next.
 */
.sapply-btn.is-send {
  flex-basis: 100%; width: 100%; min-height: 60px;
  font-size: 15px; letter-spacing: .06em;
  order: 2;                       /* below Back, whatever order the markup puts them in */
  box-shadow: 0 6px 20px rgba(2, 41, 56, .28);
}
.sapply-actions:has(.sapply-btn.is-send) [data-back] { order: 1; }
.sapply-btn {
  font-family: inherit; font-size: 13px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  color: var(--sf-silica); background: var(--sf-marine); border: 1px solid var(--sf-marine);
  border-radius: 8px; padding: 15px 30px; cursor: pointer;
  width: max-content; max-width: 100%; min-width: min-content; min-height: 48px;
}
.sapply-btn:hover { background: #04384c; }
/* ⛔ Sea Spray on Marine is fine INSIDE the button and useless as a ring on the Silica panel
   outside it — 1.05:1 measured, on the primary control of every step. Marine outermost. */
.sapply .sapply-btn:focus-visible {
  outline: 2px solid var(--sf-spray); outline-offset: 1px; box-shadow: 0 0 0 4px var(--sf-marine);
}
.sapply-btn.ghost { background: #fff; color: var(--sf-marine); }
/* 4a spec §9 — "Send moves into the third column and inverts to Sea Spray on Marine." The base
   .sapply-btn rule above is already Marine ground; this is the one place a Sea Spray FOREGROUND on
   it is warranted, so only the text (and, via the shared :focus-visible/[disabled]/.is-busy rules
   already above and below this one, nothing else) changes. */
.sapply-btn.invert { color: var(--sf-spray); }
/* The confirmation's route back to the estate is an anchor wearing the button. */
a.sapply-btn {
  display: inline-flex; align-items: center; justify-content: center; text-decoration: none;
}
.sapply-btn.ghost:hover { background: var(--sf-silica); }
.sapply-btn[disabled], .sapply-btn.is-disabled {
  background: #E5DFD3; border-color: #E5DFD3; color: var(--sf-scrub); cursor: not-allowed; opacity: 1;
}
/*
 * ⛔ 4a spec §7 — cursor:progress DROPPED. It used to be the ONLY signal a busy button gave, and a
 * touch screen has no cursor at all — so every buyer on a phone or tablet saw nothing change while
 * a save or a submit was in flight. The button also carries `disabled` while busy (setButtonBusy()
 * in the JS), so `.sapply-btn[disabled]` above still supplies a `not-allowed` cursor for anyone
 * with a mouse; this rule no longer needs to declare one of its own. --sf-marine-deep is the same
 * colour that was hard-coded here before (#04384c), now the token added in phase 1 — kept VISUALLY
 * DISTINCT from the #E5DFD3 disabled-not-busy treatment above: one means "not yet", the other means
 * "working", and source order (this rule after [disabled]) is what makes busy win when both are
 * true at once, exactly as it did before this edit.
 */
.sapply-btn.is-busy { background: var(--sf-marine-deep); border-color: var(--sf-marine-deep); }
/* The 16px Sea Spray spinner inside the busy button — 4a spec §7. Built as a <span> in the JS
   (setButtonBusy()), not CSS content, so it can sit before the label text node. */
.sapply-spinner {
  display: inline-block; width: 16px; height: 16px; margin: 0 8px -3px 0;
  border-radius: 50%; border: 2px solid rgba(191, 246, 229, .35); border-top-color: var(--sf-spray);
  animation: sapply-spin .8s linear infinite;
}
@keyframes sapply-spin { to { transform: rotate(360deg); } }
/* ⛔ 4a spec §7 — RESPECTS prefers-reduced-motion. The spinner still reads as "busy" from its
   colour and position; it just stops turning. */
@media (prefers-reduced-motion: reduce) {
  .sapply-spinner { animation: none; }
}
/* The "keep this window open" reassurance line under the button — 4a spec §7. Empty and silent
   until submit() writes to it (setBusyNote()); aria-live="polite" is already on the node in the
   markup. Sits on the Silica panel, so --sf-scrub is the right token — never on Marine (spec §1). */
.sapply-busy-note { font-size: 14px; line-height: 1.55; color: var(--sf-scrub); margin: 10px 0 0; min-height: 0; }

.sapply-legal { font-size: 13px; line-height: 1.6; color: var(--sf-scrub); margin: 18px 0 0; }
.sapply-turnstile:not(:empty) { margin-top: 16px; }

/* ---------- the rejected alert ----------
 * A rejection is ALWAYS visible. This is the state that matters most on this component: its
 * predecessor on this estate failed silently and a buyer believed they had enquired when they had
 * not. A 422 price rejection renders the server's own sentence, verbatim and loud — never a
 * clamp, never a round, never accept-and-correct. */
.sapply-alert { display: none; margin: 18px 0 0; padding: 14px 16px; border-radius: 8px; font-size: 13.5px; line-height: 1.6; }
.sapply-alert.is-shown { display: block; }
.sapply-alert.err { background: #fdf3f3; border: 1px solid var(--sf-err); color: var(--sf-marine); }
.sapply-alert.err strong { color: var(--sf-err); font-weight: 600; display: block; margin-bottom: 4px; }
.sapply-alert.err a { color: inherit; text-decoration: underline; }
/* ⛔ 4a spec §6 — tabindex="-1" in the markup makes this node a programmatic focus target for
   showValidationSummary(), the same pattern .sapply-panel already uses on a step change. No native
   ring on Silica-adjacent grounds — Sea Spray outline with Marine outside it, same compound
   treatment every other focusable control on this component carries. */
.sapply-alert:focus { outline: none; }
.sapply .sapply-alert:focus-visible {
  outline: 2px solid var(--sf-spray); outline-offset: 2px; box-shadow: 0 0 0 4px var(--sf-marine);
}
/* The Continue-time validation summary's line list — 4a spec §6. Real buttons, not links, so each
   one clears 44px and behaves like the rest of this component's tap targets. */
.sapply-alert__list { list-style: none; margin: 10px 0 0; padding: 0; display: grid; gap: 2px; }
.sapply-alert__line {
  font-family: inherit; font-size: 13.5px; font-weight: 600; line-height: 1.4; text-align: left;
  letter-spacing: normal; text-transform: none;
  color: var(--sf-err); background: none; border: 0; border-radius: 6px;
  text-decoration: underline; text-underline-offset: 2px;
  display: block; width: 100%; min-height: 44px; padding: 10px 8px; margin: 0; cursor: pointer;
}
.sapply-alert__line:hover { background: rgba(164, 38, 44, .08); }
.sapply .sapply-alert__line:focus-visible {
  outline: 2px solid var(--sf-spray); outline-offset: 1px; box-shadow: 0 0 0 3px var(--sf-marine);
}
/* The 422 is fixed on step 3 and nowhere else, so the alert carries the way there. */
.sapply-alert__act {
  font-family: inherit; font-size: 13px; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; text-align: center;
  display: block; margin: 12px 0 0; min-height: 44px; padding: 12px 22px; cursor: pointer;
  color: var(--sf-silica); background: var(--sf-marine); border: 1px solid var(--sf-marine);
  border-radius: 8px; width: max-content; max-width: 100%;
}
.sapply-alert__act:hover { background: #04384c; }
.sapply .sapply-alert__act:focus-visible {
  outline: 2px solid var(--sf-spray); outline-offset: 1px; box-shadow: 0 0 0 4px var(--sf-marine);
}

/* ---------- the confirmation ---------- */
.sapply-done { padding: 0; }
/* ⚠ THE BAND IS GONE — 4a spec §4, same removal as .sapply-panel__head above. The heading is the
   band's only child (public/js/salt-offer-form.js) and stays Marine-on-Silica, which was already
   the colour pairing under the old Sea Spray band and remains fine on the plain panel ground. */
.sapply-done__band { margin: 0 0 22px; }
.sapply-done__band h2 { font-family: 'Ruthfully', 'Kodchasan', cursive; font-weight: 400; font-size: 32px; line-height: 1.15; margin: 0; color: var(--sf-marine); }
/* ⛔ SAME INVERTED TREATMENT AS .sapply-stated — 4a spec §4 names this element explicitly. Marine
   block instead of a white card with a Marine outline (the outline is dropped: a 1px Marine
   border reads as nothing on a Marine fill, so it was dead weight once the ground changed). Scrub
   fails on Marine at ~2.5:1 (spec measured 2.4:1) so the label moves off it to Sea Spray, matching
   the figure. */
.sapply-done__ref {
  background: var(--sf-marine); border-radius: 8px; padding: 16px 18px; margin: 0 0 18px;
}
.sapply-done__ref .lab { display: block; font-size: 11.5px; font-weight: 600; letter-spacing: .14em; text-transform: uppercase; color: var(--sf-spray); margin: 0 0 6px; }
.sapply-done__ref .val { font-size: 24px; font-weight: 600; letter-spacing: .04em; color: var(--sf-spray); overflow-wrap: anywhere; }
.sapply-done p { font-size: 14.5px; line-height: 1.7; color: var(--sf-marine); margin: 0 0 14px; max-width: 58ch; }
/*
 * ⚠ THE SAME DEFECT .sapply-ack__docs a WAS WRITTEN TO FIX, ON THE SCREEN AFTER IT — and this half
 * was missed. The ported theme strips underlines from anchors, so the email address and the phone
 * number on the confirmation — the ONLY two ways to reach a person about an offer that has just
 * been sent — computed text-decoration:none, the parent's colour and the parent's weight. Nothing
 * whatever marked them as links.
 */
.sapply-done a:not(.sapply-btn) {
  color: var(--sf-marine); font-weight: 600;
  text-decoration: underline; text-underline-offset: 3px;
}
.sapply-done a:not(.sapply-btn):hover { color: #04384c; }
.sapply .sapply-done a:focus-visible {
  outline: 2px solid var(--sf-spray); outline-offset: 2px; box-shadow: 0 0 0 4px var(--sf-marine);
}
.sapply-done__out { margin: 22px 0 18px; }
/*
 * ⚠ THE LEGAL LINE LOSES WITHOUT THIS, and it is the same specificity trap the design system's own
 * stylesheet documents on its confirmation panel: `.sapply-done p` is (0,1,1) and `.sapply-legal`
 * is (0,1,0), so the RLA line rendered at 14.5px body size instead of the 13px the type scale
 * specifies. Measured off the confirmation screenshot, not reasoned about.
 */
.sapply-done p.sapply-legal { font-size: 13px; line-height: 1.6; color: var(--sf-scrub); margin: 18px 0 0; }

/* ============================================================================================
 * THE OFFER SUMMARY — 4a spec §9, the third column. Present from step 1 (empty state) so the
 * grid never reflows when a block gets chosen; fills and follows down every step after.
 *
 * ⛔ THE BASE (display:none) RULES HAVE TO SIT BEFORE THE BREAKPOINTS BELOW, NOT AFTER. Same
 * specificity, so source order alone decides, and a "mobile default" written after the media
 * queries that are meant to turn it back on would always win instead — exactly the trap
 * .sapply-rail's own base rule (above) avoids by sitting before its 900px override.
 *
 * ⚠ ITS OWN SILICA CARD, NOT SPEC-LITERAL. §4 says the "≥900px summary card" gets the stated-
 * value treatment, which is what the .sapply-stated block nested inside it carries — but that
 * block is Marine-on-Sea-Spray-text, and this aside sits directly on the Marine page shell (spec
 * §4: "page shell goes Marine"), the same ground .sapply-rail sits on with no surface of its own.
 * A Marine card on a Marine shell reads as nothing, the exact defect .sapply-panel's own shadow
 * exists to solve one column over — so this column gets the panel's own Silica-plus-shadow
 * treatment as its OUTER surface, with the Marine "stated" fact sitting inside it once there is
 * one to show. Not stated as a literal rule anywhere in the spec; the alternative was inventing a
 * second instance of the rail's unreadable-on-Marine gap, which is worse.
 * ============================================================================================ */
.sapply-summary {
  display: none; min-width: 0;
  background: var(--sf-silica); border-radius: 8px; padding: 24px 20px;
  box-shadow: 0 8px 28px rgba(0, 0, 0, .28);
}
.sapply-summary__eyebrow {
  font-size: 12.5px; font-weight: 600; letter-spacing: .12em; text-transform: uppercase;
  color: var(--sf-ok); margin: 0 0 12px;
}
.sapply-summary__empty { font-size: 14px; line-height: 1.6; color: var(--sf-marine); margin: 0; }
.sapply-summary [data-summary-fact] { margin: 0 0 18px; }
.sapply-summary .sapply-btn { width: 100%; }

/* The 900–1179px stand-in, inside the panel under the title — spec §9: "not a column ... a
   one-line strip". Base rule hidden; the 900px breakpoint below turns it on, the 1180px one
   turns it back off once the real column exists. */
.sapply-summary-strip {
  display: none; font-size: 14px; font-weight: 600; color: var(--sf-marine); margin: 0 0 18px;
}

/* ============================================================================================
 * ⚠ THE ONE BREAKPOINT, 900px. Both sides are driven in tests/offer-form-portal.test.ts, at 899 and 900.
 *
 * ⛔ IT IS NOT 640, AND THE REASON IS MEASURED. The design system's breakpoint is 640, so the rail
 * claimed its 260px column the moment the viewport passed it: panel content width went 548px at
 * 639 to 226px at 641 — narrower than the panel gets on a 320px phone — and the panel grew from
 * 1047px to 1374px tall across ONE PIXEL. A surname rendered "Whitcon" and an email clipped at
 * "…@bigpon". Everything below 900 is the stacked layout, which is the DEFAULT above; the rail
 * becomes a sidebar only where a 260px column still leaves the panel a usable measure.
 *
 * ⚠ UPDATED FOR 4a §8 — below 900px there is no rail at all any more. The meter above replaces
 * it: a real conditional render, not this rule hiding the rail's own compact bar. That bar
 * (.sapply-rail__toggle) is left in the markup below rather than stripped out — it can never
 * actually be on screen now, because the rail it lives inside only ever builds at 900px and up,
 * where the very next rule hides the toggle unconditionally — but tests/offer-form-portal.test.ts
 * still asserts it exists-but-hidden there as a rail structural check, so removing it is a
 * separate, later cleanup rather than a live behaviour anything still depends on.
 * ============================================================================================ */
@media (min-width: 900px) {
  .sapply { grid-template-columns: minmax(0, 260px) minmax(0, 1fr); gap: 40px; }

  .sapply-rail { position: sticky; top: 24px; }
  .sapply-rail__heading { display: block; }
  /* The full list is unconditionally on screen here, so the compact bar has nothing to do. */
  .sapply-rail__toggle { display: none; }
  .sapply-rail__list { margin-top: 0; }

  .sapply-panel { padding: 32px; }
  .sapply-panel__head { margin: 0 0 26px; }
  /* 4a spec §9 — "Panel title 34px → 44px at 900px; legends 18px → 19px. Nothing else in the type
     scale moves with width." (40px here before this phase was short of the spec's own number.) */
  .sapply-panel__title { font-size: 44px; }
  .sapply-legend { font-size: 19px; }
  /* 4a spec §9 — the summary strip, the 900–1179px stand-in for the ≥1180px third column. Turned
     back off inside the 1180px block below, where the aside becomes a real column instead. */
  .sapply-summary-strip { display: block; }
  /*
   * ⛔ ONE COLUMN, AT EVERY WIDTH. Keith, 22/08: "Final review page looks terrible.....that is
   * unreadable...make it one column not 3 across the page."
   *
   * 4a spec §9 asked for `repeat(auto-fit,minmax(220px,1fr))`, described as "two across from
   * 900px". That description was true of the panel the spec was written against. Once §9's own
   * third column and the container fix widened the panel to 720px at 1440 and 780px at 1920,
   * auto-fit started fitting THREE — and each card then divided down to roughly 240px, which is
   * what put the offer amount on two lines and the address on three.
   *
   * ⚠ So this is the spec's own instruction producing the opposite of the spec's intent: it named
   * a mechanism (auto-fit at a 220px floor) rather than an outcome (a readable read-back), and the
   * mechanism drifted the moment something else legitimately changed. The outcome is what matters
   * here — this screen is the last thing a buyer reads before committing to a six-figure purchase,
   * and it is a list of answers to check, not a dashboard to scan.
   *
   * A single column also removes the whole class: no card can be divided narrow enough to wrap a
   * figure, whatever the panel does next.
   */
  .sapply-review { grid-template-columns: minmax(0, 1fr); }
  .sapply-done__band { margin: 0 0 24px; }
  .sapply-done__band h2 { font-size: 40px; }

  /*
   * ⛔⛔ CRITICAL — the dt/dd side-by-side rule used to fire flat at 900px, keyed to the page
   * viewport rather than to the card's own width — see the CRITICAL comment on .sapply-review__sec
   * above for the measured wrap defect this caused. A two-across review card at any tested desktop
   * width (900/1024/1280/1440/1920/2560, both before and after the defect-4 layout fix above) works
   * out to roughly 240–350px outer width once .sapply-review's own two-across auto-fit grid divides
   * the panel in half; 380px is comfortably above the top of that range, so this stays stacked
   * (label above value, full card width — the same treatment mobile already uses) at every card
   * size measured, and only goes side-by-side if the review grid is ever changed to give cards more
   * room than it does today. Deliberate, not an oversight: a stacked value line has plenty of room
   * for "$550,000", a 60-char email, or a full street address, which a 120px label column plus
   * whatever was left over never did.
   *
   * ⛔ NESTED INSIDE THIS 900px BLOCK ON PURPOSE, NOT A TOP-LEVEL SIBLING. .sapply-review__sec's
   * container-type is set unconditionally (harmless on its own), so an unscoped @container rule
   * would fire below 900px too wherever a single-column mobile/tablet layout happened to hand a
   * card more than 380px — measured: it did, at 768px, right after this was first written as a
   * sibling rule instead of nested here. Nesting keeps the side-by-side option desktop-only, same
   * as the rule it replaced.
   */
  @container (min-width: 380px) {
    .sapply-review__rows { grid-template-columns: minmax(120px, 34%) minmax(0, 1fr); gap: 8px 16px; }
    .sapply-review__rows dd { margin-bottom: 0; }
  }
}

/* ============================================================================================
 * ⚠ THE SECOND BREAKPOINT, 1180px — 4a spec §9. Driven both sides in
 * tests/offer-form-portal.test.ts, at 1179 and 1180, the same discipline the 900px comment above
 * documents and for the same reason: a pass at one width says nothing about the other.
 *
 * ⛔ NOT INTRODUCED AT 900. The third column is 300px plus another 40px gap of its own — the same
 * shape of mistake the 900px breakpoint's history records at 640px, one column over: giving the
 * summary column its room the moment the rail becomes a sidebar would take 340px straight out of
 * a panel that is already sharing its width with a 260px rail at that point. Unlike 900, this
 * number is the spec's own (4a build spec §9) rather than one measured against a shipped defect —
 * there is no incident to cite here, only the same arithmetic: the panel's own 560px field-row
 * measure plus 32px of padding on each side wants genuine room before a third column takes more
 * of what is left.
 * ============================================================================================ */
@media (min-width: 1180px) {
  .sapply { grid-template-columns: minmax(0, 260px) minmax(0, 1fr) minmax(0, 300px); }
  .sapply-summary { display: block; position: sticky; top: 24px; }
  /* The strip was the 900–1179px stand-in for this column; now that the column is real, showing
     both would say the same three facts twice on one screen. */
  .sapply-summary-strip { display: none; }
  /* 4a spec §9 — "On the review only, Send moves into the third column." Nothing to draw here:
     public/js/salt-offer-form.js physically reparents the one [data-primary] button into
     .sapply-summary on the review (renderSummary()), rather than this rule hiding a second copy —
     a hide-in-place duplicate was tried first and broke every submit-from-review test that runs at
     the suite's default 1280px viewport, already past this breakpoint. */
}

/* ⛔ THE CLOSED SPLASH IS ONE COLUMN AT EVERY WIDTH. .sapply becomes a 260px rail column plus a
   panel at 900px, and the splash draws no rail at all — without this the entire stop message
   renders inside the 260px column at 1024 and 1440, which is narrower than a 320px phone. The
   attribute selector outranks the media-query rule on specificity, so source order cannot break
   this later. */
.sapply[data-offers-closed="1"] { grid-template-columns: minmax(0, 1fr); }

/* ── A STATED VALUE — a fact, not a field ──────────────────────────────────────────────────
   The fixed-price offer and the deposit context. Deliberately NOT shaped like an input: no
   underline, no pale inset box, no caret. A readonly <input> that looks like every other field
   reads as negotiable, and on a document that binds a purchaser to a half-million-dollar
   contract that misreading is the whole problem. This borrows the block card's treatment
   instead, because that is what it is — a fact about the block. */
/* ⛔ INVERTS TO MARINE — 4a spec §4. Was a Sea Spray block with a Marine left border; the border
   is dropped along with the Sea Spray fill, because a fact-block sitting on a Marine page shell
   reads as "lifted" by being its own solid Marine block against the lighter Silica panel, the same
   logic .sapply-panel now uses a shadow for against the page. */
.sapply .sapply-stated{
  display:block; margin:18px 0 22px; padding:18px 20px; border-radius:8px;
  background:var(--sf-marine);
}
/* ⛔ NO OPACITY ON TEXT — 4a spec §1/§2. opacity:.75 over Marine on Sea Spray was the same
   readability pattern the client complained about elsewhere; eyebrow treatment (weight 600, tracked
   caps) replaces it at full strength rather than a faded version of the same colour.
   ⚠ COLOUR UPDATED for the 4a ground inversion: the block itself is Marine now, and Ocean measures
   ~1.7:1 on Marine (fails badly) where it was ~7.8:1 on the old Sea Spray fill — Sea Spray is the
   only token in the palette that reads on Marine (~12.7:1), so the eyebrow moves to it, keeping
   the same size/tracking/weight. */
.sapply .sapply-stated__label{
  display:block; font-size:12.5px; font-weight:600; letter-spacing:.12em; text-transform:uppercase;
  color:var(--sf-spray);
}
.sapply .sapply-stated__value{
  display:block; margin-top:4px; font-size:32px; font-weight:600; color:var(--sf-spray);
  line-height:1.1;
}
/* ⛔ NO OPACITY ON TEXT — 4a spec §1/§2. opacity:.85 over Marine on Sea Spray thinned this note the
   same way; kept at full strength here too.
   ⚠ SILICA AS TEXT, DELIBERATELY — the general rule elsewhere in this file is Silica is a surface
   and never text, because Silica-on-Silica is close to unreadable. That rule is about NOT using it
   as foreground colour on its own panel ground; here the ground is Marine, and Silica-on-Marine
   measures ~13.3:1 — the spec (4a build spec §4) names this pairing explicitly for the note line,
   distinct from the label/value above which use Sea Spray. Do not "fix" this back to Sea Spray or
   Marine; both were checked and this is the one the spec calls for. */
.sapply .sapply-stated__note{
  display:block; margin-top:6px; font-size:14px; line-height:1.5; color:var(--sf-silica);
}
@media(max-width:640px){
  /* Sensible step down from the 32px base above — was 22px against the old 26px base. */
  .sapply .sapply-stated__value{ font-size:26px; }
}

/* ============================================================================================
 * "SAVE AND COME BACK LATER" — job 2648. A modal panel that gives the buyer their own resume
 * link on screen: full URL, Copy link, how long it lasts, and — only when the server says the
 * flag is on — an optional "email it to me". Fluid width throughout, so this needs no breakpoint
 * of its own; see public/js/salt-offer-form.js for the open/close/focus-trap/copy logic this paints.
 * ============================================================================================ */
.sapply-savepanel {
  position: fixed; inset: 0; z-index: 60;
  display: flex; align-items: center; justify-content: center;
  padding: 20px;
}
.sapply-savepanel__backdrop {
  /* 4a spec §10 — rgba(2,41,56,.72), darker than the .55 scrim every other dialog in this file
     shares, so the panel reads as the one true modal moment on the form. */
  position: absolute; inset: 0; background: rgba(2, 41, 56, .72); border: 0; padding: 0; margin: 0;
  cursor: default;
}
/*
 * ⛔ dvh, NOT vh — the same defect the site lane fixed on .salt-modal in BaseLayout.astro
 * (PR #34, 21/08), flagged to this lane before it could ship. On iOS Safari `vh` resolves against
 * the LARGE viewport, the height the page would have if the URL bar were hidden, whether or not it
 * actually is. So a panel sized in vh extends PAST the top of what the visitor can see, and its
 * heading and Close button sit underneath the browser chrome. This is the Finish later dialog, so
 * the control it would hide is the one that hands a buyer their way back into a half-finished
 * offer.
 *
 * The plain-vh line stays FIRST as the fallback for engines without dvh; the dvh line simply wins
 * where it is supported. env(safe-area-inset-*) keeps it clear of the notch and home indicator.
 *
 * ⚠ PLAYWRIGHT CANNOT SEE THIS AND NEITHER CAN ANY HEADLESS GATE. A headless viewport has no
 * dynamic browser chrome, so vh and the visible height are identical there and the clipping never
 * reproduces — which is exactly how the site lane's modal gate stayed green for weeks over a live
 * defect. So the regression test asserts the DECLARED UNIT in the stylesheet, not a measured
 * height, and a real handset is the only thing that confirms the rendering.
 */
.sapply-savepanel__dialog {
  position: relative; z-index: 1;
  background: #fff; color: var(--sf-marine);
  border-radius: 10px; padding: 28px 24px 24px;
  width: min(440px, 100%);
  max-height: calc(100vh - 40px - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px));
  max-height: calc(100dvh - 40px - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px));
  overflow-y: auto;
  box-shadow: 0 18px 48px rgba(2, 41, 56, .32);
}
.sapply-savepanel__dialog:focus { outline: none; }
/* Same compound ring as .sapply-panel: Sea Spray alone is close to invisible on white/Silica. */
.sapply .sapply-savepanel__dialog:focus-visible {
  outline: 2px solid var(--sf-spray); outline-offset: 2px; box-shadow: 0 0 0 5px var(--sf-marine);
}
.sapply-savepanel__close {
  position: absolute; top: 8px; right: 8px;
  width: 44px; height: 44px; display: inline-flex; align-items: center; justify-content: center;
  font-family: inherit; font-size: 22px; line-height: 1; font-weight: 400; color: var(--sf-marine);
  background: transparent; border: 0; border-radius: 8px; cursor: pointer;
}
.sapply-savepanel__close:hover { background: var(--sf-silica); }
.sapply .sapply-savepanel__close:focus-visible {
  outline: 2px solid var(--sf-spray); outline-offset: 1px; box-shadow: 0 0 0 4px var(--sf-marine);
}
.sapply-savepanel__title {
  font-family: 'Ruthfully', 'Kodchasan', cursive; font-weight: 400;
  font-size: 26px; line-height: 1.2; margin: 0 34px 14px 0; color: var(--sf-marine);
}
.sapply-savepanel__body {
  font-size: 14.5px; line-height: 1.65; color: var(--sf-marine); margin: 0 0 14px;
}
.sapply-savepanel__urlrow { display: grid; gap: 10px; margin: 0 0 14px; }
.sapply-savepanel__urlrow .sapply-label { margin: 0; }
/* 4a spec §10 — "on white": a fact the buyer can select and copy, not a field they type into, so
   it takes the dialog's own white ground rather than the Silica chip treatment, distinguished
   only by its 1px border. Solid ground either way, not the dotted "readonly-as-statement"
   treatment .sapply-input[readonly] carries elsewhere. */
.sapply-savepanel__url {
  display: block; width: 100%; min-height: 46px; padding: 11px 12px;
  font-family: inherit; font-size: 14px; line-height: 1.4; color: var(--sf-marine);
  background: #fff; border: 1px solid var(--sf-line-field); border-radius: 6px;
  overflow-wrap: anywhere;
}
.sapply .sapply-savepanel__url:focus {
  border-color: var(--sf-marine);
  outline: 2px solid var(--sf-spray); outline-offset: 2px; box-shadow: 0 0 0 5px var(--sf-marine);
}
/* 4a spec §10 — "Copy link" (primary) + "Keep going" (secondary), side by side, wrapping at
   narrow widths rather than overflowing the dialog sideways. */
.sapply-savepanel__actions { display: flex; flex-wrap: wrap; gap: 12px; margin: 0 0 6px; }
.sapply-savepanel__actions .sapply-btn { flex: 1 1 auto; }
.sapply-savepanel__status {
  font-size: 12.5px; line-height: 1.5; color: var(--sf-ok); min-height: 18px; margin: 10px 0 0;
}
.sapply-savepanel__email {
  margin: 18px 0 0; padding-top: 16px; border-top: 1px solid var(--sf-line);
  display: grid; gap: 10px;
}
.sapply-savepanel__email .sapply-row { margin: 0; }

/* "Same address as the primary purchaser" — sits directly above the address it governs, so it
   reads as a property of that field rather than a stray question. Tighter top margin than the
   acknowledgement checkboxes, which are standalone statements on their own step. */
.sapply-check--inline { margin: 4px 0 14px; }

/* ---------------------------------------------------------------------------------------------
 * THE STICKY SEND BAR — review step only.
 *
 * ⛔ Keith, 01/09: "This buttons needs to be more ovoius for such a long page, can we look at
 * updating to have this button in a multiple locations." The review prints every answer from all
 * six steps, so on a phone the send control sits well below a screenful of reading. Making it the
 * loudest thing on the page (see .sapply-btn.is-send) does nothing while it is off screen.
 *
 * ⚠ SAFE-AREA AWARE, and that is not decoration. A bar pinned to bottom:0 on iOS sits UNDER the
 * home indicator and the Safari toolbar — the same class of mistake as the Finish-later dialog
 * that clipped under the URL bar (see .sapply-savepanel__dialog above, and its 100dvh fallback).
 * The padding below reserves the inset so the button is fully tappable, not merely visible.
 * ------------------------------------------------------------------------------------------- */
.sapply-stickysend {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 40;                     /* above the panel, below the Finish-later overlay (60) */
  background: var(--sf-shell, #F2EADF);
  border-top: 1px solid rgba(2, 41, 56, .16);
  box-shadow: 0 -8px 24px rgba(2, 41, 56, .14);
  padding: 12px 16px calc(12px + env(safe-area-inset-bottom, 0px));
}
.sapply-stickysend[hidden] { display: none; }
.sapply-stickysend > .sapply-btn.is-send {
  /* Its own row already; the max-width keeps it from stretching absurdly on a desktop. */
  display: block; width: 100%; max-width: 680px; margin: 0 auto;
  min-height: 56px; box-shadow: none;
}
.sapply-stickysend__legal {
  max-width: 680px; margin: 8px auto 0;
  font-size: 12px; line-height: 1.35; text-align: center;
  color: var(--sf-label, #4A4A38);
}
/* The bar overlays the end of the page — reserve room so the last answer and the full disclaimer
   are never trapped underneath it. Applied on the review only, where the bar can exist. */
.sapply[data-step="7"] .sapply-panel { padding-bottom: 140px; }

@media (max-width: 420px) {
  .sapply-stickysend { padding: 10px 12px calc(10px + env(safe-area-inset-bottom, 0px)); }
  .sapply-stickysend > .sapply-btn.is-send { min-height: 52px; font-size: 14px; }
  .sapply-stickysend__legal { font-size: 11px; }
}

/* ⚠ A user who has asked for reduced motion still needs the button; only the shadow softens. */
@media (prefers-reduced-motion: reduce) {
  .sapply-stickysend { box-shadow: 0 -4px 12px rgba(2, 41, 56, .12); }
}

/* ---------------------------------------------------------------------------------------------
 * FINISH LATER — the email path leads, the copied link follows.
 *
 * ⛔ Keith, 22/08: "Showing the user a link they have to save is useless we need to send it to
 * where they want", and 01/09: "confirm not ask". So the send control is a full-width primary
 * button carrying the buyer's own address, and the copy-link row sits underneath as the fallback
 * for whoever wants it.
 * ------------------------------------------------------------------------------------------- */
.sapply-savepanel__email { margin: 0 0 18px; }
.sapply-savepanel__email > .sapply-btn {
  display: block; width: 100%; min-height: 52px;
  /* The address can be long; wrapping beats an ellipsis on the one string the buyer must check. */
  white-space: normal; word-break: break-word; text-transform: none;
  font-size: 14px; letter-spacing: .02em;
}
.sapply-savepanel__emailedit { margin: 0 0 10px; }
.sapply-savepanel__emailedit[hidden] { display: none; }
/* A text control, not a second button — it must not compete with the action above it. */
.sapply-savepanel__alt {
  display: inline-block; margin: 10px 0 0; padding: 0;
  background: none; border: 0; cursor: pointer;
  font-family: inherit; font-size: 13px; line-height: 1.4;
  color: var(--sf-label, #4A4A38); text-decoration: underline;
}
.sapply-savepanel__alt[hidden] { display: none; }
.sapply-savepanel__alt:hover { color: var(--sf-marine, #022938); }
/* The copy row is now secondary — quieter label, and it sits below the send control. */
.sapply-savepanel__urlrow { margin-top: 4px; }

/* =============================================================================================
 * THE PAGE SHELL — header and footer. Approved handoff, direction 2b "Marine throughout",
 * 01/09/26. Nothing inside the form panel is touched by any rule below.
 *
 * ⛔ ONE BREAKPOINT, AT 900px, AND IT IS THE ONE THE FORM ALREADY USES. The handoff asks for the
 * existing query to be reused rather than a second line introduced — two breakpoints a few pixels
 * apart is how a shell starts disagreeing with the layout it frames.
 *
 * ⚠ The dimensional values here are the handoff's PROPOSED metrics: the rule height, the header
 * padding, the wordmark height and the footer padding were set for the design, not measured off
 * salt-osullivan.com.au. The handoff flags them as the four numbers to pin against the live site.
 * Colour, type and every string ARE exact and are not proposals.
 * ============================================================================================= */

/* Sea Spray, full width, no content — the only thing between Marine and the browser chrome. */
.sapply-shell__rule { height: 4px; background: #BFF6E5; }

.sapply-masthead {
  background: #022938;
  padding: 16px 18px;
  border-bottom: 1px solid rgba(191, 246, 229, .28);
}
/*
 * ⛔ NOT A LINK, and that is the point. It used to wrap an <a href="/">. A buyer part way through
 * an offer who taps a logo out of habit should not lose the page they are on — the shell has
 * exactly one interactive element and it is the Privacy link.
 */
.sapply-masthead__mark { display: block; height: 36px; width: auto; }

.sapply-shell__footer {
  background: #022938;
  border-top: 1px solid rgba(191, 246, 229, .28);
  padding: 32px 18px;
  /* Below 900px: stacked and centred, same three items in the same order. */
  display: flex; flex-direction: column; align-items: center;
  text-align: center; gap: 14px;
  font-family: 'Kodchasan', system-ui, sans-serif;
  font-weight: 400; font-size: 13px; line-height: 1.6;
  color: #F5EFE4;
}
/*
 * Sea Spray on Marine, underlined. ⚠ NOT dimmed: salt-fixes.css thins agency-credit anchors, and
 * the old footer needed an inline opacity:1 to beat it. That anchor is gone with the credit line,
 * and this link carries no class that rule matches — but it stays full strength deliberately,
 * because Sea Spray on Marine at 13px has no headroom to give away.
 */
.sapply-shell__footer a {
  color: #BFF6E5; text-decoration: underline; text-underline-offset: 3px;
}
.sapply-shell__footer a:hover { color: #F5EFE4; }

@media (min-width: 900px) {
  .sapply-masthead { padding: 24px 40px; }
  .sapply-masthead__mark { height: 44px; }
  .sapply-shell__footer {
    padding: 40px;
    display: grid; grid-template-columns: 1fr auto 1fr;
    gap: 16px 40px; align-items: center; text-align: initial;
  }
  .sapply-shell__footer > :nth-child(1) { justify-self: start; }
  .sapply-shell__footer > :nth-child(2) { justify-self: center; }
  .sapply-shell__footer > :nth-child(3) { justify-self: end; }
}

/*
 * ⛔ THE SHELL MUST SIT ON THE SAME COLUMN AS THE FORM — 4K fix, 02/09/26.
 *
 * Keith, on a 3840px screen: the wordmark was pinned 40px from the far left edge and the footer's
 * copyright and Privacy link were pinned to the two outer corners, while the form itself stopped at
 * its 1500px cap in the middle. On a 1440px laptop the two line up by accident, because 1440 is
 * close enough to the cap that nobody notices; at 3840 the logo sits roughly 1100px away from the
 * content it belongs to and the page reads as three unrelated bands.
 *
 * ⛔ THE BACKGROUND STAYS FULL-BLEED. Only the CONTENT is constrained. Marine has to run edge to
 * edge or a light strip appears down both sides of the header and footer on a wide screen — the
 * exact fault the root-ground comment above this file already warns about.
 *
 * ⚠ `max()` NOT a plain calc: below the cap the second term goes negative, and a negative inline
 * padding would pull the wordmark off the left edge of the screen. The 40px floor is the existing
 * ≥900px value, so nothing at or below a laptop width moves by a pixel.
 *
 * ⚠ 1500px is not a new number — it is `.sapply-page-container`'s own max-width from line 71. If
 * that cap is ever changed, this must change with it or the shell silently drifts off the column
 * again; the var below is what stops the two being edited apart.
 */
.sapply { --sf-shell-max: 1500px; }

@media (min-width: 900px) {
  .sapply-masthead,
  .sapply-shell__footer {
    /*
     * ⚠ THE `+ 40px` IS THE CONTAINER'S OWN GUTTER, NOT A FUDGE. `.sapply-page-container` caps at
     * 1500px and then indents its content a further 40px, so the heading a reader actually sees
     * starts 40px inside the container box. Aligning to the box alone left the wordmark 40px to the
     * left of "Make an offer at Salt" — measured at 3840px: container 1170, H1 1210.
     */
    padding-inline: max(40px, calc((100% - var(--sf-shell-max, 1500px)) / 2 + 40px));
  }
}
