/* Hand-written overrides for this migration — client-tinystrucking.
 *
 * DELIBERATELY OUTSIDE site/public/styles/ (gotcha 49): tools/port-css.mjs owns that
 * directory and clears its own hashed outputs on every run, so a hand-written sheet
 * placed there is deleted by the next port with no error anywhere — on one site that
 * silently removed the sheet that hides the inactive device bands, and all three
 * headers then rendered at every width.
 *
 * EVERY NUMBER BELOW WAS CENSUSED ON THIS SITE. That sentence is here because the
 * file this was carried forward from (client-tempehandymanpro) had every count
 * belonging to that client or to one before it — including a SHOW-MORE/appends
 * description of a blog control this site does not have (this site's control is the
 * numbered pager that REPLACES, which is the shape that copy said had no instance),
 * and a slider population of 57 where this site has 9. The RULES were sound and
 * are kept; the arithmetic around them is re-derived.
 *
 * Population, DERIVED and not typed: pages.txt holds 38 paths — 19 static plus the
 * 19 blog posts generated/blog.rss declares as <item>s — so 114 captures
 * (38 pages x 3 bands) plus all 38 raw served documents. Re-derive from those two
 * files rather than from this line; a count copied out of a comment is what produced
 * every wrong number this header is about.
 *
 * Keep this file minimal. Anything that can come from the ported cascade should.
 */

/* The honeypot. Positioned off-screen rather than display:none — bots skip obviously
 * hidden fields, and taking it out of flow means it costs no layout, which the pixel
 * gate would otherwise measure. Live has NO honeypot on either of this site's two
 * forms, so this is ours and must cost nothing.
 *
 * /migration phase 8 specifies exactly this and one fleet repo shipped the spec
 * unimplemented — its honeypot rendered as an ordinary 40px text field and read as a
 * 46px layout defect. */
.mg-hp {
  position: absolute !important;
  left: -9999px !important;
  top: auto !important;
  width: 1px !important;
  height: 1px !important;
  overflow: hidden !important;
}

/* SPECIFICITY NOTE — read before editing any rule below.
 *
 * Duda emits a per-widget rule for EVERY widget on the site, shaped
 *
 *     #dm .dmBody div.u_1213107879 { display: block !important }
 *
 * That is (1,2,1) and it is `!important`. A hide written as a plain
 * `.mg-only-t { display:none !important }` is (0,1,0), so with `!important` on both
 * sides SPECIFICITY decides and Duda's rule wins — the element stays visible and
 * nothing in the console says so.
 *
 * So every gate below repeats its class three times behind `#dm`, giving (1,3,0),
 * which outranks (1,2,1) on class count without relying on source order. Do not
 * "tidy" the repetition away.
 */

/* ---------------------------------------------------------------------------
 * BLOG PAGINATION — ONE REAL CONTROL, ON ONE PAGE.
 *
 * Censused on the raw served html of all 38 pages (the pages.txt population) and
 * confirmed by driving live's own control. The blog-widget record count is DERIVED,
 * not typed: generated/blog-widgets.json covers /blog plus the 19 posts
 * generated/blog.rss declares — 20 blog-list pages x 3 bands = 60 records:
 *
 *   /blog             declared 19, visible 10   NUMBERED PAGER, REPLACES
 *                     <nav class="pagination-nav"> x1,
 *                     more-posts-text-container x0 (zero on all 38 pages).
 *                     data-paginate-total-elements=19, page size 10; its links carry
 *                     data-page and data-action="paginate" and SWAP the visible set
 *                     rather than appending to it. It is the only page on which the
 *                     live driver finds a control at all, which is that run's own
 *                     positive control.
 *
 *   19 blog posts     declared 18, visible 15   NO CONTROL AT ALL
 *                     pagination-nav x0, more-posts-text-container x0. LIVE ITSELF
 *                     strands the other three — recorded per post in
 *                     blog-widgets.json as shape "none", steps [15]. So there is
 *                     nothing to reveal there and nothing to hide; the rule below
 *                     has no subject on those 19 pages.
 *
 * The numbered pagination-nav IS this site's shape and it is on /blog alone; the
 * "show more / appends" shape the inherited file described has no instance here.
 *
 * blog.rss independently declares 19 items, which is the cross-check that makes a
 * truncated /blog fail rather than ship quietly, and the alias set driven out of
 * /blog equals it exactly in both directions — 19 = 19, empty each way.
 *
 * A static build has no Duda backend, so every card ships and the ones past the
 * first page are stamped mg-blog-hidden with data-mg-blog-page; runtime.js reveals
 * them in live's own page size of 10, replacing the visible set rather than
 * appending to it. This rule is what hides them initially — 19 declared minus the 10
 * page one shows = 9 cards, on the one page that has a control.
 * ------------------------------------------------------------------------- */
#dm .mg-blog-hidden.mg-blog-hidden.mg-blog-hidden {
  display: none !important;
}

/* display:none DOES NOT CHANGE CHILD INDICES, so shipping the withheld cards
 * silently moves which card is :last-child — gotcha 69.
 *
 * Duda's own rule is `.postArticle:not(:last-child){padding-bottom:Npx}`. On live the
 * last VISIBLE card is genuinely :last-child and takes none of it. In our build it is
 * followed by hidden siblings, so it stops matching and GAINS that padding, pushing
 * the control and the whole footer down by a constant amount at every width — the
 * signature of one shared element rather than a per-page fault.
 *
 * MEASURED ON THIS SITE, AND THE RULE IS CURRENTLY INERT HERE: every instance of
 * Duda's `:not(:last-child)` padding rule in the captured cascade is scoped
 * `[list-layout=recent_posts]` (20 of them, across two framework sheets), and this
 * site uses layout4 on /blog and list_slider on the 19 posts, so none of them match.
 * The rule below is kept because the premise is Duda's rather than ours and a layout
 * change would arm it — but do not read its presence as evidence the padding exists.
 *
 * Keyed on `:has(+ .mg-blog-hidden)` rather than on a stamped class, because that
 * tracks the SEQUENCE as batches are swapped instead of pinning the initial state;
 * runtime.js does not have to re-stamp anything. */
#dm .postArticle:not(.mg-blog-hidden):has(+ .postArticle.mg-blog-hidden) {
  padding-bottom: 0;
}

/* ---------------------------------------------------------------------------
 * PER-DEVICE WIDGET FORKS.
 *
 * THE DEVICE FORK ON THIS SITE IS THREE-WAY, MEASURED THREE INDEPENDENT WAYS: the
 * tablet document DIFFERS from the desktop one on 38 of 38 paths under an iPad UA
 * and desktop differs from mobile on 38 of 38 (generated/served-fork.json, verdict
 * "THREE-WAY (tablet has its own document)", dEqT 0, dEqM 0); the Duda EXPORT
 * DISAGREES on 19 of 19 pages after normalising rendition suffixes and the device
 * word, and carries three distinct dmtemplateid values across its three 19-page
 * trees; and the /404 document fetched at three UAs comes back as exactly THREE
 * distinct bodies (generated/live404/). dmtemplateid is StandardLayoutMultiD on
 * desktop, Hamburger on tablet and mobileHamburgerLayout on phone, 38/38 on each
 * band (generated/census-structure.json). data-responsive-name is ABSENT here — 0
 * occurrences across all 38 served documents — so it is not a signal on this site,
 * and the tablet declares its own canvas, <meta name="viewport" content="width=960px">,
 * on 38/38.
 *
 * The `mg-only-t` gate therefore carries a WHOLE SEPARATE DOCUMENT rather than one
 * document settled at a second viewport: tablet has its own template, its own header
 * region (headerHamburger + drawer + overlay on t and m, headerDesktop on d only) and
 * its own wrapper depth, so build-pages.py emits three pieces wherever the captures
 * disagree. THE OCCURRENCE COUNT IS NOT QUOTED because it is not yet measurable —
 * site/src/pages is empty and no page has been built — and an inherited figure here
 * would be a count of another client's build.
 *
 * THE BAND BOUNDARIES ARE DUDA'S OWN. ONE of the two readings has been re-taken on
 * this site; the other has NOT, and is recorded as missing rather than inherited:
 *
 *   (a) TAKEN. The media queries the captured cascade actually contains, counted
 *       over all 202 sheets in generated/live/css:
 *          max-width: 767px   x136      min-width: 768px   x92
 *          max-width: 1024px  x65       min-width: 1025px  x64
 *       1025 is therefore the desktop edge on Duda's own declarations rather than
 *       by assuming 1024+1.
 *
 *   (b) NOT TAKEN ON THIS SITE. The inherited copy carried a 12-width live drawer
 *       sweep from client-tempehandymanpro — a 40vw #hamburger-drawer ratio over
 *       nine widths and 85vw for #mobile-hamburger-drawer at 375. NOT ONE of those
 *       numbers is this site's: that site forked two ways with no 960 tablet canvas
 *       and this one forks three ways with one, so they are deleted rather than
 *       renumbered. Re-take with tools/scratch/probe-band-edges.mjs before quoting a
 *       behavioural edge, and mind the UA trap while doing it (gotcha 66) — a
 *       desktop UA gets the DESKTOP document at every width and reads
 *       #hamburger-drawer throughout, while the harness's phone profile gets the
 *       PHONE document and a different element, #mobile-hamburger-drawer.
 *
 * So, on (a) alone: mobile <=767, tablet 768-1024, desktop >=1025.
 *
 * These hide rather than remove, deliberately — the elements stay in the document so
 * the runtime can still address them, and display:none costs no layout.
 *
 * Only the INACTIVE bands are hidden, inside the media queries. The active band is
 * never touched, so it keeps whatever `display` the ported cascade gives it — hiding
 * all three and restoring one with `display: revert` reverts past the author cascade
 * to the UA default and replaces the widget's real display value with a plain block.
 * ------------------------------------------------------------------------- */
@media (max-width: 767px) {
  #dm .mg-only-d.mg-only-d.mg-only-d,
  #dm .mg-only-t.mg-only-t.mg-only-t {
    display: none !important;
  }
}

@media (min-width: 768px) and (max-width: 1024px) {
  #dm .mg-only-d.mg-only-d.mg-only-d,
  #dm .mg-only-m.mg-only-m.mg-only-m {
    display: none !important;
  }
}

@media (min-width: 1025px) {
  #dm .mg-only-t.mg-only-t.mg-only-t,
  #dm .mg-only-m.mg-only-m.mg-only-m {
    display: none !important;
  }
}

/* ---------------------------------------------------------------------------
 * SLIDERS: THIS SITE HAS THEM, AND THEY DO NOT MOVE. NO RULE IS NEEDED HERE.
 *
 * The inherited copy asserted a population of 57 widgets on 17 of its 40 pages.
 * Re-censused over all 38 raw served documents, parsing each widget's OWN
 * sliderscriptparams payload:
 *
 *     .flexslider                                9 widgets on 9 of 38 pages
 *     ssrimageslider / .bgGallerySlide /
 *     [data-gallery-bg] / <video>                0, in served html AND captures
 *
 * All 9 fall into a SINGLE bucket:
 *   { isAutoPlay: false, directionNav: false, isFade: true, slideshowSpeed: 7000,
 *     animationDuration: 600, pausePlay: true, controlNav: false }
 * and EVERY ONE HAS EXACTLY ONE <li> SLIDE — 9 ul.slides blocks, one <li> each — so
 * there is nothing to advance to, no arrows to bind, and a single slide cannot
 * cross-fade to anything.
 *
 * NOT YET VERIFIED ON THE SERVED BUILD. The inherited "check-slider.mjs over 45
 * slider roots on 8 pages x 3 widths, 45 ok / 0 failed" is client-tempehandymanpro's
 * run and is deleted rather than renumbered — nothing has been built here yet
 * (site/src/pages is empty). The population that run must cover is DERIVED from the
 * census above and not typed: the 9 roots on the 9 pages named there, x 3 widths.
 * Expect every MOTION assertion to be SKIPPED ("1 real slide — nothing to advance").
 * A skip is not a pass, and it must be recorded as a skip.
 *
 * Consequences, and both matter:
 *   * NOTHING IS EXCLUDED FROM THE PIXEL GATE on rotation grounds. An exclusion that
 *     is not needed is not free — it costs that region's entire pixel coverage.
 *   * .flex-viewport is absent, which is gotcha 90's fact for an isFade slider. Any
 *     driver must neither BUILD one nor GATE on one; a fleet repo shipped
 *     `if (!viewport || !ul) return;` and thereby killed every fade slider it had.
 *
 * `ed-version` IS LOAD-BEARING and lives in the PORTED cascade, not here (gotcha 28).
 * ------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------
 * ACCORDION OPEN STATE — recovering a rule the live CSS read cannot see.
 *
 * The FAQ-style accordion is a styled-component driven by Duda's runtime, which is
 * correctly not ported. The markup and the ported cascade both arrive intact, so the
 * widget PAINTS in its settled state and the pixel gate scores the page 0.000% —
 * while every click is inert.
 *
 * CENSUSED HERE on data-element-type rather than on a class token (styled components
 * generate their class names, so a class regex finds nothing on these): the accordion
 * is 34 widgets on 33 of 38 pages, from 15 DISTINCT widget ids, 0 payloads failing
 * to parse. Its two flags do NOT agree across the site — 19 widgets are firstExpanded
 * TRUE / closeOthers TRUE and 15 are FALSE / TRUE — so both are stamped per widget
 * from that widget's own initiateWidget({"type":"SSR_ACCORDION"}) props payload
 * rather than applied site-wide. THE REVEAL BEHAVIOUR IS NOT YET MEASURED HERE: the
 * inherited "38 of 38 covered, 36 auto-open when scrolled into view, 2 stay closed"
 * is client-tempehandymanpro's live run and is deleted rather than renumbered, since
 * firstExpanded is FALSE on 15 of this site's 34 widgets and no reading of that run
 * carries over. The population to cover when it is taken is the 34 widgets above.
 * This site DOES have a /faq page and /faq DOES carry the widget; the five pages
 * WITHOUT it are /about, /blog, /contact, /request-a-quote and /service-areas. The
 * inherited note about /faq being the one page without it is false here — worth
 * stating, because a probe that assumed either shape would report clean for the
 * wrong reason.
 *
 * THE CLOSED RULE IS IN THE PORT AND THE OPEN RULE IS NOT, AND THAT IS NOT A PORTING
 * MISTAKE. styled-components insert through the CSSOM, so a class exists in a
 * document's sheet only if that component actually RENDERED with it. Measured over
 * all 38 served documents: 71 <style data-styled> blocks, NONE of them empty, the
 * CLOSED rule served once per accordion widget, and NO open-state rule served
 * anywhere — zero non-zero max-height declarations in any of those blocks:
 *
 *     closed  .dygwmn  { overflow:hidden; transition:max-height .3s ease-out;
 *                        height:auto; max-height:0 }        served, x34
 *     open    minted per distinct pixel height at runtime   NOT SERVED, x0
 *
 * The inherited copy named three open classes and their heights (.eZPHSz 185px,
 * .eDGbXZ 211px, .crqarE 236px). Those were read off client-tempehandymanpro and are
 * deleted rather than renumbered; this site's open classes have not been read off
 * live at all, which is exactly why the rule below is keyed on OUR class.
 *
 * THE OPEN CLASS IS MINTED PER DISTINCT PIXEL HEIGHT, so items sharing a height share
 * a class. No open-state name can be hardcoded, and restoring the rule under OUR OWN
 * class is recovering a sheet the live read could not reach.
 *
 * The transition runs on MAX-HEIGHT, so opening means raising it. The generous ceiling
 * below reproduces the SETTLED states exactly — closed is max-height:0 from the ported
 * cascade, open is "tall enough" — which is what the gate compares. It does NOT
 * reproduce live's per-item easing curve, and that is recorded as a deviation rather
 * than hidden: the per-item open heights are viewport- and font-dependent and can
 * only be read on one page at one width, so pinning them site-wide would be a
 * fabricated precision, not a faithful one.
 *
 * Repeated behind #dm to clear Duda's own per-widget !important display rules.
 * ------------------------------------------------------------------------- */
#dm .mg-acc-open.mg-acc-open.mg-acc-open {
  max-height: 2000px;
}

/* Drawer submenu caret (review 2026-09-25). runtime.js makes the caret the toggle on
 * every drawer parent; the label keeps navigating. The caret is an inline <span>, so
 * its tap target comes from an invisible ::after overlay rather than a min-width that
 * would make the span wrap off its label. */
#dm .layout-drawer .gy-drawer-caret {
  position: relative;
}
#dm .layout-drawer .gy-drawer-caret::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 44px;
  height: 44px;
  transform: translate(-50%, -50%);
}

/* Legal pages (review 2026-09-25, tools/build-legal-pages.mjs). Duda's reset zeroes
 * margin on p, ul and h2, so the template text would otherwise render as one slab
 * with every heading pressed against the paragraph above it. */
#dm .gy-legal-row {
  max-width: 1200px;
  margin: 0 auto;
  padding: 60px 40px 80px;
  box-sizing: border-box;
}
#dm .gy-legal-row h2 {
  margin: 36px 0 12px;
}
#dm .gy-legal-row p {
  margin: 0 0 14px;
}
#dm .gy-legal-row ul {
  margin: 0 0 14px;
  padding-left: 1.4em;
  list-style: disc;
}
#dm .gy-legal-row li {
  margin: 0 0 4px;
}
@media (max-width: 767px) {
  #dm .gy-legal-row {
    padding: 40px 16px 60px;
  }
}

/* Drawer sub-pages were white on the drawer's white panel on / and /blog (their
 * per-page sheets colour the sub-nav for the desktop dropdown's dark panel), so the
 * opened "Service Areas" list looked empty there (review 2026-09-25). Give them the
 * drawer's own black ink; the current page keeps its red and hover keeps its colour. */
#dm .layout-drawer .unifiednav__container_sub-nav .unifiednav__item:not(.dmNavItemSelected):not(:hover) > .nav-item-text {
  color: rgb(0, 0, 0) !important;
}

/* Homepage (review 2026-09-25): the location pin beside "Proudly serving…" sat black
 * on the navy band and the hero's scroll-down arrow black on the dark photo. White,
 * at every width (both ids occur only on /). */
#dm svg.u_1509803257,
#dm svg.u_1509803257 *,
#dm svg.u_1723299846,
#dm svg.u_1723299846 * {
  fill: rgb(255, 255, 255) !important;
}

/* Homepage "Protecting The Water Supply in Central Texas" photos (review 2026-09-25).
 * Tablet ships them one per row, four 426px squares towering over the text column:
 * lay them out 2 x 2 with a 5px gap so the block matches the column. Phone keeps its
 * 2 x 2 but gets the same 5px gap (the photos touched). */
#dm [data-mg-band="t"] .u_1734142432 .layout-container {
  display: flex !important;
  flex-direction: row !important;
  flex-wrap: wrap !important;
  gap: 5px;
  padding: 0 !important;
}
#dm [data-mg-band="t"] .u_1734142432 .photogallery-row {
  flex: 0 0 calc(50% - 2.5px);
  width: calc(50% - 2.5px) !important;
  margin: 0 !important;
}
#dm [data-mg-band="m"] .u_1734142432 .layout-container {
  display: flex !important;
  flex-direction: column;
  gap: 5px;
  padding: 0 !important;
}
#dm [data-mg-band="m"] .u_1734142432 .photogallery-row {
  display: flex !important;
  gap: 5px;
  margin: 0 !important;
}
#dm [data-mg-band="t"] .u_1734142432 .photogallery-column,
#dm [data-mg-band="m"] .u_1734142432 .photogallery-column {
  flex: 1 1 0;
  width: auto !important;
  min-width: 0;
  float: none !important;
  margin: 0 !important;
}
#dm [data-mg-band="t"] .u_1734142432 .photoGalleryThumbs,
#dm [data-mg-band="m"] .u_1734142432 .photoGalleryThumbs {
  padding: 0 !important;
  margin: 0 !important;
}

/* The footer menu's "Service Areas" dropdown hangs BELOW the footer, invisible but
 * still part of the page's scrollable area, so every page ended in a blank strip of
 * its height (on live too). Open it upward, over the map, instead (review 2026-09-25). */
/* #dm#dm: the ported cascade pins `top` with !important at high specificity, and
 * with both top and bottom set the panel collapsed to 0px on hover. */
#dm#dm .dmFooterContainer .unifiednav__item-wrap > .unifiednav__container_sub-nav.unifiednav__container_sub-nav[data-depth="0"] {
  top: auto !important;
  bottom: 100% !important;
}

/* The seven city pages and /service-area carry their own location map in the page body, so the footer's
 * GBP map is hidden there (review 2026-09-25; tagged by page in the page data). */
#dm .gy-no-footer-map {
  display: none !important;
}

/* In-copy link contrast (review 2026-09-25; classes set by runtime.js linkContrast).
 * On light sections the brand red measured 4.19:1 on white and 3.71:1 on the grey
 * panels; rgb(214,18,26) is 5.3:1 and 4.6:1. On dark sections the red was 1.17-3.24:1,
 * so links there go white and underlined, keeping them distinguishable from the white
 * copy around them. */
#dm a.gy-link-on-light,
#dm a.gy-link-on-light * {
  color: rgb(214, 18, 26) !important;
}
#dm a.gy-link-on-dark,
#dm a.gy-link-on-dark * {
  color: rgb(255, 255, 255) !important;
}
#dm a.gy-link-on-dark {
  text-decoration: underline !important;
  text-underline-offset: 2px;
}
