/* The ONLY hand-written stylesheet in this build.
 *
 * Everything else under /styles is Duda's own cascade, ported verbatim from what
 * the live documents actually load, with nothing but url() rewritten. This file
 * exists for the few things that cannot come from there: the device gating a
 * single-document build needs, the honeypot that replaces reCAPTCHA, the blog
 * "Show More" reveal, and one parallax behaviour Duda implements in JS rather
 * than in CSS.
 *
 * The breakpoints below are Duda's own — READ OUT OF THIS SITE'S OWN RUNTIME
 * PACKAGE, not inherited from another migration (measured: 767 / 768 / 1024 /
 * 1025 are the only device breakpoints it declares). They MUST stay identical to
 * the ones tools/port-css.mjs media-gates the ported sheets with; if the two
 * disagree there is a width where a device's markup is visible with another
 * device's stylesheet.
 *
 *     mobile   (max-width: 767px)
 *     tablet   (min-width: 768px) and (max-width: 1024px)
 *     desktop  (min-width: 1025px)
 */

/* --------------------------------------------------------- per-device widgets
 * Widgets that build their own DOM client-side and genuinely disagree about
 * STRUCTURE between the three documents, not just about image renditions. All
 * variants are emitted and gated here. The class sits on the widget element
 * ITSELF rather than on a wrapper, so no descendant relationship the widget's
 * own CSS relies on is disturbed.
 *
 * display:none rather than visibility:hidden is deliberate: these variants have
 * different heights, and a hidden-but-laid-out copy would add its own height.
 *
 * The selector is DOUBLED to raise specificity. Duda ships rules like
 *   body.dmRoot #dm [list-layout="layout4"] { display: flex !important }
 * at (1,2,1); a plain `.mg-only-t { display:none !important }` is (0,1,0) and
 * LOSES even with !important, which renders every variant at once. The doubled
 * form inside `body.dmRoot #dm` is (1,3,1) and wins outright rather than relying
 * on a source-order tiebreak that would break if a sheet were reordered. */
@media (min-width: 1025px) {
  body.dmRoot #dm .mg-only-t.mg-only-t,
  body.dmRoot #dm .mg-only-m.mg-only-m { display: none !important; }
}
@media (min-width: 768px) and (max-width: 1024px) {
  body.dmRoot #dm .mg-only-d.mg-only-d,
  body.dmRoot #dm .mg-only-m.mg-only-m { display: none !important; }
}
@media (max-width: 767px) {
  body.dmRoot #dm .mg-only-d.mg-only-d,
  body.dmRoot #dm .mg-only-t.mg-only-t { display: none !important; }
}

/* ------------------------------------------------- parallax on touch (gotcha 22)
 * Duda FREEZES parallax on touch devices in its RUNTIME, not in its CSS. Measured
 * on live (tools/probe-attachment.mjs), the blog hero computes:
 *
 *     desktop 1440   background-attachment: fixed
 *     tablet   768   background-attachment: scroll
 *     mobile   375   background-attachment: scroll
 *
 * but the TABLET stylesheet still declares
 *     #dm .dmBody div.u_1325247579 { background-attachment: fixed !important; ... }
 * so a faithful CSS port keeps it fixed at 768 and renders the hero at roughly
 * viewport height instead of section height in a full-page capture — a large diff
 * that reads like a layout bug and is really a runtime behaviour we failed to
 * reproduce.
 *
 * SPECIFICITY IS THE WHOLE POINT HERE. Duda's rule is (1,2,1). A
 * `[class*=...]` selector on its own loses. `#dm .dmBody div[class*="dmSectionParall"]`
 * is also (1,2,1) and this file is the LAST stylesheet in the head, so it wins on
 * source order; the `[id]` qualifier lifts it to (1,3,1) so it wins outright.
 *
 * Duda ships TWO SPELLINGS of the same idea — dmSectionParallaxNew and
 * dmSectionParallex — and both appear on this site, so the selector matches both.
 *
 * The mobile band is included for completeness even though the mobile sheets
 * never declare `fixed` for these sections; it costs nothing and stops a future
 * re-port from regressing silently. */
@media (max-width: 1024px) {
  #dm .dmBody div[class*="dmSectionParallax"][id],
  #dm .dmBody div[class*="dmSectionParallex"][id] {
    background-attachment: scroll !important;
  }
}

/* --------------------------------------------------------------- honeypot
 * Duda sites rely on reCAPTCHA for spam protection and we remove that widget
 * (its keys are Duda's own and domain-restricted, so they cannot work here), so
 * without this every migrated form would ship with none at all.
 *
 * Off-screen rather than display:none: bots skip obviously-hidden fields. Taken
 * out of flow so it costs no layout — the pixel gate would otherwise measure it.
 * The field is named `company`, which cannot collide because Duda names every
 * real field dmform-N. */
.mg-hp {
  position: absolute !important;
  left: -9999px !important;
  top: auto !important;
  width: 1px !important;
  height: 1px !important;
  overflow: hidden !important;
  opacity: 0 !important;
  pointer-events: none !important;
}

/* ------------------------------------------------------------- accordions
 * NOTHING HERE, deliberately. Duda's own sheets already animate the
 * .accordion-item.expanded state from height 0 — measured on live, which sets no
 * inline style and leaves overflow visible. runtime.js only toggles the class
 * live toggles. Adding a transition or overflow here would restyle elements the
 * ported cascade already handles correctly.
 *
 * PARALLAX POSITION is likewise absent from this file on purpose: it is a
 * function of scroll and viewport height, so it lives in runtime.js, not here. */

/* ------------------------------------------------------------ blog "Show More"
 * /blog publishes 10 of 35 posts and a "Show More" control that asks Duda's
 * backend for ten more, three times (measured: 10 -> 20 -> 30 -> 35, after which
 * the control removes itself). A static build has no backend, so all 35 cards
 * ship and the ones past the first ten start hidden; runtime.js reveals them in
 * live's own steps of ten. display:none rather than visibility:hidden — a
 * laid-out card would add its height to the page, and the initial render has to
 * stay identical to live's un-clicked state, which is what the gate measures. */
.mg-blog-hidden {
  display: none !important;
}

/* ------------------------------------------------------- gy-added: legal pages
 * /privacy-policy and /terms-and-conditions are ADDITIONS — neither exists on
 * live, so there is no captured markup and no Duda page-template to inherit
 * typography from. They reuse the /contact-us shell (its wrapper chain, its
 * u_dmStyle_template_contact-us body class and its per-page sheets) so the
 * header, hero band and footer render identically to the rest of the site; only
 * the prose block below is new.
 *
 * Scoped to .gy-legal so nothing here can reach a ported page.
 *
 * The selectors carry `.dmNewParagraph[data-version]` deliberately. Duda's own
 * reset is
 *     .dmNewParagraph[data-version] h1, … h2, … h3, … p, … ul { margin: 0 }
 * at (0,2,1) — Duda's pages carry their spacing as per-element u_<id> rules
 * instead, which these two pages by definition do not have. A plain
 * `.gy-legal h2` is (0,1,1) and LOSES to that reset, which collapses the whole
 * policy into one unbroken block of text. Matching the reset's own shape puts
 * these at (0,3,1) so they win on specificity rather than on source order. */
.gy-legal .dmRespCol { padding: 40px 20px 60px; }
.gy-legal .dmNewParagraph { margin: 0; }

.gy-legal .dmNewParagraph[data-version] h2 {
  font-size: 26px;
  line-height: 1.3;
  margin: 34px 0 10px;
  font-weight: 700;
}
.gy-legal .dmNewParagraph[data-version] h3 {
  font-size: 20px;
  line-height: 1.3;
  margin: 22px 0 8px;
  font-weight: 700;
}
.gy-legal .dmNewParagraph[data-version] p {
  margin: 0 0 14px;
  line-height: 1.7;
}
.gy-legal .dmNewParagraph[data-version] ul {
  margin: 0 0 16px;
  padding-left: 24px;
  list-style: disc outside;
}
.gy-legal .dmNewParagraph[data-version] li {
  margin: 0 0 6px;
  line-height: 1.7;
}
/* The first block on the page is the effective date — it should not get the
 * 34px top gap a mid-document section heading gets. */
.gy-legal .dmNewParagraph:first-child p { margin-top: 0; }

/* Interlink styling is BODY TEXT ONLY per the QA checklist — these anchors are
 * all inside prose, never in a heading, button, header or footer. */
.gy-legal .dmNewParagraph[data-version] p a,
.gy-legal .dmNewParagraph[data-version] li a {
  text-decoration: underline;
  font-weight: 600;
}

/* The legal links added after each contact form's fields. Kept visually quiet
 * so they read as fine print rather than as navigation. */
.gy-legal-links {
  text-align: center;
  margin: 14px 0 0;
  font-size: 13px;
  line-height: 1.6;
}
.gy-legal-links a { text-decoration: underline; }
.gy-legal-links .gy-sep { opacity: .6; padding: 0 6px; }

/* In the FOOTER the same links live INSIDE the copyright's own paragraph, so
 * they are part of that single text run. They were briefly a third flex child
 * of `.copyright` instead, which wrapped onto a line of its own — the flex
 * children are separate boxes, and nothing makes three of them share a row.
 *
 * `.copyright` already carries, from the ported cascade:
 *     display:flex; justify-content:center !important; color:#fff !important
 * so the centring and the white come from Duda's own rules. Only the anchors
 * need the colour forced — a link takes its own colour rather than inheriting —
 * and `!important` is required to match the `div.u_1365404532` rules that set
 * the copyright's colour and size. */
.copyright .gy-legal-inline a {
  color: rgb(255, 255, 255) !important;
  text-decoration: underline;
}
.copyright .gy-legal-inline .gy-sep {
  opacity: .6;
  color: rgb(255, 255, 255) !important;
}
/* Adding the two links lengthens the copyright's second flex child, and at
 * phone width flex then shrinks the FIRST one — "© 2026" — until it wraps to
 * two lines and reads as a stray "2026". Nothing shrinks at desktop, where it
 * already fits, so this only takes effect where the extra text pushed it. */
.copyright > div:first-child {
  flex-shrink: 0;
  white-space: nowrap;
}

/* ------------------------------------------------ gy-added: accordion easing
 * Paired with the max-height handling in runtime.js. Duda's own 0.3s ease-out
 * was measured against a 30000px declared travel, so it never actually eased —
 * now that the travel is the panel's real height, a slightly longer curve reads
 * better in both directions. Scoped to the widget so nothing else is touched. */
.widget-832a29 .section {
  transition: max-height 0.35s cubic-bezier(0.4, 0, 0.2, 1) !important;
}

/* ------------------------------- gy-added: slider images filled their frame
 * Every SERVICES GALLERY slide painted the photo across only the top half of
 * its 410px card, with the media container's own #eee showing through beneath —
 * the "photos only load halfway and the rest is grey" report.
 *
 * Nothing was failing to load. The cause is OURS, not Duda's: live serves these
 * <img>s with NO width/height attributes, and `integrations/seo.mjs`'s
 * `dimensions` step stamps them on. Duda's own stylesheet then reacts to the
 * attributes it now finds:
 *
 *     img[width][height] { height: auto }            (0,2,1)   <- wins
 *     .eMmLZg / .fECgwp  { height: 100% }            (0,1,0)
 *
 * so the image sized to its intrinsic 960x640 ratio (226px of a 410px box)
 * instead of filling the frame. Measured against live: every one of the 17
 * slides renders 410px there and 106-227px here.
 *
 * Fixed here rather than by dropping the attributes, because they are what stops
 * the slider reserving no space and shifting layout while it loads. `#dm` raises
 * this to (1,2,1) so it beats the attribute selector outright rather than
 * relying on being the last sheet loaded.
 *
 * NOTE FOR THE FLEET: this is a property of seo.mjs's `dimensions` step meeting
 * Duda's `img[width][height]` rule, not of this site. Any ported Duda site whose
 * CSS sizes an image with a percentage height has the same latent defect. */
#dm img[data-grab="slide-media"] {
  height: 100%;
}

/* ------------------------------- gy-added: lightbox pinned to the viewport
 * PhotoSwipe's stylesheet declares the overlay `position: absolute`, and its own
 * JS is what promotes it to `fixed` on open. That script is not part of this
 * migration, so the overlay opened at the top of the DOCUMENT rather than the
 * top of the SCREEN.
 *
 * Measured on /Carpet-Cleaning-Saskatoon, whose gallery sits ~1200px down: on
 * live the open overlay reports position:fixed at top 0; here it reported
 * position:absolute at top -1213 — a full viewport of lightbox rendered above
 * everything the visitor could see. Scroll to the gallery, click a photo, and
 * nothing appears to happen. It is why this read as "clicking does not enlarge"
 * even though the click, the image and the controls were all working.
 *
 * Only applied while open, so the closed element keeps the geometry it shipped
 * with and nothing else in the cascade shifts. */
.pswp.pswp--open {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
