/* ============================================================================
   Unified Signal — contact page ("Switchboard")
   Scope: /contact only. Loaded after us26.css, which supplies the tokens, the
   reset, the header, the footer, the buttons and the section rhythm this file
   builds on. Nothing here redefines a token or restyles a shared component.

   WHY THIS IS A SEPARATE FILE, and not more rules in us26.css. The reason that
   still holds is the plain one: a page-scoped stylesheet leaves the homepage's
   bytes, and its cache, untouched.

   The reason that no longer holds is recorded because this comment asserted it
   for months. The old project ran a dead-selector gate over us26.css against the
   *homepage*, at one viewport, so a form, an address block and a locator map
   would all have been reported dead the moment their rules moved into that file,
   and the fix for that would have been to weaken the gate, which is not a fix.
   Neither that tool (unused.mjs) nor the contact-page checker this comment used
   to credit (verify-contact.mjs) came across to this repository. Nothing in
   ./tools/check.sh looks for a dead selector. Do not cite either one again.
   See D-1109.

   Contrast: every text pair below is >= 4.5:1; graphic accents >= 3:1, quoted
   from the token table in us26.css rather than re-measured here.
   ========================================================================= */

/* ---------- 1. Hero ---------- */
/* Not .u-hero: that component is a two-column masthead built around the
   homepage's orbit diagram, and half of it would be empty here. This is the
   same violet ground and the same type scale with one column. */
.u-ct-hero__h {
  font-size: var(--u-fs-display);
  letter-spacing: -.035em;
  max-width: 18ch;
}
.u-ct-hero__lead { max-width: 60ch; }

/* The three channels that work today. A list, not a button row: they are three
   pieces of information with an action attached, not three competing calls to
   action, and the phone number should be readable without being clicked. */
.u-ct-direct {
  list-style: none;
  margin-top: var(--u-s7);
  display: grid;
  gap: var(--u-s4);
  grid-template-columns: 1fr;
}
@media (min-width: 760px) {
  .u-ct-direct { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--u-s5); }
}
.u-ct-direct a {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-rows: auto auto;
  column-gap: var(--u-s4);
  align-items: center;
  height: 100%;
  padding: var(--u-s5);
  border: 1px solid var(--u-violet-line);
  border-radius: var(--u-r3);
  background: rgba(255, 255, 255, .04);
  color: var(--u-on-violet);
  text-decoration: none;
  transition: background-color var(--u-dur-1) var(--u-ease),
              border-color var(--u-dur-1) var(--u-ease);
}
.u-ct-direct a:hover { background: rgba(255, 255, 255, .09); border-color: var(--u-amber); }
.u-ct-direct .u-ico { grid-row: 1 / span 2; width: 26px; height: 26px; color: var(--u-amber); }
.u-ct-direct__k {
  font-family: var(--u-mono); font-size: var(--u-fs-label); font-weight: 500;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--u-on-violet-2);
}
.u-ct-direct__v { font-weight: 600; font-size: 1.0625rem; overflow-wrap: anywhere; }

/* ---------- 2. Inquiry column + routing aside ---------- */
/* Below 1000px the aside reads as a footnote to the form, which is the correct
   reading order and needs no rule. Above it, the two sit side by side with the
   form given the wider track: it is the taller of the two and the one a visitor
   came to use. */
.u-ct-cols { display: grid; gap: var(--u-s7); grid-template-columns: 1fr; }
@media (min-width: 1000px) {
  .u-ct-cols {
    grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
    column-gap: var(--u-s8);
    align-items: start;
  }
}
.u-ct-form__head { max-width: 52ch; margin-bottom: var(--u-s6); }
.u-ct-side {
  padding: var(--u-s6);
  background: #fff;
  border: 1px solid var(--u-ink-line);
  border-radius: var(--u-r3);
  box-shadow: var(--u-shadow-1);
}
@media (min-width: 1000px) { .u-ct-side { position: sticky; top: calc(var(--u-s8) + var(--u-s5)); } }
.u-ct-side__h { font-size: var(--u-fs-h3); margin-bottom: var(--u-s5); }
.u-ct-route { list-style: none; display: grid; gap: var(--u-s5); }
.u-ct-route li {
  display: grid; grid-template-columns: auto 1fr; gap: var(--u-s3);
  font-size: var(--u-fs-small); color: var(--u-ink-2); line-height: 1.55;
}
.u-ct-route b { color: var(--u-ink); }
.u-ct-route .u-ico { width: 18px; height: 18px; margin-top: .18rem; color: var(--u-amber-ink); }

/* ---------- 3. Form controls ---------- */
/* Labels are visible and above their control. No placeholder text anywhere:
   a placeholder disappears the moment a field is focused, which is exactly when
   a visitor needs it, and it is not a label for anything that reads the page
   aloud. Where a field needs explaining, it gets a persistent hint wired up
   with aria-describedby. */
.u-form { display: grid; gap: var(--u-s5); max-width: 46rem; }
.u-form__row { display: grid; gap: var(--u-s2); margin: 0; }
.u-form__row label, .u-form__set legend {
  font-weight: 600; font-size: var(--u-fs-small); color: var(--u-ink);
}
.u-form__opt { font-weight: 400; color: var(--u-ink-2); }
.u-form__hint { font-size: var(--u-fs-small); color: var(--u-ink-2); line-height: 1.5; }
.u-form input[type='text'],
.u-form input[type='email'],
.u-form input[type='tel'],
.u-form textarea {
  width: 100%;
  min-height: 48px;             /* the same 48px target the buttons use */
  padding: var(--u-s3) var(--u-s4);
  font: inherit;
  font-size: var(--u-fs-body);
  color: var(--u-ink);
  background: #fff;
  border: 1px solid var(--u-ink-line);
  border-radius: var(--u-r2);
  transition: border-color var(--u-dur-1) var(--u-ease);
}
.u-form textarea { min-height: 8.5rem; resize: vertical; line-height: 1.55; }
.u-form input:hover, .u-form textarea:hover { border-color: var(--u-ink-2); }

.u-form__set { border: 0; padding: 0; margin: 0; display: grid; gap: var(--u-s2); }
.u-form__checks { display: grid; gap: var(--u-s3); margin-top: var(--u-s2); }
@media (min-width: 620px) { .u-form__checks { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.u-form__check { display: flex; align-items: flex-start; gap: var(--u-s3); }
.u-form__check input {
  width: 20px; height: 20px; flex: none; margin-top: .12rem;
  accent-color: var(--u-violet-700);
}
.u-form__check label { font-weight: 400; font-size: var(--u-fs-small); color: var(--u-ink); }

/* The send block. The button is live now (D-1114): the form has a real action,
   so a control that reads as usable is one. The rules that dimmed a permanently
   disabled button went with it rather than lingering as dead CSS — contact.js
   still sets `disabled` for the moment between submitting and navigating, which
   needs no styling of its own because the page is leaving. The reason the send
   behaves the way it does sits beside it in plain text, not in a title
   attribute nobody can reach. */
.u-form__send {
  display: grid; gap: var(--u-s4); align-items: start;
  padding: var(--u-s5);
  border: 1px solid var(--u-ink-line);
  border-left: 3px solid var(--u-amber-solid);
  border-radius: var(--u-r2);
  background: var(--u-paper-2);
}
@media (min-width: 760px) {
  .u-form__send { grid-template-columns: auto minmax(0, 1fr); column-gap: var(--u-s5); }
}
.u-form__status {
  margin: 0; font-size: var(--u-fs-small); color: var(--u-ink-2); line-height: 1.6;
}
.u-form__status b { color: var(--u-ink); display: block; }

/* The answer to a submission, rendered by the view from TempData after the POST
   redirect. Two colours, local to this file: us26.css has no success/failure
   pair, and the --u-flow-* tokens mean "money in / money out", not "it worked".
   Borrowing them for a form result would put a meaning on them they do not have.

   Contrast on --u-paper (#fbf8f5): #14663a is 6.63:1, #9b1c17 is 7.72:1. On the
   tinted grounds below they are 6.38:1 and 7.21:1. Both clear 4.5:1 either way,
   which matters because the tint is the only thing separating the two states for
   a reader who does not distinguish the hues — hence the leading glyph as well. */
.u-form__result {
  margin: 0 0 var(--u-s5);
  padding: var(--u-s4) var(--u-s5);
  border-radius: var(--u-r2);
  border: 1px solid currentColor;
  font-size: var(--u-fs-body);
  line-height: 1.55;
}
.u-form__result::before { font-weight: 700; margin-right: .5em; }
.u-form__result--ok  { color: #14663a; background: #eef6f0; }
.u-form__result--ok::before  { content: "\2713"; }   /* check mark */
.u-form__result--warn { color: var(--u-amber-ink); background: #fdf3e8; }  /* 5.28:1 */
.u-form__result--warn::before { content: "\26A0"; }
.u-form__result--bad { color: #9b1c17; background: #fbeeed; }
.u-form__result--bad::before { content: "\26A0"; }   /* warning sign */
.u-form__result:focus-visible { outline: var(--u-focus); outline-offset: 3px; }

/* ---------- 4. Office, address and locator ---------- */
/* One column since the locator moved into the sticky panel (D-1113), and now
   with nothing below the address but the appointment note: the directions
   buttons were removed at the owner's request (D-1117). No filler took their
   place. Hours, parking and a response time would all fill the space honestly
   if anyone can source them; inventing them would not. */
.u-ct-office { display: grid; gap: var(--u-s7); align-items: start; }
.u-ct-office__text { max-width: 62ch; }

/* Removing the directions buttons (D-1117) left a four-line address alone in a
   full-width band, which read as a section that had lost something rather than
   as one that was finished. The fix lays the three things already here across
   the measure instead of inventing a fourth: where the office is, how to reach
   it, and what to do before turning up. No new copy, because hours, parking and
   a response time are the honest candidates and none of them has a source.

   The margins below are the stacked spacing these blocks carry when they follow
   one another; side by side they are all top-aligned, so it goes. */
@media (min-width: 900px) {
  .u-ct-office__text {
    max-width: none;
    display: grid;
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--u-s7);
    align-items: start;
  }
  .u-ct-office__text > .u-ct-address__lines,
  .u-ct-office__text > .u-ct-office__note { margin-top: 0; }
  .u-ct-office__note { max-width: none; }
}
.u-ct-address {
  font-style: normal;
  font-size: var(--u-fs-lead);
  line-height: 1.5;
  color: var(--u-ink);
}
.u-ct-address b { font-weight: 700; }
.u-ct-address__lines { list-style: none; display: grid; gap: var(--u-s3); margin-top: var(--u-s5); }
.u-ct-address__lines li { display: flex; align-items: center; gap: var(--u-s3); }
.u-ct-address__lines .u-ico { width: 20px; height: 20px; flex: none; color: var(--u-amber-ink); }
.u-ct-office__note {
  margin-top: var(--u-s5); font-size: var(--u-fs-small); color: var(--u-ink-2);
  max-width: 44ch;
}

/* A finding kept after the rules it justified were deleted with the directions
   buttons (D-1117), because it is about the design system rather than about
   those two buttons: `.u-btn--ghost` is a violet-ground component. us26.css:226
   gives it `color: var(--u-on-violet)`, i.e. #ffffff, and every homepage use of
   it sits in a violet section, so nothing there ever exposed the other case. On
   paper-2 that white measures 1.06:1 against #f2ece6 and the label is
   effectively invisible. Anything that puts a ghost button on a paper ground
   here needs the ground-specific pair — ink on paper-2 is 15.7:1 for text, and
   an ink-2 border 6.7:1 as a graphic, against a requirement of 3. */

/* The locator, in the sticky panel beside the form.

   It is Google's embed rather than OpenStreetMap's; the view carries the
   reasoning, which is that the OSM frame solicited donations inside the panel
   and no parameter or stylesheet of ours could reach in and stop it.

   aspect-ratio, not a height: the box is reserved before the frame loads, so a
   third-party map arriving late cannot shift the page under a reader.

   2/1 is a fitting constraint, not a taste. The panel is `position: sticky`
   above 1000px (contact.css:91) behind a sticky header, with an 88px top offset
   to clear it; a sticky element taller than the viewport pins its top and parks
   its own bottom permanently off screen, so the locator would travel with the
   form by scrolling out of reach, which is the opposite of the point.

   The budget is the shortest viewport the gates use, 900px, less that offset:
   812px for the whole panel. Measured, not predicted, because the predictions
   were wrong twice running: 757px at 1280, 764 at 1100 and 774 at 1920 with this
   2/1 figure and the caption the page carried then. Mobile is not sticky and is
   not in the budget.

   Nothing here measures a rendered height: that needs a browser and the gate has
   none. What check C23 holds instead are the two numbers this arithmetic rests
   on — the 2/1 immediately below and the 88px offset above. Change either and the
   gate fails and names this paragraph, so the re-measurement gets asked for
   rather than remembered. Measuring the panel itself is still done by hand. */
.u-ct-map { margin: var(--u-s6) 0 0; }
.u-ct-map__frame {
  display: block;
  width: 100%;
  aspect-ratio: 2 / 1;
  height: auto;
  border: 1px solid var(--u-ink-line);
  border-radius: var(--u-r3);
  /* The frame is opaque once it paints; this is what shows in the moment before
     it does, and what stays if it never does. Paper, not white, so the hole
     reads as part of the panel rather than as a broken image. */
  background: var(--u-paper-3);
}

/* This caption's opacity override is retired, and the measured history is worth
   keeping because it has now been got wrong in both directions.

   us26.css:752 dims .u-figcap__note to `opacity: .82`. Over paper-2, where this
   caption used to sit, that composites --u-ink-2 to #75697f and measures 4.38:1
   -- a real fail, found by running the gate rather than by reading the rule, and
   corrected here with an `opacity: 1` override. Moving the caption onto the
   panel's #fff made the same .82 composite to #786c83 at 4.92:1, which clears
   AA, so the override was retired. That was wrong too: the note had by then
   become the only .u-figcap__note on the site containing a *link*, and a link is
   --u-amber-ink, a far darker starting colour with far less room. axe measured
   the composite at #b36e2e on white: 4.04:1, serious, at both tested viewports.
   So the override came back, and the rule it encoded was not "paper needs
   opacity: 1" but "a caption carrying a link needs it".

   The caption is now the address line alone. Google draws its own attribution
   inside the frame, so there is no credit link under the panel and no
   .u-figcap__note here at all -- nothing left for the override to correct. Put
   one back the moment a note returns to this caption carrying a link, or the
   moment the caption returns to a paper ground. */

/* ---------------------------------------------------------------------------
   The closing artwork
   --------------------------------------------------------------------------
   The supplied asset signs the page off. It carries no heading and no caption,
   so everything here is about the ground and the size.

   The band paints --u-violet-800 exactly, because the art is 42.6% feathered
   alpha built to dissolve into that color with no edge (us26.css:726). On the
   paper grounds above it the feather reads as a gray halo instead.

   Capped well under the homepage's 1240px so it reads as a sign-off rather than
   a second hero, and never upscaled past its 1140 candidate at 1x.
--------------------------------------------------------------------------- */
.u-ct-close { background: var(--u-violet-800); padding-block: var(--u-s7); }
.u-ct-close__art {
  display: block; width: 100%; max-width: 720px; height: auto; margin-inline: auto;
}

/* ---------------------------------------------------------------------------
   The reCAPTCHA badge, once the check itself has failed
   --------------------------------------------------------------------------
   Google injects .grecaptcha-badge into our .g-recaptcha div and positions it
   itself, inline: `position: fixed; bottom: 14px; right: -186px`, 256px wide,
   with `transition: right 0.3s`. That is not a bug. The badge is meant to sit
   tucked with only its 70px logo tab showing and to slide out on hover, and on
   a healthy page it is exactly right -- which is why nothing here touches it.

   The failure state reuses the same 256px box for a sentence: "ERROR for site
   owner: Invalid domain for site key". Tucked, 186px of that sentence is past
   the right edge of the viewport, so what is readable is its tail. There is no
   horizontal scrollbar to go and find it with -- the badge is `position: fixed`
   and does not extend the scroll width -- and hover, the one gesture that would
   reveal it, does not exist on the phone where the page is mostly read.

   So the badge is pulled fully on screen, and only here: `is-captcha-broken` is
   set by contact.js at the single point where it has decided the spam check is
   not working, which means the healthy page keeps Google's tuck untouched, to
   the pixel. `!important` is not reaching for a bigger hammer than the job
   needs -- the declaration being overridden is a vendor's inline style, which
   nothing weaker can outrank, and Google's own hover handlers rewrite that same
   inline `right` on the way in and out, which nothing but `!important` survives.

   14px matches the inset Google already sets on `bottom`, so the badge sits in
   the corner rather than jammed against the edge. At 256 + 14 it still clears
   the narrowest viewport this site supports.
--------------------------------------------------------------------------- */
.is-captcha-broken .grecaptcha-badge { right: 14px !important; }
