/* CARRIED-OVER COMMENTS, 2026-09-28. This file came with the migration toolkit, and some
 * of its comments were written against EARLIER clients' sites in that lineage. Every other
 * client's page path, name and domain was removed from them on 2026-09-28 (shown as
 * "another client's page" and similar). Censuses, counts and widget ids in the comments
 * below were NOT re-measured on this site: do not cite one as a fact about this site
 * without measuring it here. The code is unchanged by that clean-up. */
/* Hand-written overrides for this migration.
 *
 * NO PREVIOUS CLIENT IS NAMED IN THIS FILE, DELIBERATELY: it is copied verbatim into
 * dist, so a prior client's name here would be PUBLISHED on this client's domain and
 * would make a residue sweep report a correct file dirty. Provenance lives in
 * MIGRATION-NOTES, which does not ship.
 *
 * 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 — THIS SITE, 2026-09-08: pages.txt holds 19 paths —
 * 14 static plus the 5 blog posts generated/blog.rss declares as <item>s (2 of which the
 * published sitemap omits) — so 57 captures (19 pages x 3 bands) plus all 57 raw served
 * documents in generated/served-by-device. Re-derive from those files rather than from
 * this line; a count copied out of a comment is what produced the stale figures this
 * file has carried through four migrations. ANY OTHER POPULATION QUOTED BELOW (53 paths,
 * 31 posts, 159 documents, a /privacy-policy page) DESCRIBES AN EARLIER SITE and is kept
 * only for the reasoning around it.
 */

/* 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, 52 of 53 pages carry exactly one form 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 — ONE REAL CONTROL, ON ONE PAGE.
 *
 * Censused on the raw served html of all 53 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 alone x 3 bands = 3 records,
 * because on THIS site no post carries a blog-list widget at all:
 *
 *   /blog             declared 31, visible 6    NUMBERED PAGER, REPLACES
 *                     <nav class="pagination-nav"> x1,
 *                     more-posts-text-container x0 (zero on all 53 pages).
 *                     data-paginate-total-elements=31, page size 6; 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 (control=true, 6 -> 6 aliases, all three bands).
 *
 *   31 blog posts     NO BLOG WIDGET OF ANY KIND. postArticle x0, pagination-nav x0,
 *                     more-posts-text-container x0, list_slider x0 on every one of
 *                     them. THIS IS THE OPPOSITE OF THE SITE THIS FILE CAME FROM,
 *                     where every post carried a related-posts slider — so the rule
 *                     below has no subject on any post here, and a "related posts"
 *                     exclusion carried forward would have hidden nothing while
 *                     reading as coverage.
 *
 * The numbered pagination-nav IS this site's shape and it is on /blog alone; the
 * "show more / appends" shape has no instance here.
 *
 * *** THE PAGER IS TRUNCATED AND THAT COST SEVEN POSTS ONCE. *** As first rendered it
 * exposes data-page [1,2,3,4,6] — the ellipsis control carries data-page="4" and PAGE 5
 * is not in the DOM until you navigate nearer to it, while 31 posts at 6 per page is
 * SIX pages. A walk that reads the control once therefore recovers 24-25 of 31. The
 * capture tool now computes ceil(total/visible) and re-reads the pager after each
 * click; it recovered 31 of 31 on all three bands.
 *
 * blog.rss independently declares 31 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 — 31 = 31, 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 — 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 /blog AND NOTHING ELSE. *** 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 53 raw served documents, postArticle appears on exactly ONE — /blog — and
 * zero of the 31 posts carry a blog-list widget of any kind. The inherited 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 still needed: /blog ships all 31 cards and
 * hides 25 of them, so the SIXTH 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. 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 **FLEX**, AND ON FLEX THE CHROME DOES NOT FORK — GOTCHA 5 IS
 * INVERTED. Measured over generated/served-by-device (159 documents, 53 paths x 3
 * device UAs, all 200):
 *
 *   dmtemplateid    FlexHeader on ALL THREE BANDS, all 53 pages
 *   <body> class    `dmRoot fix-mobile-scrolling flex-site dmResellerSite `,
 *                   BYTE-IDENTICAL on all three bands, all 53 pages
 *   data-flex-site  present on all three bands, all 53 pages
 *
 * So dmtemplateid CANNOT discriminate the fork here, and gotcha 48/88's
 * dmDesktopBody / dmTabletBody / dmMobileBody classes DO NOT EXIST on this platform.
 * Note `fix-mobile-scrolling` in that class list 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 STILL THREE-WAY, and it is measured on the served BYTES rather
 * than inferred from the template: desktop == tablet on 0 of 53, desktop == mobile on
 * 0 of 53, tablet == mobile on 0 of 53, each disagreement re-sampled to rule out a
 * flake. The two independent desktop corpora taken minutes apart
 * (generated/raw-pages and generated/served-by-device/*.d.html) are 53 of 53
 * byte-identical, which is the control that says the origin was not drifting under the
 * measurement.
 *
 * THE TABLET DECLARES ITS OWN CANVAS, AND THE THREE BANDS DECLARE THREE DIFFERENT
 * VIEWPORTS — not two. Censused 53/53 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 THEREFORE CARRIES A CONTENT FORK, NOT A CHROME FORK. The header,
 * drawer and overlay are one shape on every band (#dmFlexHeaderContainer, #flex-header,
 * one #hamburger-drawer 53/53 per band, one #layout-drawer-overlay 53/53 per band);
 * 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, over all 361 sheets in generated/live/css:
 *
 *     max-width: 767px   x197      min-width: 768px   x97
 *     max-width: 1024px  x103      min-width: 1025px  x43
 *     min-width: 0px     x71
 *
 * 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:
 *
 *   (a) An auto-advancing slider can never diff to zero — the two captures land on
 *       different slides — so the home page's slider region is a legitimate --hide
 *       candidate, and whatever is hidden needs a per-element check that asserts
 *       MOTION, because motion is exactly what hiding removes.
 *   (b) The shared check-slider.mjs asserts on `.flexslider` roots, of which this site
 *       has ZERO. It will FATAL on an empty population. That refusal is honest about
 *       ITS subject and says nothing about this site's sliders — "no FlexSlider" must
 *       not be written up as "no sliders".
 *
 * NOT YET VERIFIED ON THE SERVED BUILD — nothing has been built yet (site/src/pages is
 * empty). The population any such run must cover is DERIVED from the census above and
 * not typed: 2 roots on 1 page, x 3 widths.
 * ------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------
 * 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 (/(another client's page), /decks, /(another client's page), /(another client's page) …), 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:
 *
 *     23 of 53 served documents carry an accordion
 *     8 DISTINCT widget ids
 *       7 unique per-page widgets — /, /(another client's page),
 *         /(another client's page),
 *         /(another client's page),
 *         /(another client's page),
 *         /(another client's page),
 *         /(another client's page)
 *       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;
}

/* ---------------------------------------------------------------- flex popup --
 * THE TWO HOSTS DUDA'S RUNTIME CREATES FOR A CLICK-TRIGGERED POPUP, AND THE ONLY
 * REASON THEY ARE HAND-WRITTEN IS THAT THEY DO NOT EXIST AT CAPTURE TIME.
 *
 * #flex-runtime-popup itself is NOT here: its rules came through the ported cascade
 * normally (width 712px, height 810px, border-radius 8px, background var(--color_4)),
 * because the <dialog> IS in the served markup of /form. What is missing is the
 * overlay and the container, which Duda's runtime builds on the fly when the trigger
 * is clicked -- so no capture can contain them and port-css has nothing to port.
 *
 * EVERY VALUE BELOW WAS READ OFF LIVE with a real hit-tested click at 1440, not
 * chosen. Measured on the settled popup:
 *
 *   #flex-popup-overlay             position fixed  z-index 200  rgba(0, 0, 0, 0.533)
 *                                   1440x900 at (0,0)
 *   #flex-runtime-popup-container   position fixed  z-index 201  transparent
 *                                   1440x900 at (0,0)  display flex
 *   dialog#flex-runtime-popup       712x810 at (364, 45), background rgb(233,233,233)
 *
 * The dialog's position is CENTRING, not a coordinate to copy: (1440-712)/2 = 364 and
 * (900-810)/2 = 45 exactly. So the container centres and the dialog keeps the UA's own
 * `position:absolute; margin:auto` for `dialog[open]`. Reproducing the mechanism rather
 * than the numbers is what makes it correct at every viewport, per gotcha 46 -- the
 * captured coordinates are a function of the viewport they were measured in.
 *
 * Scroll lock goes on BODY here. That is not a guess either: live's popup sets
 * overflow:hidden on body and leaves documentElement visible, which is the OPPOSITE of
 * this site's hamburger drawer, where the lock is on documentElement and body stays
 * visible. The two are measured separately and must not be unified.
 */
#flex-popup-overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
  background-color: rgba(0, 0, 0, 0.533);
}
#flex-runtime-popup-container {
  position: fixed;
  inset: 0;
  z-index: 201;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: transparent;
}
