/* Additional photos stay out of flow and are hidden outside the desktop
   rotation, including when a desktop browser is resized down to mobile. */
.hero-slide { display: none; }

/* Desktop hero photo rotation. Each photo uses the existing .hero-img
   Ken Burns animation; the active photo crossfades every three seconds. */
@media (min-width: 900px) {
  .hero-scrim { display: none; }
  .hero-media.is-slideshow .hero-img {
    opacity: 0;
    transition: opacity 1s ease-in-out;
  }
  .hero-media.is-slideshow .hero-img.is-active { opacity: 1; }
  .hero-media.is-slideshow .hero-slide { display: block; }
  .hero-media.is-slideshow .hero-slide-shift-up-85 {
    translate: 0 -85px;
    height: calc(108% + 120px);
  }
  .hero-media.is-slideshow .hero-slide-shift-down-70 { translate: 0 70px; }
  .hero-media.is-slideshow .hero-slide-shift-down-20 { translate: 0 20px; }
  .hero-media.is-slideshow .hero-slide-shift-up-73 { translate: 0 -73px; }
}

/* Production desktop hero rules are preserved below.
   Mobile overrides are grouped at the end of this file. */

.hero-form-wrap { display: none; }

.contact-form-wrap {
  position: relative;
  justify-self: end;
  width: min(100%, 440px);
}
.contact-form-wrap .field-label.is-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

@media (min-width: 900px) {
  .hero-inner {
    display: grid;
    /* The form needs a real floor or its fields collapse; the copy takes
       whatever is left. */
    grid-template-columns: minmax(0, 1fr) minmax(400px, 440px);
    gap: clamp(32px, 4.5vw, 72px);
    align-items: center;
    text-align: left;
    /* The centred hero had room for a tall block of copy. A form is taller,
       so the top padding comes down. The bottom no longer has to clear the
       trust bar, which is gone. */
    padding-top: clamp(104px, 11vw, 128px);
    padding-bottom: clamp(56px, 6vw, 80px);
  }

  /* Undo the centring the single column layout relied on. */
  .hero-eyebrow { text-align: left; }

  /* Line the left column up with FREE ESTIMATES in the banner, block and
     buttons together. Not a fixed nudge: the banner inset and the copy column
     edge both move with the viewport, and by different amounts, so the offset
     is the difference between the two formulas. That keeps the two aligned at
     every width rather than only at the one I measured.

     A transform rather than a negative margin, so the column keeps its width
     and the copy does not rewrap. */
  .hero-copy {
    /* where the copy column starts: container margin plus gutter */
    --edge: calc(max(0px, (100vw - 1360px) / 2) + var(--gutter));
    /* the banner's own left inset, copied from banner.css */
    --banner-inset: max(
      min(80px, 6.6vw),
      calc(max(0px, (100vw - 1360px) / 2) + var(--gutter) - 124px)
    );
    /* Second axis lifts the headline, block and buttons 5px as one. They are
       all children of this element, so the alignment between them is untouched
       and the horizontal formula above still resolves the same. */
    transform: translate(calc(var(--banner-inset) - var(--edge)), -5px);
    /* Hug the blue block rather than filling the grid column, so the buttons
       have its right edge to align to. */
    width: fit-content;
  }
  .hero-copy { text-align: left; }
  .hero-title { margin-left: 0; margin-right: 0; max-width: 12ch; }
  .hero-lede { margin-left: 0; margin-right: 0; max-width: 42ch; }
  /* Right aligned so the pair finishes under the block's right corner.
     The column hugs its widest child, which is the blue block, so aligning
     to the column's right edge is aligning to the block's. */
  .hero-actions { justify-content: flex-end; }

  /* Carry the parallelogram shape into the hero actions. Keep the words and
     arrow upright inside the clipped button. */
  .hero-actions .btn {
    --hero-btn-slant: 14px;
    padding-inline: calc(28px + var(--hero-btn-slant));
    clip-path: polygon(var(--hero-btn-slant) 0, 100% 0,
      calc(100% - var(--hero-btn-slant)) 100%, 0 100%);
  }

  .hero-form-wrap {
    display: block;
    animation: riseIn .7s ease .25s both;
  }

  /* The heading now lives inside the card rather than floating on the photo
     above it. */
  .hero-quote-title {
    font-size: clamp(20px, 1.9vw, 24px);
    line-height: 1.15;
    margin-bottom: 2px;
  }

  /* The four text fields carry their label as a placeholder instead of a
     line of type above the input. The label element stays in the markup and
     is only visually hidden: a placeholder is not an accessible name, and it
     disappears as soon as the field has a value. */
  .hero-quote .field-label.is-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
  .hero-quote input::placeholder { color: var(--muted); opacity: 1; }

  /* Tighter than the contact form: this one has to fit inside the viewport
     alongside the headline, not sit in a section of its own. */
  .hero-quote {
    padding: 20px;
    gap: 10px;
    box-shadow: 0 18px 50px rgba(20, 22, 26, .35);
    /* Sits above the offset blue block behind it. */
    position: relative;
    z-index: 1;
  }

  /* A blue rectangle the same size as the card, shifted up and left so only
     those two edges show. The card covers its right and bottom, which is what
     makes the card look lifted off it rather than outlined by it. */
  .hero-form-wrap {
    position: relative;
    --lift: 14px;
    /* Nudged down off the header. A relative offset rather than a transform:
       riseIn ends on `transform: none` with `both` fill, so a translateY here
       would be wiped the moment the animation lands. Neither one reflows, and
       the blue rectangle is this element's own ::before, so it comes along. */
    top: 34px;
    left: 10px;
  }
  .hero-form-wrap::before {
    content: "";
    position: absolute;
    top: calc(-1 * var(--lift));
    left: calc(-1 * var(--lift));
    right: var(--lift);
    bottom: var(--lift);
    background: var(--accent);
    z-index: 0;
  }
  .contact-form-wrap::before {
    content: "";
    position: absolute;
    top: -14px;
    left: -14px;
    right: 14px;
    bottom: 14px;
    background: var(--accent);
    z-index: 0;
  }
  .contact-form-wrap .hero-quote { position: relative; z-index: 1; }

  /* Concrete is square, so nothing in this card is rounded. Five separate
     radii to undo: the card at 15px, the inputs at 10px, the reply buttons at
     10px, the submit button at 999px, and the global :focus-visible ring,
     which would otherwise draw a rounded outline around a square field. */
  .hero-quote,
  .hero-quote input,
  .hero-quote select,
  .hero-quote textarea,
  .hero-quote .reply-btn,
  .hero-quote .submit-btn,
  .hero-quote :focus-visible { border-radius: 0; }
  .hero-quote .field-label { font-size: 13px; }
  .hero-quote input,
  .hero-quote select,
  .hero-quote textarea { padding: 9px 12px; font-size: 15px; }
  .hero-quote textarea { min-height: 62px; }
  .hero-quote .reply-btn { padding: 7px 14px; font-size: 14px; }
  .hero-quote .reply-btn:not(.is-on):hover { border-color: var(--accent); }
  .hero-quote .submit-btn { padding: 13px 20px; font-size: 16px; }
}

/* .contact-form-wrap used to go display: contents here, twice, which left it
   with no box at all below 740px. The card now carries a blue frame on that
   box, so it has to stay a real element. Removed 2026-09-28. */

/* ------------------------------------------------- hero eyebrow

   Uppercased in CSS rather than typed in caps, so the markup stays readable
   and the words are not shouted at a screen reader. Applies at every width:
   this is a copy change, not a layout one. */
.hero-eyebrow {
  margin: 0 auto 14px;
  max-width: 34ch;
  font-size: clamp(12px, 1.15vw, 14px);
  font-weight: 700;
  letter-spacing: .14em;
  line-height: 1.45;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .92);
  text-shadow: 0 1px 12px rgba(20, 22, 26, .45);
  animation: riseIn .7s ease .05s both;
}
@media (min-width: 900px) {
  .hero-eyebrow { margin-left: 0; margin-right: 0; max-width: none; }

  /* The service line leads and the tagline follows it. 743px of copy column
     puts a hard ceiling on the service line: 35px at the old .14em tracking,
     41px at .04em. Nowhere near the headline's 78px, so the headline comes
     down to 36px instead, which is what makes the service line the larger of
     the two. Tracking is loosened only slightly so the caps still read as a
     kicker rather than a second headline. */
  .hero-eyebrow {
    /* This is an h1 now, and styles.css gives h1, h2, h3 the serif. Every
       other property that rule sets is already overridden by class here or in
       the base block; the family was the only one left, and without this the
       desktop service line switched from Figtree to Source Serif 4. `inherit`
       rather than naming the stack, so it tracks body. */
    font-family: inherit;
    font-size: clamp(20px, 2.3vw, 38px);
    letter-spacing: .04em;
    line-height: 1.12;
    margin-bottom: 18px;
    color: #FFFFFF;
  }
  /* Out of the block and on the photo now, so it carries its own weight
     again and needs the shadow back for legibility. This reverses the
     earlier ordering: it is larger than the service line, not smaller. */
  .hero-title {
    font-size: clamp(34px, 3.6vw, 56px);
    max-width: none;
    margin-bottom: 20px;
    text-shadow: 0 2px 18px rgba(20, 22, 26, .55);
    /* Lifted on its own, clear of the block and buttons below. An offset
       rather than a transform, since .hero-title runs riseIn and that lands
       on `transform: none`; and rather than trimming margin-bottom, which
       would drag the block and buttons up with it. Neither reflows. */
    position: relative;
    top: -23px;
  }
}

/* The forced two row break only applies where the copy column is wide enough
   to hold each row. Below that the line wraps on its own rather than forcing
   a break that then wraps again, which is how it ended up on four rows. */
/* Both hidden by default. .eb-break-m used to have no rule out here at all,
   which was invisible only because the element carrying it was itself hidden
   above 900px. Now that one h1 serves every width, a <br> with no default
   rule breaks the desktop line. */
.eb-break,
.eb-break-m { display: none; }
@media (min-width: 1200px) {
  .eb-break { display: inline; }
}

/* ------------------------------------------------- hero frame

   A solid blue block sitting inside the copy column. It no longer breaks out
   to the screen edge, so it closes on all four sides.

   To go back to the open sided outline: restore the negative margin-left and
   the --edge padding, swap the background off .hero-frame and onto
   .hero-frame-line, and give --run a value for the angled cut.

   Desktop only, like the rest of the split. Mobile is untouched. */
@media (min-width: 900px) {
  .hero-frame {
    background: var(--accent);
    /* Hugs the widest line rather than filling the column, so the block does
       not trail a wide empty band of blue past the shorter lines. */
    width: fit-content;
    max-width: 100%;
    padding: 28px 36px 30px;
    border-left: 7px solid #FFFFFF;
    position: relative;
  }

  /* The matching bar at the bottom right corner. */
  .hero-frame::after {
    content: "";
    position: absolute;
    right: 0;
    bottom: 0;
    width: 42%;
    height: 7px;
    background: #FFFFFF;
  }

  /* Kept in the markup so the outline version is one rule away. */
  .hero-frame-line { display: none; }

  /* On solid blue the photo legibility shadow only muddies the type. */
  .hero-frame .hero-lede { text-shadow: none; }
  .hero-frame .hero-eyebrow { text-shadow: none; }
}

/* ------------------------------------------------- hero call button

   Solid white with blue type, rather than the transparent outline it uses
   elsewhere. Applied at every width on purpose: a call button that is white
   filled on desktop and outlined on a phone would read as a bug. */
.hero-actions .btn-ghost {
  background: #FFFFFF;
  border-color: #FFFFFF;
  color: var(--accent);
}
.hero-actions .btn-ghost:hover {
  background: var(--pale);
  border-color: var(--pale);
  color: var(--accent-hover);
}

/* Preserve the production desktop; preview additions render only below 900px. */
.hero-cta, .hero-trust, .hq-sm { display: none; }
@media (max-width: 899.98px) {
  .hero-form-wrap { display: none; }
  .contact-form-wrap {
    position: relative;
    justify-self: end;
    width: min(100%, 440px);
  }

  .contact-form-wrap .field-label.is-hidden,
  .hero-quote .field-label.is-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
  .hq-sm { display: none; }

  .hero-eyebrow {
    margin: 0 auto 12px;

    max-width: none;

    font-size: clamp(23px, calc(6.1vw + 5px), 43px);
    font-weight: 700;
    letter-spacing: 0;
    line-height: 1.16;

    text-transform: none;
    color: #FFFFFF;
    text-shadow: 0 2px 16px rgba(20, 22, 26, .55);
    animation: riseIn .7s ease .05s both;
  }
  .hero-cta {
    --cta-slant: 13px;
    display: none;
    align-items: center;
    justify-content: center;
    min-height: 54px;
    margin: 26px auto 0;

    padding-inline: calc(clamp(16px, 5vw, 26px) + var(--cta-slant));
    background: var(--accent);
    color: #FFFFFF;
    font-size: clamp(15px, 4.2vw, 17px);
    font-weight: 700;
    letter-spacing: .01em;
    text-align: center;
    text-decoration: none;
    clip-path: polygon(var(--cta-slant) 0, 100% 0,
      calc(100% - var(--cta-slant)) 100%, 0 100%);
    animation: riseIn .7s ease .28s both;
  }
  a.hero-cta:hover,
  a.hero-cta:active { background: var(--accent-hover); color: #FFFFFF; text-decoration: none; }
  .hero-trust {
    display: none;
    list-style: none;
    margin: 18px auto 0;
    padding: 0;

    max-width: 24ch;
    text-align: center;
    color: rgba(255, 255, 255, .94);

    font-size: clamp(19px, calc(3.5vw + 8px), 24px);

    font-weight: 700;
    line-height: 1.3;
    text-shadow: 0 1px 12px rgba(20, 22, 26, .5);
    animation: riseIn .7s ease .34s both;
  }
  .hero-trust li + li { margin-top: 6px; }
  .hero-trust-check { font-size: .92em; margin-right: 2px; }
  .hero-trust a {
    color: inherit;
    text-decoration: none;

    white-space: nowrap;

    display: inline-block;
    padding: 4px 2px;
  }
  .hero-trust a:hover,
  .hero-trust a:active { color: #FFFFFF; text-decoration: underline; }
  .hero-cta { display: inline-flex; }
  .hero-trust { display: block; }
  .hero-actions { display: none; }


  .hero-trust { margin-bottom: 26px; }


  .hero-frame {
    position: relative;
    background: var(--accent);
    padding: clamp(18px, 5.2vw, 26px) clamp(16px, 4.6vw, 24px) clamp(20px, 5.6vw, 28px);
    border-left: clamp(5px, 1.4vw, 7px) solid #FFFFFF;

    margin-inline: 0;
    text-align: center;
  }
  .hero-frame::after {
    content: "";
    position: absolute;
    right: 0;
    bottom: 0;
    width: 42%;
    height: clamp(5px, 1.4vw, 7px);
    background: #FFFFFF;
  }

  .hero-frame .hero-eyebrow,
  .hero-frame .hero-lede { text-shadow: none; }

  .hero-frame-line { display: none; }


  .hero-lede { margin-top: 12px; max-width: none; }


  .hero-form-wrap {
    display: block;

    --lift: 7px;
    width: calc(100% - 14px);

    margin: 13px auto 0;
  }


  .hero-form-wrap::before {
    content: "";
    position: absolute;
    inset: calc(-1 * var(--lift));
    background: var(--accent);
    z-index: 0;
  }
  .hero-form-wrap .hero-quote { position: relative; z-index: 1; }


  #hf-quote-form .field-reply { display: none; }

  /* The contact card takes the hero card's treatment: blue frame on all four
     sides, 14px off the width to pay for the two vertical edges, and the same
     heading swap. It keeps its reply row; only the hero copy loses that.

     .contact-form-wrap already has a ::before in the 900px block, an offset
     rectangle showing two edges. Overriding all four insets turns that into a
     frame rather than stacking a second layer on it. */
  .contact-form-wrap {
    position: relative;
    --lift: 7px;
    width: calc(100% - 14px);
    margin-inline: auto;
    justify-self: center;
    /* styles.css dissolves .contact-left below 740px and orders its four
       children: heading 1, .quote-form 2, .quick-cards 3, .contact-note 4.
       That worked while this wrapper was display: contents and the form
       itself was the grid item. Now that the wrapper is a real box, it is the
       item being ordered, and without this it defaults to 0 and sorts ahead of
       the heading. */
    order: 2;
  }
  .contact-form-wrap::before {
    content: "";
    position: absolute;
    inset: calc(-1 * var(--lift));
    background: var(--accent);
    z-index: 0;
  }
  .contact-form-wrap .hero-quote { position: relative; z-index: 1; }


  /* The canvas exposed behind and below the hero quote card is warm off-white.
     The next section begins on white, so the transition is visible. */
  .hero { background-color: #FAF9F7; }
  #services.services { background-color: #FFFFFF; }
  .hero-media { inset: 0 0 auto 0; height: 100svh; }


  .hero-inner { padding-bottom: clamp(40px, 7vw, 64px); }


  .hero-copy {
    display: flex;
    flex-direction: column;
    justify-content: center;
    min-height: calc(90svh - 132px);
  }


  .hero-form-wrap { position: relative; z-index: 1; }

  .hero-quote-title {
    font-size: clamp(21px, 5.4vw, 26px);
    line-height: 1.15;
    margin: 0 0 2px;
    text-align: center;
  }
  .hq-lg { display: none; }
  .hq-sm { display: inline; }


  .hero-quote { text-align: left; }
  .hero-quote input::placeholder { color: var(--muted); opacity: 1; }
  .eb-break,
  .eb-break-m { display: inline; }
  .hero-actions .btn-ghost {
    background: #FFFFFF;
    border-color: #FFFFFF;
    color: var(--accent);
  }
  .hero-actions .btn-ghost:hover {
    background: var(--pale);
    border-color: var(--pale);
    color: var(--accent-hover);
  }
  .hero-title { display: none; }
}

/* The mobile call and quote buttons stay separate from the scrolling page. */
@media (max-width: 739.98px) {
  /* env() is read at every use site, never through a custom property.

     Safari does not reliably resolve env() stored in a var: it falls back to
     the 0px default, while Chrome resolves it. That is exactly how this
     presented, correct on Chrome for Android and wrong on an iPhone, with the
     banner sitting at top: 0 under the status bar and the strip below
     collapsing to nothing.

     The repetition is the point. Do not fold these back into a variable. */
  .callbar {
    --floating-cta-gutter: clamp(16px, 5vw, 24px);
    --floating-cta-column: calc(50vw - var(--floating-cta-gutter) - 6px);
    --floating-cta-width: calc(var(--floating-cta-column) * .9 - 4px);
    --floating-cta-inset: calc(var(--floating-cta-gutter) + var(--floating-cta-column) * .05 + 2px);
    --floating-cta-center-shift: calc((100vw - var(--floating-cta-width)) / 2 - var(--floating-cta-inset) - 3px);
    position: static;
    display: contents;
    background: transparent;
    box-shadow: none;
  }
  .callbar a {
    position: fixed;
    bottom: calc(env(safe-area-inset-bottom, 0px) + 24px);
    left: calc((100vw - var(--floating-cta-width)) / 2);
    width: var(--floating-cta-width);
    min-height: 58px;
    z-index: 71;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #FFFFFF;
    background: var(--ink);
    clip-path: polygon(14px 0, 100% 0, calc(100% - 14px) 100%, 0 100%);
    translate: 0 calc(100% + env(safe-area-inset-bottom, 0px) + 24px);
    visibility: hidden;
    transition: transform .95s cubic-bezier(.65, 0, .35, 1),
      translate .95s cubic-bezier(.22, 1, .36, 1),
      opacity .45s ease .32s,
      background-color .65s ease-in-out,
      color .65s ease-in-out,
      visibility 0s linear .95s;
  }
  .callbar.is-on a {
    translate: 0 0;
    visibility: visible;
    transition: transform .95s cubic-bezier(.65, 0, .35, 1),
      translate .95s cubic-bezier(.22, 1, .36, 1),
      opacity .45s ease .32s,
      background-color .65s ease-in-out,
      color .65s ease-in-out,
      visibility 0s;
  }
  .callbar-call {
    z-index: 72;
    background: var(--ink);
    transform: translateX(calc(0px - var(--floating-cta-center-shift)));
  }
  .callbar.is-over-footer .callbar-call {
    background: #FFFFFF;
    color: #000000;
  }
  .callbar a.callbar-quote {
    background: #6A6A6A;
    color: #FFFFFF;
  }
  .callbar-quote {
    transform: translateX(var(--floating-cta-center-shift));
    opacity: 1;
    visibility: visible;
    transition: transform .95s cubic-bezier(.65, 0, .35, 1),
      translate .95s cubic-bezier(.22, 1, .36, 1),
      opacity .45s ease,
      visibility 0s;
  }
  .callbar.is-call-only .callbar-call { transform: translateX(0); }
  .callbar.is-call-only .callbar-quote {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: transform .95s cubic-bezier(.65, 0, .35, 1),
      translate .95s cubic-bezier(.22, 1, .36, 1),
      opacity .45s ease,
      visibility 0s linear .95s;
    transform: translateX(0);
  }

  @media (max-width: 289.98px) {
    .callbar a,
    .callbar.is-on a {
      left: 16px;
      width: calc(100vw - 32px);
      transform: none;
      clip-path: none;
    }
    .callbar a.callbar-quote {
      bottom: calc(env(safe-area-inset-bottom, 0px) + 24px);
    }
    .callbar a.callbar-call {
      bottom: calc(env(safe-area-inset-bottom, 0px) + 90px);
    }
  }
  @media (prefers-reduced-motion: reduce) {
    .callbar a,
    .callbar.is-on a { transition: none; }
    .callbar.is-on a { translate: 0 0; }
  }

  /* Full-screen coverage also exposes the top inset. Keep the existing
     mobile controls and hero copy clear of the sensor housing. */

  /* Dormant while viewport-fit is not cover, which is the current setting:
     every env(safe-area-inset-*) below resolves to its 0px fallback, so this
     strip has no height and the offsets add nothing. All of it is kept as the
     correct behaviour should cover ever come back.

     Paint that inset. The offsets below already start the banner under the
     status bar, but nothing was filling the strip above it, so whatever
     passed behind showed through: the hero photo at the top of the page, and
     on a Safari that ignores a media-qualified theme-color, the banner's own
     blue sampled up into the bar.

     Fixed and opaque, so it is a lid rather than a gap: content scrolls
     beneath it and never appears in it. Height collapses to zero wherever
     there is no inset, which is every desktop browser and any build without
     viewport-fit=cover, so the strip costs nothing there.

     On body rather than .wrap, which carries overflow-x: hidden and so is a
     scroll container; a fixed child of one is fragile to clip.

     z-index clears the banner at 70, the header at 60 and the floating CTAs
     at 71 and 72. pointer-events off so it never eats a tap meant for the
     browser chrome. */
  body::before {
    content: "";
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: env(safe-area-inset-top, 0px);
    background: var(--pale);
    z-index: 80;
    pointer-events: none;
  }

  .anno { top: env(safe-area-inset-top, 0px); }
  .wrap:has(.site-header.is-scrolled) .anno {
    transform: translateY(calc(-100% - env(safe-area-inset-top, 0px)));
  }
  .site-header { top: calc(56px + env(safe-area-inset-top, 0px)); }
  .site-header.is-scrolled { top: env(safe-area-inset-top, 0px); }
  .hero-inner { padding-top: calc(132px + env(safe-area-inset-top, 0px)); }
  .hero-copy { min-height: calc(90svh - 132px - env(safe-area-inset-top, 0px)); }
  html { scroll-padding-top: calc(84px + env(safe-area-inset-top, 0px)); }
}

@media (max-width: 226.98px) {
  .hero-trust { display: none; }
}

@media (max-width: 349.98px) {
  .site-header,
  .site-header.is-scrolled { top: env(safe-area-inset-top, 0px); }
  .hero-inner { padding-top: calc(76px + env(safe-area-inset-top, 0px)); }
  .hero-copy { min-height: calc(90svh - 76px - env(safe-area-inset-top, 0px)); }
}

@media (max-width: 329.98px) {
  .hero-inner { padding-top: calc(116px + env(safe-area-inset-top, 0px)); }
  .hero-copy { min-height: calc(90svh - 116px - env(safe-area-inset-top, 0px)); }
}

@media (max-width: 214.98px) {
  .hero-inner { padding-top: calc(130px + env(safe-area-inset-top, 0px)); }
  .hero-copy { min-height: calc(90svh - 130px - env(safe-area-inset-top, 0px)); }
}

/* Keep both request forms' submit actions white with the same blue frame at
   every breakpoint. */
#hf-submit-btn,
#submit-btn {
  --hero-submit-frame: clamp(5px, 1.4vw, 7px);
  border: var(--hero-submit-frame) solid var(--accent);
  border-radius: 0;
  background: #FFFFFF;
  color: var(--accent);
  padding: calc(16px - var(--hero-submit-frame)) calc(24px - var(--hero-submit-frame));
}
#hf-submit-btn:hover,
#submit-btn:hover {
  background: var(--pale);
  color: var(--accent-hover);
  border-color: var(--accent-hover);
}

/* Between 740px and 900px the banner still shows the rating and free estimate
   message, so avoid repeating those two entries in the hero trust list. */
@media (min-width: 740px) and (max-width: 899.98px) {
  .hero-trust-banner-duplicate { display: none; }
}

@media (max-width: 739.98px) {
  /* Keep the exposed area around the request form warm off-white while the
     white services section rises behind the card by the measured overlap. */
  .hero {
    overflow: visible;
    background: transparent;
    z-index: 1;
  }
  #services.services {
    --mobile-hero-overlap: 0px;
    position: relative;
    z-index: 0;
    margin-top: calc(0px - var(--mobile-hero-overlap));
    padding-top: calc(clamp(76px, 9vw, 136px) + var(--mobile-hero-overlap));
  }
}
