

/* mkr-template-hardening v9 */

/* <source> must never generate a box.
   source is NOT in the HTML spec's display:none UA list — it simply isn't
   painted because <picture> renders only its <img>. Templates set <picture> to
   display:contents, which dissolves the picture box and hoists its <source>
   children into the parent's formatting context; inside a grid each one then
   blockifies into a real grid item. With two sources per photo a 12-photo,
   4-column gallery became 36 items and photos landed one per row behind
   invisible cells. */
source{ display:none!important; }

/* A photo-stream whose items were emitted inside one wrapper div. The grid then
   holds a single item and every photo stacks in one column. display:contents
   dissolves the wrapper so the photos become the grid items they were meant to
   be. !important because one hydrator variant puts the wrapper grid in an
   inline style attribute. */
[data-mkr-section="photo-stream"] > div:only-child:not([class]){
  display:contents!important;
}

/* Injected MKR widgets are emitted outside the template's content container and
   with no max-width, so they run edge-to-edge while every other section sits in
   the content column. !important on the inline padding because the generated
   per-element #id rule always beats an attribute selector. */
[data-mkr-google-reviews]{
  width:100%;
  max-width:var(--maxw, 1240px);
  margin-inline:auto;
  box-sizing:border-box;
  padding-inline:clamp(1.1rem, 4vw, 2.4rem)!important;
}

/* The Google-reviews widget's LAYOUT, as a fallback.
   The widget is styled ENTIRELY by inline styles — it renders self-contained, so
   it can be concatenated into any template. That makes it the single block on the
   page with NO class-based CSS of its own, so once GrapesJS lifts those inline
   styles into per-element #i… rules, every pixel of it depends on those rules
   surviving. They did not: the footer patch swallowed them into
   @media (min-width:769px) (see splitSwallowedFooterPatch), and below 769px a
   live site served the widget as bare markup — no cards, no rows, run-together
   text, black stars and a 128px avatar.
   Keyed on the .grw-* classes and the widget's own structure, both of which
   survive the round-trip untouched. Deliberately NOT !important and
   class-specificity only, so the real inline / #id styles — which carry the
   school's own accent and theme — still win wherever they survive. */
[data-mkr-google-reviews] .grw-head{
  display:flex; flex-wrap:wrap; align-items:center; gap:14px; margin-bottom:18px;
}
[data-mkr-google-reviews] .grw-grid{
  display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:16px;
}
[data-mkr-google-reviews] .grw-list{ display:flex; flex-direction:column; gap:14px; }
[data-mkr-google-reviews] .grw-card{
  display:flex; flex-direction:column; gap:8px;
  padding:16px; border:1px solid #e7ebee; border-radius:14px; box-sizing:border-box;
}
[data-mkr-google-reviews][data-mkr-grw-theme="light"] .grw-card{ background:#fff; color:#0b2129; }
[data-mkr-google-reviews][data-mkr-grw-theme="dark"] .grw-card{
  background:#111827; color:#f8fafc; border-color:#374151;
}
/* Card header row: avatar + name/time column + the Google glyph. */
[data-mkr-google-reviews] .grw-card > div:first-child{ display:flex; align-items:center; gap:10px; }
[data-mkr-google-reviews] .grw-card > div:first-child > div{ min-width:0; flex:1; }
[data-mkr-google-reviews] .grw-card picture{ display:contents; }
[data-mkr-google-reviews] .grw-card > div:first-child img{
  width:40px; height:40px; border-radius:50%; object-fit:cover; flex:0 0 auto;
}
[data-mkr-google-reviews] .grw-card > div:last-child{ font-size:13.5px; line-height:1.55; }
/* Star rows. The filled run carries aria-hidden; the greyed-out remainder is the
   span nested inside it. */
[data-mkr-google-reviews] span[aria-hidden="true"]{ color:#FBBC04; }
[data-mkr-google-reviews] span[aria-hidden="true"] span{ color:#d6d9dc; }
/* A legibility floor, not a brand match: the button's real colour is the school's
   accent and lives in the inline style. Without a background here the white label
   would sit on nothing at all. */
[data-mkr-google-reviews] .grw-review-btn{
  margin-left:auto; padding:9px 16px; border-radius:10px;
  background:#008CCF; color:#fff; font-weight:700; text-decoration:none; white-space:nowrap;
}

/* Phone: the head runs out of room, the CTA wraps to a line of its own, and
   margin-left:auto then strands it against the right edge — measured on a live
   375px page as a 163px button beside 177px of nothing, under a left-aligned
   rating row. The house rule is a full-width body button on mobile.
   !important because the CTA's margin is an inline style — and, once the editor
   has lifted it, a per-element #id rule — either of which outranks an attribute
   selector. This is a media query, so it cannot live in the inline style the
   widget renders with; it has to be here. */
@media (max-width:600px){
  [data-mkr-google-reviews] .grw-head{ gap:10px; }
  [data-mkr-google-reviews] .grw-review-btn{
    margin-left:0!important;
    width:100%!important;
    box-sizing:border-box!important;
    text-align:center;
  }
}

/* Quote bands, sized to the quote.
   A template's quote band is display type — .qband blockquote is
   clamp(1.4rem, …, 2.2rem) over max-width:24ch — laid out for a dozen-odd
   words of marketing copy. Real reviews are routinely five times that, and a
   478-character one filled a 770px band with an 18-line wall of 22px bold text.
   The hydrator stamps data-mkr-quote-len (m = long, l = long enough to lead
   with a sentence and disclose the rest), so the type steps down and the measure
   widens only where it is needed; a short quote keeps the template's own look.
   Attribute selector, no !important: the header stylesheet loads AFTER the page
   stylesheet, so equal specificity resolves this way round, and a school that
   really styled its own blockquote by id still wins. */
blockquote[data-mkr-quote-len]{
  font-size:clamp(1.05rem, .82rem + .95vw, 1.5rem);
  line-height:1.45;
  max-width:46ch;
}
/* <details> requires <summary> FIRST in the markup, which puts the toggle
   between the lede and the rest — the quote reads as if interrupted once opened.
   Flex order:N moves it visually to the end without touching the DOM order the
   element needs. Closed, only the summary renders, so the order is moot. */
blockquote[data-mkr-quote-len] .mkr-quote-more{
  display:flex; flex-direction:column; align-items:center;
}
blockquote[data-mkr-quote-len] .mkr-quote-rest{ order:1; }
blockquote[data-mkr-quote-len] .mkr-quote-more > summary{
  order:2;
  display:inline-block; margin-top:.7rem; cursor:pointer;
  font-size:.74em; font-weight:700; letter-spacing:.03em; text-transform:uppercase;
  text-decoration:underline; text-underline-offset:3px; opacity:.82;
  list-style:none;
}
blockquote[data-mkr-quote-len] .mkr-quote-more > summary::-webkit-details-marker{ display:none; }
blockquote[data-mkr-quote-len] .mkr-quote-more > summary:hover{ opacity:1; }
/* Two real labels, CSS picks which shows — so the accessible name is correct in
   both states, which a content:"" pseudo-element cannot guarantee. */
blockquote[data-mkr-quote-len] .mkr-quote-less-label{ display:none; }
blockquote[data-mkr-quote-len] .mkr-quote-more[open] .mkr-quote-more-label{ display:none; }
blockquote[data-mkr-quote-len] .mkr-quote-more[open] .mkr-quote-less-label{ display:inline; }
/* The disclosed remainder reads as a continuation of the lede, so it keeps the
   quote's own type. */
blockquote[data-mkr-quote-len] .mkr-quote-rest{ display:block; margin-top:.4rem; }

/* The brand block's LAYOUT, as a fallback.
   The generated header markup (logo + school name + tagline) carries no classes
   — its layout came from inline styles, which GrapesJS converts to per-element
   id rules the first time the header is saved from the editor. Those id rules
   live only in the Header-css payload, and upsertHeaderFooter publishes the
   header with an EMPTY Header-css. So any site whose header HTML is the
   editor-saved (id-keyed, style-less) form while its stylesheet is regenerated
   gets a header with NO brand CSS: picture stops being display:contents, the logo
   renders at its intrinsic size (a 500px PNG became a 149px-tall logo and a
   150px-tall header bar), the name/tagline wrapper stops being a flex column so
   the tagline runs inline after the name, and the school name wraps over four
   lines. Keyed on the data-mkr-* contract so it holds for every template.
   Deliberately NOT !important and attribute-selector specificity only, so a real
   per-element id rule — i.e. styling the school actually saved — still wins. */
[data-mkr-logo]{ display:inline-flex; align-items:center; gap:10px; }
[data-mkr-logo] picture{ display:contents; }

/* Size the logo by its SHAPE, not by a fixed height.
   A flat height:36px assumes every school uploads a wide horizontal lockup.
   Plenty upload a STACKED mark — name over sub-name over artwork. One real logo
   is 374x210 (aspect 1.78); at height:36px it rendered 64x36 and the words in it
   were about six pixels tall, i.e. unreadable, while the same file was perfectly
   legible in the page body. The template made it worse: .brand img{width:34px;
   height:34px} slots a square badge, so without a rule here the logo is also
   squashed out of aspect.

   A max-height + max-width pair is aspect-aware without anyone having to measure
   the file. Whichever limit binds first wins, and the crossover is at aspect
   200/52 = 3.85:
     wide lockup   (aspect 5.0) -> width binds  -> 200x40
     stacked mark  (aspect 1.78)-> height binds -> 92x52   (was 64x36)
   The min(...,45vw) keeps a wide logo from crowding the burger on a 320px phone.
   Measured on a real header: 320px and 375px both clear the toggle by ~20px, the
   bar grows 69px -> 75px only when the logo actually needs the room, and neither
   width overflows.

   object-fit:contain so a template that DOES force both dimensions letterboxes
   the logo instead of stretching it.

   The selector repeats the attribute — [data-mkr-logo][data-mkr-logo] img — to
   raise specificity to (0,2,1), and it has to. The plain one-attribute version
   scored (0,1,1), the SAME as the template's .brand img{width:34px;height:34px},
   and on a tie the later rule wins: ours arrives in the header stylesheet linked
   from <head>, theirs in the <style data-mkr-template-style> the page carries in
   its <body>, which comes after. So this rule lost every time and the square
   badge won — a real 550x180 wordmark (aspect 3.06) shipped squashed to a third
   of its width, illegible in the header while fine everywhere else in the page.

   Repeating the attribute rather than reaching for !important keeps the original
   intent intact: (0,2,1) beats a template's class rule but still loses to a real
   per-element id rule, i.e. styling the school actually saved in the editor. */
[data-mkr-logo][data-mkr-logo] img{
  height:auto; width:auto;
  max-height:52px; max-width:min(200px, 45vw);
  object-fit:contain; display:block; flex:0 0 auto;
}
[data-mkr-logo] > span{
  display:inline-flex; flex-direction:column; justify-content:center; line-height:1.15;
}

/* The tagline is sized by an inline font-size:0.72em on the generated brand
   markup, so it shrinks with the school name and drops under 11px on tablets.
   The display:block here is part of the same fallback as the brand rules above. */
[data-mkr-field="school.tagline"]{ display:block; font-weight:400; opacity:.7; line-height:1.2; }
[data-mkr-field="school.tagline"]{ font-size:max(11.5px, .72em)!important; }

/* A phone number is never a wrap opportunity. Templates put the click-to-call
   link in the header row beside the school name; a long name squeezes the link
   and the number breaks at its hyphens — "507-" / "824-" / "6222" on three
   lines. Keyed on the tel: href (plain HTML semantics) so it holds for every
   template's phone link wherever it sits, and so it survives the header being
   republished from the editor — the class-keyed version of this rule lived only
   in the published stylesheet, never in the Header-styles payload the editor
   regenerates from, so every header submit dropped it and re-broke the number. */
a[href^="tel:"]{ white-space:nowrap; }

/* ...and let the brand yield the space instead. The generated brand block
   (logo + school name + tagline) is a flex item whose min-content width is set
   by the school name, so with the default min-width:auto it refuses to shrink
   and squeezes the phone/CTA block next to it. The name is already styled to
   wrap inside the brand; this lets it. */
[data-mkr-logo]{ min-width:0; }

/* ...and the header's call-to-action is the one thing in that row that must NOT
   yield. It is a control with a fixed label, not flowing text, but as a flex item
   it inherits flex-shrink:1 and white-space:normal — so when the row got tight
   flex squeezed it below its content width and "Book a Tour" wrapped onto two
   lines, growing the button to 74px inside a 69px bar and pushing it out of the
   header. Measured on a real launch: 97x74 wrapped, 137x50 correct.

   Pinning it (nowrap + no shrink) makes the BRAND absorb the squeeze instead,
   which is what the min-width:0 above is for: the school name re-wraps and the
   brand goes 413 -> 295 while the button stays 137x50 and the bar stays 69px, at
   every width down to the drawer breakpoint, with no page overflow.

   Scoped to the bar on purpose. The same data-mkr-cta anchors the in-page
   hero/section buttons, and those are wide ("See Our Programs" is 201px) and sit
   in containers where nowrap could push past a narrow phone.

   nav is in the selector because header alone matched NOTHING. 26 of the 27
   prebuilt templates wrap the bar in <nav class="site-header"> and never emit a
   <header> element at all, so the rule above sat in every published stylesheet
   guarding a bar that does not exist, and the wrap it was written to stop went on
   happening. Measured on sunny-days at the wizard's own 1440px preview width, with
   a 200px logo and a 33-character school name: 84x103 — "Book" / "a" / "Tour" on
   three lines inside a bar bloated from 68px to 103px. With nav added the brand
   absorbs the squeeze (466 -> 413) and the button is 137x52 in a 68px bar again.

   Widening to nav is precise, not a net cast wider: across all 27 templates
   exactly 27 of the 135 data-mkr-cta anchors sit inside a nav/header, and every
   one of them is that bar's book-tour button. The other 108 are the in-page CTAs
   this rule must keep its hands off, and none of them is inside a nav. */
header [data-mkr-cta], nav [data-mkr-cta]{ white-space:nowrap; flex:0 0 auto; }

/* Nothing may make the page wider than the device it is being read on.
   This is the containment half of the mobile-overflow guard — repairStylesheet
   removes the rule that caused it, this stops the next one from being felt.

   One section with margin-right:-1052.09375px under @media (max-width:576px)
   measured 1427px on a 375px phone. Chrome's answer to content it cannot fit is
   to grow the LAYOUT viewport to hold it and scale the result down: innerWidth
   went 375 -> 1427 and innerHeight 812 -> 3090, so the whole homepage rendered
   zoomed out ~3.8x. One bad section, every section unreadable.

   Templates set this on body, which is not enough — the overflow propagates
   from html, and body alone still let the layout viewport grow. With the rule
   here the same broken page measured 375 x 812 again.

   clip, never hidden: hidden makes the root a scroll container, which breaks
   position:sticky headers — most templates have one. clip cuts the overflow
   without creating a scroll container, so sticky still works, and children with
   their own overflow-x:auto (wide tables, carousels) keep scrolling normally. */
html{ overflow-x:clip; max-width:100%; }
body{ overflow-x:clip; max-width:100%; }

/* Scroll-reveal must not be able to hide content permanently. Reveal-gated
   blocks start at opacity:0 and are shown by template JS loaded from a CDN; if
   that request is blocked or fails, the page body never appears. Reveal them
   regardless after 4s — in the normal case the observer has long since run. */
[data-rv]{ animation: mkr-reveal-failsafe .01s linear 4s forwards; }
@keyframes mkr-reveal-failsafe{ to{ opacity:1; transform:none; } }
.no-js [data-rv]{ opacity:1; transform:none; }
@media (prefers-reduced-motion:reduce){
  [data-rv]{ animation:none; opacity:1; transform:none; }
}
