/* One Stop Home Improvement — Bootstrap 5.3 theme layer
 * -----------------------------------------------------------------------
 * This file does not restyle Bootstrap. It sets Bootstrap's own CSS
 * variables to the brand tokens, which is Bootstrap's supported way to
 * theme it without a Sass build. Layout, grid, navbar, dropdown, collapse
 * and buttons all stay stock Bootstrap.
 *
 * Order: bootstrap.min.css, then this file.
 * ------------------------------------------------------------------- */

/* 1. Fonts — self-hosted in /fonts. */
@font-face {
  font-family: Jost;
  src: url("/fonts/jost-variable-latin.woff2") format("woff2-variations");
  font-weight: 300 700; font-style: normal; font-display: swap;
}
/* Two faces only: Jost for everything you read or click, Playfair Display
   (SIL OFL) for the display type — slogan, section titles, figures, card
   titles. One weight, roman and italic — 23KB each, where the
   variable pair would be 77KB. */
@font-face {
  font-family: "Playfair Display";
  src: url("/fonts/playfair-display-latin-500-normal.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Playfair Display";
  src: url("/fonts/playfair-display-latin-500-italic.woff2") format("woff2");
  font-weight: 500; font-style: italic; font-display: swap;
}

/* 2. Brand tokens.
 *
 * White chrome, baby-blue sections, a charcoal footer. Blue is kept for the
 * things you act on (buttons, the active menu item) and for the two tinted
 * bands; it no longer paints the masthead or the footer.
 *
 * The blues share hue 205 with the steel blue #1c679c, so the baby-blue
 * grounds, the buttons and the links read as one family. The logo keeps its
 * own colours.
 *
 * One palette, light only — see the note after the tokens.
 */
:root {
  --os-page:        #ffffff;
  --os-raised:      #f5f8fa;
  --os-sunken:      #edf2f6;
  --os-header:      #ffffff;
  --os-pill:        #ffffff;   /* the menu pill and the dropdown */
  --os-pill-shadow: 0 12px 48px rgba(14, 31, 53, .10), 0 1px 3px rgba(14, 31, 53, .07);
  --os-nav-ink:     #33414e;   /* 10.1:1 on white */
  --os-baby:        #c7e2f6;   /* section ground: stats band, closing band */
  --os-baby-soft:   #ebf5fc;   /* menu hover */
  --os-baby-ink:    #3b5163;   /* muted text on baby blue */
  --os-baby-link:   #14507b;   /* links on baby blue */
  --os-baby-rule:   rgba(18, 33, 43, .16);
  --os-charcoal:    #23282d;   /* footer, and the dark button over the photo */
  --os-charcoal-hov:#3a4249;
  --os-on-dark:     #ffffff;
  --os-on-dark-mut: #b4bec7;
  --os-dark-rule:   rgba(255, 255, 255, .14);
  --os-foot-hover:  #b9dcf5;   /* link hover in the footer, both themes */
  --os-serif:       "Playfair Display", Georgia, "Times New Roman", serif;
  --os-ink:         #12212b;
  --os-ink-2:       #46596a;
  --os-ink-muted:   #586c7e;
  --os-accent:      #1c679c;   /* white on it: 6.05:1 */
  --os-accent-hov:  #14507b;
  --os-on-accent:   #ffffff;
  --os-hairline:    #e3e9ee;
  --os-border:      #7e8f9d;
  --os-toggler-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='%2312212b' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M5 8h20M5 15h20M5 22h20'/%3e%3c/svg%3e");
}
/* Light only. The white masthead and baby-blue bands are the design, so the
   page does not follow a phone's or computer's dark mode. "only" also stops
   Chrome's forced-dark setting from repainting it. */
html:root { color-scheme: only light; }

/* 3. Hand the tokens to Bootstrap. One mapping serves all three theme
      states, because each value is a var() that re-resolves per state. */
:root {
  --bs-body-bg:            var(--os-page);
  --bs-body-color:         var(--os-ink-2);
  --bs-emphasis-color:     var(--os-ink);
  --bs-heading-color:      var(--os-ink);
  --bs-secondary-color:    var(--os-ink-muted);
  --bs-border-color:       var(--os-hairline);
  --bs-link-color:         var(--os-accent);
  --bs-link-hover-color:   var(--os-accent-hov);
  /* Bootstrap 5.3 colours links from the -rgb pair, so it is set too (#1c679c, #14507b). */
  --bs-link-color-rgb:     28, 103, 156;
  --bs-link-hover-color-rgb: 20, 80, 123;
  --bs-primary:            var(--os-accent);

  /* Jost's x-height is .46 against Inter's .546, so body text steps up to 17px
     to read at the same size Inter did at 16. */
  --bs-body-font-family:   Jost, "Century Gothic", system-ui, "Segoe UI", Roboto, sans-serif;
  --bs-body-font-size:     1.0625rem;
  --bs-body-font-weight:   400;
  --bs-body-line-height:   1.7;

  /* Bootstrap's own radius scale, unmodified. Structural bands stay square;
     the things you click or read sit on the radius. */
}

/* 4. Display face. Bootstrap has no font-family utility, so these are the
      whole typographic addition. */
.ff-display { font-family: Jost, "Century Gothic", system-ui, sans-serif; }
.tracked    { letter-spacing: .14em; }
.tracked-sm { letter-spacing: .1em; }

/* 5. Masthead — stock .navbar on white, driven by its own variables.
      Home to Contact sit in one white pill lifted by a soft shadow (after
      braverebels.com); the phone number and the estimate button stand
      outside it. The pill only exists at xl, where the menu is a row. */
.navbar.os-masthead {
  --bs-navbar-color:              var(--os-nav-ink);
  --bs-navbar-hover-color:        var(--os-ink);
  --bs-navbar-active-color:       var(--os-ink);
  --bs-navbar-brand-color:        var(--os-ink);
  --bs-navbar-brand-hover-color:  var(--os-ink);
  --bs-navbar-toggler-border-color: transparent;
  --bs-navbar-toggler-icon-bg:    var(--os-toggler-icon);
  --bs-navbar-toggler-focus-width: 2px;
  --bs-navbar-padding-y: 0;
  background-color: var(--os-header);
  min-height: 68px;
  transition: box-shadow .25s ease;
}
@media (min-width: 1200px) { .navbar.os-masthead { min-height: 92px; } }
/* sticky-top sits on <header>, not the nav: a sticky element can only travel
   inside its parent, and <header> is exactly the nav's height. */
.os-header.sticky-top { top: env(safe-area-inset-top, 0px); }
/* Keeps the bar 68px tall on phones even when the open menu wraps below it.
   The negative margin puts the menu lines on the same side margin as the
   logo: the button's .75rem padding plus the icon's 4px inset is 1rem. */
.os-masthead .navbar-toggler { min-height: 68px; margin-right: -1rem; }
/* Once the page moves under the bar, a hairline and a soft shadow separate
   the two. Toggled by one IntersectionObserver, not a scroll handler. */
.os-masthead.is-scrolled { box-shadow: 0 1px 0 var(--os-hairline), 0 10px 30px rgba(14, 31, 53, .07); }
.os-sentinel { position: absolute; top: 0; left: 0; width: 1px; height: 1px; pointer-events: none; }

/* The lockup sits straight on white. */
.os-masthead .os-brand { display: flex; align-items: center; }
.os-masthead .os-brand img { height: 30px; width: auto; display: block; }
@media (min-width: 1200px) { .os-masthead .os-brand img { height: 40px; } }

.os-masthead .nav-link {
  font-family: Jost, "Century Gothic", system-ui, sans-serif;
  font-size: .8125rem; font-weight: 500; text-transform: uppercase;
  letter-spacing: .12em;
  transition: color .18s ease, background-color .18s ease;
}
/* Services only opens its list, so it is a <button>, not a link. It takes the
   full row like the links around it, label on the left. */
.os-masthead button.nav-link { width: 100%; text-align: start; }
@media (min-width: 1200px) { .os-masthead button.nav-link { width: auto; } }
.os-masthead .nav-link:focus-visible,
.os-masthead .btn:focus-visible { outline: 2px solid var(--os-accent); outline-offset: 2px; box-shadow: none; }

@media (min-width: 1200px) {
  .os-masthead .os-pill {
    background: var(--os-pill); border-radius: 999px;
    padding: .375rem; column-gap: .125rem;
    box-shadow: var(--os-pill-shadow);
  }
  .os-masthead .os-pill .nav-link { padding: .625rem 1.125rem; border-radius: 999px; }
  .os-masthead .os-pill .nav-link:hover { background: var(--os-baby-soft); }
  .os-masthead .os-pill .nav-link.active,
  .os-masthead .os-pill .nav-link.show { background: var(--os-baby); color: var(--os-ink); }
}
/* The phone number is the highest-intent control on a contractor's page,
   so it reads as a number, not as another menu word. */
.os-masthead .os-tel { letter-spacing: .04em; font-size: .9375rem; color: var(--os-ink); }
.os-masthead .os-tel:hover { color: var(--os-accent); }

.os-masthead .dropdown-menu {
  --bs-dropdown-bg:              var(--os-pill);
  --bs-dropdown-color:           var(--os-ink);
  --bs-dropdown-link-color:      var(--os-ink);
  --bs-dropdown-link-hover-color: var(--os-ink);
  --bs-dropdown-link-hover-bg:   var(--os-baby-soft);
  --bs-dropdown-link-active-color: var(--os-ink);
  --bs-dropdown-link-active-bg:  var(--os-baby);
  --bs-dropdown-border-width:    0;
  --bs-dropdown-border-radius:   1rem;
  --bs-dropdown-padding-x:       .375rem;
  --bs-dropdown-padding-y:       .375rem;
  --bs-dropdown-item-padding-x:  1rem;
  --bs-dropdown-item-padding-y:  .55rem;
  --bs-dropdown-item-border-radius: .625rem;
  --bs-dropdown-min-width:       17rem;
  --bs-dropdown-spacer:          .75rem;
  --bs-dropdown-font-size:       .9375rem;
}
@media (min-width: 1200px) {
  .os-masthead .dropdown-menu { box-shadow: 0 18px 50px rgba(14, 31, 53, .14), 0 1px 3px rgba(14, 31, 53, .07); }
}
@media (max-width: 1199.98px) {
  /* In the drop-down menu (tablets, narrow windows) the list opens in place. */
  .os-masthead .dropdown-menu { --bs-dropdown-bg: var(--os-raised); margin-bottom: .5rem; }
}

/* 5b. Phone menu, under 576px — a Bootstrap Offcanvas panel that slides in
       from the right like an app screen: logo and close button on top, big
       serif rows, the phone number and the estimate button pinned to the
       bottom. Tablets and desktops keep the menu above, unchanged. */
.os-masthead .offcanvas-sm {
  --bs-offcanvas-width: min(440px, 100vw);
  --bs-offcanvas-bg: var(--os-page);
  --bs-offcanvas-color: var(--os-ink);
  --bs-offcanvas-padding-x: 1rem;
  --bs-offcanvas-padding-y: 0;
  --bs-offcanvas-border-width: 0;
  --bs-offcanvas-transition: transform .4s cubic-bezier(.22, .61, .36, 1);
}
.offcanvas-backdrop { background-color: #12212b; }
.offcanvas-backdrop.show { opacity: .45; }
@media (max-width: 575.98px) {
  .os-masthead .offcanvas-sm.show,
  .os-masthead .offcanvas-sm.showing { box-shadow: -24px 0 60px rgba(14, 31, 53, .18); }
  .os-masthead .offcanvas-header {
    min-height: 68px; justify-content: space-between;
    border-bottom: 1px solid var(--os-hairline);
  }
  /* 48px target; the negative margin puts the ✕ on the logo's side margin. */
  .os-masthead .offcanvas-header .btn-close {
    margin: 0 -1rem 0 0; padding: 1rem; background-size: 1.05rem; opacity: .75;
  }
  .os-masthead .offcanvas-header .btn-close:focus-visible { box-shadow: none; outline: 2px solid var(--os-accent); outline-offset: 2px; }
  .os-masthead .offcanvas-body {
    display: flex; flex-direction: column;
    padding-top: .5rem; padding-bottom: calc(1.5rem + env(safe-area-inset-bottom, 0px));
  }
  .os-masthead .offcanvas-sm .os-pill .nav-link {
    display: flex; align-items: center; justify-content: space-between;
    padding: 1rem 0; border-bottom: 1px solid var(--os-hairline);
    font-family: var(--os-serif); font-size: 1.75rem; line-height: 1.2; font-weight: 500;
    letter-spacing: 0; text-transform: none; color: var(--os-ink);
  }
  /* A chevron on each plain row, the way an app lists its screens. */
  .os-masthead .offcanvas-sm .os-pill .nav-link:not(.dropdown-toggle)::after {
    content: ""; flex: none; width: .55rem; height: .55rem; margin-right: .4rem;
    border-top: 1.5px solid var(--os-ink-muted); border-right: 1.5px solid var(--os-ink-muted);
    transform: rotate(45deg);
  }
  .os-masthead .offcanvas-sm .os-pill .nav-link.active { color: var(--os-accent); }
  /* Services opens in place: Bootstrap's filled caret becomes the same thin
     chevron, pointing down, and turns up while the list is open. The shift
     keeps the visible stroke centred on the row as it turns. */
  .os-masthead .offcanvas-sm .dropdown-toggle::after {
    flex: none; width: .55rem; height: .55rem; margin: 0 .4rem 0 0;
    border: 0; border-right: 1.5px solid var(--os-ink-muted); border-bottom: 1.5px solid var(--os-ink-muted);
    transform: translateY(-.2rem) rotate(45deg); transition: transform .25s ease;
  }
  .os-masthead .offcanvas-sm .dropdown-toggle.show::after { transform: translateY(.2rem) rotate(225deg); }
  .os-masthead .offcanvas-sm .dropdown-menu {
    --bs-dropdown-bg: transparent;
    --bs-dropdown-padding-x: 0;
    --bs-dropdown-padding-y: .35rem;
    --bs-dropdown-item-padding-x: 0;
    --bs-dropdown-item-padding-y: .6rem;
    --bs-dropdown-font-size: 1.0625rem;
    --bs-dropdown-link-color: var(--os-ink-2);
    --bs-dropdown-link-hover-bg: transparent;
    --bs-dropdown-link-hover-color: var(--os-accent);
    --bs-dropdown-link-active-bg: transparent;
    --bs-dropdown-link-active-color: var(--os-accent);
    margin: 0; border-radius: 0; border-bottom: 1px solid var(--os-hairline); box-shadow: none;
  }
  .os-masthead .offcanvas-sm .os-actions { margin-top: auto; padding-top: 2rem; row-gap: .75rem; }
  .os-masthead .offcanvas-sm .os-tel { font-size: 1.25rem; padding: .25rem 0; text-align: center; }
  .os-masthead .offcanvas-sm .os-actions .btn { display: block; width: 100%; --bs-btn-padding-y: 1rem; }
}
@media (max-width: 575.98px) and (prefers-reduced-motion: no-preference) {
  /* The rows glide in one after another as the panel arrives. */
  .os-masthead .offcanvas-sm .navbar-nav > li {
    opacity: 0; transform: translateX(1.25rem);
    transition: opacity .3s ease, transform .5s cubic-bezier(.22, .61, .36, 1);
  }
  .os-masthead .offcanvas-sm:is(.showing, .show) .navbar-nav > li { opacity: 1; transform: none; }
  .os-masthead .offcanvas-sm:is(.showing, .show) .os-pill > li:nth-child(1) { transition-delay: .08s; }
  .os-masthead .offcanvas-sm:is(.showing, .show) .os-pill > li:nth-child(2) { transition-delay: .12s; }
  .os-masthead .offcanvas-sm:is(.showing, .show) .os-pill > li:nth-child(3) { transition-delay: .16s; }
  .os-masthead .offcanvas-sm:is(.showing, .show) .os-pill > li:nth-child(4) { transition-delay: .20s; }
  .os-masthead .offcanvas-sm:is(.showing, .show) .os-pill > li:nth-child(5) { transition-delay: .24s; }
  .os-masthead .offcanvas-sm:is(.showing, .show) .os-actions > li:nth-child(1) { transition-delay: .30s; }
  .os-masthead .offcanvas-sm:is(.showing, .show) .os-actions > li:nth-child(2) { transition-delay: .34s; }
}

/* 5c. Quick actions in the bar — shown under 1200px, wherever the menu
       button is. Phones: two icons, request an estimate (envelope) and call
       (telephone). Wider screens: the estimate button with its label, the same
       one the desktop bar uses, plus the phone icon. Bootstrap Icons 1.13.1
       (MIT), inlined as SVG. The phone icons repeat in the open phone menu's
       header. The group is a flex item that takes the space between the logo
       and the menu button (flex-basis 0, so it never wraps the bar) and centres
       its icons there: the gap to the logo equals the gap to the menu lines.
       Steel blue on white is 6.05:1; each icon is a 44px tap target. */
.os-bar-actions { flex: 1 1 0; min-width: 0; }
.os-bar-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 2.75rem; height: 2.75rem; border-radius: 50%;
  color: var(--os-accent);
  transition: color .18s ease, background-color .18s ease;
}
.os-bar-icon:hover,
.os-bar-icon:active { color: var(--os-accent-hov); background-color: var(--os-baby-soft); }
.os-bar-icon:focus-visible { outline: 2px solid var(--os-accent); outline-offset: 2px; }
.os-bar-icon svg { width: 1.3125rem; height: 1.3125rem; }
.os-bar-actions .btn svg { width: 1rem; height: 1rem; flex: none; }
/* Wider screens pair the button with the phone icon. The icon's 44px target
   has 11.5px of air inside it; pulling that into the gap keeps the visible
   gaps either side of the pair equal. */
@media (min-width: 576px) { .os-bar-actions .btn + .os-bar-icon { margin-right: -.71875rem; } }
/* Under 318px (a folding phone's cover screen) the pair would reach the logo:
   the call icon stays, and the estimate is one tap away in the menu. */
@media (max-width: 317.98px) { .os-bar-actions .os-bar-mail { display: none; } }

/* 6. Buttons — Bootstrap's .btn variable API, no new button CSS.
      Two grounds, two jobs, so they never read as the same button:
        --solid  steel blue, the primary action: Request an estimate, in the
                 masthead and in the closing band.
        --dark   charcoal, the secondary action over the photograph (Our
                 process). A glass button was tried there first and failed —
                 its own edge vanished against the bright photo. */
.btn.btn-os-solid {
  --bs-btn-color: var(--os-on-accent);
  --bs-btn-bg: var(--os-accent);
  --bs-btn-border-color: var(--os-accent);
  --bs-btn-hover-color: var(--os-on-accent);
  --bs-btn-hover-bg: var(--os-accent-hov);
  --bs-btn-hover-border-color: var(--os-accent-hov);
  --bs-btn-active-color: var(--os-on-accent);
  --bs-btn-active-bg: var(--os-accent-hov);
  --bs-btn-active-border-color: var(--os-accent-hov);
  --bs-btn-focus-shadow-rgb: 28, 103, 156;
}
.btn.btn-os-dark {
  --bs-btn-color: #ffffff;                 /* 14.9:1 on this ground */
  --bs-btn-bg: var(--os-charcoal);
  --bs-btn-border-color: var(--os-charcoal);
  --bs-btn-hover-color: #ffffff;
  --bs-btn-hover-bg: var(--os-charcoal-hov);
  --bs-btn-hover-border-color: var(--os-charcoal-hov);
  --bs-btn-active-color: #ffffff;
  --bs-btn-active-bg: var(--os-charcoal-hov);
  --bs-btn-active-border-color: var(--os-charcoal-hov);
  --bs-btn-focus-shadow-rgb: 255, 255, 255;
  box-shadow: 0 10px 28px rgba(9, 14, 20, .32);
}
.btn.btn-os-solid,
.btn.btn-os-dark {
  --bs-btn-padding-x: 1.6rem;
  --bs-btn-padding-y: .85rem;
  --bs-btn-font-size: .8125rem;
  --bs-btn-font-weight: 500;
  font-family: Jost, "Century Gothic", system-ui, sans-serif;
  text-transform: uppercase; letter-spacing: .12em;
}
.os-masthead .btn.btn-os-solid { --bs-btn-padding-y: .75rem; }

/* 7. The hero. The photograph is the page: masthead above it, slogan and
      reviews over it, footer below. Everything is measured against the
      shipped photo, not guessed.

      Slogan: white caps at 64px are "large text", so the threshold is 3:1,
      not 4.5:1. Over this photograph unscrimmed that comes out at 1.2:1;
      a 45% scrim reaches 3.02:1 and 54% reaches ~3.9:1, which is where the
      gradient peaks through the slogan band.

      Cards: 70% white over the scrimmed photo leaves the darkest patch at
      L 0.476, so ink text lands at 8.2:1 — the veil can stay genuinely
      translucent and still be comfortably legible. */
.os-hero { position: relative; isolation: isolate; background: var(--os-charcoal); overflow: hidden; }
.os-hero__media {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; object-position: center 56%; z-index: -2;
}
.os-hero__scrim {
  position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(to bottom,
    rgba(15, 36, 51, .18)  0%,
    rgba(15, 36, 51, .30) 16%,
    rgba(15, 36, 51, .54) 40%,
    rgba(15, 36, 51, .50) 58%,
    rgba(15, 36, 51, .26) 80%,
    rgba(15, 36, 51, .20) 100%);
}
/* One gap, used three times: above the slogan, slogan to button, button to
   the bottom of the photo. space-evenly hands out any spare height in equal
   shares, so they stay equal at every screen size. The photo stops at about
   60% of the screen, so the reviews under it are on screen as the page opens. */
.os-hero__inner {
  --os-hero-gap: clamp(2.25rem, 4.2vw, 4.5rem);
  position: relative; display: flex; flex-direction: column; justify-content: space-evenly;
  row-gap: var(--os-hero-gap); padding-block: var(--os-hero-gap);
  min-height: clamp(420px, 54vh, 620px);
}
.os-hero__slogan-wrap { display: flex; justify-content: center; }

@media (prefers-reduced-motion: no-preference) {
  .os-hero__media { animation: os-settle 1400ms cubic-bezier(.22,.61,.36,1) both; }
}
@keyframes os-settle {
  from { transform: scale(1.06); }
  to   { transform: none; }
}

/* 8. The slogan, over the photograph. */
.os-slogan { margin: 0; text-align: center; color: #ffffff; }
.os-slogan__main {
  display: block;
  font-family: var(--os-serif);
  /* 3rem floor: on phones the line breaks into "One call, / done right."
     instead of running edge to edge at 2.5rem. */
  font-size: clamp(3rem, 7.5vw, 5.5rem); line-height: 1.15; font-weight: 500;
  letter-spacing: -.005em; text-wrap: balance;
  /* Playfair's tallest letters (the italic d and h, .795em) sit .195em below
     the top of this line box (ascent 1.082, descent .251, line-height 1.15).
     Pulled up by that much, the gap above the slogan is measured to the
     letters, the same way the other gaps are measured to the button. */
  margin-top: -.195em;
}
.os-slogan__main em { font-style: italic; }
/* The descriptive half of the h1 — what search engines and a skimming
   homeowner actually need. Sentence case: a long line in capitals is slow. */
/* White type here measured 2.6:1 over this photo (needs 4.5:1 at this
   size). It sits on frosted glass instead — ink on the veil reads at ~8:1
   and the photo stays untouched. */
.os-slogan__sub {
  display: block; width: fit-content; max-width: min(46ch, 100%); margin: 1.5rem auto 0;
  padding: .7rem 1.25rem; color: #12212b;
  background: rgba(255, 255, 255, .72);
  -webkit-backdrop-filter: blur(16px) saturate(150%);
          backdrop-filter: blur(16px) saturate(150%);
  border: 1px solid rgba(255, 255, 255, .5); border-radius: var(--bs-border-radius-lg);
  font-family: Jost, "Century Gothic", system-ui, sans-serif;
  font-size: clamp(1.125rem, 1.5vw, 1.3125rem); line-height: 1.6; font-weight: 400;
  letter-spacing: 0; text-transform: none; text-wrap: balance;
}

/* fadeInUp, taken verbatim from animate.css 4.1.1 (MIT, Daniel Eden) —
   502 bytes of the library's 95KB, because one animation is all the page
   uses. The element is visible by default and script only adds the class,
   so with JavaScript off or blocked the slogan still reads. */
.animate__animated {
  animation-duration: var(--animate-duration, 1s);
  animation-fill-mode: both;
}
.animate__fadeInUp { animation-name: fadeInUp; }
.animate__delay-1 { animation-delay: .25s; }
@keyframes fadeInUp {
  from { opacity: 0; transform: translate3d(0, 60px, 0); }
  to   { opacity: 1; transform: translate3d(0, 0, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .animate__animated { animation: none !important; }
}

/* 9. Reviews — on white, straight under the photograph, so they are on
      screen as the page opens. Playfair italic for the words, small tracked
      capitals for the name, and a hairline above each instead of a box. */
.os-reviews { padding-block: clamp(2.25rem, 3.6vw, 3.25rem) clamp(3.5rem, 7vw, 6rem); }
/* The opening quote mark sits on the hairline and breaks it, so it costs no
   height of its own — that is what keeps the cards above the fold. */
.os-review {
  position: relative; margin: 0; height: 100%; display: flex; flex-direction: column;
  padding-top: 1.5rem; border-top: 1px solid var(--os-hairline);
}
.os-review__mark {
  position: absolute; top: -.36em; left: 0; padding-right: .5rem;
  background: var(--os-page); color: var(--os-accent);
  font-family: var(--os-serif); font-style: italic; font-size: 3rem; line-height: 1;
}
.os-review__text {
  font-family: var(--os-serif); font-style: italic; font-weight: 500;
  font-size: 1.0625rem; line-height: 1.6; color: var(--os-ink); margin: 0 0 1rem;
  font-variant-numeric: lining-nums;   /* Playfair italic defaults to old-style figures */
}
.os-review__cite {
  margin-top: auto; font-family: Jost, "Century Gothic", system-ui, sans-serif;
  font-size: .75rem; font-weight: 500; letter-spacing: .14em; text-transform: uppercase;
  color: var(--os-ink-muted);
}
/* Safari before 18 and Firefox without the flag ignore backdrop-filter, so
   the tagline's veil thickens to stay legible rather than turning grey. */
@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .os-slogan__sub { background: rgba(255, 255, 255, .88); }
}

/* 9b. Stats band — baby blue, directly under the photograph.
       Ink figures on #c7e2f6 are 12.2:1; the labels in #3b5163 are 6.2:1. */
.os-stats { background: var(--os-baby); color: var(--os-ink); }
.os-stat__value {
  font-family: var(--os-serif);
  font-size: clamp(2.5rem, 5.5vw, 4rem); line-height: 1; font-weight: 500;
  letter-spacing: 0; font-variant-numeric: lining-nums tabular-nums;
  margin: 0 0 .5rem; display: block;
}
.os-stat__label {
  font-family: Jost, "Century Gothic", system-ui, sans-serif;
  font-size: .75rem; letter-spacing: .14em; text-transform: uppercase;
  color: var(--os-baby-ink); margin: 0;
}

/* 9c. Stat dividers. The figures run start / centre / end, so the rules sit
       on the column edges, at the thirds, rather than beside each figure. */
@media (min-width: 768px) {
  .os-stats .col + .col { border-left: 1px solid var(--os-baby-rule); }
}

/* 9d. Section heads. */
.os-section-head { margin-bottom: clamp(2.5rem, 5vw, 4rem); }
.os-eyebrow {
  font-family: Jost, "Century Gothic", system-ui, sans-serif; font-size: .75rem; font-weight: 500;
  letter-spacing: .16em; text-transform: uppercase; color: var(--os-ink-muted); margin: 0 0 .5rem;
}
.os-h2 {
  font-family: var(--os-serif); font-weight: 500; margin: 0;
  font-size: clamp(2.25rem, 4.5vw, 3.5rem); line-height: 1.1; letter-spacing: -.005em;
  color: var(--os-ink);
}

/* 9e. Signature work — three portrait cards, the page's showcase. Photos are
       served at 480/800/1200w; a card is never wider than ~440px. */
.os-work-card { display: block; color: inherit; text-decoration: none; }
.os-work-card__media {
  display: block; aspect-ratio: 4 / 5; overflow: hidden;
  border-radius: var(--bs-border-radius-lg); background: var(--os-sunken);
}
.os-work-card__media img { display: block; width: 100%; height: 100%; object-fit: cover; }
.os-work-card__place {
  margin: 1.5rem 0 .5rem; font-family: Jost, "Century Gothic", system-ui, sans-serif;
  font-size: .75rem; font-weight: 500; letter-spacing: .16em; text-transform: uppercase;
  color: var(--os-accent);                       /* 6.05:1 on white */
}
.os-work-card__title {
  font-family: var(--os-serif); font-size: 1.625rem; font-weight: 500; line-height: 1.25;
  color: var(--os-ink); margin: 0 0 .5rem; transition: color .18s ease;
}
.os-work-card:hover .os-work-card__title { color: var(--os-accent); }
.os-work-card__scope { font-size: 1rem; line-height: 1.55; color: var(--os-ink-2); margin: 0; }

/* 9f. Services — four large cards, 2 x 2. Photos at 600/1000/1400w. */
.os-service { display: block; color: inherit; text-decoration: none; height: 100%; }
.os-service__media {
  display: block; aspect-ratio: 4 / 3; overflow: hidden;
  border-radius: var(--bs-border-radius-lg); background: var(--os-sunken);
}
.os-service__media img { display: block; width: 100%; height: 100%; object-fit: cover; }
@media (prefers-reduced-motion: no-preference) {
  .os-service__media img,
  .os-work-card__media img { transition: transform .8s cubic-bezier(.22,.61,.36,1); }
  .os-service:hover .os-service__media img,
  .os-work-card:hover .os-work-card__media img { transform: scale(1.04); }
}
.os-service__title {
  font-family: var(--os-serif); font-size: 1.75rem; font-weight: 500; line-height: 1.25;
  letter-spacing: 0; color: var(--os-ink); margin: 1.5rem 0 .5rem; transition: color .18s ease;
}
.os-service:hover .os-service__title { color: var(--os-accent); }
.os-service__text { font-size: 1.0625rem; line-height: 1.55; color: var(--os-ink-2); margin: 0; }
/* The other six trades, as one line of links. */
.os-services__also {
  margin: clamp(3rem, 6vw, 4.5rem) 0 0; line-height: 2.2;
  font-family: Jost, "Century Gothic", system-ui, sans-serif;
  font-size: .8125rem; letter-spacing: .12em; text-transform: uppercase; color: var(--os-ink-muted);
}
.os-services__also > span:first-child { color: var(--os-ink); font-weight: 500; margin-right: 1rem; }
.os-services__also span[aria-hidden] { margin: 0 .6rem; }
.os-services__also a { color: var(--os-ink-2); text-decoration: none; }
.os-services__also a:hover { color: var(--os-accent); text-decoration: underline; text-underline-offset: 4px; }

/* 9g. Closing call to action — the same baby blue as the stats band, so the
       page opens and closes on it, then drops into the charcoal footer. */
.os-cta { background: var(--os-baby); color: var(--os-ink); }
.os-cta__title {
  font-family: var(--os-serif); font-weight: 500;
  font-size: clamp(2rem, 4.5vw, 3.25rem); line-height: 1.1; letter-spacing: -.005em;
  color: var(--os-ink); margin: 0 0 1rem;
}
.os-cta__line { font-size: 1.125rem; color: var(--os-baby-ink); margin: 0 0 2rem; }
.os-cta__line a { color: var(--os-baby-link); font-weight: 500; text-decoration: none; }
.os-cta__line a:hover { text-decoration: underline; }
.os-cta__line a:focus-visible { outline: 2px solid var(--os-accent); outline-offset: 3px; }

/* 10. Footer — dark grey, the one heavy block on the page. It ends the page
       under white and baby-blue sections the way the masthead no longer
       opens it. The reversed lockup reads on it without a plate. */
.os-footer { background: var(--os-charcoal); color: var(--os-on-dark); }
.os-footer a { color: var(--os-on-dark); text-decoration: none; }
.os-footer a:hover { color: var(--os-foot-hover); text-decoration: underline; }
.os-footer a:focus-visible { outline: 2px solid #ffffff; outline-offset: 3px; }
.os-footer .os-muted { color: var(--os-on-dark-mut); }
.os-footer .os-rule { border-top: 1px solid var(--os-dark-rule); }
.os-footer .os-brand-foot { display: inline-block; margin-bottom: 1.5rem; }
.os-footer img { height: 44px; width: auto; display: block; }
/* Wrapped link text needs to read as one item, so the space goes between
   items rather than between every line. */
.os-footer ul { line-height: 1.5; }
.os-footer li + li { margin-top: .5rem; }

/* 12. Inner pages — page head, lede, and the process page's photo and steps. */
.os-page-head { padding-block: clamp(4rem, 9vw, 7.5rem) clamp(2.5rem, 5vw, 4rem); }
.os-page-title {
  font-family: var(--os-serif); font-weight: 500; margin: 0 0 1.25rem;
  font-size: clamp(2.75rem, 6vw, 4.75rem); line-height: 1.05; letter-spacing: -.01em;
  color: var(--os-ink);
}
.os-lede {
  font-size: clamp(1.125rem, 1.6vw, 1.375rem); line-height: 1.55; color: var(--os-ink-2);
  max-width: 44ch; margin: 0; text-wrap: pretty;
}
.os-process-photo { margin: 0; }
.os-process-photo__media {
  display: block; overflow: hidden; aspect-ratio: 1600 / 549;
  border-radius: var(--bs-border-radius-lg); background: var(--os-sunken);
}
.os-process-photo__media img { display: block; width: 100%; height: 100%; object-fit: cover; }
.os-process-photo__cap { margin-top: .9rem; font-size: .9375rem; color: var(--os-ink-muted); }
.os-steps__list { list-style: none; padding-left: 0; margin-bottom: 0; }
/* The hairline is drawn inside the column, so it stops at the gutter. */
.os-step::before {
  content: ""; display: block; border-top: 1px solid var(--os-hairline); margin-bottom: 1.5rem;
}
.os-step__num {
  display: block; margin-bottom: 1rem;
  font-family: var(--os-serif); font-style: italic; font-size: 3rem; line-height: 1;
  font-variant-numeric: lining-nums; color: var(--os-accent);
}
.os-step__title {
  font-family: var(--os-serif); font-size: 1.625rem; font-weight: 500; line-height: 1.25;
  color: var(--os-ink); margin: 0 0 .6rem;
  font-variant-numeric: lining-nums; /* the 5 in "5-year" sits on the line */
}
.os-step__text { font-size: 1.0625rem; line-height: 1.6; color: var(--os-ink-2); margin: 0; max-width: 38ch; }

/* 13. Back to top — a pill in the primary blue, fixed bottom right. It fades
       up once the first section has scrolled away and hides again at the top.
       White on #1c679c is 6.05:1. */
.os-totop {
  position: fixed; z-index: 1010; /* under the header (1020), so an open menu covers it */
  right: calc(1rem + env(safe-area-inset-right, 0px));
  bottom: calc(1rem + env(safe-area-inset-bottom, 0px));
  display: inline-flex; align-items: center; gap: .5rem;
  height: 3rem; padding: 0 1.15rem 0 1rem; border-radius: 999px;
  background: var(--os-accent); color: #ffffff; text-decoration: none;
  font-family: Jost, "Century Gothic", system-ui, sans-serif;
  font-size: .75rem; font-weight: 500; letter-spacing: .14em; text-transform: uppercase;
  box-shadow: 0 12px 40px rgba(14, 31, 53, .22), 0 2px 6px rgba(14, 31, 53, .12);
  opacity: 0; visibility: hidden; transform: translateY(.75rem);
  transition: opacity .3s ease, transform .45s cubic-bezier(.22, .61, .36, 1),
              visibility 0s linear .45s, background-color .18s ease;
}
.os-totop.is-shown { opacity: 1; visibility: visible; transform: none; transition-delay: 0s; }
.os-totop:hover { background: var(--os-accent-hov); color: #ffffff; }
.os-totop:focus-visible { outline: 2px solid var(--os-accent); outline-offset: 3px; }
.os-totop svg { flex: none; }
@media (min-width: 992px) { .os-totop { right: 1.5rem; bottom: 1.5rem; } }
@media (min-width: 1200px) { .os-totop { right: 3rem; bottom: 2rem; } }
@media (prefers-reduced-motion: reduce) {
  .os-totop { transform: none; transition: opacity .2s ease, visibility 0s linear .2s; }
}

/* 14. Contact page — the direct lines under the page title, the rendering
       beside the form (sticky on desktop, in place of a map), and the form on
       Bootstrap's own controls. */
.os-contact-list { display: flex; flex-wrap: wrap; gap: 1.25rem 3.5rem; margin: 2.25rem 0 0; }
.os-contact-list li { display: flex; flex-direction: column; gap: .3rem; }
.os-contact-list__k {
  font-size: .75rem; font-weight: 500; letter-spacing: .16em; text-transform: uppercase;
  color: var(--os-ink-muted);
}
.os-contact-list a,
.os-contact-list span:not(.os-contact-list__k) { font-size: 1.125rem; color: var(--os-ink); letter-spacing: .01em; }
.os-contact-list a { text-decoration: none; transition: color .18s ease; }
.os-contact-list a:hover { color: var(--os-accent); }

/* Every "Request an estimate" link lands here (#estimate): clear of the sticky header. */
.os-contact { scroll-margin-top: calc(68px + 1.5rem); }
@media (min-width: 1200px) { .os-contact { scroll-margin-top: calc(92px + 1.5rem); } }
.os-contact__photo { margin: 0; }
.os-contact__photo img {
  display: block; width: 100%; height: auto;
  border-radius: var(--bs-border-radius-lg); background: var(--os-sunken);
}
/* Tablets: a wide 2:1 band, so the form starts higher up the screen. */
@media (min-width: 768px) and (max-width: 991.98px) {
  .os-contact__photo img { aspect-ratio: 2 / 1; object-fit: cover; }
}
/* Desktop: the picture holds its place beside the form and fills the screen's
   height (never taller), so it reads like the map it replaces. */
@media (min-width: 992px) {
  .os-contact__photo { position: sticky; top: calc(68px + 2rem); }
  .os-contact__photo img { height: min(calc(100vh - 68px - 4rem), 52rem); object-fit: cover; }
}
@media (min-width: 1200px) {
  .os-contact__photo { top: calc(92px + 2rem); }
  .os-contact__photo img { height: min(calc(100vh - 92px - 4rem), 52rem); }
}

/* The form: Bootstrap's .form-control, set to the site's type and colours.
   Field edges use --os-border (3.4:1 on white, above the 3:1 minimum). */
.os-form {
  --bs-form-invalid-color: #b42318;          /* 6.1:1 on white */
  --bs-form-invalid-border-color: #b42318;
}
.os-form .form-label {
  margin-bottom: .5rem;
  font-size: .75rem; font-weight: 500; letter-spacing: .14em; text-transform: uppercase;
  color: var(--os-ink-2);
}
.os-form-optional { margin-left: .4rem; letter-spacing: .04em; text-transform: none; font-weight: 400; color: var(--os-ink-muted); }
.os-form .form-control {
  padding: .85rem 1rem; font-size: 1.0625rem; line-height: 1.5; color: var(--os-ink);
  border-color: var(--os-border); border-radius: var(--bs-border-radius-lg);
}
.os-form .form-control::placeholder { color: var(--os-ink-muted); opacity: 1; }
.os-form .form-control:focus { border-color: var(--os-accent); box-shadow: 0 0 0 .25rem rgba(28, 103, 156, .16); }
.os-form .form-control.is-invalid:focus { box-shadow: 0 0 0 .25rem rgba(180, 35, 24, .14); }
.os-form textarea.form-control { min-height: 10.5rem; resize: vertical; }
.os-form .invalid-feedback { font-size: .9375rem; margin-top: .4rem; }
/* The photo list's own message: a separate class, so jQuery Validation never
   mistakes it for one of its labels and hides it. */
.os-pond-error { margin-top: .5rem; font-size: .9375rem; color: var(--bs-form-invalid-color); }
.os-form .form-text { margin-top: .6rem; font-size: .875rem; color: var(--os-ink-muted); }

/* Honeypot: off screen for people, still in the form for bots. */
.os-hp { position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden; }

/* reCAPTCHA's corner badge would sit on the Back to top button. Google allows
   hiding it when the page says the site is protected by reCAPTCHA, as the
   line under the Send button does. */
.grecaptcha-badge { visibility: hidden; }

.os-form-note { margin-bottom: 1.25rem; padding: .9rem 1.1rem; border-radius: var(--bs-border-radius-lg); font-size: 1rem; line-height: 1.5; }
.os-form-note--info { background: var(--os-baby-soft); color: var(--os-ink); }
.os-form-note--error { background: #fdecea; color: #7a1b12; }
.os-form-note a { color: inherit; }

.os-form-done { padding: clamp(2rem, 4vw, 3rem); border-radius: var(--bs-border-radius-lg); background: var(--os-baby-soft); }
.os-form-done__title { font-family: var(--os-serif); font-weight: 500; font-size: clamp(1.75rem, 3vw, 2.25rem); line-height: 1.2; margin: 0 0 .75rem; }
.os-form-done__title:focus { outline: none; }
.os-form-done p { font-size: 1.0625rem; color: var(--os-ink-2); }
.os-form-done a { color: var(--os-accent); }
.os-form-done a:hover { color: var(--os-accent-hov); }

/* FilePond 5 keeps its own shadow DOM; it is themed only through the custom
   properties and parts it exposes. Photos show as a grid of thumbnails. */
file-pond {
  --default-font-family: Jost, "Century Gothic", system-ui, sans-serif;
  --default-color: var(--os-ink);
  --default-background-color: var(--os-page);
  --default-accent-color: var(--os-accent);
  --default-border-radius: var(--bs-border-radius-lg);
  --entry-background-color: var(--os-sunken);
  display: block;
}
file-pond::part(frame-element) { border: 1px dashed var(--os-border); background: var(--os-raised); }
/* "browse" in the drop area, in the link colour (5.7:1 on the drop area). */
file-pond::part(browse) { color: var(--os-accent); font-weight: 500; text-decoration: underline; text-decoration-color: currentColor; }
file-pond::part(browse):hover { color: var(--os-accent-hov); }
file-pond::part(entry-list) { display: grid; grid-template-columns: repeat(auto-fill, minmax(8.5rem, 1fr)); gap: .5rem; }
file-pond::part(entry-list-item) { align-self: start; }
/* Uniform 4:3 thumbnails: a tidy grid, and heights are known before the images decode. */
file-pond::part(entry-media) { aspect-ratio: 4 / 3; height: auto !important; }
/* Before the script arrives, the native file input sits in the same box. */
file-pond:not(:defined) {
  padding: 1.5rem; border: 1px dashed var(--os-border); border-radius: var(--bs-border-radius-lg);
  background: var(--os-raised);
}

/* 15. Portfolio — the grid is Bootstrap only (row-cols, ratio, object-fit).
       Lightbox3 is themed through its own custom properties; "html" outranks
       the defaults in lightbox3.css, which loads after this file. */
html .lightbox3-overlay { --lb-image-border-radius: var(--bs-border-radius-lg); }

/* 16. Address suggestions on the contact form (/js/address.js): Bootstrap's
       dropdown menu under the field, set like the site menu's dropdown.
       Google's terms require the "Google Maps" attribution under suggestions
       shown without a map: Roboto or a sans-serif, weight 400, 12-16px, #5E5E5E,
       on one line, never translated or recased. */
.os-suggest {
  --bs-dropdown-bg:              var(--os-pill);
  --bs-dropdown-color:           var(--os-ink);
  --bs-dropdown-link-color:      var(--os-ink);
  --bs-dropdown-link-hover-color: var(--os-ink);
  --bs-dropdown-link-hover-bg:   var(--os-baby-soft);
  --bs-dropdown-link-active-color: var(--os-ink);
  --bs-dropdown-link-active-bg:  var(--os-baby);
  --bs-dropdown-border-color:    var(--os-hairline);
  --bs-dropdown-border-radius:   var(--bs-border-radius-lg);
  --bs-dropdown-padding-x:       .375rem;
  --bs-dropdown-padding-y:       .375rem;
  --bs-dropdown-item-padding-x:  .75rem;
  --bs-dropdown-item-padding-y:  .5rem;
  --bs-dropdown-item-border-radius: .625rem;
  --bs-dropdown-font-size:       1rem;
  box-shadow: var(--os-pill-shadow);
}
.os-suggest .dropdown-item { cursor: pointer; white-space: normal; line-height: 1.35; }
.os-suggest .dropdown-item strong { font-weight: 600; }
.os-suggest__sub { display: block; font-size: .875rem; color: var(--os-ink-muted); }
.os-suggest__attr {
  padding: .375rem .75rem .125rem; text-align: right; white-space: nowrap;
  font: 400 12px/1.2 Roboto, Arial, sans-serif; color: #5e5e5e;
}

/* 17. Service and area pages (tools/gen_services.py): the home page's parts,
       plus a smaller page name over the photo (service names run longer than
       the slogan) and the checked "What's included" list. */
.os-slogan--page .os-slogan__main { font-size: clamp(2.5rem, 5.6vw, 4.5rem); }
/* These photos are brighter than the home page's, so the veil is deeper: the
   white page name measures at least 3:1 (large text) over the brightest 5% of
   the photo behind it, on every page, at desktop and phone sizes. */
.os-hero--page .os-hero__scrim {
  background: linear-gradient(to bottom,
    rgba(15, 36, 51, .40)  0%,
    rgba(15, 36, 51, .56) 18%,
    rgba(15, 36, 51, .68) 40%,
    rgba(15, 36, 51, .64) 60%,
    rgba(15, 36, 51, .44) 82%,
    rgba(15, 36, 51, .34) 100%);
}
.os-includes li { font-size: 1.0625rem; line-height: 1.5; color: var(--os-ink); }
.os-includes svg { flex: none; width: 1rem; height: 1rem; margin-top: .3rem; color: var(--os-accent); }

/* 11. Section rhythm — two gaps Bootstrap's scale doesn't reach without a
       Sass build. Opened up by about a third: white space is the cheapest
       luxury signal there is. */
.py-section { padding-block: clamp(3.5rem, 7vw, 6rem); }
.py-section-lg { padding-block: clamp(5rem, 11vw, 9.5rem); }
