/* ==========================================================================
   AAA Hedge Trimming — motion layer
   --------------------------------------------------------------------------
   Thesis: the business cuts to a line and holds it. Every reveal on this site
   is that cut. Type rises from behind a clean horizontal edge, a blade line
   sweeps the heading, photography is wiped open top-down, and the dark
   sections carry slow dappled light — sun through foliage.

   Rules this file obeys:
   - Nothing is hidden until JS confirms it can animate (html.mo-ready).
     If the script fails or motion is reduced, the page renders as-is.
   - Transform / opacity / clip-path / filter only. No layout animation.
   - Loops pause when offscreen or when the tab is hidden.
   ========================================================================== */

:root{
  --mo-ease:cubic-bezier(0.16,1,0.3,1);   /* confident arrival */
  --mo-ease-soft:cubic-bezier(0.33,1,0.68,1);
  --mo-cut:640ms;                          /* the trim pass */
  --mo-state:220ms;                        /* routine state change */
  --mo-feedback:140ms;                     /* immediate acknowledgment */
}

/* ==========================================================================
   0. PRE-PAINT HOLD
   A tiny inline script in <head> sets html.mo-pre before first paint, so the
   hero does not flash in unstyled and then animate. motion.js lifts it the
   moment the entrance starts, and the same inline script lifts it on a
   timeout so a failed or blocked motion.js can never leave the hero blank.
   ========================================================================== */

html.mo-pre .hero-copy > *,
html.mo-pre .hero-media,
html.mo-pre .page-head-inner > *{ opacity:0; }

/* ==========================================================================
   1. THE CUT — masked type reveal
   Words sit inside a clipping box and rise into it. The box edge is the
   blade line: type does not fade in, it is cut level.
   ========================================================================== */

.mo-w{
  display:inline-block;
  overflow:hidden;
  vertical-align:top;
  /* descenders would clip against the mask; pad then pull back */
  padding-bottom:0.16em;
  margin-bottom:-0.16em;
}
.mo-wi{
  display:inline-block;
  will-change:transform;
}

html.mo-ready .mo-cut .mo-wi{
  transform:translate3d(0,110%,0);
}
html.mo-ready .mo-cut.is-in .mo-wi{
  transform:translate3d(0,0,0);
  transition:transform var(--mo-cut) var(--mo-ease);
  transition-delay:var(--mo-d,0ms);
}

/* The blade: a hairline that sweeps the heading as the words land, then goes.
   Drawn on a wrapper so it spans the true text block. */
.mo-blade{ position:relative; }
.mo-blade::after{
  content:"";
  position:absolute;
  left:0; right:0; bottom:-0.12em;
  height:1px;
  background:var(--eucalyptus,#4F7A57);
  transform:scaleX(0);
  transform-origin:0 50%;
  opacity:0;
  pointer-events:none;
}
html.mo-ready .mo-blade.is-in::after{
  animation:mo-blade-pass 1100ms var(--mo-ease) var(--mo-blade-d,180ms) forwards;
}
@keyframes mo-blade-pass{
  0%   { transform:scaleX(0);  opacity:0; }
  22%  { opacity:0.9; }
  60%  { transform:scaleX(1);  opacity:0.9; }
  100% { transform:scaleX(1);  opacity:0; }
}

/* ==========================================================================
   2. FOCUS — blur to sharp
   Supporting copy resolves the way a lens finds its subject. Bounded to
   short text blocks so the filter never covers a large area.
   ========================================================================== */

html.mo-ready .mo-focus{
  opacity:0;
  filter:blur(9px);
  transform:translate3d(0,12px,0);
}
html.mo-ready .mo-focus.is-in{
  opacity:1;
  filter:blur(0);
  transform:none;
  transition:opacity 700ms var(--mo-ease-soft) var(--mo-d,0ms),
             filter 760ms var(--mo-ease-soft) var(--mo-d,0ms),
             transform 760ms var(--mo-ease) var(--mo-d,0ms);
}

/* ==========================================================================
   3. RISE — the quiet supporting entrance
   Used where a block simply needs to arrive. Deliberately smaller and
   faster than the cut so the headings keep the authority.
   ========================================================================== */

html.mo-ready .mo-rise{
  opacity:0;
  transform:translate3d(0,18px,0);
}
html.mo-ready .mo-rise.is-in{
  opacity:1;
  transform:none;
  transition:opacity 560ms var(--mo-ease-soft) var(--mo-d,0ms),
             transform 640ms var(--mo-ease) var(--mo-d,0ms);
}

/* ==========================================================================
   4. WIPE — photography opens top-down, the way a hedge face is cut
   ========================================================================== */

html.mo-ready .mo-wipe{
  clip-path:inset(0 0 100% 0);
}
html.mo-ready .mo-wipe.is-in{
  clip-path:inset(0 0 0 0);
  transition:clip-path 900ms var(--mo-ease) var(--mo-d,0ms);
}
html.mo-ready .mo-wipe > img,
html.mo-ready .mo-wipe picture > img{
  transform:scale(1.08);
}
html.mo-ready .mo-wipe.is-in > img,
html.mo-ready .mo-wipe.is-in picture > img{
  transform:scale(1);
  transition:transform 1400ms var(--mo-ease) var(--mo-d,0ms);
}

/* ==========================================================================
   5. DAPPLED LIGHT — the only ambient loop on the site
   Two soft pools drift across the dark sections. Paused offscreen.
   ========================================================================== */

/* overflow:hidden is load-bearing — the light pool is inset past the section
   edges so it can drift, and without the clip it widens the document. */
.mo-dapple{ position:relative; isolation:isolate; overflow:hidden; }
.mo-dapple::before{
  content:"";
  position:absolute;
  inset:-25%;
  z-index:0;
  pointer-events:none;
  background:
    radial-gradient(closest-side at 28% 34%, rgba(157,193,160,0.10), transparent 72%),
    radial-gradient(closest-side at 74% 66%, rgba(79,122,87,0.14), transparent 70%),
    radial-gradient(closest-side at 52% 88%, rgba(168,84,44,0.07), transparent 68%);
  filter:blur(6px);
  opacity:0;
  transition:opacity 1200ms ease;
}
html.mo-ready .mo-dapple.mo-lit::before{
  opacity:1;
  animation:mo-drift 26s ease-in-out infinite alternate;
}
.mo-dapple.mo-paused::before{ animation-play-state:paused; }
@keyframes mo-drift{
  0%   { transform:translate3d(-3%,-2%,0) scale(1);    }
  50%  { transform:translate3d(2%,3%,0)   scale(1.08); }
  100% { transform:translate3d(4%,-3%,0)  scale(1.02); }
}
/* keep real content above the light */
.hero .hero-copy,
.hero .hero-media,
.quote-cta .quote-inner,
.quote-band .quote-band-inner,
.services .services-inner{ position:relative; z-index:1; }

/* ==========================================================================
   6. SCROLL PROGRESS — a trim line advancing across the top
   ========================================================================== */

.mo-progress{
  position:fixed;
  top:0; left:0;
  height:2px;
  width:100%;
  z-index:130;
  background:var(--clay,#A8542C);
  transform:scaleX(0);
  transform-origin:0 50%;
  pointer-events:none;
  opacity:0;
  transition:opacity 300ms ease;
}
.mo-progress.is-on{ opacity:1; }

/* ==========================================================================
   7. HEADER — condenses once you leave the hero
   ========================================================================== */

/* A header that condenses is a layout change by definition, so the two
   transitions below are deliberate rather than casual. `contain` keeps the
   reflow inside the header subtree, and it crosses the threshold at most
   twice per scroll direction rather than running every frame. */
.site-header{
  contain:layout style;
  transition:box-shadow 250ms ease, background-color 300ms ease;
}
.site-header .header-row{
  transition:padding 300ms var(--mo-ease);
}
.site-header .wordmark span{
  transition:opacity 240ms ease, max-height 300ms var(--mo-ease);
  overflow:hidden;
  max-height:2em;
}
html.mo-ready .site-header.mo-condensed .header-row{
  padding-top:0.55rem;
  padding-bottom:0.55rem;
}
html.mo-ready .site-header.mo-condensed .wordmark span{
  opacity:0;
  max-height:0;
}

/* Nav: a single underline that travels between items rather than five
   independent borders blinking on and off. */
.primary-nav{ position:relative; }
/* Width is fixed at 1px and the travel is one transform: translate to the
   link, scale to its width. Nothing here touches layout. */
.primary-nav .mo-ink{
  position:absolute;
  bottom:0;
  height:1px;
  width:1px;
  background:var(--clay,#A8542C);
  transform-origin:0 50%;
  opacity:0;
  pointer-events:none;
  transition:transform 420ms var(--mo-ease), opacity 220ms ease;
  will-change:transform;
}
.primary-nav .mo-ink.is-on{ opacity:1; }
html.mo-ready .primary-nav a:hover,
html.mo-ready .primary-nav a.is-active{ border-bottom-color:transparent; }

/* ==========================================================================
   8. BUTTONS — a fill that wipes in from the cut edge, plus magnetism
   ========================================================================== */

.btn{
  position:relative;
  overflow:hidden;
  isolation:isolate;
  transition:color var(--mo-state) ease,
             border-color var(--mo-state) ease,
             transform 480ms var(--mo-ease);
}
.btn > *{ position:relative; z-index:1; }
.btn::before{
  content:"";
  position:absolute;
  inset:0;
  z-index:0;
  transform:scaleY(0);
  transform-origin:50% 100%;
  transition:transform 420ms var(--mo-ease);
}
.btn-clay::before{ background:var(--clay-deep,#853F20); }
.btn-outline-dark::before{ background:var(--paper,#F4EFE3); }
.btn-outline-light::before{ background:var(--ink,#1C2620); }

html.mo-ready .btn:hover::before,
html.mo-ready .btn:focus-visible::before{ transform:scaleY(1); }
html.mo-ready .btn-clay:hover{ background:var(--clay,#A8542C); }
html.mo-ready .btn-outline-dark:hover{ color:var(--forest,#132318); border-color:var(--paper,#F4EFE3); }
html.mo-ready .btn-outline-light:hover{ color:var(--paper,#F4EFE3); border-color:var(--ink,#1C2620); }
.btn:active{ transform:translateY(1px) scale(0.985); transition-duration:var(--mo-feedback); }

/* Clippings: a short burst of green flecks from the pointer when the
   primary call to action is taken. Cheap, 420ms, then removed. */
.mo-clip-fleck{
  position:fixed;
  width:3px; height:9px;
  border-radius:1px;
  pointer-events:none;
  z-index:400;
  opacity:0.95;
  will-change:transform,opacity;
}

/* ==========================================================================
   9. GALLERY — the frame opens, the caption is cut in
   ========================================================================== */

.gallery-item img{
  transition:transform 900ms var(--mo-ease), filter 500ms ease;
}
html.mo-ready .gallery-item:hover img{ transform:scale(1.06); }
.gallery-cap{
  transform:translate3d(0,100%,0);
  opacity:1;
  transition:transform 420ms var(--mo-ease), opacity 240ms ease;
}
html.mo-ready .gallery-item:hover .gallery-cap,
html.mo-ready .gallery-item:focus-within .gallery-cap{ transform:none; }
@media (hover:none){
  html.mo-ready .gallery-cap{ transform:none; }
}
/* a clay rule draws along the base of the frame on hover */
.gallery-item::after{
  content:"";
  position:absolute;
  left:0; right:0; bottom:0;
  height:2px;
  background:var(--clay,#A8542C);
  transform:scaleX(0);
  transform-origin:0 50%;
  transition:transform 520ms var(--mo-ease);
  z-index:2;
  pointer-events:none;
}
html.mo-ready .gallery-item:hover::after,
html.mo-ready .gallery-item:focus-within::after{ transform:scaleX(1); }

/* ==========================================================================
   10. SPOTLIGHT — cursor light on the dark service rows
   ========================================================================== */

.mo-spot{ position:relative; }
.mo-spot::before{
  content:"";
  position:absolute;
  inset:0;
  z-index:0;
  pointer-events:none;
  opacity:0;
  transition:opacity 420ms ease;
  background:radial-gradient(
    240px circle at var(--mo-x,50%) var(--mo-y,50%),
    rgba(157,193,160,0.10),
    transparent 70%
  );
}
html.mo-ready .mo-spot:hover::before{ opacity:1; }
.mo-spot > *{ position:relative; z-index:1; }

/* Service rows: the top rule draws in when the row arrives */
.service-row{ position:relative; }
.service-row::before{
  content:"";
  position:absolute;
  top:-1px; left:0; right:0;
  height:1px;
  background:var(--mint,#9DC1A0);
  transform:scaleX(0);
  transform-origin:0 50%;
  opacity:0.6;
}
html.mo-ready .service-row.is-in::before{
  transform:scaleX(1);
  transition:transform 900ms var(--mo-ease) var(--mo-d,0ms);
}

/* ==========================================================================
   11. FAQ — real height, not a jump
   ========================================================================== */

.faq details{ overflow:hidden; }
.faq summary{ transition:color var(--mo-state) ease; }
html.mo-ready .faq summary:hover{ color:var(--eucalyptus-deep,#375C40); }
.faq summary::after{ transition:transform 380ms var(--mo-ease), color var(--mo-state) ease; }

/* The wrapper's height is animated in pixels by motion.js. The 0fr/1fr grid
   trick was tried first and resolves to a zero-height track here, so the
   answer would open to nothing. Explicit measured height is reliable. */
.mo-acc{ overflow:hidden; }
/* The answer is visible in its resting state and the keyframe only plays over
   the top, with no fill. A frame loop that never delivers cannot hide it. */
details[open] > .mo-acc > div > p{
  animation:mo-acc-in 340ms var(--mo-ease) 60ms;
}
@keyframes mo-acc-in{
  from{ opacity:0; transform:translate3d(0,-8px,0); }
  to  { opacity:1; transform:none; }
}

/* ==========================================================================
   12. AREAS — the list is trimmed left to right
   ========================================================================== */

html.mo-ready .areas-list li{
  opacity:0;
  transform:translate3d(-10px,0,0);
}
html.mo-ready .areas-list li.is-in{
  opacity:1;
  transform:none;
  transition:opacity 420ms ease var(--mo-d,0ms),
             transform 520ms var(--mo-ease) var(--mo-d,0ms);
}
.areas-list a{ transition:color var(--mo-state) ease, text-decoration-color var(--mo-state) ease; }

/* ==========================================================================
   13. FORM — the field underline draws on focus
   ========================================================================== */

.quote-form .field{ position:relative; }
.quote-form input,
.quote-form textarea,
.quote-form select{
  transition:border-color var(--mo-state) ease, background-color var(--mo-state) ease;
}
.quote-form .field::after{
  content:"";
  position:absolute;
  left:0; right:0; bottom:0;
  height:2px;
  background:var(--eucalyptus,#4F7A57);
  transform:scaleX(0);
  transform-origin:0 50%;
  transition:transform 340ms var(--mo-ease);
  pointer-events:none;
}
html.mo-ready .quote-form .field:focus-within::after{ transform:scaleX(1); }
html.mo-ready .quote-form .field.invalid::after{
  background:var(--clay,#A8542C);
  transform:scaleX(1);
}
@keyframes mo-nudge{
  0%,100%{ transform:translateX(0); }
  25%    { transform:translateX(-4px); }
  75%    { transform:translateX(4px); }
}
html.mo-ready .quote-form .field.mo-shake{ animation:mo-nudge 320ms ease; }

.form-msg{
  opacity:0;
  transform:translate3d(0,-6px,0);
  transition:opacity 320ms ease, transform 360ms var(--mo-ease);
}
.form-msg.show{ opacity:1; transform:none; }

/* Submit: a working indicator rather than a word swap */
.btn-submit{ position:relative; }
.btn-submit.is-busy{ pointer-events:none; }
.btn-submit.is-busy::after{
  content:"";
  position:absolute;
  left:0; bottom:0;
  height:2px;
  width:100%;
  background:rgba(255,255,255,0.85);
  transform-origin:0 50%;
  animation:mo-indeterminate 1100ms ease-in-out infinite;
  z-index:2;
}
@keyframes mo-indeterminate{
  0%   { transform:scaleX(0) translateX(0); }
  50%  { transform:scaleX(0.6) translateX(35%); }
  100% { transform:scaleX(0) translateX(200%); }
}

/* ==========================================================================
   14. LIGHTBOX — grows from the thumbnail that opened it
   ========================================================================== */

.lightbox{
  transition:opacity 300ms ease, backdrop-filter 300ms ease;
}
.lightbox #lbImage{
  will-change:transform,opacity;
}
.lightbox.mo-flying #lbImage{
  transition:transform 460ms var(--mo-ease), opacity 260ms ease;
}
.lightbox-btn{ transition:transform var(--mo-state) var(--mo-ease), opacity var(--mo-state) ease, background-color var(--mo-state) ease; }
html.mo-ready .lightbox-btn:hover{ transform:scale(1.09); }
html.mo-ready .lightbox-btn:active{ transform:scale(0.96); }

/* ==========================================================================
   15. DRAWER — links are cut in behind the panel edge
   ========================================================================== */

.mobile-drawer{ transition:transform 420ms var(--mo-ease); }
html.mo-ready .mobile-drawer li{
  opacity:0;
  transform:translate3d(24px,0,0);
  transition:opacity 320ms ease, transform 420ms var(--mo-ease);
}
html.mo-ready .mobile-drawer.is-open li{
  opacity:1;
  transform:none;
  transition-delay:calc(90ms + var(--mo-i,0) * 45ms);
}
html.mo-ready .mobile-drawer .drawer-phone{
  opacity:0;
  transform:translate3d(0,12px,0);
  transition:opacity 320ms ease 380ms, transform 420ms var(--mo-ease) 380ms;
}
html.mo-ready .mobile-drawer.is-open .drawer-phone{ opacity:1; transform:none; }
.menu-toggle span{ transition:transform 320ms var(--mo-ease), opacity 200ms ease; }
html.mo-ready .menu-toggle:hover span:nth-child(1){ transform:translateX(2px); }
html.mo-ready .menu-toggle:hover span:nth-child(3){ transform:translateX(-2px); }

/* ==========================================================================
   16. GHOST NUMERAL — drifts against the scroll
   ========================================================================== */

.about-ghost{
  will-change:transform;
  transition:none;
}

/* ==========================================================================
   17. STICKY CALL BAR
   ========================================================================== */

.mobile-cta-bar{
  transform:translate3d(0,110%,0);
  transition:transform 460ms var(--mo-ease), opacity 260ms ease;
}
.mobile-cta-bar.is-visible{ transform:none; }

/* ==========================================================================
   18. LINKS — underline grows from the start, it does not blink on
   ========================================================================== */

.reviews-source a,
.areas-note a,
.footer-social a,
.reviews-agg strong{
  background-image:linear-gradient(currentColor,currentColor);
  background-size:0% 1px;
  background-position:0 100%;
  background-repeat:no-repeat;
  transition:background-size 380ms var(--mo-ease);
}
html.mo-ready .reviews-source a:hover,
html.mo-ready .areas-note a:hover,
html.mo-ready .footer-social a:hover,
html.mo-ready .reviews-agg:hover strong{ background-size:100% 1px; }

.footer-cols nav a{
  display:inline-block;
  transition:color var(--mo-state) ease, transform 320ms var(--mo-ease);
}
html.mo-ready .footer-cols nav a:hover{ transform:translateX(4px); }

/* ==========================================================================
   19. REVIEWS — arrive as a list, capped stagger
   ========================================================================== */

html.mo-ready .review{
  opacity:0;
  transform:translate3d(0,22px,0);
}
html.mo-ready .review.is-in{
  opacity:1;
  transform:none;
  transition:opacity 560ms var(--mo-ease-soft) var(--mo-d,0ms),
             transform 660ms var(--mo-ease) var(--mo-d,0ms);
}
.review-stars{ transition:transform 420ms var(--mo-ease); transform-origin:0 50%; }
html.mo-ready .review:hover .review-stars{ transform:scale(1.06); }

/* ==========================================================================
   20. HEDGE-LINE DIVIDER — draws across when it arrives
   ========================================================================== */

html.mo-ready .hedge-line svg path{
  clip-path:inset(0 100% 0 0);
}
html.mo-ready .hedge-line.is-in svg path{
  clip-path:inset(0 0 0 0);
  transition:clip-path 1100ms var(--mo-ease);
}

/* ==========================================================================
   21. REDUCED MOTION
   Nothing above is allowed to leave content hidden. The script never adds
   .mo-ready when the user asks for reduced motion, so every initial state
   in this file is inert. These rules cover the ambient loop and the few
   effects that live outside the .mo-ready gate.
   ========================================================================== */

@media (prefers-reduced-motion: reduce){
  .mo-dapple::before{ animation:none !important; opacity:0.6; }
  .mo-progress{ display:none; }
  .gallery-cap{ transform:none; }
  .btn::before{ display:none; }
  .mo-acc{ transition:none; }
  .mo-acc > div > p{ opacity:1; transform:none; }
  .mobile-cta-bar{ transition:opacity 200ms ease; }
  .reviews-source a, .areas-note a, .footer-social a, .reviews-agg strong{
    background-size:0 1px;
  }
}
