/* Turf Supply Partner - site enhancements
   Edit freely. Loaded by the "Turf Supply Partner - Site Enhancements" mu-plugin. */

:root{
  --ts-green:#8CFF00;
  --ts-dark:#07100A;
  --ts-dark-2:#0E1A12;
  --ts-light:#F1F5EC;
  --ts-deep:#1A5B22;
  --ts-border:#D9E0D2;
  --ts-muted:#5C6660;
}

html{ scroll-behavior:smooth; scroll-padding-top:90px; }
/* Guard against accidental sideways scroll. This must NOT go on <body>:
   an overflow value there turns body into its own scroll container and
   stops window scroll events firing. `clip` also avoids that. */
html{ overflow-x:clip; }

/* ---------- 1. Header: transparent over hero, dark once scrolled ---------- */
.ts-header{
  position:fixed; top:0; left:0; right:0; z-index:999;
  transition:background-color .28s ease, box-shadow .28s ease;
  background-color:transparent !important;
}
/* Literal hex rather than a custom property: this rule must not depend on
   --ts-dark resolving on this element. */
.ts-header.ts-stuck{
  background-color:#07100A !important;
  box-shadow:0 1px 0 rgba(255,255,255,.09);
}
/* Sentinel the script observes to detect "scrolled past the top" without
   depending on scroll events firing. */
.ts-scroll-sentinel{
  position:absolute; top:0; left:0; width:1px;
  height:40px; /* overridden inline by the script to match its threshold */
  pointer-events:none; visibility:hidden;
}
/* Both header groups size to their contents; the right group is pushed to the
   right edge so nav + CTA stay flush regardless of how wide the logo is. */
.ts-header .ts-logo{ width:auto !important; flex:0 0 auto; }
.ts-header .ts-header-right{
  width:auto !important; flex:0 0 auto;
  margin-left:auto; justify-content:flex-end;
}
.ts-logo{ text-decoration:none; }
.ts-logo .ts-logo-mark img{ display:block; }
.ts-header .ts-nav ul.hfe-nav-menu{ align-items:center; }

/* keep the CTA visible next to the hamburger on small screens */
@media (max-width:1024px){
  .ts-header-right{ gap:14px !important; }
}
@media (max-width:480px){
  .ts-header .ts-btn-apply .elementor-button{ padding:12px 14px !important; font-size:11px !important; }
  .ts-header .ts-btn-apply .elementor-button-icon{ display:none; }
}

/* ---------- 2. Hero ----------
   Note: phones use the 1280px variant, not a smaller one. The artwork is
   2.33:1, so `cover` on a tall phone viewport scales to WIDTH, and a 768px
   file would be upscaled ~2.3x and look soft.
   The approved hero artwork is set on the section in Elementor as the uploaded
   PNG (media library ID 24). That original stays the configured asset and the
   declared fallback below; these rules only change the ENCODING delivered to
   the browser - same photograph, WebP instead of a 2.6 MB PNG, sized to the
   viewport. Replacing the artwork means replacing the Elementor setting AND
   regenerating these derivatives. */
.ts-hero{
  background-image:url("/wp-content/uploads/2026/09/turf-warehouse-hero.png");
  background-image:-webkit-image-set(
    url("/wp-content/uploads/2026/09/turf-warehouse-hero.webp") type("image/webp"),
    url("/wp-content/uploads/2026/09/turf-warehouse-hero.png") type("image/png")) !important;
  background-image:image-set(
    url("/wp-content/uploads/2026/09/turf-warehouse-hero.webp") type("image/webp"),
    url("/wp-content/uploads/2026/09/turf-warehouse-hero.png") type("image/png")) !important;
}
@media (max-width:1280px){
  .ts-hero{
    background-image:-webkit-image-set(
      url("/wp-content/uploads/2026/09/turf-warehouse-hero-1280.webp") type("image/webp"),
      url("/wp-content/uploads/2026/09/turf-warehouse-hero.png") type("image/png")) !important;
    background-image:image-set(
      url("/wp-content/uploads/2026/09/turf-warehouse-hero-1280.webp") type("image/webp"),
      url("/wp-content/uploads/2026/09/turf-warehouse-hero.png") type("image/png")) !important;
  }
}
@media (max-width:768px){
  .ts-hero{
    background-image:-webkit-image-set(
      url("/wp-content/uploads/2026/09/turf-warehouse-hero-1280.webp") type("image/webp"),
      url("/wp-content/uploads/2026/09/turf-warehouse-hero.png") type("image/png")) !important;
    background-image:image-set(
      url("/wp-content/uploads/2026/09/turf-warehouse-hero-1280.webp") type("image/webp"),
      url("/wp-content/uploads/2026/09/turf-warehouse-hero.png") type("image/png")) !important;
  }
  /* On a phone the copy sits over the whole frame, so add a vertical scrim
     on top of Elementor's left-to-right overlay to keep text readable. */
  .ts-hero::after{
    content:""; position:absolute; inset:0; z-index:0; pointer-events:none;
    background:linear-gradient(180deg, rgba(7,16,10,.75) 0%, rgba(7,16,10,.55) 45%, rgba(7,16,10,.85) 100%);
  }
  .ts-hero > *{ position:relative; z-index:1; }
}

.ts-accent{ color:var(--ts-green); }

/* ---------- 2b. Partner form embeds (GoHighLevel / WebSEO) ----------
   form_embed.js rewrites the markup into
     .ep-iFrameContainer > .ep-wrapper > iframe(position:absolute)
   but leaves both wrappers at height:0, so the iframe drops out of flow and
   its card collapses to just its padding. Giving the wrappers an explicit
   height puts the absolutely-positioned iframe back in a sized box.

   Both states are handled: the wrapped markup above, and the raw iframe for
   the case where the vendor script is blocked or has not run yet. */
/* Reserve the height on the Elementor widget only. form_embed.js manages the
   iframe's own position/size/visibility, including a 0-height .ep-wrapper with
   an absolutely positioned iframe - that is its normal working state. Earlier
   this stylesheet forced height and position onto those vendor wrappers to stop
   the card collapsing; that fought the script's own layout. Giving the widget a
   min-height gives the card its height without touching the vendor's internals. */
.ts-form-embed{ min-height:688px; }
.ts-form-embed iframe{ border-radius:8px; }
@media (max-width:1024px){ .ts-form-embed{ min-height:700px; } }
@media (max-width:767px){ .ts-form-embed{ min-height:730px; } }

/* Hero form card - white, rounded, elevated, floating over the artwork. */
.ts-hero-form{ align-self:center; }
.ts-hero-copy{ min-width:0; }

@media (max-width:767px){
  /* Mobile order: headline, copy, CTA, then the form. */
  .ts-hero-copy{ order:1; }
  .ts-hero-form{ order:2; }

  /* The hero is now ~1500px tall on a phone because it carries the form.
     `cover` on a 2.33:1 photo in a 0.25:1 box would crop to a ~10% vertical
     sliver of the image - unrecognisable. Render it as a band across the top
     at its natural proportions instead, biased right onto the turf rolls, and
     let solid dark carry the copy and form below it. */
  .ts-hero{
    background-size:auto 300px !important;
    background-position:72% top !important;
    background-repeat:no-repeat !important;
    background-color:#07100A !important;
  }
  .ts-hero::after{
    background:linear-gradient(180deg,
      rgba(7,16,10,.45) 0px,
      rgba(7,16,10,.86) 190px,
      #07100A 300px);
  }
}
.ts-hero .ts-h1{ margin:0; }
.ts-hero-cta .elementor-button{ transition:background-color .2s ease, color .2s ease, border-color .2s ease; }

/* ---------- 3. Green trust bar ---------- */
.ts-greenbar .ts-bar-item{ margin:0; position:relative; }
@media (min-width:768px){
  .ts-greenbar .ts-bar-item:not(:last-child)::after{
    content:""; position:absolute; right:-15px; top:50%; transform:translateY(-50%);
    width:1px; height:14px; background:rgba(7,16,10,.35);
  }
}

/* ---------- 4. Gap-aware card rows ----------
   Elementor sets percentage widths on flex children; the container gap is added
   on top of that, which would push the last card onto a new line. These calcs
   subtract each row's share of the gap. */
.ts-card-row{ align-items:stretch; }
/* Each width subtracts its share of the gap PLUS a couple of pixels of slack.
   Subtracting only the exact gap share makes the row total exactly 100%, and
   sub-pixel rounding then wraps the last item onto its own line. */
.ts-cards-2 > .e-con{ width:calc(50% - 14px) !important; }
.ts-cards-3 > .e-con{ width:calc(33.333% - 15px) !important; }
.ts-cards-4 > .e-con{ width:calc(25% - 17px) !important; }
.ts-faq-grid > .e-con{ width:calc(50% - 12px) !important; }

@media (max-width:1024px){
  .ts-cards-4 > .e-con{ width:calc(50% - 10px) !important; }
  .ts-cards-3 > .e-con{ width:calc(50% - 10px) !important; }
}
@media (max-width:767px){
  .ts-cards-2 > .e-con,
  .ts-cards-3 > .e-con,
  .ts-cards-4 > .e-con,
  .ts-faq-grid > .e-con{ width:100% !important; }
}

/* Split heading rows: big headline left, supporting paragraph right.
   Same slack rule as the card rows above - 54% + 46% - 56px gap came to
   exactly 100% and wrapped the paragraph onto its own line. */
/* Flex sizing rather than percentage widths: the boxed section's horizontal
   padding makes the inner width smaller than the container width, so a
   percentage + gap combination overflowed and wrapped the paragraph. */
.ts-split-head{ flex-wrap:nowrap; }
.ts-split-head > .e-con{ width:auto !important; min-width:0; }
.ts-split-head > .e-con:first-child{ flex:1 1 56%; }
.ts-split-head > .e-con:last-child{ flex:0 1 38%; }
@media (max-width:1024px){
  .ts-split-head{ flex-wrap:wrap; }
  .ts-split-head > .e-con:first-child,
  .ts-split-head > .e-con:last-child{ width:100% !important; flex:1 1 100%; }
}
.ts-h2{ margin:0; }
.ts-h3{ margin:0; }
.ts-eyebrow{ margin:0; }
.ts-para p:last-child{ margin-bottom:0; }
.ts-legal{ max-width:860px; }

/* Cards
   Equal height comes from the row's `align-items:stretch`, which only applies
   to items whose cross-size is auto. An explicit `height:100%` here opts the
   card OUT of stretching and makes it size to its own content instead - that
   was the cause of the mismatched card heights. Leave the height alone. */
.ts-card, .ts-feat-card, .ts-who-card, .ts-step, .ts-prod-card{
  height:auto;
  align-self:stretch;
}

/* Bullet lists anchor to the bottom of the card so both lists finish on the
   same line, while the eyebrow, heading and paragraph stay top-aligned. */
.ts-card .ts-bullets{ margin-top:auto; }

@media (max-width:767px){
  /* Stacked cards should size to their content - stretching here would only
     add dead whitespace. */
  .ts-card, .ts-feat-card, .ts-who-card, .ts-step, .ts-prod-card{ align-self:auto; }
  .ts-card .ts-bullets{ margin-top:0; }
}

/* Turf product cards link out to the Superior Turf Direct catalog. */
.ts-prod-card{
  text-decoration:none;
  transition:background-color .18s ease, transform .18s ease;
}
.ts-prod-card:hover{ background-color:#132218 !important; transform:translateY(-2px); }
.ts-prod-card .ts-card-link{ margin-top:auto; padding-top:6px; }
.ts-prod-card .ts-card-link p{
  margin-bottom:0; font-weight:700; letter-spacing:.06em; text-transform:uppercase;
}
.ts-prod-card .ts-card-link a{ color:#8CFF00; text-decoration:none; }
.ts-prod-card .ts-card-link a:hover{ text-decoration:underline; }
.ts-prod-card .ts-h3 a{ color:#FFFFFF; text-decoration:none; }
.ts-prod-card:hover .ts-h3 a{ color:#8CFF00; }
@media (prefers-reduced-motion:reduce){ .ts-prod-card:hover{ transform:none; } }
.ts-feat-num{ margin:0; opacity:.55; }
.ts-step-num{ margin:0; }
.ts-bullets .elementor-icon-list-item{ align-items:flex-start; }
.ts-bullets .elementor-icon-list-icon{ margin-top:4px; }

/* Inventory band - cropped from the approved hero artwork (rolls only, no bay
   door) so it reads as a product/inventory detail rather than a repeat of the
   hero scene. Fixed height + object-fit keeps it a slim band at any width. */
.ts-inventory-band img{
  display:block; width:100%; height:320px;
  object-fit:cover; object-position:center 55%;
}
@media (max-width:1024px){ .ts-inventory-band img{ height:250px; } }
@media (max-width:767px){ .ts-inventory-band img{ height:180px; } }

/* the marketing strip */
.ts-strip .ts-strip-title, .ts-strip .ts-strip-note{ margin:0; }
@media (max-width:767px){ .ts-strip{ text-align:left; } }

/* ---------- 5. Apply section ---------- */
.ts-section-apply > .e-con:first-child{ width:44%; }
.ts-section-apply > .e-con.ts-form-card{ width:calc(56% - 56px); }
@media (max-width:1024px){
  .ts-section-apply > .e-con:first-child,
  .ts-section-apply > .e-con.ts-form-card{ width:100% !important; }
}
.ts-email-line a{ color:var(--ts-deep); font-weight:600; text-decoration:underline; }
.ts-email-line a:hover{ color:var(--ts-dark); }

/* ---------- 6. FormLayer: two-column grid + industrial styling ---------- */
.ts-form .formlayer-form-fields-wrapper{
  display:grid;
  grid-template-columns:repeat(2, minmax(0,1fr));
  gap:18px 16px;
  margin-top:6px;
}
.ts-form .ts-col-6{ grid-column:span 1; }
.ts-form .ts-col-12,
.ts-form .formlayer-submit-wrap,
.ts-form .formlayer-form-status{ grid-column:1 / -1; }
@media (max-width:600px){
  .ts-form .formlayer-form-fields-wrapper{ grid-template-columns:1fr; }
  .ts-form .ts-col-6{ grid-column:1 / -1; }
}

.ts-form .formlayer-label{
  display:block; margin-bottom:7px;
  font-family:Inter, sans-serif; font-size:11px; font-weight:700;
  letter-spacing:.14em; text-transform:uppercase; color:var(--ts-dark);
}
.ts-form .required-indicator{ color:var(--ts-deep); margin-left:3px; }
.ts-form .formlayer-input{
  width:100%; box-sizing:border-box;
  padding:13px 14px;
  font-family:Inter, sans-serif; font-size:15px; color:var(--ts-dark);
  background:#FBFCF9;
  border:1px solid var(--ts-border); border-radius:0;
  transition:border-color .16s ease, box-shadow .16s ease, background-color .16s ease;
}
.ts-form .formlayer-input::placeholder{ color:#A3ADA5; }
.ts-form .formlayer-input:focus{
  outline:none; background:#fff;
  border-color:var(--ts-deep);
  box-shadow:0 0 0 3px rgba(26,91,34,.12);
}
.ts-form select.formlayer-input{ appearance:none; -webkit-appearance:none; cursor:pointer;
  background-image:linear-gradient(45deg,transparent 50%,var(--ts-dark) 50%),linear-gradient(135deg,var(--ts-dark) 50%,transparent 50%);
  background-position:calc(100% - 19px) calc(50% + 2px), calc(100% - 14px) calc(50% + 2px);
  background-size:5px 5px,5px 5px; background-repeat:no-repeat; padding-right:40px; }
.ts-form textarea.formlayer-input{ resize:vertical; min-height:110px; }
.ts-form .formlayer-help-text{ margin-top:6px; font-size:12px; color:#8A948C; }

.ts-form .formlayer-gdpr-wrap{ display:flex; align-items:flex-start; gap:10px; }
.ts-form .formlayer-gdpr-wrap input[type=checkbox]{ margin-top:3px; width:16px; height:16px; accent-color:var(--ts-deep); flex:0 0 auto; }
.ts-form .formlayer-gdpr-label{ font-family:Inter, sans-serif; font-size:13px; line-height:1.55; color:var(--ts-muted); }

.ts-form .formlayer-submit-wrap{ margin-top:4px; }
.ts-form .formlayer-submit-btn{
  width:100%; cursor:pointer;
  padding:18px 26px; border:0; border-radius:0;
  font-family:Inter, sans-serif; font-size:13px; font-weight:700;
  letter-spacing:.09em; text-transform:uppercase;
  transition:background-color .18s ease, color .18s ease;
}
.ts-form .formlayer-submit-btn:hover{
  background-color:var(--ts-green) !important;
  color:var(--ts-dark) !important;
}
.ts-form .formlayer-form-status:not(:empty){
  padding:13px 15px; margin-bottom:4px;
  font-family:Inter, sans-serif; font-size:14px; line-height:1.55;
  border-left:4px solid var(--ts-deep); background:#EEF5EA; color:var(--ts-dark);
}

/* ---------- 7. FAQ accordion ---------- */
.ts-faq .elementor-accordion .elementor-accordion-item{ margin-bottom:12px; }
.ts-faq .elementor-tab-title{ transition:color .15s ease; }

/* ---------- 8. Footer ---------- */
.ts-footer-note p:last-child,
.ts-footer-email p:last-child,
.ts-copyright p:last-child{ margin-bottom:0; }
.ts-footer-email a{ color:var(--ts-green); text-decoration:none; }
.ts-footer-email a:hover{ text-decoration:underline; }
.ts-footer-rule{ padding:0; }

/* ---------- 9. Mobile / tablet header + buttons ---------- */

/* UAE collapses the menu with visibility:hidden, which still reserves the
   list's full width in the flex row. That squeezed the CTA's container until
   the button overflowed and was clipped. Taking the collapsed list out of
   flow frees the space and turns it into a proper full-width dropdown. */
@media (max-width:1024px){
  /* descendant, not child: UAE nests the list inside a <nav> element */
  .ts-header .ts-nav ul.hfe-nav-menu{
    position:absolute;
    top:100%; left:0; right:0; width:auto;
    flex-direction:column;
    background:#07100A;
    margin:0;
    box-shadow:0 12px 24px rgba(0,0,0,.35);
  }
  /* The nav shrinks to just its toggle; the CTA must never be squeezed.
     min-width keeps the widget as wide as its own toggle button, otherwise
     the toggle overflows its box and collides with the CTA. */
  .ts-header .ts-nav{ flex:0 0 auto; width:auto; min-width:40px; }
  .ts-header .ts-nav .elementor-widget-container,
  .ts-header .ts-nav .hfe-nav-menu-layout{ position:static; display:flex; align-items:center; }
  .ts-header .ts-nav .hfe-nav-menu__toggle{ margin:0; }
  .ts-header .ts-header-right > .elementor-widget-button{ flex:0 0 auto; }
  .ts-header .ts-btn-apply .elementor-button{ white-space:nowrap; }
  .ts-header .hfe-nav-menu__toggle{ display:flex; align-items:center; }
}

@media (max-width:767px){
  /* Full-width CTAs read as intentional on a phone and are easier to hit. */
  .ts-hero-cta, .ts-prod-cta{ width:100%; }
  .ts-hero-cta > .elementor-widget-button,
  .ts-prod-cta > .elementor-widget-button{ width:100%; }
  .ts-hero-cta .elementor-button,
  .ts-prod-cta .elementor-button{ width:100%; justify-content:center; }
  /* logo width at this breakpoint comes from the widget's own mobile
     setting (118px = 40px tall); nothing to override here. */
}

/* The mark + text lockup is now a single wordmark asset, so there is no
   separate text element left to hide. The fit problem is unchanged though:
   below 480px the logo, hamburger and full CTA share one row, so the logo
   steps down rather than the CTA losing its readable label.
   106px wide = 36px tall at the asset's 2.9556 aspect. */
@media (max-width:480px){
  .ts-header .ts-logo .ts-logo-mark img{ width:106px !important; }
  .ts-header .ts-btn-apply .elementor-button{ padding:12px 16px !important; font-size:11px !important; }
}

/* ---------- 10. Wide canvas (1540) column ratios ----------
   Percentage widths plus a flex gap always overflow by the gap, which makes a
   `wrap` row break onto two lines. Using flex-basis with shrink enabled and
   nowrap lets the two columns absorb the gap instead of wrapping. */
/* Selectors are id-qualified on purpose: Elementor's own per-element rules are
   (0,2,0) and beat a plain class, which is why an earlier class-only version of
   this silently did nothing and the form column collapsed to its padding. */
/* Hero is the one section that keeps a wide canvas (1540), and only because the
   embedded form needs the room. The 40/60 split starved the H1: at 1680 the copy
   column measured 596px while the form sat at 896px for an iframe that only needs
   ~450px. 48/52 hands the headline the room it needs without costing the form
   anything it was using. */
@media (min-width:1025px){
  #hero .ts-hero-inner > .e-con-inner{ flex-wrap:nowrap; }
  #hero .ts-hero-copy{ flex:0 0 calc(48% - 20px); width:auto; min-width:0; }
  #hero .ts-hero-form{ flex:1 1 auto; width:auto; min-width:340px; }

  /* 'We Supply The Turf.' is the longest authored line and measures 7.63x the
     font size in Anton, so the type has to track the column or it breaks
     mid-phrase. 5.75vw matches the column's growth from 1025px up and tops out
     at the 90px target once the 1540 container stops growing (~1565px). */
  #hero .ts-h1 .elementor-heading-title{ font-size:clamp(46px, 5.75vw, 90px); }
}

/* Tablet: a true 90% of the viewport. Elementor's own 90% is measured inside
   the section's horizontal padding, which lands at ~84% - so the gutter is
   dropped here and the inner container carries the full 90%/5%/5% split. */
@media (min-width:768px) and (max-width:1024px){
  .ts-section,
  .ts-hero,
  .ts-header,
  .ts-footer{ padding-left:0 !important; padding-right:0 !important; }
  .ts-section > .e-con-inner,
  .ts-header > .e-con-inner,
  .ts-footer > .e-con-inner,
  #hero .ts-hero-inner > .e-con-inner{ width:90% !important; max-width:90% !important; }
}

/* Below 1025 a 40% copy column is too narrow to read, so the hero stacks and
   the copy gets the full measure back. */
@media (max-width:1024px){
  #hero .ts-hero-inner > .e-con-inner{ flex-wrap:wrap; }
  #hero .ts-hero-copy,
  #hero .ts-hero-form{ width:100% !important; flex:1 1 100%; }
}
@media (min-width:1025px){
  #apply > .e-con-inner{ flex-wrap:nowrap; align-items:flex-start; }
  #apply > .e-con-inner > .e-con:first-child{ flex:0 1 45%; width:auto !important; min-width:0; }
  #apply > .e-con-inner > .ts-form-card{ flex:0 1 55%; width:auto !important; min-width:0; }
}

/* A wider canvas must not turn prose into 150-character lines. Cards and grids
   take the extra width; running text keeps a readable measure. At the 18px body
   size 62ch lands at ~690px, inside the 600-700px target. */
.ts-split-head .ts-para p{ max-width:62ch; }
.ts-hero-para p{ max-width:56ch; }
.ts-legal{ max-width:90ch; }

/* Section headings are capped at the same 700px measure so a 68px display line
   breaks where the design wants it to, not wherever the column happens to end.
   Elementor's per-element rules are (0,2,0), so the class alone would lose -
   these set max-width only, which Elementor does not emit for headings. */
@media (min-width:1025px){
  .ts-h2 .elementor-heading-title{ max-width:700px; }
  .ts-split-head .ts-h2 .elementor-heading-title{ max-width:640px; }
}

/* ---------- 10b. Partner Inquiry headline ----------
   The four-line break came from hard-coded <br> tags, not a narrow column -
   every other heading on the page wraps exactly as authored. The breaks are
   gone; this measure is what decides where it falls now. */
#apply .ts-apply-headline{ width:100%; max-width:720px; }
#apply .ts-apply-headline .elementor-heading-title{ min-width:500px; }
@media (max-width:1024px){
  #apply .ts-apply-headline{ max-width:100%; }
  #apply .ts-apply-headline .elementor-heading-title{ min-width:0; }
}

/* ---------- 10c. Partner Inquiry: image card + benefit row ----------
   These fill the ~400px of dead space the tall embedded form left beside the
   left column. Same encoding trick as the hero: Elementor keeps the uploaded
   PNG as the configured asset, the stylesheet delivers WebP. !important is
   required because Elementor's per-element rule outspecifies a plain class. */
.ts-apply-imgcard{
  background-image:url("/wp-content/uploads/2026/09/turf-warehouse-rolls.png");
  background-image:-webkit-image-set(
    url("/wp-content/uploads/2026/09/turf-partner-card.webp") type("image/webp"),
    url("/wp-content/uploads/2026/09/turf-warehouse-rolls.png") type("image/png")) !important;
  background-image:image-set(
    url("/wp-content/uploads/2026/09/turf-partner-card.webp") type("image/webp"),
    url("/wp-content/uploads/2026/09/turf-warehouse-rolls.png") type("image/png")) !important;
  overflow:hidden;
}
@media (max-width:800px){
  .ts-apply-imgcard{
    background-image:-webkit-image-set(
      url("/wp-content/uploads/2026/09/turf-partner-card-800.webp") type("image/webp"),
      url("/wp-content/uploads/2026/09/turf-warehouse-rolls.png") type("image/png")) !important;
    background-image:image-set(
      url("/wp-content/uploads/2026/09/turf-partner-card-800.webp") type("image/webp"),
      url("/wp-content/uploads/2026/09/turf-warehouse-rolls.png") type("image/png")) !important;
  }
}
.ts-apply-imgcard .ts-imgcard-title{ max-width:15ch; }
.ts-apply-imgcard .ts-imgcard-copy p{ margin-bottom:0; max-width:46ch; }

/* Four benefits across, gap-aware so the row never wraps a stray item. */
.ts-apply-icons > .e-con{ width:calc(25% - 15px) !important; }
.ts-benefit .ts-benefit-icon{ line-height:1; }
.ts-benefit .ts-benefit-title{ margin:0; }
.ts-benefit .ts-benefit-copy p{ margin-bottom:0; }
@media (max-width:1024px){
  .ts-apply-icons > .e-con{ width:calc(50% - 10px) !important; }
}
@media (max-width:767px){
  .ts-apply-icons > .e-con{ width:calc(50% - 10px) !important; }
  /* Required mobile order: heading, copy, benefits, image card, email. */
  .ts-apply-icons{ order:1; }
  .ts-apply-imgcard{ order:2; }
  .ts-email-line{ order:3; }
}
@media (max-width:389px){
  .ts-apply-icons > .e-con{ width:100% !important; }
}

/* ---------- 11. Small screens (320 / 375 / 390 / 430) ---------- */
@media (max-width:767px){
  /* shorter photo band so the copy and form start sooner */
  .ts-hero{ background-size:auto 240px !important; }
  .ts-hero::after{
    background:linear-gradient(180deg,
      rgba(7,16,10,.45) 0px,
      rgba(7,16,10,.86) 150px,
      #07100A 240px);
  }
  .ts-inventory-band img{ height:150px; }
  .ts-hero-inner > .e-con-inner{ gap:28px; }
}

/* Narrow phones: claw back every px of gutter so form fields stay usable. */
@media (max-width:389px){
  /* Sections keep the 20px mobile gutter the spec calls for; only the header
     needs the extra few px to fit logo + toggle + CTA on one row at 320px. */
  .ts-hero-form{ padding:8px !important; }
  .ts-form-card{ padding:16px 12px !important; }
  .ts-card,
  .ts-feat-card,
  .ts-who-card,
  .ts-step,
  .ts-prod-card{ padding:22px 18px !important; }
  /* header keeps the same 20px gutter as every other container; the CTA is
     small enough at this size that it no longer needs the extra 4px */
  .ts-header .ts-btn-apply .elementor-button{
    padding:10px 12px !important; font-size:10px !important; letter-spacing:.06em !important;
  }
  .ts-greenbar{ padding-left:16px !important; padding-right:16px !important; }
  .ts-greenbar .ts-bar-item{ font-size:11px !important; }
}

/* The embed must never be the thing that creates a sideways scroll. */
.ts-form-embed,
.ts-form-embed .elementor-widget-container,
.ts-form-embed .ep-iFrameContainer,
.ts-form-embed .ep-wrapper{ max-width:100%; }
.ts-form-embed iframe{ max-width:100%; }

/* ---------- 12. Motion: respect user preference ---------- */
@media (prefers-reduced-motion:reduce){
  html{ scroll-behavior:auto; }
  *,*::before,*::after{ transition-duration:.01ms !important; animation-duration:.01ms !important; }
}


/* ---------- 11. Mobile display typography ----------
   Phone headlines are sized off the measured advance width of their longest
   authored line, not a scaled-down desktop value. In Anton the hero's longest
   line ('We Supply The Turf.') runs 7.385x its font size and the widest H2 line
   runs 9.695x, so the divisors below are those ratios plus a little slack. The
   44px subtracted is the 20px gutter each side plus 4px of rounding safety.

   Result: 3 authored hero lines hold all the way down to 320px. The floor is
   what 320px can physically take - at the 42px the brief asks for, 'We Supply
   The Turf.' overflows by 5px and breaks into a 4th line, which the same brief
   asks us to avoid. 375px and up sit inside the requested 42-52px range. */
@media (max-width:767px){
  #hero .ts-h1 .elementor-heading-title{
    font-size:clamp(36px, calc((100vw - 44px) / 7.45), 52px);
  }
  /* Elementor's per-element rule here is
     .elementor-8 .elementor-element.elementor-element-XXXX .elementor-heading-title
     - four classes, (0,4,0), because the page-scope .elementor-8 prefix counts
     too. A plain class selector loses, and so does a body-prefixed one. This is
     (0,4,1) so it wins on specificity rather than on load order. The hero H1
     rule above gets there via #hero, which outranks any number of classes. */
  body .ts-section .ts-h2:not(.ts-apply-headline) .elementor-heading-title{
    /* The 31px floor is set by the one headline that has to hold two authored
       lines at 320px: 'Materials For The Job.' runs 8.609x its size, so 32px is
       the last size that fits 276px. Above that the line breaks after
       'Materials' and strands 'One.' on a line of its own. */
    font-size:clamp(31px, calc((100vw - 44px) / 9.0), 44px);
  }

  /* Stops the orphaned last word the brief calls out. Hard <br>s still break
     where they are authored; this only redistributes the soft wrap inside a
     segment that is too long to hold, so 'Support For The Next One.' falls as
     two even phrases instead of leaving 'One.' alone on a line. */
  .ts-h1 .elementor-heading-title,
  .ts-h2 .elementor-heading-title,
  .ts-h3 .elementor-heading-title{ text-wrap:balance; }
}


/* ---------- 12. Product CTA shine ----------
   Applied by adding .ts-btn-shine to a button, so future product CTAs opt in
   explicitly rather than inheriting this from a shared style. Currently on the
   products-section CTA and the footer CTA.

   The sweep is a single composited transform on a pseudo-element, which the
   browser runs off the main thread and throttles by itself once the button
   scrolls out of view - that is what keeps it cheap enough to leave on for
   phones.

   Timing is deliberately mostly idle: the pass occupies the first 16% of a 7s
   cycle (~1.1s of motion, ~5.9s of rest) so it reads as an occasional highlight
   rather than a loop demanding attention. */
.ts-btn-shine .elementor-button{
  position:relative;
  overflow:hidden;
  isolation:isolate;
  transition:background-color .25s ease, color .25s ease, border-color .25s ease;
}

/* Elementor renders the label inside .elementor-button-content-wrapper. It needs
   to sit above the sweep, otherwise the gradient washes over the text. */
.ts-btn-shine .elementor-button-content-wrapper{ position:relative; z-index:1; }

.ts-btn-shine .elementor-button::after{
  content:"";
  position:absolute;
  top:-60%;
  bottom:-60%;
  left:0;
  width:45%;
  z-index:0;
  pointer-events:none;
  transform:translateX(-160%) skewX(-20deg);
  animation:ts-shine 7s ease-in-out infinite;
  /* Dark outline button: a white core carrying a lime edge reads as reflected
     brand light rather than generic white glare. */
  background:linear-gradient(90deg,
    rgba(255,255,255,0) 0%,
    rgba(255,255,255,.10) 35%,
    rgba(200,255,140,.30) 50%,
    rgba(255,255,255,.10) 65%,
    rgba(255,255,255,0) 100%);
}

/* On the lime-filled button a lime sweep is invisible against its own
   background, so that one gets a plain white highlight instead. */
.ts-btn-shine.ts-btn-primary .elementor-button::after{
  background:linear-gradient(90deg,
    rgba(255,255,255,0) 0%,
    rgba(255,255,255,.28) 40%,
    rgba(255,255,255,.55) 50%,
    rgba(255,255,255,.28) 60%,
    rgba(255,255,255,0) 100%);
}

@keyframes ts-shine{
  0%   { transform:translateX(-160%) skewX(-20deg); }
  16%  { transform:translateX(320%)  skewX(-20deg); }
  100% { transform:translateX(320%)  skewX(-20deg); }
}

/* No :hover override here on purpose. Elementor already emits
   .elementor-22 .elementor-element.elementor-element-852bd8d .elementor-button:hover
   at (0,5,0) - lime fill, dark text, solid lime border - which outranks anything
   this file can say with classes alone. That state already satisfies the brief
   (border brighter, background changes, text high contrast) and matches every
   other button on the site, so it stays. A competing rule here would simply be
   dead weight in the stylesheet.

   :focus-visible is a different matter: Elementor styles hover only, so keyboard
   users got no state change at all. This gives them the same affordance plus a
   visible ring. Nothing competes for it, so (0,4,0) is enough. */
.ts-btn-shine .elementor-button:focus-visible{
  background-color:#8CFF00;
  border-color:#8CFF00;
  color:#07100A;
  outline:2px solid #FFFFFF;
  outline-offset:2px;
}

/* Phones: same effect, slower cadence. Fewer passes per minute means fewer
   composite frames on the devices least able to spare them. */
@media (max-width:767px){
  .ts-btn-shine .elementor-button::after{ animation-duration:9s; }
}

/* Reduced motion: the sweep and the arrow drift are decoration, so both stop.
   The border and background hover states are feedback, so those stay. */
@media (prefers-reduced-motion:reduce){
  .ts-btn-shine .elementor-button::after{ animation:none; opacity:0; }
}


/* ---------- 13. Header row at the narrowest widths ----------
   The old square mark was 34px wide, so the header row had slack to spare. The
   Superior Turf Direct wordmark is 2.9556:1, so at the header's 36px height it
   asks for ~106px and the row stops fitting.

   The row is logo + 20px gap + [toggle 40 + 14px gap + CTA 132]. That totals
   302px, against 280px of content width at 320px, so it overflows from roughly
   341px down - and only there. At 375px the same row has 335px to sit in and
   needs no help.

   Rather than shrink the CTA label to an abbreviation, the gaps give back what
   they can first and the logo takes the remainder. */
@media (max-width:360px){
  .ts-header > .e-con-inner{ gap:8px !important; }
  .ts-header .ts-header-right{ gap:8px !important; }
  /* 92px wide = 31px tall. Under the 35px the brief asks for, but at 320px the
     row is 280px wide and the CTA is the element that must stay legible. */
  .ts-header .ts-logo .ts-logo-mark img{ width:92px !important; }
  .ts-header .ts-btn-apply .elementor-button{ padding:10px 10px !important; }
}

/* ---------- 14. Premium CTA animation ----------
   One reusable class, .premium-cta-animation, carried by exactly five buttons:
   the header CTA, both hero CTAs and the two product CTAs. Nav links, text
   links and form submits are deliberately outside it.

   Scale and glow are two separate animations on the same element rather than
   one combined keyframe set. Every button breathes by the same amount, but a
   solid lime button and a dark outline button need different glows, and
   splitting them means one shared scale set instead of a duplicate per variant.
   Comma-separated animations let a variant override animation-name for the glow
   alone and inherit the scale untouched.

   Only transform and box-shadow animate. Neither participates in layout, so no
   button can shift its neighbours or change the header's height. */
.premium-cta-animation .elementor-button{
  animation:ts-cta-scale 3.5s ease-in-out infinite,
            ts-cta-glow-lime 3.5s ease-in-out infinite;
  transition:background-color .3s ease, color .3s ease, border-color .3s ease,
             transform .3s ease, box-shadow .3s ease;
}

@keyframes ts-cta-scale{
  0%, 100%{ transform:scale(1); }
  50%     { transform:scale(1.03); }
}
@keyframes ts-cta-scale-sm{
  0%, 100%{ transform:scale(1); }
  50%     { transform:scale(1.02); }
}

/* Solid lime buttons: header CTA, hero primary, products CTA. */
@keyframes ts-cta-glow-lime{
  0%, 100%{ box-shadow:0 0 12px rgba(140,255,0,.25); }
  50%     { box-shadow:0 0 28px rgba(140,255,0,.45); }
}
@keyframes ts-cta-glow-lime-sm{
  0%, 100%{ box-shadow:0 0 10px rgba(140,255,0,.18); }
  50%     { box-shadow:0 0 20px rgba(140,255,0,.32); }
}

/* Dark and outline buttons: hero secondary, footer products. A neutral shadow
   carries most of the weight with only a trace of lime, so the secondary reads
   as quieter than the primary beside it rather than competing with it. */
@keyframes ts-cta-glow-soft{
  0%, 100%{ box-shadow:0 0 10px rgba(0,0,0,.30), 0 0  8px rgba(140,255,0,.12); }
  50%     { box-shadow:0 0 20px rgba(0,0,0,.35), 0 0 16px rgba(140,255,0,.22); }
}
@keyframes ts-cta-glow-soft-sm{
  0%, 100%{ box-shadow:0 0  8px rgba(0,0,0,.28), 0 0  6px rgba(140,255,0,.10); }
  50%     { box-shadow:0 0 14px rgba(0,0,0,.32), 0 0 12px rgba(140,255,0,.16); }
}

/* Variant assignment. .ts-cta-outline is the shared marker for the dark
   outline CTAs - hero secondary and footer products - so the pair is described
   once here instead of listing each button's own class. (0,4,0) against the
   base rule's (0,3,0), so it wins without needing !important. */
.premium-cta-animation.ts-cta-outline .elementor-button{
  animation-name:ts-cta-scale, ts-cta-glow-soft;
}

/* Phones: 1.02 and thinner glows. Separate keyframe sets rather than custom
   properties - an unregistered custom property interpolates discretely and
   would step between values instead of breathing through them. */
@media (max-width:767px){
  .premium-cta-animation .elementor-button{
    animation-name:ts-cta-scale-sm, ts-cta-glow-lime-sm;
  }
  .premium-cta-animation.ts-cta-outline .elementor-button{
    animation-name:ts-cta-scale-sm, ts-cta-glow-soft-sm;
  }
}

/* Hover hands control to the pointer. The animation is switched off rather than
   paused: a running animation outranks a transition in the cascade, so a paused
   one would hold whatever scale it stopped at and ignore the hover entirely.
   Removing it lets the transition above ease from wherever the breath reached. */
.premium-cta-animation .elementor-button:hover,
.premium-cta-animation .elementor-button:focus-visible{
  animation:none;
  transform:scale(1.05);
  box-shadow:0 0 32px rgba(140,255,0,.5);
}
.premium-cta-animation.ts-cta-outline .elementor-button:hover,
.premium-cta-animation.ts-cta-outline .elementor-button:focus-visible{
  box-shadow:0 0 22px rgba(0,0,0,.35), 0 0 20px rgba(140,255,0,.30);
}

/* Arrow drifts right on hover. Elementor wraps the glyph in
   .elementor-button-icon; transforming that span moves it without reflowing
   the label beside it. */
.premium-cta-animation .elementor-button-icon{
  display:inline-flex;
  transition:transform .3s ease;
}
.premium-cta-animation .elementor-button:hover .elementor-button-icon,
.premium-cta-animation .elementor-button:focus-visible .elementor-button-icon{
  transform:translateX(5px);
}

@media (prefers-reduced-motion:reduce){
  /* The glow is colour rather than motion, so a static resting one stays; the
     breathing, the hover scale and the arrow drift all go. !important so the
     accessibility guarantee does not depend on source order against the
     variant and mobile rules above. */
  .premium-cta-animation .elementor-button{
    animation:none !important;
    box-shadow:0 0 12px rgba(140,255,0,.25);
  }
  .premium-cta-animation.ts-cta-outline .elementor-button{
    box-shadow:0 0 10px rgba(0,0,0,.30), 0 0 8px rgba(140,255,0,.12);
  }
  .premium-cta-animation .elementor-button:hover,
  .premium-cta-animation .elementor-button:focus-visible{ transform:none; }
  .premium-cta-animation .elementor-button-icon{ transition:none; }
  .premium-cta-animation .elementor-button:hover .elementor-button-icon,
  .premium-cta-animation .elementor-button:focus-visible .elementor-button-icon{
    transform:none;
  }
}


/* ---------- 15. Hero CTA geometry ----------
   .ts-cta-hero is the shared geometry marker for the two hero CTAs. They now
   carry identical padding, type and tracking from their widget settings, so
   only two things are left for CSS to settle.

   min-height, because identical padding still does not produce identical
   heights here: the outline button has a 1px border top and bottom that the
   lime one does not, which left them at 53px and 51px. box-sizing is already
   border-box, so a shared min-height absorbs that border and lands both on
   exactly 56px.

   inline-flex, because Elementor renders .elementor-button as inline-block.
   Under inline-block the extra height from min-height collects below the label
   instead of centring it, so the text would sit high in the button. */
.ts-cta-hero .elementor-button{
  min-height:56px;
  display:inline-flex;
  align-items:center;
  justify-content:center;
}

/* Below roughly 337px the primary's longer label stops fitting between 36px of
   horizontal padding and the arrow: at 320px the label has 183px to work in and
   "EXPLORE PARTNER FIT" needs ~200px, so it wrapped to a second line and the
   button grew to 66px while the secondary stayed at 56px - the exact mismatch
   this section exists to prevent.

   At this width both buttons are full-width with centred content, so the
   horizontal padding is doing no visual work; trimming it gives the label
   215px and restores a single line. !important because Elementor's own padding
   rule is .elementor-8 .elementor-element.elementor-element-X .elementor-button,
   which is (0,4,0) and outranks this selector. */
@media (max-width:360px){
  .ts-cta-hero .elementor-button{
    padding-left:20px !important;
    padding-right:20px !important;
  }
}
