/*
 * Tenant template utility classes — Wave 6 of the CSP `style-src 'unsafe-inline'`
 * migration (BUG-0012 / BUG-0509). Used by all 91 tenant home.html templates.
 *
 * These templates share a common code path (all generated from a base shell)
 * so the inline-style patterns repeat ~90× each across the fleet. Migrating
 * one pattern drops ~90 attrs fleet-wide.
 *
 * Uses `var(--brand-*)` custom properties so a single class ruleset works
 * across every tenant's unique brand palette — the variables are defined
 * per-template in the shell's <style> :root block.
 */

/* Issue #12036: pull the shared forced-colors / prefers-contrast a11y partial
 * into all 103 tenant templates via the file they already <link>, so Windows
 * High Contrast users get usable buttons/badges/focus-rings WITHOUT editing
 * 102 template files and WITHOUT adding a new <link> tag (the critical-css
 * fleet gate locks each template's stylesheet shape to the deferrable
 * allowlist). @import must lead the file — it is only honored before any
 * other rule. Baseline a11y: applied unconditionally, no gate. */
@import url("/css/a11y-forced-colors.css");

.pm-template-capabilities {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
  max-width: 1120px;
  margin: 0 auto;
  padding: 24px clamp(18px, 4vw, 32px);
}
.pm-template-capabilities__item {
  border: 1px solid rgba(17, 24, 39, 0.12);
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.82);
  box-shadow: 0 10px 28px rgba(15, 23, 42, 0.08);
  padding: 16px;
}
.pm-template-capabilities__item strong {
  display: block;
  color: var(--brand-text, #111827);
  font-size: 0.96rem;
  margin-bottom: 6px;
}
.pm-template-capabilities__item span {
  color: var(--brand-muted, #4b5563);
  display: block;
  font-size: 0.9rem;
  line-height: 1.5;
}
@media (max-width: 760px) {
  .pm-template-capabilities {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 767px) {
  .floating-orb,
  .hero-circle {
    display: none !important;
  }

  a[href^="tel:"],
  #contact-phone,
  #footer-phone,
  .footer-contact-link,
  .footer-contact-item,
  .contact-item a[href^="tel:"],
  .contact-item span[id*="phone"] {
    max-width: 100%;
    min-width: 0 !important;
    overflow-wrap: anywhere !important;
    word-break: break-all !important;
    line-break: anywhere !important;
    white-space: normal !important;
  }

  a[href^="tel:"],
  .footer-contact-link[href],
  #contact-phone[href],
  #footer-phone[href] {
    display: block !important;
    min-width: 44px !important;
    min-height: 44px;
    line-height: 1.3;
  }
}

/*
 * Issue #10785 — fleet-wide horizontal-overflow guard at the document root.
 *
 * Several home.html templates set `body { overflow-x: hidden }` but the
 * `<html>` element's measured `scrollWidth` was still exceeding the iPhone-SE
 * 375px viewport by 62-77px because absolutely-positioned hero decorations
 * (`.floating-orb` at right: -60px / left: -50px, `.exp-circle`, etc.)
 * propagate to the document root. Per HTML5 overflow-propagation rules,
 * `body { overflow-x }` only affects the viewport's scrollbar behavior, not
 * `documentElement.scrollWidth`. Setting it on `<html>` clips at the root.
 *
 * `clip` (rather than `hidden`) preserves `position: sticky` semantics on
 * descendants by NOT establishing a new scroll-context.
 */
html { overflow-x: clip; }

/*
 * Fleet contact/location layout guard.
 *
 * Older hand-authored tenant templates use `.contact-detail` rows and
 * `.contact-card` rows with anonymous content wrappers. Their inline CSS sets
 * the row display but does not give the content side `min-width: 0`, so address
 * and email values can collapse to intrinsic text width on mobile/tablet. This
 * shared utility file is linked by every tenant home template; the `!important`
 * flags intentionally beat each template's inline CSS without changing the
 * visual theme.
 */
.contact-detail,
.contact-card,
.contact-chip,
.contact-pill,
.contact-line,
.contact-zen-item,
.contact-nail-detail,
.footer-contact-row,
.footer-brand-contact,
.location-layout,
.location-inner {
  min-width: 0 !important;
  max-width: 100% !important;
}

.contact-detail > div:not([class*="icon"]),
.contact-card > div:not([class*="icon"]),
.contact-line > div:not([class*="icon"]),
.contact-zen-item > div:not([class*="icon"]),
.contact-nail-detail > div:not([class*="icon"]),
.loc-item > div:not([class*="icon"]) {
  flex: 1 1 auto !important;
  min-width: 0 !important;
  max-width: 100% !important;
}

.contact-detail-value,
.contact-detail-value a,
.contact-detail-value span,
.contact-chip,
.contact-pill,
.contact-line,
.contact-line a,
.contact-line span,
.contact-zen-text,
.contact-zen-text a,
.contact-zen-text span,
.contact-nail-text,
.contact-nail-text a,
.contact-nail-text span,
.footer-contact-row,
.footer-contact-row a,
.footer-contact-row span,
.footer-brand-contact,
.footer-brand-contact a,
.hours-contact,
.cc-value,
.cc-value a,
.cc-value span,
#contact-address,
#contact-email,
#footer-address,
#footer-email {
  display: block !important;
  width: 100% !important;
  min-width: 0 !important;
  max-width: 100% !important;
  overflow-wrap: anywhere !important;
  word-break: normal !important;
  white-space: normal !important;
}

/* Issue #12556 / 2026-05-25: tenant-template contrast override and
 * forced-colors support. This mode must improve legibility without wiping out
 * tenant identity. Keep brand color tokens intact and layer in stronger text,
 * borders, surfaces, and focus treatments instead of forcing the whole fleet
 * into black-on-white. */
html[data-pm-contrast="aaa"] {
  --brand-text: #120a1f !important;
  --brand-muted: #2f2440 !important;
  --brand-text-on-primary: #ffffff !important;
  --brand-text-on-bg: #120a1f !important;
  --pm-contrast-surface: rgba(255, 255, 255, 0.94);
  --pm-contrast-surface-strong: #ffffff;
  --pm-contrast-border: rgba(18, 10, 31, 0.42);
  --pm-contrast-ring: var(--brand-accent, #7cf5d2);
  --pm-contrast-shadow: 0 18px 48px rgba(18, 10, 31, 0.18);
}

html[data-pm-contrast="aaa"] body,
html[data-pm-contrast="aaa"] main {
  color: var(--brand-text) !important;
}

html[data-pm-contrast="aaa"] .card,
html[data-pm-contrast="aaa"] .panel,
html[data-pm-contrast="aaa"] .service-card,
html[data-pm-contrast="aaa"] .team-card,
html[data-pm-contrast="aaa"] .testimonial-card,
html[data-pm-contrast="aaa"] .contact-card,
html[data-pm-contrast="aaa"] .gallery-card,
html[data-pm-contrast="aaa"] .stat-card,
html[data-pm-contrast="aaa"] .feature-card,
html[data-pm-contrast="aaa"] .package-card,
html[data-pm-contrast="aaa"] .booking-card,
html[data-pm-contrast="aaa"] .info-card {
  background: var(--pm-contrast-surface) !important;
  color: var(--brand-text) !important;
  border: 1px solid var(--pm-contrast-border) !important;
  box-shadow: var(--pm-contrast-shadow) !important;
}

html[data-pm-contrast="aaa"] a,
html[data-pm-contrast="aaa"] p,
html[data-pm-contrast="aaa"] li,
html[data-pm-contrast="aaa"] span,
html[data-pm-contrast="aaa"] label,
html[data-pm-contrast="aaa"] h1,
html[data-pm-contrast="aaa"] h2,
html[data-pm-contrast="aaa"] h3,
html[data-pm-contrast="aaa"] h4,
html[data-pm-contrast="aaa"] h5,
html[data-pm-contrast="aaa"] h6 {
  color: inherit;
}

html[data-pm-contrast="aaa"] .section-desc,
html[data-pm-contrast="aaa"] .service-desc,
html[data-pm-contrast="aaa"] .service-duration,
html[data-pm-contrast="aaa"] .team-role,
html[data-pm-contrast="aaa"] .testimonial-text,
html[data-pm-contrast="aaa"] .contact-detail,
html[data-pm-contrast="aaa"] .footer-text,
html[data-pm-contrast="aaa"] small {
  color: var(--brand-muted) !important;
}

html[data-pm-contrast="aaa"] button,
html[data-pm-contrast="aaa"] .btn,
html[data-pm-contrast="aaa"] input,
html[data-pm-contrast="aaa"] select,
html[data-pm-contrast="aaa"] textarea {
  border: 2px solid currentColor !important;
  box-shadow: none;
}

html[data-pm-contrast="aaa"] button,
html[data-pm-contrast="aaa"] .btn,
html[data-pm-contrast="aaa"] .btn-primary,
html[data-pm-contrast="aaa"] .nav-book {
  background: var(--brand-primary-dark, var(--brand-primary, #120a1f)) !important;
  color: var(--brand-text-on-primary, #ffffff) !important;
  border-color: var(--brand-text-on-primary, #ffffff) !important;
}

html[data-pm-contrast="aaa"] .btn-secondary {
  background: color-mix(in srgb, var(--brand-primary-dark, #120a1f) 18%, transparent) !important;
  color: currentColor !important;
}

html[data-pm-contrast="aaa"] nav:not(.scrolled),
html[data-pm-contrast="aaa"] .site-nav:not(.scrolled),
html[data-pm-contrast="aaa"] .main-nav:not(.scrolled) {
  background: rgba(18, 24, 36, 0.82) !important;
  color: var(--brand-text-on-primary, #ffffff) !important;
}

html[data-pm-contrast="aaa"] nav:not(.scrolled) .nav-logo,
html[data-pm-contrast="aaa"] nav:not(.scrolled) .nav-links a:not(.nav-book),
html[data-pm-contrast="aaa"] .site-nav:not(.scrolled) .nav-logo,
html[data-pm-contrast="aaa"] .site-nav:not(.scrolled) .nav-links a:not(.nav-book),
html[data-pm-contrast="aaa"] .main-nav:not(.scrolled) .nav-logo,
html[data-pm-contrast="aaa"] .main-nav:not(.scrolled) .nav-links a:not(.nav-book) {
  color: var(--brand-text-on-primary, #ffffff) !important;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.45);
}

html[data-pm-contrast="aaa"] nav.scrolled,
html[data-pm-contrast="aaa"] .site-nav.scrolled,
html[data-pm-contrast="aaa"] .main-nav.scrolled {
  background: rgba(255, 255, 255, 0.96) !important;
  color: var(--brand-text) !important;
  border-bottom: 1px solid var(--pm-contrast-border) !important;
}

html[data-pm-contrast="aaa"] nav.scrolled .nav-logo,
html[data-pm-contrast="aaa"] nav.scrolled .nav-links a:not(.nav-book),
html[data-pm-contrast="aaa"] .site-nav.scrolled .nav-logo,
html[data-pm-contrast="aaa"] .site-nav.scrolled .nav-links a:not(.nav-book),
html[data-pm-contrast="aaa"] .main-nav.scrolled .nav-logo,
html[data-pm-contrast="aaa"] .main-nav.scrolled .nav-links a:not(.nav-book) {
  color: var(--brand-text) !important;
  text-shadow: none;
}

html[data-pm-contrast="aaa"] #hero-bg .btn-outline,
html[data-pm-contrast="aaa"] #experience .btn-outline,
html[data-pm-contrast="aaa"] footer .btn-outline {
  background: rgba(255, 255, 255, 0.14) !important;
  color: var(--brand-text-on-primary, #ffffff) !important;
  border-color: rgba(255, 255, 255, 0.9) !important;
}

html[data-pm-contrast="aaa"] a:focus-visible,
html[data-pm-contrast="aaa"] button:focus-visible,
html[data-pm-contrast="aaa"] .btn:focus-visible,
html[data-pm-contrast="aaa"] [role="button"]:focus-visible,
html[data-pm-contrast="aaa"] input:focus-visible,
html[data-pm-contrast="aaa"] select:focus-visible,
html[data-pm-contrast="aaa"] textarea:focus-visible,
html[data-pm-contrast="aaa"] [tabindex]:focus-visible {
  outline: 3px solid var(--pm-contrast-ring) !important;
  outline-offset: 3px !important;
  box-shadow: 0 0 0 6px color-mix(in srgb, var(--pm-contrast-ring) 28%, transparent) !important;
}

html[data-pm-contrast="aaa"] .pm-high-contrast-toggle {
  background: var(--pm-contrast-surface-strong) !important;
  color: var(--brand-text) !important;
  border: 2px solid var(--brand-primary-dark, #120a1f) !important;
}

body:has(#pm-cookie-banner.pm-cb--visible) .pm-high-contrast-toggle {
  bottom: calc(104px + env(safe-area-inset-bottom, 0px)) !important;
}

@media (max-width: 560px) {
  body:has(#pm-cookie-banner.pm-cb--visible) .pm-high-contrast-toggle {
    bottom: calc(172px + env(safe-area-inset-bottom, 0px)) !important;
  }
}

@media (forced-colors: active) {
  :is(body, main, section, article, nav, header, footer, .card, .panel, .modal, .drawer, .chip, .badge, .tab, .hero, .cta) {
    forced-color-adjust: auto;
    background: Canvas !important;
    color: CanvasText !important;
    border-color: CanvasText !important;
  }

  :is(button, .btn, input, select, textarea, [role="button"], [role="tab"], [role="menuitem"]) {
    forced-color-adjust: auto;
    background: Canvas !important;
    color: ButtonText !important;
    border: 2px solid ButtonText !important;
    box-shadow: none !important;
  }

  :is(a, .link, .brand-link) {
    color: LinkText !important;
    text-decoration-color: LinkText !important;
  }
}

.u-t-gold-inherit        { color: #7cf5d2; font-weight: inherit; }
.u-t-accent              { color: var(--brand-accent); }
.u-t-btn-plain           { background: none; border: 0; }
.u-t-center-mt-3         { text-align: center; margin-top: 3rem; }
.u-t-mt-2                { margin-top: 2rem; }
.u-t-rel-top-2           { position: relative; top: 2rem; }
.u-t-strong-text         { color: var(--brand-text); font-weight: 600; }
.u-t-footer-link         { color: rgba(255, 255, 255, 0.88); text-decoration: none; }
.u-t-footer-link:focus-visible { outline: 2px solid var(--brand-accent, #7cf5d2); outline-offset: 3px; border-radius: 6px; color: rgba(255, 255, 255, 0.9); }
.u-t-card-panel          { background: #fff; padding: 1.5rem 0; }
.u-t-bg-white            { background: #fff; }
.u-t-touch-target        { display: inline-flex; align-items: center; justify-content: center; min-height: 44px; padding: 0.65rem 0.9rem; line-height: 1.2; }

.pm-sr-only {
  position: absolute !important;
  width: 1px !important;
  height: 1px !important;
  padding: 0 !important;
  margin: -1px !important;
  overflow: hidden !important;
  clip: rect(0, 0, 0, 0) !important;
  white-space: nowrap !important;
  border: 0 !important;
}

.pm-search-wrap {
  position: relative;
  display: inline-flex;
  align-items: center;
  flex: 0 1 220px;
  margin-left: clamp(0.75rem, 2vw, 1.25rem);
  margin-right: auto;
  font: 600 0.86rem/1.4 var(--pm-font-ui, system-ui, -apple-system, sans-serif);
  z-index: 102;
}

.pm-search-input {
  width: min(220px, 24vw);
  min-width: 15ch;
  max-width: 240px;
  min-height: 44px;
  appearance: none;
  border: 1px solid rgba(255, 255, 255, 0.44);
  background: rgba(255, 255, 255, 0.14);
  color: #fff;
  padding: 0.55rem 0.85rem;
  border-radius: 999px;
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.16);
  backdrop-filter: blur(14px);
}

.pm-search-input::placeholder {
  color: rgba(255, 255, 255, 0.78);
}

nav.scrolled .pm-search-input,
.site-nav.scrolled .pm-search-input,
.main-nav.scrolled .pm-search-input {
  background: rgba(255, 255, 255, 0.94);
  color: var(--brand-text, #120a1f);
  border-color: rgba(18, 10, 31, 0.18);
}

nav.scrolled .pm-search-input::placeholder,
.site-nav.scrolled .pm-search-input::placeholder,
.main-nav.scrolled .pm-search-input::placeholder {
  color: color-mix(in srgb, var(--brand-text, #120a1f) 62%, transparent);
}

.pm-search-input:focus-visible {
  outline: 3px solid var(--brand-accent, #7cf5d2);
  outline-offset: 2px;
}

.pm-search-results {
  position: absolute;
  top: calc(100% + 0.55rem);
  right: 0;
  left: auto;
  z-index: 9999;
  width: min(420px, 92vw);
  max-height: 60vh;
  overflow: auto;
  display: none;
  background: #fff;
  color: #111;
  border: 1px solid rgba(0, 0, 0, 0.12);
  border-radius: 14px;
  box-shadow: 0 18px 42px rgba(0, 0, 0, 0.2);
}

.pm-search-results[data-open="1"] { display: block; }
.pm-search-results ul { list-style: none; margin: 0; padding: 0.25rem 0; }
.pm-search-results li { padding: 0.6rem 0.85rem; display: block; border-bottom: 1px solid rgba(0, 0, 0, 0.06); }
.pm-search-results li:last-child { border-bottom: 0; }
.pm-search-results a { display: block; color: inherit; text-decoration: none; }
.pm-search-results a:hover,
.pm-search-results a:focus-visible { background: rgba(0, 0, 0, 0.04); outline: none; }
.pm-search-name { font-weight: 700; margin: 0 0 0.15rem; }
.pm-search-snippet { font-size: 0.88em; color: #555; margin: 0; }
.pm-search-snippet mark { background: transparent; font-weight: 800; color: inherit; }
.pm-search-kind { display: inline-block; font-size: 0.72em; text-transform: uppercase; letter-spacing: 0.05em; color: #777; margin-right: 0.5rem; }
.pm-search-empty { padding: 0.75rem 0.85rem; color: #555; font-size: 0.92em; }
.pm-search-empty strong { color: #111; }
.pm-search-suggestions { margin: 0.5rem 0 0; padding: 0; list-style: none; display: flex; flex-wrap: wrap; gap: 0.35rem; }
.pm-search-suggestions li { padding: 0; border: none; }
.pm-search-suggestions a { display: inline-block; padding: 0.2rem 0.55rem; background: rgba(0, 0, 0, 0.06); border-radius: 999px; font-size: 0.85em; }

@media (max-width: 1180px) {
  .pm-search-wrap {
    display: none;
  }
}

/*
 * #11063 / #11064 / #11065 / #11066 — fleet-wide 44x44 tap-target floor
 * for the shared interactive elements injected into every tenant home:
 *
 *   * `a#contact-phone`, `a#footer-phone`               (#11063 — 20 pages)
 *   * `a#contact-email`, `a#footer-email`,              (#11064 — 19 pages)
 *     `.footer-contact-link` (eyebrows-lashes footer-address)
 *   * `.footer-col ul li > a` + `.section-anchor`       (#11065 — 19 pages
 *     in-page nav chips rendered at 40px tall)
 *   * `.pm-map-directions-link`                         (#11066 — 14 pages —
 *     CSP-blocked JS-injected `<style>` regression; rule MUST live in a
 *     real `<link rel="stylesheet">` file so the `style-src 'self' 'nonce-…'`
 *     header does not strip it)
 *
 * Loaded by every tenant home via the `<link rel="stylesheet"
 * href="/css/tenant-template-utilities.css">` declared in the template
 * shell, so a single edit covers all 102 templates. WCAG 2.5.5 floor is
 * 44x44; we use min-height + flex padding instead of fixed `height` so
 * the visual rhythm of the existing typography is preserved.
 */
a#contact-phone,
a#contact-email,
a#footer-phone,
a#footer-email,
.footer-contact-link {
  display: inline-flex;
  align-items: center;
  /* #11973 — both axes of the WCAG 2.5.5 floor. min-height was already set,
     but the audit measured these anchors at 13-32px WIDE × 98-277px tall on
     60 demo tenants: at the ≤480px narrow-viewport block these anchors
     inherit `.contact-item a { overflow-wrap: anywhere; word-break:
     break-word }`, which breaks the phone/email string CHARACTER-by-character
     inside its `min-width:0` flex column, collapsing the inline-flex item to
     a tall vertical strip well under the 44px tap floor. min-width pins the
     horizontal axis so a wrapped string still presents a >=44px tap target;
     the ≤480px override below restores normal wrapping so short phone/email
     strings stay on one line in the first place. */
  min-width: 44px;
  min-height: 44px;
  padding: 4px 0;
  box-sizing: border-box;
}

.footer-col ul li > a,
.section-anchor {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 4px 0;
  box-sizing: border-box;
}

/*
 * `.pm-map-directions-link` — moved here from `public/js/shared/map-utils.js`
 * 2026-05-12 for the #11066 / #10933 CSP-fix-then-regression cycle. The JS
 * file used to call `document.createElement('style')` and inject the rule
 * at runtime; under the page CSP (`style-src 'self' 'nonce-…'`) that
 * unattributed `<style>` element was discarded, leaving the link at the
 * browser-default ~21px height. Keeping `display: inline-block` preserves
 * the #10579 CSP regression-test substring contract (the test currently
 * asserts a literal CSS string presence on map-utils.js; replaced 2026-05-12
 * with a real CSS file assertion).
 */
.pm-map-directions-link {
  display: inline-block;
  margin-top: 1rem;
  padding: 10px 14px;
  min-width: 44px;
  min-height: 44px;
  box-sizing: border-box;
  color: var(--brand-primary, #5b7f6e);
  font-weight: 600;
  text-decoration: none;
  font-size: 0.9rem;
  line-height: 1.4;
}
.pm-map-directions-link:hover,
.pm-map-directions-link:focus {
  text-decoration: underline;
}

.map-placeholder.pm-map-has-embed,
.map-box.pm-map-has-embed,
.map-panel.pm-map-has-embed,
.map-card.pm-map-has-embed,
.map-location-card.pm-map-has-embed {
  display: block !important;
  padding: 0 !important;
  min-height: 280px;
  overflow: hidden;
  background: var(--brand-bg, #f8fafc) !important;
  color: var(--text-primary, #17242f) !important;
}

.map-placeholder.pm-map-has-embed::after,
.map-box.pm-map-has-embed::after,
.map-panel.pm-map-has-embed::after,
.map-card.pm-map-has-embed::after,
.map-location-card.pm-map-has-embed::after {
  content: none !important;
}

.map-placeholder.pm-map-has-embed > :not(.pm-map-embed):not(.pm-map-directions-link),
.map-box.pm-map-has-embed > :not(.pm-map-embed):not(.pm-map-directions-link),
.map-panel.pm-map-has-embed > :not(.pm-map-embed):not(.pm-map-directions-link),
.map-card.pm-map-has-embed > :not(.pm-map-embed):not(.pm-map-directions-link),
.map-location-card.pm-map-has-embed > :not(.pm-map-embed):not(.pm-map-directions-link) {
  display: none !important;
}

.pm-map-embed {
  display: block;
  position: relative;
  width: 100%;
  min-height: 280px;
  border: 0;
  border-radius: inherit;
  overflow: hidden;
  background:
    linear-gradient(135deg, rgba(91, 127, 110, 0.18), rgba(247, 243, 233, 0.96)),
    var(--brand-bg, #f8fafc);
}

.pm-map-road,
.pm-map-park,
.pm-map-pin,
.pm-map-label {
  position: absolute;
}

.pm-map-road {
  background: rgba(255, 255, 255, 0.9);
  box-shadow: 0 0 0 1px rgba(23, 36, 47, 0.08);
}

.pm-map-road-a { left: -10%; top: 23%; width: 120%; height: 18px; transform: rotate(-8deg); }
.pm-map-road-b { left: -8%; top: 60%; width: 120%; height: 16px; transform: rotate(9deg); }
.pm-map-road-c { left: 26%; top: -12%; width: 18px; height: 130%; transform: rotate(13deg); }
.pm-map-road-d { left: 68%; top: -12%; width: 16px; height: 130%; transform: rotate(-15deg); }

.pm-map-park {
  border-radius: 28px;
  background: rgba(91, 127, 110, 0.18);
  box-shadow: inset 0 0 0 1px rgba(91, 127, 110, 0.15);
}

.pm-map-park-a { left: 8%; top: 8%; width: 28%; height: 28%; }
.pm-map-park-b { right: 7%; bottom: 10%; width: 30%; height: 26%; }

.pm-map-pin {
  left: 50%;
  top: 47%;
  width: 34px;
  height: 34px;
  margin: -17px 0 0 -17px;
  border-radius: 50% 50% 50% 0;
  background: #d93672;
  transform: rotate(-45deg);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.24);
}

.pm-map-pin::after {
  content: "";
  position: absolute;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: #fff;
  left: 11px;
  top: 11px;
}

.pm-map-label {
  left: 18px;
  right: 18px;
  bottom: 18px;
  padding: 14px 16px;
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.92);
  box-shadow: 0 12px 30px rgba(23, 36, 47, 0.16);
  color: var(--text-primary, #17242f);
  font-weight: 700;
  line-height: 1.35;
}

.pm-map-label small {
  display: block;
  margin-top: 4px;
  font-weight: 600;
  color: var(--text-muted, #5b6b76);
}

.pm-map-has-embed .pm-map-directions-link {
  margin: 0.75rem;
}

/*
 * #11067 — `.nav-logo` floor for the 4 templates whose nav-logo rule
 * omitted vertical sizing (barbershop=shop, nail-salon, blowout-bar,
 * massage). Other templates already enforce 44px+ via per-template
 * rules. This shared declaration fills the gap without forcing every
 * template to duplicate the snippet. Falling back to inline-flex centers
 * the logo text vertically when the parent nav is taller than the text.
 */
a.nav-logo,
a#nav-logo {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  box-sizing: border-box;
}

/* Keyboard navigation affordance across all tenant-facing surfaces. */
a:focus-visible,
button:focus-visible,
[role="button"]:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: 3px solid var(--brand-accent, #7cf5d2);
  outline-offset: 2px;
}

/* Prevent overflow from tenant-provided/seeded content images on narrow viewports. */
img {
  max-width: 100%;
  height: auto;
}

/*
 * Launch-blocker #6585: reserve sticky-nav + safe-area space so sticky nav
 * titles never sit on top of hero eyebrow copy on the affected tenant home
 * templates (mobile + desktop).
 */
:root {
  --pm-sticky-nav-offset: calc(env(safe-area-inset-top, 0px) + 5.75rem);
}

#mainNav {
  background: rgba(18, 24, 36, 0.72);
  backdrop-filter: blur(8px);
}
#hero-bg {
  padding-top: calc(env(safe-area-inset-top, 0px) + 4.5rem);
  padding-top: var(--pm-sticky-nav-offset);
}

@media (max-width: 768px) {
  :root {
    --pm-sticky-nav-offset: calc(env(safe-area-inset-top, 0px) + 6.5rem);
  }
}

@media (min-width: 1280px) {
  :root {
    --pm-sticky-nav-offset: calc(env(safe-area-inset-top, 0px) + 6rem);
  }
}

/* Some templates add their own hero-inner top padding, but the actual first
   painted hero copy is the eyebrow chip/text. Give that element a shared
   minimum top offset so sticky-nav title + transparent nav never collide with
   the hero copy on small screens or short desktop viewports. */
#hero-bg .hero-eyebrow {
  position: relative;
  z-index: 2;
  margin-top: max(0.75rem, env(safe-area-inset-top, 0px));
}

#hero-bg .hero-text,
#hero-bg .hero-copy,
#hero-bg .hero-content {
  padding-top: clamp(0.75rem, 1vw, 1.25rem);
  scroll-margin-top: calc(var(--pm-sticky-nav-offset) + 1rem);
}

@media (max-width: 768px) {
  #hero-bg .hero-eyebrow {
    margin-top: max(1rem, env(safe-area-inset-top, 0px));
  }

  #hero-bg .hero-text,
  #hero-bg .hero-copy,
  #hero-bg .hero-content {
    padding-top: 1.25rem;
  }
}

/* Launch-blocker #6585 uses the same shared offset for hand-authored tenant
   templates that rely on `.hero-inner` / `.hero-label` instead of `#hero-bg`
   so both generated and hand-authored hero variants clear the sticky nav on
   desktop + mobile. */
.hero .hero-eyebrow,
.hero-eyebrow,
.hero .hero-label,
.hero-label {
  scroll-margin-top: var(--pm-sticky-nav-offset);
}

#hero-bg .hero-text,
#hero-bg .hero-copy,
#hero-bg .hero-content,
#hero-bg .hero-tagline,
#hero-bg .hero-eyebrow,
.hero .hero-tagline,
.hero-tagline,
.hero .hero-label,
.hero-label {
  scroll-margin-top: calc(var(--pm-sticky-nav-offset) + 1rem);
}

.hero .hero-inner,
.hero-inner {
  padding-top: max(4.5rem, var(--pm-sticky-nav-offset));
}

.hero .hero-inner,
.hero-inner {
  padding-top: max(
    var(--pm-sticky-nav-offset),
    calc(var(--pm-sticky-nav-offset) + 2rem)
  );
}

.hero .hero-eyebrow,
.hero-eyebrow,
.hero .hero-label,
.hero-label {
  position: relative;
  z-index: 2;
  margin-top: max(0.75rem, env(safe-area-inset-top, 0px));
}

@media (max-width: 768px) {
  .hero .hero-inner,
  .hero-inner {
    padding-top: max(
      calc(var(--pm-sticky-nav-offset) + 1.5rem),
      5.25rem
    );
  }

  .hero .hero-eyebrow,
  .hero-eyebrow,
  .hero .hero-label,
  .hero-label {
    margin-top: max(1rem, env(safe-area-inset-top, 0px));
  }
}

#hero-bg .hero-content {
  padding-top: calc(var(--pm-sticky-nav-offset) + 1.75rem);
}

@media (max-width: 768px) {
  #hero-bg {
    padding-top: var(--pm-sticky-nav-offset);
  }

  #hero-bg .hero-content { padding-top: calc(var(--pm-sticky-nav-offset) + 1.5rem); }
}

/* iOS WebKit browsers (Safari, Edge, Chrome) resize 100vh as the browser
   toolbar expands/collapses during scroll. Use the stable small viewport unit
   on touch layouts so tenant hero imagery cannot grow while scrolling back up. */
@supports (min-height: 100svh) {
  @media (max-width: 768px) {
    section.hero,
    .hero,
    header.hero,
    #hero-bg,
    #hero,
    #hero_bg_image,
    .hero-section,
    .hero-wrapper,
    .pm-hero-carousel {
      min-height: calc(100svh - var(--pm-banner-offset, 0px)) !important;
      height: calc(100svh - var(--pm-banner-offset, 0px)) !important;
      max-height: calc(100svh - var(--pm-banner-offset, 0px)) !important;
      overflow: hidden !important;
    }

    #hero-bg .hero-content,
    #hero-bg .hero-text,
    #hero-bg .hero-copy,
    .hero .hero-content,
    .hero .hero-inner,
    .pm-hero-carousel .pm-hero-slide-overlay {
      max-height: calc(100svh - var(--pm-sticky-nav-offset, 6.5rem) - var(--pm-banner-offset, 0px) - 1.25rem) !important;
      padding-top: clamp(1.25rem, 6svh, 3.25rem) !important;
      padding-bottom: clamp(1rem, 4svh, 2rem) !important;
      overflow: hidden !important;
    }

    #hero-bg .hero-scroll-hint,
    .hero .hero-scroll-hint,
    .pm-hero-carousel .hero-scroll-hint {
      display: none !important;
    }
  }
}

/* Ensure staff-name links in tenant home team cards meet mobile tap target size. */
.team-name > a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  line-height: 1.2;
  padding: 0.5rem 0;
}
.team-name > a:focus-visible {
  outline: 2px solid var(--brand-accent, #7cf5d2);
  outline-offset: 3px;
  border-radius: 6px;
}

/*
 * Real-content service-card visual (real-content-only standard, 2026-05-01).
 * Replaces emoji-icon containers (`.service-icon`) on every tenant template
 * fleet-wide. When a service has `image_url` populated, real photo renders.
 * When empty, a subtle CSS gradient swatch (no emoji, no fake stock photo)
 * fills the same slot — maintains card visual rhythm without violating
 * real-content rule.
 */
.service-photo {
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: 14px;
  overflow: hidden;
  margin-bottom: 1.25rem;
  background: var(--brand-bg, #f4f4f4);
}
.service-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.service-photo-fallback {
  position: relative;
}

/*
 * Real-content staff-card visual. Same principle: real headshot when
 * present, branded gradient + member's actual initials when not.
 */
.staff-photo {
  width: 144px;
  height: 144px;
  border-radius: 50%;
  overflow: hidden;
  margin: 0 auto 1rem;
  background: var(--brand-bg, #f4f4f4);
}
.staff-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.staff-photo-fallback {
  display: flex;
  align-items: center;
  justify-content: center;
}
.staff-initials {
  color: rgba(255, 255, 255, 0.92);
  font-family: 'Cormorant Garamond', 'Playfair Display', Georgia, serif;
  font-size: 2.4rem;
  font-weight: 500;
  letter-spacing: 0.05em;
}

/*
 * Pre/post-grid copy slots used by templates with specific real-content
 * messaging (e.g., perfecting-session pricing inclusions). Rendered by
 * PMServiceCardUtils.renderServiceCardsGrid({ aboveGridCopy, belowGridCopy }).
 */
.services-grid-copy {
  text-align: center;
  font-family: 'Cormorant Garamond', 'Playfair Display', Georgia, serif;
  font-style: italic;
  font-size: 1rem;
  color: var(--brand-text, #1a1a1a);
  opacity: 0.78;
  max-width: 720px;
  margin: 0 auto;
  padding: 0 1rem;
}
.services-grid-copy-above {
  margin-bottom: 1.5rem;
}
/* Contrast-aware nav-logo color (founder rule 2026-05-07).
   --brand-text-on-primary is SSR-injected per-tenant by
   lib/presentation/brand-injection.js pickContrastingColor based on
   the tenant's brand-primary luminance.
   IMPORTANT: NO !important — many templates use a transparent nav
   over a hero image where the inline `color: #fff` is the right
   answer regardless of brand-primary luminance (the hero image is
   the actual background, not brand-primary). The variable fills in
   for templates that don't set nav-logo color, and the inline rule
   wins where it exists. Light-brand-with-no-inline tenants get a
   readable fallback; transparent-nav-over-hero templates keep their
   existing #fff that they spec'd intentionally. */
.nav-logo,
.nav-logo:link,
.nav-logo:visited,
.nav-logo:hover,
.nav-logo:active { color: var(--brand-text-on-primary, #fff); }

/* ───────────────────────────────────────────────────────────────
 * Shared gallery + location section CSS (founder rule 2026-05-07).
 * The injected `<section id="gallery">` + `<section id="location">`
 * blocks added to all 102 templates by
 * scripts/maint/inject-gallery-and-map.mjs use these `.pm-*` classes.
 * Without this CSS the sections render as bare H2 + empty div +
 * plain link. Brand variables come from SSR injection.
 * ─────────────────────────────────────────────────────────────── */
.pm-gallery-section { padding: 4rem 1.5rem; background: var(--brand-bg, #f8fafc); color: var(--brand-text-on-bg, #1a1a1a); }
.pm-gallery-container { max-width: 1200px; margin: 0 auto; }
.pm-gallery-heading { text-align: center; margin: 0 0 2rem 0; font-size: clamp(1.5rem, 3vw, 2.25rem); color: var(--brand-text-on-bg, #1a1a1a); }
.pm-gallery-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 1rem; }
@media (max-width: 480px) { .pm-gallery-grid { grid-template-columns: repeat(2, 1fr); gap: 0.5rem; } }
.pm-gallery-item { aspect-ratio: 1; border-radius: 12px; overflow: hidden; position: relative; background: linear-gradient(135deg, var(--brand-primary-light, #818cf8), var(--brand-accent, #8b5cf6)); }
@media (hover: hover) {
  .pm-gallery-item { transition: transform 0.25s ease, box-shadow 0.25s ease; }
  .pm-gallery-item:hover { transform: translateY(-3px); box-shadow: 0 12px 32px rgba(0, 0, 0, 0.15); }
}
.pm-gallery-img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pm-gallery-link { display: block; width: 100%; height: 100%; }
.pm-gallery-source-tag { position: absolute; bottom: 8px; left: 8px; padding: 2px 8px; background: rgba(0, 0, 0, 0.6); color: #fff; font-size: 11px; border-radius: 999px; text-transform: capitalize; pointer-events: none; }
.pm-gallery-empty { text-align: center; padding: 2rem; color: var(--brand-muted, #888); font-style: italic; }

/* #11425 — /portfolio browse-by-service filter chips + status line. Reuses the
   shared gallery grid/lightbox; only the filter strip is portfolio-specific. */
.pm-portfolio-filter { max-width: 1200px; margin: 0 auto 1.5rem auto; padding: 0 1.5rem; display: flex; flex-wrap: wrap; align-items: center; gap: 0.75rem; }
.pm-portfolio-filter-label { font-weight: 600; color: var(--brand-text-on-bg, #1a1a1a); }
.pm-portfolio-filter-chips { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.pm-portfolio-chip { border: 1px solid var(--brand-primary, #6366f1); background: transparent; color: var(--brand-primary, #6366f1); padding: 0.4rem 0.9rem; border-radius: 999px; font-size: 0.9rem; cursor: pointer; line-height: 1.2; }
.pm-portfolio-chip:hover { background: var(--brand-primary-light, #818cf8); color: #fff; }
.pm-portfolio-chip.is-active { background: var(--brand-primary, #6366f1); color: #fff; border-color: var(--brand-primary, #6366f1); }
.pm-portfolio-chip-count { opacity: 0.75; font-size: 0.8em; }
.pm-portfolio-status { text-align: center; color: var(--brand-muted, #888); font-size: 0.9rem; margin-bottom: 1rem; min-height: 1.2em; }

/* Carousel layout — used by hair / nail / brow / pet-grooming etc. The
   shared client adds the .pm-gallery-carousel class + injects prev/next
   arrows + a horizontally-scrolling .pm-gallery-track. Tiles snap. */
.pm-gallery-carousel { position: relative; display: block; }
.pm-gallery-track {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(260px, 30%);
  gap: 1rem;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
  padding: 0.25rem 0 0.5rem 0;
}
.pm-gallery-track .pm-gallery-item { aspect-ratio: 4 / 3; scroll-snap-align: start; flex: 0 0 auto; }
@media (max-width: 720px) { .pm-gallery-track { grid-auto-columns: 80%; } }
.pm-gallery-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 44px; height: 44px;
  border: 0;
  border-radius: 50%;
  background: var(--brand-primary, #6366f1);
  color: var(--brand-text-on-primary, #fff);
  font-size: 1.5rem; line-height: 1;
  cursor: pointer;
  box-shadow: 0 4px 12px rgba(0,0,0,0.18);
  z-index: 2;
  transition: opacity 0.15s ease, transform 0.15s ease;
}
.pm-gallery-arrow:hover { opacity: 0.92; }
.pm-gallery-arrow:active { transform: translateY(-50%) scale(0.96); }
.pm-gallery-arrow-prev { left: -8px; }
.pm-gallery-arrow-next { right: -8px; }
@media (max-width: 480px) { .pm-gallery-arrow-prev { left: 4px; } .pm-gallery-arrow-next { right: 4px; } }

/* Masonry layout — used by photography / tattoo / art categories. Native
   image aspect ratios preserved; columns reflow. CSS columns is the
   simplest masonry that respects accessibility (DOM order = visual
   order). */
.pm-gallery-masonry {
  column-count: 3;
  column-gap: 1rem;
  display: block;
}
@media (max-width: 900px) { .pm-gallery-masonry { column-count: 2; } }
@media (max-width: 480px) { .pm-gallery-masonry { column-count: 1; } }
.pm-gallery-masonry .pm-gallery-item {
  break-inside: avoid;
  margin-bottom: 1rem;
  aspect-ratio: auto;
  background: transparent;
  border-radius: 12px;
  overflow: hidden;
}
.pm-gallery-masonry .pm-gallery-img { height: auto; aspect-ratio: auto; display: block; width: 100%; }

/* #11120: optional brand watermark overlay on each gallery item. Server
   response includes { watermark: { enabled, src } } when site_content
   .gallery_watermark_enabled='1'. CSS overlay (not image composition) means
   the original upload is never modified — toggle is instant on/off. */
.pm-gallery-watermark {
  position: absolute;
  right: 8px;
  bottom: 8px;
  width: 22%;
  max-width: 96px;
  height: auto;
  opacity: 0.3;
  pointer-events: none;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.4));
}
.pm-gallery-masonry .pm-gallery-item { position: relative; }
.pm-gallery-link { position: relative; display: block; width: 100%; height: 100%; }

/* #11114: keyboard-accessible lightbox. Esc closes, ArrowLeft / ArrowRight
   navigate, click backdrop closes, focus returns to the triggering tile. */
.pm-gallery-lightbox[hidden] { display: none !important; }
.pm-gallery-lightbox {
  position: fixed;
  inset: 0;
  z-index: 9999;
  background: rgba(0, 0, 0, 0.92);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2.5rem 1rem;
  outline: 0;
}
.pm-gallery-lightbox-inner {
  position: relative;
  max-width: min(1200px, 95vw);
  max-height: 95vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
}
.pm-gallery-lightbox-img {
  max-width: 100%;
  max-height: 80vh;
  object-fit: contain;
  display: block;
  border-radius: 6px;
  box-shadow: 0 12px 48px rgba(0, 0, 0, 0.6);
}
.pm-gallery-lightbox-caption {
  color: #f1f5f9;
  font-size: 0.95rem;
  text-align: center;
  max-width: 80ch;
  margin-top: 0.5rem;
  min-height: 1.4em;
}
.pm-gallery-lightbox-close,
.pm-gallery-lightbox-prev,
.pm-gallery-lightbox-next {
  position: absolute;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  border: 2px solid rgba(255, 255, 255, 0.4);
  border-radius: 999px;
  cursor: pointer;
  font-size: 1.6rem;
  line-height: 1;
  width: 48px;
  height: 48px;
  min-width: 44px;
  min-height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s ease, transform 0.15s ease;
}
.pm-gallery-lightbox-close:hover,
.pm-gallery-lightbox-prev:hover,
.pm-gallery-lightbox-next:hover { background: rgba(0, 0, 0, 0.8); }
.pm-gallery-lightbox-close:focus-visible,
.pm-gallery-lightbox-prev:focus-visible,
.pm-gallery-lightbox-next:focus-visible { outline: 3px solid #fff; outline-offset: 2px; }
.pm-gallery-lightbox-close { top: -56px; right: -8px; }
.pm-gallery-lightbox-prev { top: 50%; left: -56px; transform: translateY(-50%); }
.pm-gallery-lightbox-next { top: 50%; right: -56px; transform: translateY(-50%); }
@media (max-width: 720px) {
  .pm-gallery-lightbox-close { top: -48px; right: 0; }
  .pm-gallery-lightbox-prev { left: 4px; }
  .pm-gallery-lightbox-next { right: 4px; }
}
body.pm-gallery-lightbox-open { overflow: hidden; }
.pm-gallery-item:not([data-lightbox-skip="1"]) { cursor: zoom-in; }

/* #11507 — per-stylist portfolio strip on staff cards. Hidden until
   PMStaffPortfolioUtils fills it (ON + has-approved-work only). 44x44-min tap
   targets (WCAG 2.5.5); opens the shared lightbox (with #11526 swipe nav). */
.pm-staff-portfolio-mount[hidden] { display: none; }
.pm-staff-portfolio-heading {
  font-size: 0.85rem;
  font-weight: 600;
  margin: 0.75rem 0 0.4rem;
  opacity: 0.85;
}
.pm-staff-portfolio-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  align-items: center;
  justify-content: center;
}
.pm-staff-portfolio-thumb {
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  width: 56px;
  height: 56px;
  min-width: 44px;
  min-height: 44px;
  border-radius: 8px;
  overflow: hidden;
  line-height: 0;
}
.pm-staff-portfolio-thumb-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.pm-staff-portfolio-thumb:focus-visible,
.pm-staff-portfolio-more:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }
.pm-staff-portfolio-more {
  min-width: 44px;
  min-height: 44px;
  padding: 0 0.6rem;
  border: 1px solid currentColor;
  background: none;
  color: inherit;
  border-radius: 8px;
  cursor: pointer;
  font-size: 0.8rem;
}

.pm-location-section { padding: 4rem 1.5rem; background: var(--brand-bg, #f8fafc); color: var(--brand-text-on-bg, #1a1a1a); text-align: center; }
.pm-location-container { max-width: 720px; margin: 0 auto; }
.pm-location-heading { margin: 0 0 1rem 0; font-size: clamp(1.5rem, 3vw, 2.25rem); color: var(--brand-text-on-bg, #1a1a1a); }
.pm-location-address { margin: 0 0 1rem 0; font-size: 1.05rem; color: var(--brand-text-on-bg, #1a1a1a); }
.pm-location-link { display: inline-block; padding: 0.65rem 1.25rem; border-radius: 8px; background: var(--brand-primary, #6366f1); color: var(--brand-text-on-primary, #fff); text-decoration: none; font-weight: 600; transition: opacity 0.15s ease, transform 0.15s ease; min-height: 44px; }
.pm-location-link:hover { opacity: 0.9; transform: translateY(-1px); }

/* Service-card hover regression fix: shared service-card-utils.js
   now wraps cards in <a> for the click-to-book deep-link. Anchors
   default to inline display, which breaks templates whose
   `.service-card:hover` uses `transform: translateY()`. Force block
   so the transform continues to work across all 102 templates. */
a.service-card,
a.service-card:link,
a.service-card:visited { display: block; color: inherit; text-decoration: none; }

.services-grid-copy-below {
  margin-top: 1.5rem;
}

/* ───────────────────────────────────────────────────────────────
 * Issue #11096 — CSP `style-src 'nonce-...'` strict mode (Wave-8
 * close-out aftermath). Three sources of CSP style-src violations
 * caught by smoke-2026-05-12 on 82/204 demo pages:
 *
 *   1. `public/js/shared/map-utils.js:19` did
 *      `document.createElement('style'); style.textContent = '...';`
 *      and appended without setting `style.nonce`. Strict
 *      style-src 'self' 'nonce-...' blocks the JS-created <style>
 *      tag because it carries no nonce. Fix: the `.pm-map-directions-link`
 *      ruleset moved here so it ships as part of the page's
 *      already-linked stylesheet — no JS injection at all.
 *   2. `public/js/shared/service-card-utils.js:71` emitted raw
 *      `style="background: <gradient>"` on `.service-photo-fallback`.
 *      Nonces don't apply to inline `style=` attributes, so this
 *      violated the strict directive regardless of nonce wiring.
 *      Fix: 8 numbered `.pm-svc-bg-0..7` classes selected by index;
 *      the gradient palette is fixed (8 entries, brand-neutral) so
 *      we can map index → class at render-time.
 *   3. Same story for `staff-card-utils.js:52` → `.pm-staff-bg-0..7`.
 *
 * CSSOM mutations (e.g., `el.style.background = X`) are NOT blocked
 * by style-src per CSP3, but inline `style=` attribute parsing in
 * server-emitted HTML strings IS — that's the distinction.
 * ─────────────────────────────────────────────────────────────── */

/*
 * map-utils directions link — was JS-injected via createElement('style')
 * by ensureDirectionsStyles() before #11096. Now ships statically with
 * the stylesheet so CSP nonce-only style-src never sees it.
 *
 * #10933: WCAG 2.5.5 / iOS HIG 44x44 tap target. Padding + min
 * dimensions ensure the rendered link is >= 44x44 on all viewports
 * without disturbing typography. `display: inline-block` preserved
 * for #10579 CSP-regression contract.
 */
.pm-map-directions-link {
  display: inline-block;
  margin-top: 1rem;
  padding: 10px 14px;
  min-width: 44px;
  min-height: 44px;
  box-sizing: border-box;
  color: var(--brand-primary, #5b7f6e);
  font-weight: 600;
  text-decoration: none;
  font-size: 0.9rem;
  line-height: 1.4;
}
.pm-map-directions-link:hover,
.pm-map-directions-link:focus { text-decoration: underline; }

.map-placeholder.pm-map-has-embed,
.map-box.pm-map-has-embed,
.map-panel.pm-map-has-embed,
.map-card.pm-map-has-embed,
.map-location-card.pm-map-has-embed {
  display: block !important;
  padding: 0 !important;
  min-height: 280px;
  overflow: hidden;
  background: var(--brand-bg, #f8fafc) !important;
  color: var(--text-primary, #17242f) !important;
}
.map-placeholder.pm-map-has-embed::after,
.map-box.pm-map-has-embed::after,
.map-panel.pm-map-has-embed::after,
.map-card.pm-map-has-embed::after,
.map-location-card.pm-map-has-embed::after { content: none !important; }
.map-placeholder.pm-map-has-embed > :not(.pm-map-embed):not(.pm-map-directions-link),
.map-box.pm-map-has-embed > :not(.pm-map-embed):not(.pm-map-directions-link),
.map-panel.pm-map-has-embed > :not(.pm-map-embed):not(.pm-map-directions-link),
.map-card.pm-map-has-embed > :not(.pm-map-embed):not(.pm-map-directions-link),
.map-location-card.pm-map-has-embed > :not(.pm-map-embed):not(.pm-map-directions-link) { display: none !important; }
.pm-map-embed {
  display: block;
  position: relative;
  width: 100%;
  min-height: 280px;
  border: 0;
  border-radius: inherit;
  overflow: hidden;
  background:
    linear-gradient(135deg, rgba(91, 127, 110, 0.18), rgba(247, 243, 233, 0.96)),
    var(--brand-bg, #f8fafc);
}
.pm-map-road, .pm-map-park, .pm-map-pin, .pm-map-label { position: absolute; }
.pm-map-road { background: rgba(255, 255, 255, 0.9); box-shadow: 0 0 0 1px rgba(23, 36, 47, 0.08); }
.pm-map-road-a { left: -10%; top: 23%; width: 120%; height: 18px; transform: rotate(-8deg); }
.pm-map-road-b { left: -8%; top: 60%; width: 120%; height: 16px; transform: rotate(9deg); }
.pm-map-road-c { left: 26%; top: -12%; width: 18px; height: 130%; transform: rotate(13deg); }
.pm-map-road-d { left: 68%; top: -12%; width: 16px; height: 130%; transform: rotate(-15deg); }
.pm-map-park { border-radius: 28px; background: rgba(91, 127, 110, 0.18); box-shadow: inset 0 0 0 1px rgba(91, 127, 110, 0.15); }
.pm-map-park-a { left: 8%; top: 8%; width: 28%; height: 28%; }
.pm-map-park-b { right: 7%; bottom: 10%; width: 30%; height: 26%; }
.pm-map-pin { left: 50%; top: 47%; width: 34px; height: 34px; margin: -17px 0 0 -17px; border-radius: 50% 50% 50% 0; background: #d93672; transform: rotate(-45deg); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.24); }
.pm-map-pin::after { content: ""; position: absolute; width: 12px; height: 12px; border-radius: 50%; background: #fff; left: 11px; top: 11px; }
.pm-map-label { left: 18px; right: 18px; bottom: 18px; padding: 14px 16px; border-radius: 14px; background: rgba(255, 255, 255, 0.92); box-shadow: 0 12px 30px rgba(23, 36, 47, 0.16); color: var(--text-primary, #17242f); font-weight: 700; line-height: 1.35; }
.pm-map-label small { display: block; margin-top: 4px; font-weight: 600; color: var(--text-muted, #5b6b76); }
.pm-map-has-embed .pm-map-directions-link { margin: 0.75rem; }

/*
 * Service-card fallback gradient swatches. The DEFAULT_FALLBACK_BACKGROUNDS
 * literal in public/js/shared/service-card-utils.js is the canonical palette;
 * these eight classes mirror it 1:1 so the renderer can pick by index.
 * Custom palettes passed via opts.fallbackBackgrounds still work — those
 * call sites use CSSOM (el.style.background) which is CSP-clean.
 */
.pm-svc-bg-0 { background: linear-gradient(135deg, #cce4dd 0%, #8fb9a8 100%); }
.pm-svc-bg-1 { background: linear-gradient(135deg, #efe1d1 0%, #b89876 100%); }
.pm-svc-bg-2 { background: linear-gradient(135deg, #d8d4e3 0%, #8d83a6 100%); }
.pm-svc-bg-3 { background: linear-gradient(135deg, #ead7c6 0%, #c8a98e 100%); }
.pm-svc-bg-4 { background: linear-gradient(135deg, #c8d6e2 0%, #7d96b3 100%); }
.pm-svc-bg-5 { background: linear-gradient(135deg, #e3d4d8 0%, #b08c93 100%); }
.pm-svc-bg-6 { background: linear-gradient(135deg, #d4e0c4 0%, #8aa57e 100%); }
.pm-svc-bg-7 { background: linear-gradient(135deg, #e9dccd 0%, #c0a98a 100%); }

/* Staff-card fallback gradient swatches. Mirrors DEFAULT_FALLBACK_BACKGROUNDS
   in public/js/shared/staff-card-utils.js. */
.pm-staff-bg-0 { background: linear-gradient(135deg, #6b8e7e 0%, #3d5a4e 100%); }
.pm-staff-bg-1 { background: linear-gradient(135deg, #b89876 0%, #806749 100%); }
.pm-staff-bg-2 { background: linear-gradient(135deg, #8d83a6 0%, #5e5573 100%); }
.pm-staff-bg-3 { background: linear-gradient(135deg, #c8a98e 0%, #8b7458 100%); }
.pm-staff-bg-4 { background: linear-gradient(135deg, #7d96b3 0%, #4f6680 100%); }
.pm-staff-bg-5 { background: linear-gradient(135deg, #b08c93 0%, #7d5e63 100%); }
.pm-staff-bg-6 { background: linear-gradient(135deg, #8aa57e 0%, #5b7150 100%); }
.pm-staff-bg-7 { background: linear-gradient(135deg, #c0a98a 0%, #8c7758 100%); }

/* #11185 / inline-style sweep — generic hidden util for tenant templates. */
.u-hidden { display: none !important; }

/*
 * #11021 (smoke-2026-05-11 mobile-320): fleet-wide tenant-home contact
 * section overflow at 320px viewport. The shared template uses
 *
 *   <div class="contact-item">
 *     <div class="contact-icon">📍</div>
 *     <div>
 *       <strong>86 Front St, Exeter, NH 03833</strong>
 *       <p>Your booking confirmation includes the bar address…</p>
 *     </div>
 *   </div>
 *
 * with `.contact-item { display: flex; gap: 1rem }` and
 * `.contact-icon { flex-shrink: 0; width: 40px }`. The inner text-column
 * `<div>` has NO `min-width: 0` so an unbreakable address string forces
 * the column wider than the available track, pushing scrollWidth past 320.
 *
 * Likewise `.services-grid { grid-template-columns: repeat(auto-fill,
 * minmax(280px, 1fr)) }` cannot satisfy a 280px column at 272px container
 * width (320 - 48px section padding).
 *
 * Fix lives in shared utilities CSS (loaded by every template via
 * <link rel="stylesheet" href="/css/tenant-template-utilities.css">), so a
 * single rule covers all 102 demos + every paying-customer tenant.
 */
@media (max-width: 480px) {
  /* Pull section gutters in so the inner container has more room. */
  section { padding-left: 1rem !important; padding-right: 1rem !important; }

  /* Allow flex children to shrink below their content min-size and wrap
     unbreakable strings (long addresses, URLs, emails). */
  .contact-item,
  .contact-item > div,
  .footer-contact-item {
    min-width: 0;
    flex-wrap: wrap;
  }
  .contact-item > div { flex: 1 1 0; }
  .contact-item strong,
  .contact-item p,
  .contact-item a,
  .footer-contact-item,
  .footer-contact-item a {
    overflow-wrap: anywhere;
    word-break: break-word;
  }

  /* #11973 — exempt the phone/email tap-target anchors from the aggressive
     character-level breaking applied to the contact rows above. Those
     anchors are SSR-rewritten from <span> to <a> by updateAnchorById() in
     lib/presentation/html/html-token-replacer.js when the tenant has a valid
     phone/email, so they ARE the `a` matched by the audit's tap query and by
     the `.contact-item a`/`.footer-contact-item a` rule. Phone numbers stay on
     one line (nowrap); emails wrap only at normal break opportunities (e.g.
     after `@`) instead of every character, so neither collapses into the
     13-32px-wide vertical strip the fleet audit flagged. The base-rule
     min-width:44px floor still guarantees the tap target if a very long email
     forces a wrap. */
  a#contact-phone,
  a#footer-phone {
    max-width: 100%;
    overflow-wrap: anywhere;
    word-break: break-word;
    white-space: normal;
  }
  a#contact-email,
  a#footer-email {
    max-width: 100%;
    overflow-wrap: anywhere;
    word-break: break-word;
    white-space: normal;
  }

  #contact-phone,
  #contact-email,
  #footer-phone,
  #footer-email,
  .contact-detail,
  .contact-detail > *,
  .contact-detail-value,
  .contact-detail-value a,
  .footer-contact-row,
  .footer-contact-link,
  .footer-contact a,
  .footer-contact div,
  .contact-zen-item,
  .contact-zen-item > *,
  .contact-zen-text,
  .contact-zen-text a {
    min-width: 0;
    max-width: 100%;
    overflow-wrap: anywhere;
    word-break: break-word;
    white-space: normal;
    box-sizing: border-box;
  }
  #footer-phone,
  #footer-email {
    display: flex !important;
    align-items: center;
    min-height: 44px;
    margin-bottom: 8px;
  }

  .pm-content-flag-button,
  .pm-gallery-flag-button {
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    min-width: 44px !important;
    min-height: 44px !important;
    padding: 10px 12px !important;
    line-height: 1.2 !important;
    box-sizing: border-box !important;
  }

  /* The minmax(280px, 1fr) services-grid does not fit 320px viewports.
     Collapse to a single column so the auto-fill track can shrink to
     fit the container. */
  .services-grid { grid-template-columns: 1fr !important; }
}
