/* Hand-written overrides for this migration.
 *
 * 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 keeps having to be
 * written because this file keeps arriving describing an earlier repo in the chain: it
 * arrived here carrying the population of a repo TWO generations back (59 pages, 15
 * posts, 177 captures) in prose that reads as measurement. Those repos are deliberately
 * NOT named: this file is copied verbatim into dist, so a previous client's name here
 * would be PUBLISHED on this client's domain, and it would make a residue sweep report a
 * correct file dirty. The provenance chain is recorded in MIGRATION-NOTES, which does not
 * ship. Gotcha 64 — a document is
 * only generated where it interpolates, and the sentences between the numbers belong to
 * whoever it was copied from. The RULES were sound and are kept; every measurement
 * around them is re-taken.
 *
 * Population, DERIVED and not typed: pages.txt holds 37 paths — 20 static (including
 * /tc-pp, which is in neither the sitemap nor the feed) plus the 16 blog posts
 * generated/blog.rss declares as <item>s — so 111 captures (37 pages x 3 bands) plus all
 * 37 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 any of this site's forms —
 * measured, exactly THREE of the 37 pages carry a form (/, /contact and /reviews),
 * each has exactly one, and none has a hidden trap field —
 * so this is ours and must cost nothing. It is also the ONLY spam protection now, since
 * Duda's reCAPTCHA is removed (gotcha 17: its keys are Duda's own and domain-restricted,
 * so they cannot work on the client's domain).
 *
 * /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 — A REAL CONTROL ON SEVENTEEN PAGES, NOT ONE.
 *
 * Censused on the raw served html of all 37 pages (the pages.txt population) and on the
 * 111 captures. THIS IS THE OPPOSITE OF THE SITE THIS FILE CAME FROM, whose header
 * asserted "NO BLOG WIDGET OF ANY KIND" on its posts — here every post has one, so a
 * reader who trusted the inherited prose would conclude these rules have no subject when
 * they are in fact load-bearing on 17 of 37 pages.
 *
 *   /blog0bfe8403     declared 16, visible 10  -> 6 WITHHELD
 *                     NUMBERED PAGER, REPLACES: <nav class="pagination-nav"> x1,
 *                     postArticle x10, lastArticle x1,
 *                     more-posts-text-container x0 and "Show More" x0 (the append-shaped
 *                     control has NO instance anywhere on this site).
 *
 *   each of the 16    declared 15, visible 10  -> 5 WITHHELD each
 *   blog posts        A related-posts widget on EVERY post. 15 is 16 minus the post
 *                     itself, which is the arithmetic that confirms the widget is
 *                     "the other posts" rather than a second copy of the index.
 *
 * THE INDEX SLUG IS /blog0bfe8403, NOT /blog. `/blog` answers 404 on live. Nothing may
 * hardcode it; the index is derived as the page carrying data-paginate-total-elements
 * together with .mainBlog.
 *
 * generated/blog.rss independently declares 16 <item>s, which is the cross-check that
 * makes a truncated recovery FAIL rather than ship quietly — the declared total on the
 * index and the feed's item count must agree, and they do (16 = 16).
 *
 * 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,
 * because that is what this site's numbered pager does. This rule is what hides them
 * initially.
 * ------------------------------------------------------------------------- */
#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 — the signature of one
 * shared element rather than a per-page fault.
 *
 * *** MEASURED ON THIS SITE, AND IT IS ARMED HERE — 30px, ON EXACTLY ONE CARD. ***
 * The comment this file arrived with said the rule was "currently inert here" because
 * every `:not(:last-child)` padding rule in THAT site's cascade was scoped
 * `[list-layout=recent_posts]` and its layouts did not match. That was a true
 * measurement of a previous client and is false here, which is exactly why an
 * inherited "this does not apply" is worth re-measuring: the matching rule on this
 * site is
 *
 *   #dm [blog-posts-feature-flag=true][list-layout=recent_posts][posts-padding="15"]
 *       .postArticle:not(:last-child){padding-bottom:30px}
 *
 * *** THE SUBJECT OF THIS RULE ON THIS SITE IS SEVENTEEN PAGES, NOT ONE. *** The file
 * arrived describing a site where "every blog POST carries TWO related-posts widgets",
 * with a per-widget padding table measured there. NONE OF THAT IS TRUE HERE: censused
 * over all 37 raw served documents, postArticle appears on SEVENTEEN — the index
 * /blog0bfe8403 and every one of the 16 blog posts (each carrying a related-posts widget) — and
 * so the rule has a subject on 17 of 37 pages. The inherited per-widget padding table
 * has been REMOVED rather than retargeted, because a number kept for its shape is the
 * derived-artifact failure and a wrong one here reads as proof.
 *
 * What IS true here, and why the rule is needed on every one of those 17: the index
 * /blog0bfe8403 ships all 16 cards and hides 6, and each blog post ships all 15 related
 * cards and hides 5 — so on every one of them the TENTH card, the last one live actually
 * renders, stops being :last-child and gains the 30px that Duda's rule withholds from the
 * real last card. That is gotcha 69 exactly: display:none does not change child indices,
 * and a constant absolute delta on every page of a family is the signature of one shared
 * element rather than a per-page fault. The measured delta is recorded in MIGRATION-NOTES
 * after the gate rather than asserted here before it.
 *
 * *** AND THE OVERRIDE BELOW WAS ALREADY PRESENT AND ALREADY MATCHING — IT LOST THE
 * SPECIFICITY FIGHT, WHICH IS THE FAILURE THAT LOOKS LIKE THE RULE NOT BEING THERE.
 * The selector matched (the last visible card's nextElementSibling really is
 * `.postArticle.mg-blog-hidden`), and the card still computed 30px. Counting:
 *
 *   ours   #dm + .postArticle + :not(.mg-blog-hidden) + :has(.postArticle.mg-blog-hidden)
 *          = (1,4,0)   — :has() takes the specificity of its most specific argument
 *   Duda's #dm + 3 attribute selectors + .postArticle + :not(:last-child)
 *          = (1,5,0)   — and it carries NO !important
 *
 * (1,5,0) beats (1,4,0), so the override was outranked and silently did nothing.
 * `!important` is what settles it, because Duda's rule here is not !important — this
 * is gotcha 70's specificity trap and the "a pixel count identical across runs means
 * the fix never took effect" rule arriving together.
 *
 * Keyed on `:has(+ .mg-blog-hidden)` rather than on a stamped class, because that
 * tracks the SEQUENCE as batches are revealed 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 !important;
}

/* ---------------------------------------------------------------------------
 * PER-DEVICE WIDGET FORKS.
 *
 * THIS SITE IS DUDA'S **CLASSIC** PLATFORM, AND THE CHROME FORKS THREE WAYS. That is the
 * OPPOSITE of the site this file came from, which was FLEX and whose header asserted at
 * length that the chrome does not fork and that the band-identity classes "DO NOT EXIST
 * on this platform". They exist here, they are load-bearing, and a reader who trusted the
 * inherited prose would have stripped them. Measured over the 111 captures (37 paths x 3
 * device UAs, all 200):
 *
 *   dmtemplateid    desktop StandardLayoutMultiD   37/37
 *                   tablet  StandardLayoutMultiD   37/37
 *                   mobile  mobileHamburgerLayout  37/37
 *   band identity   dmDesktopBody / dmTabletBody / dmMobileBody, one per band, 37/37 each
 *   freeheader      "true" on desktop and tablet, ABSENT on mobile (gotcha 33)
 *   #iscrollBody    present on desktop and tablet, ABSENT on mobile (gotcha 14)
 *   .site_content   ABSENT on desktop and tablet, present on mobile (gotcha 72)
 *
 * *** DESKTOP AND TABLET SHARE A dmtemplateid AND ARE STILL DIFFERENT DOCUMENTS. *** So
 * dmtemplateid alone cannot discriminate the fork here either, for the opposite reason to
 * the FLEX case: not because it is uniform across all three, but because it is uniform
 * across TWO of the three that genuinely differ. Gotcha 62 is why this is measured on the
 * served bytes rather than read off the template name — home alone: desktop 233369 B
 * (md5 c85c2f2c50d9baa374933c6d90b72047), tablet 187806 B (86ddda5aa133ff19b37d042a781d93c5),
 * mobile 231140 B (25c87d469024f8a575ebeeebfe1cedb9): three sizes, three hashes.
 *
 * Note `fix-mobile-scrolling` is present on ALL THREE bands and is LOAD-BEARING (gotcha 35): Duda's
 * body{overflow:hidden} is undone only by it, and a noise filter that judges a class by
 * how its NAME reads will strip it, after which the page cannot be scrolled at any
 * width while every document height and every pixel stays identical.
 *
 * THE CONTENT FORK IS THREE-WAY TOO, and it is measured on the served BYTES rather than
 * inferred from the template: desktop == tablet on 0 of 37 and desktop == mobile on
 * 0 of 37, with EVERY disagreement re-sampled on a second fetch and reproduced, so none
 * of them is a flake. So the chrome fork and the content fork agree on this site — which
 * gotcha 62 warns is NOT guaranteed, and is why they were measured separately rather
 * than one being read off the other.
 *
 * THE TABLET DECLARES ITS OWN CANVAS, AND THE THREE BANDS DECLARE THREE DIFFERENT
 * VIEWPORTS — not two. Censused 37/37 on each band:
 *
 *   desktop  initial-scale=1, minimum-scale=1, maximum-scale=5, viewport-fit=cover
 *   tablet   width=960px
 *   mobile   width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=5,
 *            viewport-fit=cover
 *
 * So gotcha 3's 960 canvas DOES apply here — measured, not carried: gotcha 62 exists
 * because that value is a common default and not a platform constant. And desktop does
 * NOT declare device-width, which the inherited text asserted.
 *
 * THE `mg-only-t` GATE CARRIES BOTH FORKS ON THIS SITE. The chrome forks three ways
 * (see the dmtemplateid / band-identity / freeheader / #iscrollBody / .site_content table
 * above), which is the OPPOSITE of the FLEX site this file came from where it did not
 * fork at all. The
 * drawer and overlay exist on the MOBILE BAND ONLY —
 * censused 0/37 desktop, 0/37 tablet, 37/37 mobile, so gotcha 74's "each band with a
 * drawer needs its own overlay" has exactly one band to satisfy here, not three);
 * what differs per band is the page content. THE OCCURRENCE COUNT IS NOT QUOTED because
 * it is not yet measurable — 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, RE-COUNTED ON THIS SITE. The media queries the
 * captured cascade actually contains, counted over all 248 sheets in
 * generated/live/css (204) and generated/live/linked-css (44):
 *
 *     max-width: 767px   x239      min-width: 768px   x226
 *     max-width: 1024px  x14       min-width: 1025px  x90
 *
 * So the boundaries are 767/768 and 1024/1025, which is what the three gates below use.
 * The other widths present (1200, 1400, 468, 444, 320) are widget-internal and do not
 * define a device band.
 *
 *
 * 1025 is therefore the desktop edge on Duda's own declarations rather than by assuming
 * 1024+1. (The inherited counts were 136/92/65/64 over 202 sheets — a different site.)
 *
 * NOT TAKEN ON THIS SITE, AND RECORDED AS MISSING RATHER THAN INHERITED: a live drawer
 * ratio sweep across many widths. The inherited copy carried a 12-width sweep from
 * a previous client (40vw for #hamburger-drawer, 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 used two DIFFERENT drawer elements, while this site has ONE
 * #hamburger-drawer on every band — so they are deleted rather than renumbered. Re-take
 * before quoting a behavioural edge, and mind the UA trap (gotcha 66): Duda picks its
 * document by USER-AGENT, so a desktop UA at a narrow viewport gets the DESKTOP document
 * and reads the wrong element.
 *
 * 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: TWO WIDGETS ON ONE PAGE, AND THEY DO MOVE. NO RULE IS NEEDED HERE.
 *
 * RE-CENSUSED ON THIS SITE, and the inherited section described the opposite site.
 * That copy asserted 9 single-slide roots across 9 pages with isAutoPlay:false and
 * isFade:true — "nothing to advance to, no arrows to bind" — which is
 * a previous client's shape, not this one.
 *
 * Measured over the 59 served documents: `ssrimageslider` occurs on exactly ONE page,
 * the home page, 6 occurrences. tools/derive-slider-config.mjs reads each widget's own
 * declared autoPagination config and finds TWO widgets, both:
 *
 *     autoPlay=true   interval=7s   pauseOnHover=false   animation=slide
 *
 * So this site's sliders DO auto-advance, on a 7-second interval, and the interval is
 * read from each widget's own payload rather than from runtime.js's inherited constant
 * (gotcha 90: every widget parameter comes from that page's own config, never from the
 * site the driver was written against).
 *
 * TWO CONSEQUENCES FOR THE GATE, both of them the reason this is written down:
 *
 * *** THIS SITE HAS NO SLIDER OF ANY KIND, AND THAT IS MEASURED RATHER THAN ASSUMED. ***
 * Censused over all 37 raw served documents for every slider shape the fleet has met:
 *
 *     flexslider 0    ssrimageslider 0    data-auto="slider" 0    bgGallerySlide 0
 *     dmImageSlider 0    mediaSlider 0    slick-/swiper- 0
 *
 * Consequences, all of them simplifications, recorded so the ABSENCE is a finding rather
 * than an oversight:
 *   (a) there is no auto-advancing region that "can never diff to zero", so no slider
 *       --hide is needed and none is taken — an exclusion that is not needed is coverage
 *       given away for nothing;
 *   (b) the shared check-slider.mjs would FATAL here on an empty population, which is the
 *       correct and honest result for a site with no sliders, and it is recorded as
 *       "did not apply" rather than as a pass;
 *   (c) gotchas 28, 46 (the carousel shape), 89 and 90 have no subject on this site.
 *
 * The four `data-gallery-bg` section backgrounds are NOT sliders: each decodes to a
 * payload holding exactly ONE slide, so they do not rotate, and per gotcha 87 that means
 * any difference there is real and must be diagnosed rather than hidden.
 * ------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------
 * ACCORDION OPEN STATE — recovering a rule the live CSS read cannot see.
 *
 * RE-CENSUSED ON THIS SITE, AND THE INHERITED SECTION HAD ALREADY CLAIMED TO BE.
 * That is the part worth recording: the block that arrived here opened with the words
 * "RE-CENSUSED ON THIS SITE" and then gave a remodeling company's widget ids and page
 * slugs (/bathrooms, /decks, /kitchens, /outdoor-spaces …), none of which exist on an
 * electrical contractor's site. So gotcha 64 nests: a section can carry the SENTENCE
 * asserting it was re-measured while carrying the previous client's numbers, and the
 * sentence is what makes the numbers believable. All of it is deleted rather than
 * renumbered.
 *
 * THIS SITE, from generated/accordion-config.json (derived from live's own
 * initiateWidget({"type":"SSR_ACCORDION"}) payloads, 0 failing to parse) and
 * corroborated against an `ssraccordion` census of the raw served html:
 *
 *     26 of 37 served documents carry an accordion
 *     8 DISTINCT widget ids
 *       7 unique per-page widgets — /, /commercial-electricians-grand-junction,
 *         /electrical-panel-upgrades-grand-junction,
 *         /ev-charger-installation-grand-junction,
 *         /generator-installation-and-repair-grand-junction,
 *         /lighting-installation-grand-junction,
 *         /service-areas/local-electrician-fruita
 *       1 SHARED widget (2600780144) on 16 blog posts
 *     EVERY widget: firstExpanded FALSE, closeOthers TRUE, 5 items, LAYOUT_1
 *
 * BOTH BEHAVIOURAL FLAGS AGREE SITE-WIDE HERE, which is worth stating rather than
 * leaving implicit: a per-widget stamp is still used, because a site-wide rule that
 * happens to be right today is the shape that ships unnoticed the day one widget
 * differs. Note firstExpanded FALSE everywhere means every accordion ships CLOSED —
 * so, unlike the site two generations back, there is no expandFirstItem height to
 * reproduce.
 *
 * ONE PROPERTY DOES VARY, AND IT IS NOT BEHAVIOURAL. addSchemaMarkup is TRUE on the one
 * shared blog widget and FALSE on all 7 unique ones. That is why live emits a FAQPage
 * JSON-LD block on the blog posts and on the home page but not on every accordion page,
 * and it is reproduced from each page's own captured head rather than from this config.
 *
 * THE REVEAL BEHAVIOUR IS NOT YET MEASURED HERE. The population to cover when it is
 * taken is the 23 pages above.
 *
 * 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
 * 59 served documents: 24 <style data-styled> blocks, NONE empty, the CLOSED rule
 * served once per accordion instance, 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, x22
 *     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 another site 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.
 * ------------------------------------------------------------------------- */
#dm .mg-acc-open.mg-acc-open.mg-acc-open {
  max-height: 2000px;
}
