/* 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 2026-08-20: /blog declares data-paginate-total-elements="19"
 * and visible-items="10", and renders exactly 10 distinct data-blog-post-alias
 * values. The control is a "Show More" APPEND button (.morePosts /
 * .more-posts-text-container), not a numbered nav — driving it goes 10 -> 19, after
 * which the control removes itself. blog.rss independently declares 19 items.
 *
 * NOTE the 19 blog POSTS use the OTHER control: a numbered nav.pagination-nav for
 * their related-posts widget, declaring 18 (the feed minus themselves) and serving
 * 10. This site ships BOTH of Duda's pagination shapes at once, on different
 * widgets, so a tool keyed on either one alone reports a clean run over the other.
 * A static build has no Duda backend, so all 19 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 a whole SCALE of them (10px through 100px in 10px steps), and
 * which one applies is decided per band by the cascade rather than by this file,
 * so no value is restated here. On live the 10th card IS the last child and takes
 * none of it. In our build the 10th card is followed by 9 hidden siblings (19 posts
 * declared, 10 served), 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, 93 captures, <style>/<script>
 * stripped first): .flexslider is on 6 of 31 pages, identically on all three bands.
 * There are ZERO .bgGallerySlide elements on any band, and exactly ONE photo
 * gallery, on a single page. 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 27 of 31 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;
}


/* ---------------------------------------------------- drawer submenu caret --
 * Added 2026-09-10 (website review), alongside runtime.js's rewritten drawer
 * submenu handler. See that block for why the previous handler never ran.
 *
 * THE TAP TARGET IS AN ::after OVERLAY, NOT A SIZED CARET. The caret is an
 * inline <span> inside the parent's own <a>; giving it min-width/min-height to
 * reach 44px turns it into a box wide enough to wrap and the carets end up
 * floating above and to the right of their labels instead of beside them. The
 * overlay gives it a real touch area while the caret keeps its inline footprint.
 *
 * THE FLIP ROTATES ::before, NOT THE ICON. `.icon` already carries a per-band
 * `transform: scale(1.3)` — measured on this site's own drawer — and a rotate on
 * the same element replaces it, so the caret would silently shrink as it turned.
 * ------------------------------------------------------------------------- */
#dm .mg-subnav-caret {
  position: relative;
  cursor: pointer;
}

#dm .mg-subnav-caret::after {
  content: "";
  position: absolute;
  /* Centred on the caret and grown outwards to a 44px touch target. */
  top: 50%;
  left: 50%;
  width: 44px;
  height: 44px;
  transform: translate(-50%, -50%);
  background: transparent;
}

#dm .mg-subnav-caret::before {
  display: inline-block;
  transition: transform 0.3s ease-out;
}

#mobile-hamburger-drawer .unifiednav__item-wrap_open > a .mg-subnav-caret::before {
  transform: rotate(180deg);
}

/* The sub-nav's own collapse. The ported cascade already gives it
 * `max-height: 0; overflow: hidden` and a max-height transition; this only makes
 * the transition duration explicit and collapses the padding ALONGSIDE the
 * height. max-height clips the CONTENT box, so a group collapsed to 0 that still
 * carries vertical padding stands a few pixels tall and shows a sliver of the
 * panel under every parent. Measured 0px here already, so this is a guard against
 * a future cascade change rather than a fix for a present fault. */
/* Scoped to the phone drawer: the same <ul> class is the DESKTOP hover dropdown,
 * whose look must not change. */
#mobile-hamburger-drawer ul.unifiednav__container_sub-nav {
  transition: max-height 0.3s ease-out;
}

#mobile-hamburger-drawer .unifiednav__item-wrap:not(.unifiednav__item-wrap_open) > ul.unifiednav__container_sub-nav {
  padding-top: 0 !important;
  padding-bottom: 0 !important;
  border-top-width: 0 !important;
  border-bottom-width: 0 !important;
}

/* Blog search "no results" line — added 2026-09-10 with runtime.js blogSearch().
 * Live's own wording; the block sits inside the list widget, so it takes the
 * widget's text colour and only needs room around it. */
#dm .mg-blog-noresults {
  margin: 0;
  padding: 30px 15px;
  text-align: center;
}

/* ------------------------------------------------------------- lightbox --
 * Added 2026-09-10 with runtime.js's rebuilt lightbox(). Keyed on our own
 * .mg-pswp-live class, set only while open, so the shipped-closed .pswp root is
 * untouched at rest.
 *
 * position:FIXED is the whole fix for the overlay opening off-screen: PhotoSwipe's
 * base CSS is position:absolute and only PhotoSwipe's own JS promotes it, so
 * without this the overlay anchors to the top of the DOCUMENT and a visitor who
 * scrolled to the gallery sees it open a screen above where they are looking.
 * ------------------------------------------------------------------------- */
.pswp.mg-pswp-live {
  position: fixed !important;
  inset: 0 !important;
  width: 100% !important;
  height: 100% !important;
  z-index: 2147483000 !important;
  overflow: hidden;
}

.pswp.mg-pswp-live .pswp__bg {
  opacity: 1 !important;
  background: #000;
}

.pswp.mg-pswp-live .pswp__scroll-wrap,
.pswp.mg-pswp-live .pswp__container {
  position: absolute;
  inset: 0;
}

.pswp.mg-pswp-live .mg-pswp-img {
  position: absolute;
  inset: 0;
  margin: auto;
  display: block;
  max-width: 92vw;
  max-height: calc(100vh - 88px);
  width: auto;
  height: auto;
  object-fit: contain;
}

/* The UI must be visible — PhotoSwipe's --hidden class holds it at 0.001. */
.pswp.mg-pswp-live .pswp__ui,
.pswp.mg-pswp-live .pswp__top-bar,
.pswp.mg-pswp-live .pswp__button--close,
.pswp.mg-pswp-live .pswp__button--arrow--left,
.pswp.mg-pswp-live .pswp__button--arrow--right,
.pswp.mg-pswp-live .pswp__counter {
  opacity: 1 !important;
  visibility: visible !important;
}

/* ------------------------------------------------------- legal links --
 * Added 2026-09-10 (website review) with tools/apply-review-footer.py.
 *
 * FOOTER. The line sits inside `.copyright`, which Duda lays out as a NO-WRAP
 * FLEX ROW (measured: display:flex, flex-wrap:nowrap, justify-content:center on
 * every band) — so, unstyled, the links became a third item on the same row as
 * "© 2026 | All Rights Reserved", and wrapped to two lines on phone. Wrapping
 * the row and giving the links the full basis puts them on their own line,
 * centred the way the copyright itself is centred on all three bands.
 *
 * THE INK IS SET, NOT INHERITED. The links measured rgb(70,57,57) — the page's
 * body ink, not the footer's — on the footer's rgb(43,43,43): about 1.3:1, which
 * passes every "the links are present" check and is invisible. The copyright
 * text beside them is white, so they are white. */
#dm .copyright:has(> .mg-legal-links) {
  flex-wrap: wrap;
}

#dm .copyright > .mg-legal-links {
  flex: 0 0 100%;
  text-align: center;
  padding-top: 6px;
  color: #fff;
  font-size: inherit;
  line-height: 1.6;
}

#dm .copyright > .mg-legal-links a,
#dm .copyright > .mg-legal-links a:visited {
  color: #fff !important;
  text-decoration: underline;
}

#dm .copyright > .mg-legal-links a:hover,
#dm .copyright > .mg-legal-links a:focus-visible {
  text-decoration: none;
}

/* CONTACT FORM. One line under the last field.
 * clear:both — the submit is wrapped in the FLOATED `.dmformsubmit.dmWidget.R`
 * (float:left, measured), so a block that is merely last in the markup flows up
 * alongside it: unstyled, this line's top was 63px ABOVE the button's bottom and
 * it wrapped to 3 lines on tablet and 4 on phone.
 * padding-top, NOT margin-top — a margin collapses against the preceding box and
 * can measure 0. The form sits on rgb(250,250,250), so the body ink (rgb(43,43,43))
 * reads correctly here; the footer's white would not. */
#dm .mg-form-legal {
  clear: both;
  display: block;
  width: 100%;
  padding-top: 15px;
  padding-bottom: 10px;   /* the next band is a full-bleed photo; without this the line touches it */
  text-align: left;
  white-space: nowrap;
  color: rgb(43, 43, 43);
  font-size: 14px;
  line-height: 1.5;
}

#dm .mg-form-legal a,
#dm .mg-form-legal a:visited {
  color: rgb(43, 43, 43) !important;
  text-decoration: underline;
}

/* ---------------------------------------------------------- legal pages --
 * /privacy-policy and /terms-and-conditions (tools/build-legal-pages.py). Duda's
 * reset zeroes margin on p, ul and h2, so the template renders as one unbroken
 * slab with every heading pressed against the paragraph above it. The rhythm is
 * set explicitly here: a clear space above each heading, a smaller one between
 * paragraphs and lists. The column is capped for a readable line length. */
#dm .mg-legal-row .mg-legal {
  max-width: 860px;
  margin: 0 auto;
  padding: 40px 20px 60px;
  text-align: left;
  color: rgb(43, 43, 43);
}

#dm .mg-legal-row .mg-legal p,
#dm .mg-legal-row .mg-legal ul {
  margin: 0 0 14px !important;
  line-height: 1.7;
}

#dm .mg-legal-row .mg-legal ul {
  padding-left: 1.4em !important;
  list-style: disc outside !important;
}

#dm .mg-legal-row .mg-legal li {
  margin: 0 0 4px;
  list-style: inherit;
}

#dm .mg-legal-row .mg-legal h2 {
  margin: 34px 0 12px !important;
  font-size: 24px;
  line-height: 1.3;
  text-align: left;
}

#dm .mg-legal-row .mg-legal h3 {
  margin: 22px 0 10px !important;
  font-size: 19px;
  line-height: 1.3;
  text-align: left;
}

#dm .mg-legal-row .mg-legal a {
  text-decoration: underline;
  word-break: break-word;
}
