/* chassis.css: mechanics only. Generated by the Studio renderer; do not hand-edit. */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body { margin: 0; font-family: var(--cx-font-body); font-size: var(--cx-body); color: var(--cx-fg); background: var(--cx-bg); -webkit-font-smoothing: antialiased; }
img, picture, video, canvas, svg { display: block; max-width: 100%; }
img { height: auto; }
picture { display: contents; }
picture > source { display: none; }
h1 { font-size: var(--cx-h1); line-height: 1.08; text-wrap: balance; margin: 0 0 .5em; }
h2 { font-size: var(--cx-h2); line-height: 1.15; text-wrap: balance; margin: 0 0 .5em; }
h3 { font-size: var(--cx-h3); line-height: 1.25; margin: 0 0 .4em; }
p, li { line-height: 1.6; }
p { max-width: var(--cx-measure); margin: 0 0 var(--sp-para, 1em); }
h2 + p, h3 + p { margin-top: 0; }
/* Prose rhythm only. An eyebrow is a <p> too, and this rule put a whole band (108px at 1440) between
   every eyebrow and its heading on 20 sections of one site (audit F44). The label belongs to its heading. */
:is(p, ul, ol, figure):not([class*="eyebrow"]):not([class*="kicker"]) + h2 { margin-top: var(--cx-band); }
/* The eyebrow-to-heading gap itself is a floor (floors.css), so a skin's heading margin cannot undo it. */
p + h3, ul + h3, ol + h3 { margin-top: calc(var(--cx-gap) * 1.5); }
.cx-wrap { width: min(100% - (var(--sp-gutter, var(--cx-gap)) * 2) - env(safe-area-inset-left, 0px) - env(safe-area-inset-right, 0px), var(--cx-wrap)); margin-inline: auto; }
/* 16px on very narrow screens (the owner's default); the fluid gutter reaches 20px by a 390px phone. */
@media (max-width: 359px) { :root { --sp-gutter: 16px; } }
figure > figcaption { margin-top: var(--sp-caption, 10px); }
.cx-stack > * + * { margin-top: var(--cx-gap); }
:focus-visible { outline: 2px solid var(--cx-accent); outline-offset: 2px; }
/* Bypass blocks (WCAG 2.4.1): a keyboard visitor lands on the skip link before the nav, and nobody
   else ever sees it. The site shipped without one. */
/* Which fields are required is a thing the visitor should be able to see before pressing submit, not
   discover from a browser bubble afterwards. The form shipped with three required fields and nothing
   saying so, beside copy that asked for an address the form treated as optional. */
.cx-req { font-weight: 400; opacity: .75; }
.cx-hint { display: block; margin-top: 6px; opacity: .75; }
.cx-skip { position: absolute; left: -9999px; top: 0; z-index: 100; padding: 12px 16px; background: var(--r-bg); color: var(--r-fg); border: 2px solid var(--r-accent); text-decoration: none; }
.cx-skip:focus { left: 8px; top: 8px; }
/* hidden means hidden. The attribute's own UA rule is display:none at specificity 0-0-0, so ANY class
   that sets display outranks it: the review link ships hidden until the platform supplies a real one,
   it carries .r-btn, and .r-btn's display:inline-flex un-hid it. A dead "Leave a review" button with
   href="#" rendered on the reviews page of a gate-green build, and the controls gate could not see it
   because the markup says hidden. !important because the whole point is that nothing may override it. */
[hidden] { display: none !important; }
.cx-sr { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.cx-hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
/* :not(.r-btn) because the submit IS a site button and the field sizing here outranks it. At
   .cx-form button this rule is 0-1-1 and .r-btn is 0-1-0, so the one button on the page that is also
   a form control came out 51px tall with 14px of inner padding against the site's 46px and 26px: the
   odd button out on every page that carries a form, and the kind of thing nobody can name and
   everybody sees. A form that ships a bare <button> still gets field sizing, which is the point of
   the rule; a button wearing the site's own class keeps the site's shape. */
.cx-form input, .cx-form select, .cx-form textarea, .cx-form button:not(.r-btn) {
  font: inherit; font-size: max(1rem, var(--cx-body)); width: 100%;
  padding: .65em .8em; border: 1px solid var(--cx-line); border-radius: var(--cx-radius);
  background: var(--cx-bg); color: var(--cx-fg);
}
.cx-form textarea { resize: vertical; }
/* The file picker's own button is painted by the browser, and on a dark form it is the one white
   control in the column: the eye reported "one field is light while every other field is dark" on the
   contact page and named the wrong control, because the only light thing in the form was this. It is
   part of the field, so it wears the field's palette - a quiet outline, not a second submit. The text
   beside it ("No file chosen") belongs to the browser and cannot be styled or renamed. */
.cx-form input[type="file"] { padding: .45em .55em; }
.cx-form input[type="file"]::file-selector-button {
  font: inherit; font-size: .92em; font-weight: 600; margin-right: .7em; padding: .4em .8em;
  background: transparent; color: var(--cx-fg); border: 1px solid var(--cx-line);
  border-radius: var(--cx-radius); cursor: pointer;
}
.cx-form input[type="file"]::file-selector-button:hover { border-color: var(--cx-fg); }
.cx-form button { width: auto; cursor: pointer; border-color: transparent; }
.cx-field { display: grid; gap: var(--sp-form-label, .35em); max-width: none; }
.cx-field label { font-weight: 600; }
a[href^="tel:"], .cx-form button, .cx-bar a, .cx-nav a { min-height: 44px; }
.cx-form button, .cx-bar a { display: inline-flex; align-items: center; justify-content: center; }
/* Sticky chrome must never hide what has focus (WCAG 2.4.11): scroll padding at the top for the header
   and, on a phone, at the bottom for the action bar. A keyboard visitor tabbing to Submit at 390x667
   left it wholly under the bar. This replaces the per-anchor scroll-margin, which would add to it. */
html { scroll-padding-top: calc(var(--cx-head-h, 64px) + var(--cx-gap)); }
.cx-form-status { margin: .75em 0 0; max-width: none; }
.cx-form-status.is-error { font-weight: 600; }
.cx-form [aria-invalid="true"] { border-color: currentColor; box-shadow: 0 0 0 1px currentColor; }
.cx-form button[aria-busy="true"] { cursor: progress; opacity: .8; }
.cx-form-done { padding: calc(var(--cx-gap) * 1.25); border: 1px solid var(--cx-line); border-radius: var(--cx-radius); }
.cx-form-done:focus { outline: none; }
.cx-form-done p { margin: 0 0 .5em; }
.cx-form-done-title { font-weight: 700; font-size: 1.15em; }
.cx-head { position: sticky; top: 0; z-index: 50; background: var(--cx-bg); }
.cx-navbtn { display: none; align-items: center; gap: .55rem; min-height: 44px; min-width: 44px; padding: 0 .25rem; background: none; border: 0; font: inherit; color: inherit; cursor: pointer; }
.cx-navbtn-bars { display: inline-block; width: 22px; }
.cx-navbtn-bars span { display: block; height: 2px; margin: 4px 0; background: currentColor; }
.cx-navbtn-bars span { transition: transform .2s, opacity .2s; }
/* Open, the bars become an X and the label says Close: a control that reads "Menu" while the menu is
   open does not say what pressing it will do. */
.cx-navbtn[aria-expanded="true"] .cx-navbtn-bars span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.cx-navbtn[aria-expanded="true"] .cx-navbtn-bars span:nth-child(2) { opacity: 0; }
.cx-navbtn[aria-expanded="true"] .cx-navbtn-bars span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }
.cx-nav ul { list-style: none; margin: 0; padding: 0; display: flex; gap: 1.25rem; }
.cx-nav a { text-decoration: none; }
.cx-navbtn[hidden], .cx-nav[hidden] { display: none; }
/* Which of the two shows is a CSS decision made before the first paint, keyed on the js class the
   head sets: no script means the list and no button, script means the button and a collapsed list.
   Nothing mutates after paint, so a phone page does not jump when the menu resolves. */
/* The desktop nav that did not fit (navScript adds cx-nav-tight): the button, and the links as a panel
   under the header rather than a second row inside it. */
html.cx-nav-tight .cx-navbtn { display: inline-flex; margin-left: auto; }
html.cx-nav-tight .cx-navbtn ~ * { margin-left: 0; }
html.cx-nav-tight .cx-nav { display: none; }
html.cx-nav-tight .cx-nav.is-open { display: block; position: absolute; top: 100%; right: max(var(--cx-gap), calc((100vw - var(--cx-wrap, 1200px)) / 2)); min-width: 260px; max-height: calc(100dvh - var(--cx-head-h, 64px)); overflow-y: auto; padding: 8px 20px; background: var(--r-bg, var(--cx-bg, #fff)); color: var(--r-fg, inherit); border: 1px solid var(--r-line, rgba(0,0,0,.12)); box-shadow: 0 16px 36px -16px rgba(0,0,0,.4); z-index: 60; }
html.cx-nav-tight .cx-nav.is-open ul { display: flex; flex-direction: column; gap: 0; }
html.cx-nav-tight .cx-nav.is-open a { display: flex; align-items: center; min-height: 44px; }
html.cx-nav-tight .cx-nav-tel { display: none; }
@media (max-width: 900px) {
  html.js .cx-navbtn { display: inline-flex; }
  html.js .cx-nav { display: none; }
  html.js .cx-nav.is-open { display: block; }
}
img:not(.cx-bleed):not([class*="logo"]):not([class*="icon"]), picture:not(.cx-bleed) > img { border-radius: var(--cx-radius-media, 8px); }
.cx-bleed, .cx-bleed > img { border-radius: 0; }
@media (max-width: 900px) {
  .cx-nav { flex-basis: 100%; width: 100%; }
  .cx-nav ul { flex-direction: column; gap: 0; }
  .cx-nav a { display: flex; align-items: center; min-height: 44px; }
  .cx-navbtn { margin-left: auto; order: 99; }
  .cx-head:not(.cx-util-head) a[href^="tel:"]:not(.cx-nav a) { display: none; }
  body { padding-bottom: calc(var(--cx-bar-h, 0px) + env(safe-area-inset-bottom, 0px)); }
  html { scroll-padding-bottom: calc(var(--cx-bar-h, 0px) + env(safe-area-inset-bottom, 0px) + var(--cx-gap)); }
  /* The open menu fits the screen and scrolls itself; it used to stand 538px tall inside a sticky
     header on an 844px phone. Without script the list is always open, so the header stops sticking. */
  .cx-nav.is-open { max-height: calc(100dvh - var(--cx-head-h, 64px) - var(--cx-bar-h, 0px)); overflow-y: auto; overscroll-behavior: contain; }
  html:not(.js) .cx-head { position: static; }
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; scroll-behavior: auto !important; }
}
.cx-util-head { display: flex; align-items: center; justify-content: space-between; gap: var(--cx-gap); padding: .75rem var(--cx-gap); }
.cx-util-head .cx-util-tel { white-space: nowrap; }
.cx-util { width: min(100% - (var(--cx-gap) * 2), var(--cx-measure)); margin-inline: auto; padding-block: var(--cx-band); }
.cx-foot { padding: var(--cx-band) var(--cx-gap); }
