/* 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.
 *
 * 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. */
.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 specificity (1,2,1) and it is `!important`. A hide written as a plain
 * `.mg-only-t { display:none !important }` is (0,1,0), so `!important` on both
 * sides means SPECIFICITY decides and Duda's rule wins — the element stays
 * visible and nothing in the console says so.
 *
 * Measured: with the plain selector, /blog rendered all THREE per-device widget
 * copies at once — 30 cards against live's 10, document height 7640px against
 * live's 3548px, and the gate scored 59.4%.
 *
 * 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 index: THIS SITE DOES HAVE A PAGER, and it withholds most of the posts.
 *
 * Measured on live: /blog declares data-paginate-total-elements="29" and
 * visible-items="10", and renders exactly 10 distinct data-blog-post-alias values.
 * The control is an APPEND-style <a class="more-posts-text-container"> ("Show
 * More"), not a numbered nav — driving it goes 10 -> 20 -> 29, after which the
 * control removes itself. blog.rss independently declares 29 items.
 *
 * A static build has no Duda backend, so all 26 cards ship and the extras are
 * stamped mg-blog-hidden with data-mg-blog-page in live's own batch size;
 * runtime.js reveals them a batch at a time. This rule is what hides them
 * initially, so unlike the port it came from it is NOT a no-op here. */
#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.
 *
 * Duda's own rule is `.postArticle:not(:last-child){padding-bottom:Npx}` — this
 * cascade carries it at 100px, 20px and 10px for the three bands. On live the
 * 10th card IS the last child and takes none of it. In our build the 10th card is
 * followed by 19 hidden siblings, so it stops matching :last-child 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.
 *
 * 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 as cards appear.
 *
 * Specificity: Duda's rule is (0,2,1) and not !important, so this (0,4,1) plain
 * rule wins on merit without needing one. */
#dm .postArticle:not(.mg-blog-hidden):has(+ .postArticle.mg-blog-hidden) {
  padding-bottom: 0;
}

/* PER-DEVICE WIDGET FORKS.
 *
 * Duda builds .dmPhotoGallery and .mainBlog client-side and the three device
 * documents genuinely differ, so build-pages.py emits each widget three times and
 * stamps the copies mg-only-d / mg-only-t / mg-only-m. Without these rules ALL
 * THREE render at every width — which is exactly what the first gate run showed:
 * / came back at 77% with a +10458px height delta and /blog at 62% with +10150,
 * both of them simply the same widget painted three times over.
 *
 * The band boundaries are Duda's own, read out of the ported cascade: 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 — an
 * earlier version hid all three and restored one with `display: revert`, which
 * reverts past the author cascade to the UA default and would have replaced 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;
  }
}

/* MEDIA-SLIDER FILM ROLE — NO OVERRIDE, and that is the measured answer.
 *
 * An earlier revision added a filmstrip rule here, reasoning from the SERVED html
 * where the film role carries `.gDtRCy` with position:absolute, right:-200%,
 * flex-direction:row and translateX(-0%). That was WRONG, and it is recorded
 * rather than quietly deleted because the reasoning is seductive.
 *
 * The served html is the authority for what the RUNTIME WRITES, but this rule is
 * not something the runtime writes — it belongs to the pre-hydration render. Once
 * the component hydrates it re-renders under a different generated class and the
 * filmstrip rule is simply not applied.
 *
 * MEASURED on live, settled, at 1440:
 *     class="sc-gEvEer jKjUdt"  position:relative  display:block
 *     left:0px  right:0px  transform:none  children:2
 *
 * which is precisely what this build already produced WITHOUT any override. So
 * the correct action here is to add nothing.
 *
 * INHERITED CONTEXT, RETARGETED TWICE NOW. The paragraph that stood here described
 * a slider on a client two migrations back, and the retarget before this one
 * replaced it with a claim about cristosgaragedoors' /reviews page — a page this
 * site does not have. Neither was ever a fact about insulationspecialistusa.net.
 * The general lesson both recorded still holds and is why the block above adds
 * nothing: a slider showing a different photograph than live is usually the CAPTURE
 * landing on a different slide, not a CSS defect, and the remedy is to exclude it
 * from the pixel gate and verify it per element.
 *
 * CENSUSED ON THIS SITE (tools/census-structure.mjs, 159 captures, <style>/<script>
 * stripped first): .flexslider is on 24 of 53 pages, identically on all three bands.
 * There is also exactly ONE .bgGallerySlide, on a single page, and photo galleries
 * on 2. Whether any of them auto-advances is measured BEFORE anything is excluded,
 * and each exclusion is recorded in visual-diff.config.json's hiddenRegions with the
 * measurement behind it — a hidden region gets no pixel coverage at all, so the
 * per-element check is the only coverage it has.
 */

/* ---------------------------------------------------------------------------
 * ACCORDION OPEN STATE — recovering a rule the live CSS read could not see.
 *
 * Duda's FAQ 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: an accordion (matched on
 * data-grab, not on a class token — styled-components generate their class names,
 * so a class regex finds nothing on these) is present on 30 of 53 pages, identically
 * on all three bands.
 *
 * The state is a generated class on the panel host, and the two names are NOT
 * hardcoded here for the reason gotcha 75 gives: styled-components mint a fresh
 * instance name per render. What IS measured is the mechanism —
 *
 *     closed: .dygwmn { overflow:hidden; transition:max-height .3s ease-out;
 *                       height:auto; max-height:0px; }
 *
 * — so the transition runs on MAX-HEIGHT, and opening means raising it.
 *
 * THE OPEN RULE IS ABSENT FROM THE PORTED CASCADE, and that is not a porting
 * mistake. styled-components insert their rules through the CSSOM, so a class
 * only exists in a document's sheet if that component actually RENDERED with it.
 * No panel is ever open on a freshly loaded page, so `.gnWefg` — the open class
 * live uses — appears in ZERO of the ported sheets while `.dygwmn` appears in 4.
 * Restoring it is recovering a sheet the live read could not reach, which is the
 * same situation gotcha 46 describes for a slider's resting state, arrived at
 * from the opposite direction.
 *
 * Keyed on our own class rather than Duda's generated one so nothing here depends
 * on a name that changes per render. Repeated behind #dm to clear Duda's own
 * per-widget `!important` display rules (gotcha 70).
 *
 * Lives in gm/, NOT in styles/: port-css cleans its own hashed output from that
 * directory on every run and would take this file with it (gotcha 49).
 * ------------------------------------------------------------------------- */
#dm .mg-acc-open.mg-acc-open.mg-acc-open {
  max-height: 2000px;
}

/* ---------------------------------------------------------------------------
 * REVIEW ADDITIONS — legal pages, their links, and the drawer submenus.
 *
 * All of this is OURS, not ported, and every selector is keyed on a `gy-`
 * class or on the drawer, so nothing here can reach the ported cascade.
 * ------------------------------------------------------------------------- */

/* Legal page rhythm. Duda's reset zeroes margin on p / ul / h2 — MEASURED, every
 * child of .gy-legal-body computed margin-top:0 and margin-bottom:0 at both 1440
 * and 375 — so the templates ran together as one block with no separation
 * between a section heading and the paragraph above it. These are the only
 * elements on the page without inherited spacing, so it is set explicitly. */
#dm .gy-legal-body > h2 {
  margin: 34px 0 10px;
}
#dm .gy-legal-body > h2:first-child {
  margin-top: 0;
}
#dm .gy-legal-body > p {
  margin: 0 0 14px;
}
#dm .gy-legal-body > ul {
  margin: 0 0 18px;
  padding-left: 22px;
  list-style: disc;
}
#dm .gy-legal-body > ul > li {
  margin-bottom: 5px;
}
/* The h1 sits in its own paragraph widget above the body. */
#dm .gy-legal-row .dmNewParagraph > h1 {
  margin-bottom: 18px;
}
@media (max-width: 767px) {
  #dm .gy-legal-body > h2 { margin: 26px 0 8px; }
  #dm .gy-legal-body > p  { margin-bottom: 12px; }
  #dm .gy-legal-body > ul { margin-bottom: 15px; }
}

/* Footer legal links follow the COPYRIGHT BLOCK'S OWN alignment, per band:
 * left on desktop and tablet, centred on phone. Measured on the copyright above
 * them — textAlign computes `left` at 1440 and 768, `center` at 375 — so a single
 * value is wrong on one side or the other. Centring everywhere was tried first
 * and put the links under a left-aligned copyright line on two of three bands.
 *
 * Scoped on `data-mg-band`, which already separates the two footers this site
 * emits: the desktop/tablet footer is data-mg-band="dt" and the phone footer is
 * data-mg-band="m". Both ship on every page, so a width media query would have
 * had to guess at the same boundary the markup already states.
 *
 * NOT applied to the contact-form block, which stays with the form's own
 * left-aligned fields — centring it there would float the line away from the
 * Submit button it belongs to. */
#dm [data-mg-band="dt"] .gy-legal-links:not(.gy-legal-links--form) {
  text-align: left;
}
#dm [data-mg-band="m"] .gy-legal-links:not(.gy-legal-links--form) {
  text-align: center;
}

/* Contact-form legal links: clear breathing room above the line.
 * MEASURED, the gap between the submit widget's bottom and this block's top was
 * 0px at 1440, 768 and 375 despite a 14px margin-top — the margin collapsed
 * against the submit widget's own box, so the sentence sat directly against the
 * button. PADDING cannot collapse, so the separation is real at every width. */
#dm .gy-legal-links--form {
  padding-top: 15px;
}

/* ---------------------------------------------------------------------------
 * DRAWER SUBMENUS — collapsed until the visitor opens them.
 *
 * Live ships these flat and permanently expanded: measured on live at 375 and
 * 768, `ul.unifiednav__container_sub-nav` has real height both before and after
 * the drawer opens, and the caret next to "Services" / "Areas Served" is
 * decoration. That makes the drawer a single long list — 18 links deep — with no
 * way to collapse a section. Replaced with a real accordion on the client's
 * instruction after their manual pass.
 *
 * SCOPED TO .layout-drawer, which is the tablet and phone drawer markup only.
 * The desktop header nav lives in a different container and keeps its hover
 * dropdowns untouched.
 *
 * The height itself is driven from JS (runtime.js) against the panel's measured
 * scrollHeight; this rule only supplies the closed state and the transition, for
 * the same reason the accordion does — a fixed open value either clips a long
 * submenu or turns the transition into a snap.
 * ------------------------------------------------------------------------- */
#dm .layout-drawer ul.unifiednav__container_sub-nav {
  max-height: 0;
  overflow: hidden;
  transition: max-height .3s ease-out;
}
#dm .layout-drawer .unifiednav__item-wrap_open > ul.unifiednav__container_sub-nav {
  max-height: none;
}
/* The caret flips to show state — using DUDA'S OWN MECHANISM, which is to
 * rotate the icon's ::before rather than the .icon span.
 *
 * Rotating .icon directly does not work and silently does nothing: Duda already
 * sets `transform: scale(...)` on that span, per band and with a long selector —
 * scale(1.3) under .dmMobileBody, scale(1.2) under .dmTabletBody — so a rotate on
 * the same element is simply overridden. Measured: the computed transform stayed
 * matrix(1.3,0,0,1.3,0,0) in BOTH states. Composing scale into our own rule would
 * have worked but would have hardcoded ONE band's scale onto both, resizing the
 * tablet caret.
 *
 * Duda's own dropdown rules rotate `.icon::before` by 180deg on hover/open, so
 * that is what is reused here — the pseudo-element carries no scale of its own.
 *
 * !important is needed and is not decoration: the ported cascade sets this
 * rotation with !important of its own (gotcha 70, the same reason the accordion
 * rule repeats its class). Without it, a plain rule at (1,5,2) specificity was
 * simply ignored and the computed value stayed matrix(-1,0,0,-1,0,0) in BOTH
 * states — the caret never moved, and reading only the OPEN state would have
 * looked like a pass because 180deg is what it was already stuck at. */
/* BOTH states are written down, because the ported cascade already rotates this
 * ::before by 180deg at rest — measured, it computes matrix(-1,0,0,-1,0,0) with
 * the drawer merely open and nothing hovered, which is why the caret read as an
 * UP chevron on a collapsed group. Setting only the open state therefore changed
 * nothing: it asked for the 180 that was already there. Closed is pinned to 0deg
 * so it points down ("this opens"), open flips it up ("this closes"). Verified by
 * screenshot at 3x, not by the computed value alone. */
#dm .layout-drawer li.unifiednav__item-wrap > a.unifiednav__item_has-sub-nav .icon::before {
  transform: rotate(0deg) !important;
  transition: transform .3s ease-out;
}
#dm .layout-drawer li.unifiednav__item-wrap_open > a.unifiednav__item_has-sub-nav .icon::before {
  transform: rotate(180deg) !important;
}
