/* The ONLY stylesheet this migration authors. Everything else under
 * site/public/styles/ is Duda's own cascade, ported byte-for-byte.
 *
 * Three rule groups, all of them serving markup that build-pages.py emits and
 * that has no counterpart on live:
 *
 *   .mg-only-d / -t / -m   per-device widget variants. Duda builds
 *                          .dmPhotoGallery client-side and it genuinely differs
 *                          between the three documents on / (3/2/1 columns,
 *                          -300h vs -400w crop) and /outreach-for-schools
 *                          (3/3/4 columns). We ship one body, so all three
 *                          variants are in the document and the two that are not
 *                          this band's are hidden. Without these rules all three
 *                          render at once, stacked.
 *
 *   .mg-hp                 the honeypot that replaces the reCAPTCHA we remove.
 *                          Off-screen rather than display:none, because bots skip
 *                          obviously-hidden fields; out of flow so it costs no
 *                          layout for a pixel diff to measure. The same
 *                          declarations are ALSO inlined on the input itself, so
 *                          a missing or mis-ordered sheet cannot turn it into a
 *                          visible empty text box at the top of all five forms.
 *
 * The media queries match __mg's band split exactly (m <=767, t 768-1024,
 * d >=1025); a mismatch here would show two variants at one width.
 *
 * Loaded LAST in <head>, after the 140 ported sheets, so !important is not needed
 * for .mg-hp and is used for the widget gating only because Duda's own
 * `display:block`/`display:flex` widget rules are themselves specific. */

/* SPECIFICITY IS LOAD-BEARING HERE, AND A BARE CLASS IS NOT ENOUGH.
 * Duda's own per-element rule for the widget we are gating is
 *
 *     #dm .dmBody div.u_1729911806 { width: calc(100% + 0px) !important;
 *                                    display: block !important }
 *
 * — an id + two classes + a type. `.mg-only-t{display:none!important}` is a
 * single class, so with !important on BOTH sides specificity decides and Duda
 * wins: measured at 1440, all three gallery variants computed display:block and
 * rendered stacked, making / 2600px too tall (48.8% at 1440, 50.9% at 768).
 * Being last in the cascade does not help — order only breaks a TIE.
 *
 * The selectors below repeat the class (`.mg-only-t.mg-only-t`) to land strictly
 * above that rule rather than merely tying with it, so this keeps working if the
 * sheet order ever changes. */
@media (max-width: 767px) {
  #dm .dmBody div.mg-only-d.mg-only-d,
  #dm .dmBody div.mg-only-t.mg-only-t,
  .mg-only-d,
  .mg-only-t {
    display: none !important;
  }
}

@media (min-width: 768px) and (max-width: 1024px) {
  #dm .dmBody div.mg-only-d.mg-only-d,
  #dm .dmBody div.mg-only-m.mg-only-m,
  .mg-only-d,
  .mg-only-m {
    display: none !important;
  }
}

@media (min-width: 1025px) {
  #dm .dmBody div.mg-only-t.mg-only-t,
  #dm .dmBody div.mg-only-m.mg-only-m,
  .mg-only-t,
  .mg-only-m {
    display: none !important;
  }
}

.mg-hp {
  position: absolute;
  left: -9999px;
  top: auto;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* ---------------------------------------------------------------------------
 * DELIBERATE DEVIATION FROM LIVE: form outcome messages made readable.
 *
 * Duda paints five of this site's ten form outcome nodes in var(--color_8),
 * which is white, on backdrops that are white or #f5f5f5. Measured on live
 * www.thesmilezone.org and on our build, and the two AGREE EXACTLY on all ten
 * nodes -- so this is faithfully ported, not a migration error, and it is being
 * changed on purpose. Same call as the Joe's white-on-white precedent.
 *
 * Why it is worth deviating for: the invisible node is the SUCCESS message on
 * four of the five forms. The visitor submits, the lead is delivered, and they
 * see nothing -- so they cannot tell a delivered lead from a dead form.
 * /request-dental-records is the worst case and the reason this was raised:
 * BOTH its success and its error node are invisible, and a dental-records
 * request that appears to do nothing is one the patient submits again.
 *
 * Measured per page AND per band (375 / 768 / 1440) before choosing a colour --
 * a section background can differ by band, and a single desktop reading would
 * not be evidence for mobile. All five failing nodes sit on a light backdrop at
 * all three bands, none on an image, so a dark colour is correct for every one.
 * Contrast before -> after, worst band:
 *
 *   /contact-us              success  1.09 -> 10.76   on 245,245,245
 *   /request-dental-records  success  1.00 -> 11.73   on 255,255,255
 *   /request-dental-records  error    1.00 -> 11.73   on 255,255,255
 *   /news-letter-sign-up     success  1.09 -> 10.76   on 245,245,245
 *   /volunteer               success  1.09 -> 10.76   on 245,245,245
 *
 * /outreach-for-schools is NOT touched -- both its nodes already inherit
 * 56,56,56 and measure 11.73. Nothing that already passes is altered.
 *
 * #383838 is not a new colour: it is rgb(56,56,56), the value this site's own
 * working outcome nodes already inherit, so the fix uses the site's existing
 * text colour rather than introducing one.
 *
 * ZERO PIXEL IMPACT ON THE GATE. Every one of these nodes carries an inline
 * style="display:none" and is revealed only by runtime.js after a submit, which
 * no capture performs. The gate compares them hidden on both sides, before and
 * after this change.
 *
 * SPECIFICITY, per the note at the top of this file. The rules being overridden
 * are `#dm .dmBody div.u_XXXXXXXX .dmform-success {color: var(--color_8)
 * !important}` -- (1 id, 3 classes, 1 type). !important is on both sides, so
 * specificity decides and being last in the cascade would only break a tie. The
 * page-alias attribute plus the repeated final class lands strictly above them.
 * Scoped per page ON PURPOSE: these are five individually measured nodes, not a
 * blanket rule, so a future page whose backdrop is dark cannot inherit a fix
 * that would make it invisible the other way. */

body[data-page-alias="contact-us"] #dm .dmBody div.u_1980619540 .dmform-success.dmform-success,
body[data-page-alias="request-dental-records"] #dm .dmBody div.u_1348455760 .dmform-success.dmform-success,
body[data-page-alias="request-dental-records"] #dm .dmBody div.dmform-error.dmform-error,
body[data-page-alias="news-and-events/news-letter-sign-up"] #dm .dmBody div.u_1858440631 .dmform-success.dmform-success {
  color: #383838 !important;
}

/* ---------------------------------------------------------------------------
 * /news-and-events/volunteer success node — WHITE, gy-added on operator
 * instruction after manual review.
 *
 * This node was previously in the #383838 group above. That was wrong for THIS
 * page: the volunteer form sits on a dark blue photographic background, so
 * rgb(56,56,56) put dark grey text on a dark photo and the confirmation was
 * very nearly illegible. Captured both ways at 1440 against the deployed page,
 * with the node revealed exactly as runtime.js reveals it — dark is unreadable,
 * white is clean.
 *
 * It is consistent with the rest of this form: the gy-form-legal consent line
 * directly above it is already #ffffff on this same page, and for the same
 * reason. The group above keeps #383838 because those nodes sit on light
 * backdrops; this stays scoped to the one page rather than becoming a blanket
 * rule, per the note on that group.
 *
 * ZERO PIXEL IMPACT ON THE GATE, on the same grounds as the group above: the
 * node carries an inline style="display:none" and is revealed only by
 * runtime.js after a submit, which no capture performs. */
body[data-page-alias="news-and-events/volunteer"] #dm .dmBody div.u_1159232954 .dmform-success.dmform-success {
  color: #ffffff !important;
}

/* ---------------------------------------------------------------------------
 * bounceInCombo — gy-added. Supplies the ONE entrance animation this migration
 * could not port.
 *
 * Six elements on / carry data-anim-desktop="bounceInCombo". Duda does not ship
 * keyframes for it in any of the 140 ported sheets (verified: zero matches for
 * `@keyframes bounceInCombo` and zero `.bounceInCombo` rules) because its own
 * runtime composes it at play time from a base effect plus the per-element
 * `intensity` and `dir` in data-anim-extended. We do not ship that composer, so
 * runtime.js's entranceReveal() was adding a class with nothing behind it: the
 * six elements became VISIBLE on scroll but did not move. `tada`, the seventh,
 * does have keyframes in the ported sheets and already worked.
 *
 * This site's six all decode to {"animation":"bounceInCombo","duration":1,
 * "delay":0|0.1|0.2,"intensity":0.4,"dir":"in-place"} — one shape, so a single
 * in-place scale bounce covers every instance and nothing needs to key off
 * intensity. Modelled on animate.css `bounceIn` (Duda's own base for this
 * effect), amplitude damped to suit intensity 0.4 rather than the stock 1.0.
 *
 * ZERO SETTLED-PIXEL IMPACT, so the visual gate is unaffected either way:
 * animation-fill-mode:both leaves the element at the 100% keyframe, which is
 * scale(1) / opacity 1 — identical to the un-animated resting state the gate
 * measures. Only the frames BETWEEN 0 and 100% differ, and no capture is taken
 * mid-flight.
 *
 * runtime.js writes animation-duration and animation-delay inline from the
 * decoded config, so neither is set here. */
@keyframes bounceInCombo {
  0% {
    opacity: 0;
    transform: scale3d(0.88, 0.88, 0.88);
  }
  40% {
    opacity: 1;
    transform: scale3d(1.04, 1.04, 1.04);
  }
  65% {
    transform: scale3d(0.985, 0.985, 0.985);
  }
  85% {
    transform: scale3d(1.008, 1.008, 1.008);
  }
  100% {
    opacity: 1;
    transform: scale3d(1, 1, 1);
  }
}

.animated.bounceInCombo {
  animation-name: bounceInCombo;
  animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}

/* ---------------------------------------------------------------------------
 * .gy-was-h1 — keeps the demoted second heading pixel-identical to the <h1> it
 * used to be.
 *
 * /news-and-events/donors and /news-and-events/events each carried TWO <h1>s
 * (faithfully ported — live has the same defect). Page.astro demotes the second
 * to <h2> so the document outline has exactly one H1, which is what the review
 * checklist requires.
 *
 * That demotion is NOT free: Duda sizes these headings by TAG, not by the class
 * on the element, so the same markup computes 35px as h1 and 30px as h2
 * (measured in-browser on /news-and-events/donors before the change). Left
 * alone, a pure-semantics fix would visibly shrink both headings on a strict 1:1
 * port. This pins the h2 back to the h1 metrics so the RENDER is unchanged and
 * only the outline moves.
 *
 * Both headings resolve to the same computed values on their page, so one rule
 * covers both. font-size is the only property that actually differed; weight,
 * family, line-height and margins already matched. It is restated here rather
 * than assumed so a future change to Duda's h2 rules cannot silently reopen it.
 *
 * Specificity per the note at the top of this file: Duda's own heading rules are
 * tag selectors inside `#dm .dmBody`, so the class is repeated to land above
 * them without needing !important. */
#dm .dmBody h2.gy-was-h1.gy-was-h1 {
  font-size: 35px;
  font-weight: 700;
  line-height: normal;
  margin-top: 0;
  margin-bottom: 0;
}

/* ---------------------------------------------------------------------------
 * Media-slider pagination bullets — gy-added.
 *
 * runtime.js's mediaSliders() marks the current bullet with
 * data-grab="pagination-button-bullet active", and this is what makes that
 * state VISIBLE. It cannot be left to Duda's own hashed bullet classes: on some
 * widgets (1871148454 is the clearest) BOTH the active and the inactive button
 * carry BOTH hashes, in different order, so the two render identically and the
 * visitor cannot tell which slide they are on.
 *
 * Opacity is used rather than colour because the hashed rules already set
 * background-color per widget (white on one, #ced6d9 on another) at a
 * specificity of #dm#dm#dm — matching that per widget would mean hardcoding
 * hashes, which is exactly what the driver avoids. Dimming the inactive ones
 * works against any of those palettes.
 *
 * Attribute selectors only, so nothing here depends on a styled-components hash
 * surviving a Duda republish. */
[data-grab="pagination-button-bullet"] {
  opacity: 0.4;
  transition: opacity 0.2s ease-in-out;
}

[data-grab="pagination-button-bullet active"] {
  opacity: 1;
}

/* ---------------------------------------------------------------------------
 * Form legal notice — colour per page. gy-added.
 *
 * CORRECTED after review. The first version painted this #383838 everywhere, on
 * the strength of a contrast measurement that was WRONG: the probe walked up for
 * the nearest opaque background-COLOR and found rgb(245,245,245), but on four of
 * the five form pages that colour is painted OVER by a background-IMAGE on the
 * same element — u_1271107742 and its twins, a dmSectionParallaxNew section whose
 * background is Dentistry-2880w.webp at size:cover, background-attachment:fixed,
 * with hasBackgroundOverlay. Dark grey text was sitting on a photograph.
 *
 * The right reference is not a computed ratio against a photo whose luminance
 * varies across its own area — it is what THIS FORM'S OWN LABELS do, since they
 * sit on the same backdrop and the site already made that call. Measured, per
 * page, on the visible (non-hide="true") labels:
 *
 *   /contact-us               photo   field labels WHITE ("Message", the label
 *                                     directly above the notice, is white; the
 *                                     #383838 ones are radio OPTION labels on a
 *                                     white option list, not field labels)
 *   /news-and-events/volunteer        photo   7/7 white
 *   /news-letter-sign-up              photo   3/3 white
 *   /request-dental-records           photo   15/15 white
 *   /outreach-for-schools     no image        6/6 #383838
 *
 * So: white on the four photo-backed forms, dark on the one flat form. No
 * text-shadow, because the labels carry none and the section's overlay is what
 * keeps white legible — adding one here would make this line the only text in
 * the form that has it.
 *
 * The anchors are given the same colour plus an underline: this is body text, so
 * the interlink treatment applies, and an unstyled link inheriting white would
 * be indistinguishable from the sentence around it. */
body[data-page-alias="contact-us"] #dm .dmBody p.gy-form-legal.gy-form-legal,
body[data-page-alias="contact-us"] #dm .dmBody p.gy-form-legal.gy-form-legal a,
body[data-page-alias="news-and-events/volunteer"] #dm .dmBody p.gy-form-legal.gy-form-legal,
body[data-page-alias="news-and-events/volunteer"] #dm .dmBody p.gy-form-legal.gy-form-legal a,
body[data-page-alias="news-and-events/news-letter-sign-up"] #dm .dmBody p.gy-form-legal.gy-form-legal,
body[data-page-alias="news-and-events/news-letter-sign-up"] #dm .dmBody p.gy-form-legal.gy-form-legal a,
body[data-page-alias="request-dental-records"] #dm .dmBody p.gy-form-legal.gy-form-legal,
body[data-page-alias="request-dental-records"] #dm .dmBody p.gy-form-legal.gy-form-legal a {
  color: #ffffff;
}

body[data-page-alias="outreach-for-schools"] #dm .dmBody p.gy-form-legal.gy-form-legal,
body[data-page-alias="outreach-for-schools"] #dm .dmBody p.gy-form-legal.gy-form-legal a {
  color: #383838;
}

#dm .dmBody p.gy-form-legal.gy-form-legal a {
  text-decoration: underline;
}

/* ---------------------------------------------------------------------------
 * "A message from your founder" — tablet width. gy-added, on operator
 * instruction after manual review. A DELIBERATE DEVIATION FROM LIVE: live has
 * this same defect at tablet and it is being corrected, not reproduced.
 *
 * The section is div.u_1089972661 on /. Its horizontal padding comes from
 *     #dm .dmBody div.u_1089972661 { padding: 0 200px !important }
 * which lives in styles/24a7690e2ab977fb.css — a sheet Duda serves to DESKTOP
 * AND TABLET BOTH (media (min-width:1025px) and (768px-1024px)). At 1440px a
 * 200px inset is the intended look and leaves ~1040px of content. At tablet it
 * leaves 353px of a 753px viewport, and after the inner column's own 40px the
 * quote renders about 213px wide — a narrow ribbon down the middle of the page.
 *
 * 40px is not a chosen number: it is what every neighbouring section on this
 * page already computes at tablet. Measured at 753px on the built page —
 *     u_1523479242  40px   (SPECIAL NEEDS & INCLUSIVE CARE)
 *     u_1404423554  40px   (#TheSmileZone)
 *     u_1089972661  200px  <- this one, the only outlier
 *     u_1197073940  40px   (3rd Annual Charity Golf Tournament)
 *     u_1438927719  40px   (TheSmileZone x Compassion Buckeye)
 * so this makes the section stretch exactly like the sections above and below
 * it, rather than to some new width.
 *
 * SCOPED TO THE TABLET BAND ONLY. Desktop keeps its 200px inset untouched — the
 * media query cannot match there — and mobile already has its own rule
 * (padding:0 30px 30px) in the mobile sheet, which this does not reach either.
 *
 * Specificity: the rule being overridden is (1 id, 2 classes, 1 type) with
 * !important, so the class is repeated to land strictly above it. */
@media (min-width: 768px) and (max-width: 1024px) {
  #dm .dmBody div.u_1089972661.u_1089972661 {
    padding-left: 40px !important;
    padding-right: 40px !important;
  }
}

/* ---------------------------------------------------------------------------
 * Donate popup — gy-added. Presentation for the modal runtime.js opens from the
 * floating Donate button (see the popup block in runtime.js for the wiring).
 *
 * REWRITTEN after review: the first version collapsed /donate's two columns into
 * one and hid the spacer, which threw away the whole design. What live actually
 * shows is /donate's own row, rendered at its natural size:
 *
 *   div.u_1694205906   the row  — background-image dental-outreach-6ba805f1
 *                                 (the child with the lollipop), size:cover,
 *                                 max-width 960px
 *   div.u_1594819959   left col — hide-for-small, holds div.u_1763880589, a
 *                                 400px SPACER. That spacer is what gives the row
 *                                 its height, which is what lets the background
 *                                 photo show at all. Hiding it collapsed the
 *                                 modal to the height of the Stripe button.
 *   div.u_1043075735   right col — background-color var(--color_1), the orange
 *                                 panel, padding 50px 40px, holds the
 *                                 stripe-buy-button.
 *
 * So the correct rule is to override almost NOTHING and let the ported page CSS
 * do its job. The modal only has to be wide enough for the 960px row and get out
 * of the way. Below 768px the left column removes itself (hide-for-small) and the
 * orange panel goes full width, which is the same thing live does — no extra
 * mobile rule needed.
 *
 * #dmPopup is Duda's SHARE popup, reused because that is exactly what live reuses
 * — its runtime AJAXes the donate page into this same #dmPopup > .data. The one
 * thing that has to be suppressed is the hardcoded "Share by:" heading in
 * .dmPopupTitle: a ported sheet sets it display:none but a later one restores it
 * via `.dmPopup .dmPopupTitle{…}`, so it renders. All of this is scoped to
 * .mg-popup-donate, which our driver adds and removes, so the share popup keeps
 * its own styling if it is ever used. */
#dmPopup.mg-popup-donate .dmPopupTitle {
  display: none !important;
}

#dmPopup.mg-popup-donate .data {
  width: 100%;
  box-sizing: border-box;
  padding: 0;
}

/* .u_dm_content carries padding:20px as a PAGE gutter. Inside the modal that
 * inset the row to 697px in a 737px box, leaving a band of popup background
 * down both sides and pulling the photo in off the edge. Live's popup row
 * measures the full 737 — flush to the modal on both sides — so the gutter is
 * removed here. Scoped to the donate popup, so /donate as a standalone page
 * keeps its normal page padding. */
#dmPopup.mg-popup-donate .data .u_dm_content {
  padding: 0 !important;
}

/* The close control sits over the orange panel, so it needs to clear the row's
 * own stacking and stay legible against it. */
#dmPopup.mg-popup-donate .dmPopupClose {
  z-index: 2;
  background-color: rgba(255, 255, 255, 0.75);
}

/* ---------------------------------------------------------------------------
 * Media-slider images fill their slide. gy-added.
 *
 * The slide image was rendering at its own aspect ratio instead of filling the
 * slide box — the whole photo letterboxed inside the frame rather than cropped
 * to it. Measured on our build (/blog, widget 1493687327): the media container
 * is 315x180 and the image computed 315x206.102, i.e. 315 x (670/1024) — its
 * natural ratio, overflowing the box.
 *
 * CAUSE: integrations/seo.mjs's `dimensions` step stamps width="1024"
 * height="670" onto images that lack them (548 site-wide). Browsers derive
 * `aspect-ratio: auto 1024/670` from that pair, and with the height left auto
 * the image sizes from the ratio and stops matching its container. Duda's own
 * markup carries NO width/height on these images, so the attributes are ours,
 * not the original's — this is a migration artefact, not a ported defect.
 *
 * MEASURED ON LIVE before fixing, same widget on www.thesmilezone.org/blog:
 * the slide image computes width:100%, height:100%, object-fit:cover, and
 * carries no width, height or style attribute at all — slide 0 of widget
 * 1871148454 measured 179x180 against a container of exactly 179x180. This
 * rule reproduces those three computed values, so the result matches live
 * rather than merely looking better.
 *
 * Explicit width AND height are what neutralise the derived aspect-ratio; one
 * alone would not. object-fit:cover is restated because it is the whole point
 * of forcing the box — without it the image would stretch instead of crop.
 * object-position is deliberately NOT set here: it defaults to 50% 50%, and the
 * few slides live gives a different value get it inline from runtime.js.
 *
 * Attribute selector only, so it holds for every one of the 12 widgets without
 * depending on a styled-components hash. */
img[data-grab="slide-media"] {
  width: 100% !important;
  height: 100% !important;
  object-fit: cover;
}
