/**
 * RYB Connect — accessibility & responsive fix layer
 * ---------------------------------------------------------------------------
 * Loaded LAST so it can correct issues that live scattered across the large
 * legacy stylesheets (ryb-connect-filter.css ~169KB, ryb-connect-design.css
 * ~100KB) without editing them in dozens of places. Keeping the corrections in
 * one file makes them reviewable and revertable in a single diff.
 *
 * Scope: every public funnel surface — wizard, custom funnel, finder/filter,
 * camps / new-camps / trials templates, embeddable form.
 *
 * Sections
 *   1. Utilities (screen-reader-only)
 *   2. Sticky submit bar overlap        (consent checkbox was unreachable)
 *   3. Focus visibility                 (keyboard users had no focus ring)
 *   4. Touch targets (>= 44px)
 *   5. Viewport / overflow robustness
 *   6. Text contrast (WCAG AA)
 *   7. Third-party chat-widget safe area
 *   8. Reduced motion
 */

:root {
  /* Space reserved above a pinned bar so it cannot sit on top of content.
     Two values because the two cases differ:
       - form: the consent control now lives INSIDE the action bar, so the
         body only needs a normal gap before the bar (a large value just
         leaves dead space between the last field and the bar).
       - class list: the sticky `.controls` bar genuinely overlaps the last
         result card, so that list still needs a full bar's height. */
  --ryb-form-bottom-space: 32px;
  --ryb-sticky-submit-space: 152px;
  /* Accessible text colors, verified against #fff:
     --ryb-text-muted  #5b6472  ~6.0:1
     --ryb-text-subtle #6b7280  ~4.8:1 (>=4.5:1, use at >=13px) */
  --ryb-text-muted: #5b6472;

  /* The tick for a ticked consent box.

     `--ryb-checkbox-image` is a per-site theme setting and it defaults to
     `none`. Every site that has not uploaded its own tick therefore renders a
     checked consent box as a solid purple square with nothing in it, which is
     not a state a parent can read — and this is the box that records TCPA
     consent to be texted. A CSS variable that is *defined* as `none` never
     falls back, so the theme value is layered over this one at the use site:
     `background-image: var(--ryb-checkbox-image), var(--ryb-checkbox-tick)`.
     Sites with their own artwork are unaffected; the rest get a tick. */
  --ryb-checkbox-tick: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M3 8.5l3.2 3.2L13 5' stroke='%23fff' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  --ryb-text-subtle: #6b7280;
  --ryb-focus-ring: var(--ryb-primary-color, #6026d5);
}

/* ==========================================================================
   1. Utilities
   ========================================================================== */

/* Visually hidden but exposed to assistive tech. Used by the form <label>s
   that replaced placeholder-only labelling. */
.ryb-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;
}

/* Error text should not occupy space when empty (we now keep the node in the
   DOM so aria-describedby stays valid instead of removing it). */
#ryb-connect .error-message:empty {
  display: none !important;
}

/* ==========================================================================
   2. Sticky submit bar overlap
   --------------------------------------------------------------------------
   `.step-control` / `.controls` are position:sticky; bottom:0 with an opaque
   white background. When the scroll container overflows, the bar is pulled up
   from its flow position and paints over the content immediately above it —
   which is the SMS-consent checkbox and the last form field. On a 900px-tall
   laptop the consent control was completely covered, so users could not see
   or tick it. Reserve space equal to the bar's height.
   ========================================================================== */

#ryb-connect .box.child .form-box.step-box,
#ryb-connect.custom-funnel .box.child .form-box.step-box {
  padding-bottom: var(--ryb-form-bottom-space) !important;
}

/* Returning-user branch hides the contact form, leaving the filters block as
   the last content above the bar. */
#ryb-connect .box.child .step-box--filters:last-of-type {
  padding-bottom: var(--ryb-form-bottom-space) !important;
}

/* Padding alone cannot win this one, and it is worth being precise about why.
   The bar is an opaque element pinned to the bottom of the same box that
   scrolls. Whenever the form is taller than (scrollport - bar), the bar paints
   over the tail of the form at rest, and no amount of padding below the form
   changes that — padding only adds scrollable space, which is space the bar is
   pinned in front of. The bar leaving the scroll container is the only true
   fix, and that is a structural change to a layout three funnels share.

   Two things make the remaining overlap harmless:

   1. scroll-padding-bottom. Every scroll the browser performs for us — moving
      to a focused field, jumping to a validation error — now stops with the
      target above the bar instead of underneath it. This is the part that
      actually matters: it means nobody can be typing into a field they cannot
      see, which is the failure mode worth caring about.

   2. A short fade above the bar. Content passing under an opaque edge gets
      sliced in half and reads as a rendering fault; the same content dissolving
      into the bar reads as "there is more above, scroll". */
#ryb-connect .box.child .step-control--submit {
  position: sticky;
}

#ryb-connect .box.child .step-control--submit::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 100%;
  height: 28px;
  background: linear-gradient(to top, #fff, rgba(255, 255, 255, 0));
  pointer-events: none;
}

/* --------------------------------------------------------------------------
   Consent relocated into the action bar.
   The SMS-consent control now renders inside `.step-control--submit` so it is
   always visible next to the submit button. All of its original styling was
   scoped to `#registration-form .checkbox-wrapper`, which no longer matches —
   reproduce it here.
   -------------------------------------------------------------------------- */

#ryb-connect .step-control--submit .checkbox-wrapper--consent {
  display: flex !important;
  align-items: flex-start !important;
  gap: 10px !important;
  margin: 0 0 14px 0 !important;
  text-align: left !important;
}

#ryb-connect .step-control--submit .checkbox-wrapper--consent input[type="checkbox"] {
  -webkit-appearance: none;
  appearance: none;
  flex: 0 0 auto;
  min-width: 24px;
  min-height: 24px;
  width: 24px;
  height: 24px;
  margin: 0;
  background: #fff;
  border: 2px solid rgba(0, 0, 0, 0.25);
  border-radius: 6px;
  cursor: pointer;
  /* Not `all`: this box also has its size, margin and background-size set
     above, and `transition: all` would animate any of them the moment a media
     query or a state class changed one. */
  transition: background-color 0.15s ease, border-color 0.15s ease,
    box-shadow 0.15s ease;
  background-position: center;
  background-repeat: no-repeat;
  background-size: 16px 16px;
}

#ryb-connect .step-control--submit .checkbox-wrapper--consent input[type="checkbox"]:hover {
  border-color: var(--ryb-primary-color);
}

#ryb-connect .step-control--submit .checkbox-wrapper--consent input[type="checkbox"]:checked {
  background-color: var(--ryb-primary-color);
  border-color: var(--ryb-primary-color);
  background-image: var(--ryb-checkbox-image), var(--ryb-checkbox-tick);
}

/* 14px, not 13px: this is the SMS-consent disclosure — meaningful body copy,
   not a caption. 14px is the floor for secondary body text; 13px is only
   defensible for metadata. Kept in this always-loaded layer rather than the
   refinements layer so it can't be A/B'd away. */
#ryb-connect .step-control--submit .checkbox-wrapper--consent label {
  font-size: 14px !important;
  line-height: 1.5 !important;
  color: var(--ryb-text-muted) !important;
  cursor: pointer;
  margin: 0 !important;
}

/* --------------------------------------------------------------------------
   The same consent control inside the overlay dialogs.

   `.sms-consent-label` is a plain block, so the checkbox and its sentence do
   not sit on one row: the box renders as a bare 35x52 native input stretched by
   the generic field rule, and the second line of the sentence wraps back under
   the box instead of staying with the rest of the text. It reads as a stray
   empty field next to a broken paragraph.

   Same 24px box, same tick, same 14px copy as the funnel version above, so the
   control means the same thing wherever a parent meets it.

   Selectors carry `#registration-form` because the box is losing to
   `#registration-form .input-wrapper input { height: 52px !important }` in
   ryb-connect-design.css — the rule that styles the three text fields. The
   checkbox shares their wrapper, so it inherits a text field's height and
   padding. An id in the selector is what it takes to outrank that.
   -------------------------------------------------------------------------- */
.ryb-connect-overlay .sms-consent-label {
  display: flex !important;
  align-items: flex-start !important;
  gap: 10px !important;
  text-align: left !important;
  cursor: pointer;
}

#registration-form .input-wrapper input[type="checkbox"],
.ryb-connect-overlay .sms-consent-label input[type="checkbox"] {
  -webkit-appearance: none;
  appearance: none;
  flex: 0 0 auto !important;
  min-width: 24px !important;
  min-height: 24px !important;
  width: 24px !important;
  height: 24px !important;
  margin: 1px 0 0 0 !important;
  padding: 0 !important;
  background: #fff;
  border: 2px solid rgba(0, 0, 0, 0.25) !important;
  border-radius: 6px !important;
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease,
    box-shadow 0.15s ease;
  background-position: center;
  background-repeat: no-repeat;
  background-size: 16px 16px;
}

#registration-form .input-wrapper input[type="checkbox"]:hover,
.ryb-connect-overlay .sms-consent-label input[type="checkbox"]:hover {
  border-color: var(--ryb-primary-color) !important;
}

#registration-form .input-wrapper input[type="checkbox"]:checked,
.ryb-connect-overlay .sms-consent-label input[type="checkbox"]:checked {
  background-color: var(--ryb-primary-color) !important;
  border-color: var(--ryb-primary-color) !important;
  background-image: var(--ryb-checkbox-image), var(--ryb-checkbox-tick) !important;
}

.ryb-connect-overlay .sms-consent-label .sms-consent-text {
  flex: 1 1 auto !important;
  font-size: 14px !important;
  line-height: 1.45 !important;
  color: var(--ryb-text-muted) !important;
}

/* --------------------------------------------------------------------------
   A consent box with nothing to consent to.

   The SMS-consent control sits in the pinned action bar rather than in the form
   body, so that an opaque bar cannot hide it (see the partial for why). The
   side effect: hiding the contact fields from returning families left the
   consent line behind, offering to text a phone number the page no longer asks
   for.

   Keyed off `.step-box--form.show` — the same class the PHP partial sets on a
   direct load and Step0Service sets on the click path. Deriving it in CSS from
   that one signal is deliberate: the original bug here was those two paths
   disagreeing about who sees the contact step, and a rule that reads the result
   cannot drift from it the way a third copy of the condition would.

   `display: none` rather than visibility, so the checkbox leaves the tab order
   along with the fields it belongs to.
   -------------------------------------------------------------------------- */
#ryb-connect.custom-funnel .box.child:not(:has(.step-box--form.show))
  .step-control--submit .checkbox-wrapper--consent {
  display: none !important;
}

/* Class-selection step: the sticky `.controls` bar covered the final card. */
#ryb-connect .box.classes.show .wizard-results,
#ryb-connect .box.classes.show .classes-list {
  padding-bottom: var(--ryb-sticky-submit-space) !important;
}

/* Keep the browser's own scrolling from parking a field under the bar — on
   focus, on scrollIntoView(), on jump-to-error.

   The value has to clear the WHOLE bar, and the bar is taller than it looks:
   consent checkbox + submit button + the legal line under it measure 196px on
   the class funnel. The previous value (152 + 16 = 168px) stopped 28px short,
   so focusing the phone field left its bottom third under the bar — the
   browser considered it "in view" and scrolled no further. 208px = the
   measured bar plus a 12px breathing gap. */
#ryb-connect .bg-white,
#ryb-connect .ryb-connect-content,
#ryb-connect.custom-funnel .bg-white {
  scroll-padding-bottom: 208px !important;
}

/* Below 768px the legacy CSS already switches the bar to position:relative,
   so the reserved space is no longer needed and would just add dead scroll. */
@media only screen and (max-width: 768px) {
  #ryb-connect .box.child .form-box.step-box,
  #ryb-connect.custom-funnel .box.child .form-box.step-box,
  #ryb-connect .box.child .step-box--filters:last-of-type {
    padding-bottom: 24px !important;
  }
}

/* ==========================================================================
   3. Focus visibility
   --------------------------------------------------------------------------
   design.css sets `outline: none !important` on some :focus-visible selectors,
   which deletes the focus ring authored in wizard.css. Because this file loads
   last, an equally-specific !important rule here wins on source order.
   ========================================================================== */

#ryb-connect a:focus-visible,
#ryb-connect button:focus-visible,
#ryb-connect input:focus-visible,
#ryb-connect select:focus-visible,
#ryb-connect textarea:focus-visible,
#ryb-connect [tabindex]:focus-visible,
#ryb-connect [role="button"]:focus-visible,
#ryb-connect [role="option"]:focus-visible,
#ryb-connect .wizard-stage .user-type li:focus-visible,
#ryb-connect .wizard-stage .user-type li a:focus-visible,
#ryb-connect.custom-funnel .wizard-stage .user-type li a:focus-visible,
#class-finder [tabindex]:focus-visible,
#class-finder [role="button"]:focus-visible,
#class-finder [role="option"]:focus-visible,
.ryb-connect-modal :focus-visible,
.ryb-connect-overlay :focus-visible {
  outline: 3px solid var(--ryb-focus-ring) !important;
  outline-offset: 2px !important;
  border-radius: 4px;
}

/* The SMS-consent checkbox is `-webkit-appearance: none` with `outline: none`
   and had no :focus rule anywhere — keyboard users got zero indication that
   focus had landed on a control they are legally required to read. */
#ryb-connect #registration-form .checkbox-wrapper input[type="checkbox"]:focus-visible,
#ryb-connect .checkbox-wrapper input[type="checkbox"]:focus-visible,
.ryb-connect-modal .checkbox-wrapper input[type="checkbox"]:focus-visible {
  outline: 3px solid var(--ryb-focus-ring) !important;
  outline-offset: 2px !important;
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--ryb-focus-ring) 20%, transparent) !important;
}

/* Newly keyboard-operable controls get a hover/focus affordance. */
#ryb-connect [role="button"],
#class-finder [role="button"],
#class-finder [role="option"] {
  cursor: pointer;
}

/* ==========================================================================
   4. Touch targets (WCAG 2.5.5 / iOS HIG: 44x44 minimum)
   ========================================================================== */

/* The primary Enroll CTA in the results grid was forced to ~32px tall on the
   exact devices where it is tapped with a thumb. */
@media only screen and (max-width: 767px) {
  #class-finder .result-grid-cell .enroll-button,
  #class-finder .enroll-button,
  #class-finder a.enroll,
  #class-finder button.enroll {
    min-height: 44px !important;
    padding: 10px 18px !important;
    font-size: 14px !important;
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
  }
}

/* --------------------------------------------------------------------------
   The overlay owns the whole screen, whatever the host theme thinks.

   A WordPress block theme lays out the direct children of the post content with
   `is-layout-constrained`, which sets `max-width: <content size>` and a top
   margin on each child. `max-width` still applies to a position:fixed element,
   so an overlay that renders as a sibling of the widget gets clamped to the
   theme's text column: the backdrop covers a strip of the page instead of all
   of it, and the dialog centres inside that strip rather than the screen.
   Measured on a stock block theme at 1440px wide: 645px of backdrop.

   The camps partial no longer emits it there, but any theme, page builder or
   future include site could reintroduce the same clamp, and a half-covered
   backdrop is a bad enough failure to be worth pinning outright.
   -------------------------------------------------------------------------- */
.ryb-connect-overlay {
  inset: 0 !important;
  width: 100% !important;
  height: 100% !important;
  max-width: none !important;
  max-height: none !important;
  margin: 0 !important;
}

/* --------------------------------------------------------------------------
   Hidden modal panels must be hidden from the keyboard too.

   These dialogs swap between a choice panel and a registration form by
   collapsing one to `opacity: 0; height: 0` with overflow left visible. The
   panel measures nothing, but its inputs keep their full 52px boxes: they stay
   in the tab order, they still count toward the dialog's scroll height, and
   moving focus into the dialog on open lands on a field nobody can see —
   scrolling the dialog past its own title and close button on the way. That is
   what the camps capture step was doing: an apparently empty white box with the
   caret blinking in an invisible input.

   `visibility: hidden` takes the whole subtree out of the tab order and the
   accessibility tree; `overflow: hidden` stops the collapsed panel inflating
   the scroll height. Visibility is excluded from the panel transition so the
   fade still runs at full speed instead of flipping halfway through.
   -------------------------------------------------------------------------- */
.ryb-connect-modal .registration-form:not(.show-field),
.ryb-connect-modal .modal-content:not(.show-field) {
  visibility: hidden !important;
  overflow: hidden !important;
}

.ryb-connect-modal .registration-form,
.ryb-connect-modal .modal-content {
  transition-property: opacity, transform, height !important;
}

/* Modal / overlay dismiss controls — the last exit from a dialog. */
.ryb-connect-modal .close-modal,
.ryb-connect-overlay .close-modal,
#ryb-connect .close-modal,
.redirect-modal-close,
.camps-modal-close {
  min-width: 44px !important;
  min-height: 44px !important;
  display: inline-flex !important;
  align-items: center !important;
  justify-content: center !important;
}

/* --------------------------------------------------------------------------
   Phone header: the funnel title runs into the phone number.

   The header packs brand, funnel title, phone link and a Back home button onto
   one row. Below ~430px the title's box shrinks but the words do not, and with
   no overflow rule the text spills straight across the phone number — measured
   on a 390px viewport, "Find a Class" printed over "910-295-0724". A phone
   number a parent cannot read is the worst thing on that row to lose.

   Truncating to "Find a Cla…" is not much better, so drop the title outright at
   this width. Nothing is lost: the page heading directly below says what the
   funnel is, in bigger type.
   -------------------------------------------------------------------------- */
@media only screen and (max-width: 430px) {
  #ryb-connect .header-funnel-title,
  #ryb-connect header .header-brand > .divider,
  #ryb-connect.custom-funnel .header-funnel-title {
    display: none !important;
  }
}

/* Between 430px and the desktop layout there is room for the title, but only
   if it agrees to be clipped rather than to overlap its neighbour. */
@media only screen and (min-width: 431px) and (max-width: 768px) {
  #ryb-connect .header-funnel-title,
  #ryb-connect.custom-funnel .header-funnel-title {
    overflow: hidden !important;
    text-overflow: ellipsis !important;
    white-space: nowrap !important;
    min-width: 0 !important;
  }
}

/* --------------------------------------------------------------------------
   Phone: the dropdowns lose their chevron and stop looking like dropdowns.

   `#ryb-connect .standard-select` draws its arrow as a background-image. Below
   500px a later rule sets `background: #fff !important` on the same element to
   force a light field, and the shorthand resets background-image along with it.
   Result on a 390px phone: Gender and Age render as plain white boxes with no
   affordance at all, on the step where picking them is the whole job.

   Restore the arrow with the longhand, so the white background that rule wanted
   still applies.
   -------------------------------------------------------------------------- */
@media only screen and (max-width: 500px) {
  #ryb-connect .wizard-stage .dropdown-wrapper select,
  #ryb-connect .standard-select,
  #ryb-connect.custom-funnel .standard-select {
    background-color: #fff !important;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23555' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E") !important;
    background-repeat: no-repeat !important;
    background-position: calc(100% - 16px) center !important;
    background-size: 16px !important;
    padding-right: 44px !important;
  }
}

/* Filter pills: the camps template got a 44px floor with a "WCAG 2.5.5"
   comment; the new-camps fork of the same component never received it. */
#class-finder .camps-filter-pill,
#class-finder.template-new-camps .camps-filter-pill {
  min-height: 44px !important;
  display: inline-flex !important;
  align-items: center !important;
}

/* Day-column accordion toggle is a real control (it expands the day's results),
   and it sits on a row of its own, so a 44px box costs nothing. */
#class-finder .accordion {
  min-width: 44px !important;
  min-height: 44px !important;
  display: inline-flex !important;
  align-items: center !important;
  justify-content: center !important;
}

/* The result-card "info" affordance is also a real control, but it shares a row
   with the class name inside a day column that is only ~170px wide on a 1440px
   screen. Giving it a 44px BOX took 20px off the title and pushed every name
   onto two lines ("Kinder Gym" wrapping after "Kinder"). Keep the 24px circle
   the design layer draws and hang the 44px target off it instead: the pseudo-
   element overflows the box, so the finger gets its target and the title keeps
   its width. */
#class-finder .result-grid-cell .info {
  position: relative !important;
  display: inline-flex !important;
  align-items: center !important;
  justify-content: center !important;
}
#class-finder .result-grid-cell .info::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 44px;
  height: 44px;
  transform: translate(-50%, -50%);
  /* Hit area only — never paints, and must not sit above the tooltip. */
  background: transparent;
  z-index: 0;
}

/* Camp row expand/collapse chip. */
#class-finder .camp-row-toggle {
  min-height: 44px !important;
  min-width: 44px !important;
}

/* --------------------------------------------------------------------------
   Text inputs below 16px make iOS Safari zoom the page the moment the field
   takes focus. The page does not zoom back out, so the rest of the form is
   then wider than the viewport and has to be panned sideways — on the exact
   screen where the lead is entered.

   Measured on the live funnel: the three selects render at 16px and are safe;
   name / email / phone render at 14px and are not. Raising them to 16px fixes
   the zoom and, incidentally, makes the two halves of the field set match.

   This lives here rather than in ryb-connect-refinements.css on purpose: it is
   a defect, not a preference, so it must survive the refinements layer being
   dequeued for an A/B test.
   -------------------------------------------------------------------------- */

#ryb-connect .box.child input[type="text"],
#ryb-connect .box.child input[type="email"],
#ryb-connect .box.child input[type="tel"],
#ryb-connect .input-wrapper input[type="text"],
#ryb-connect .input-wrapper input[type="email"],
#ryb-connect .input-wrapper input[type="tel"],
.ryb-connect-modal .input-wrapper input[type="text"],
.ryb-connect-modal .input-wrapper input[type="email"],
.ryb-connect-modal .input-wrapper input[type="tel"] {
  font-size: 16px !important;
}

/* ==========================================================================
   5. Viewport / overflow robustness
   ========================================================================== */

/* Mobile browsers report 100vh as the LARGE viewport (URL bar excluded), so
   the bottom of the funnel rendered below the fold while body scroll was
   locked. dvh tracks the visible viewport. */
@supports (height: 100dvh) {
  #ryb-connect {
    height: 100dvh;
  }

  #ryb-connect .wizard-stage {
    height: calc(100dvh - 130px);
  }

  body.admin-bar #ryb-connect {
    height: calc(100dvh - 36px);
  }
}

/* The wizard error modal was a hard 360x200 box with no max-width, so the
   default copy overflowed the card and the card itself was wider than a
   360px phone. */
#ryb-connect .error-overlay .error-modal,
.error-overlay .error-modal {
  width: auto !important;
  max-width: min(360px, calc(100vw - 32px)) !important;
  height: auto !important;
  min-height: 200px !important;
  overflow: visible !important;
}

/* Finder filter row could not wrap between 769px and 1250px, and the parent
   clips overflow — making filters unreachable rather than scrollable. */
#class-finder .filter-top .filter-controls {
  flex-wrap: wrap !important;
}

/* Never let a funnel scroll the page sideways. */
#ryb-connect,
#class-finder {
  max-width: 100%;
}

#ryb-connect img,
#class-finder img {
  max-width: 100%;
  height: auto;
}

/* Long unbroken strings (emails, URLs, class names) shouldn't force overflow. */
#ryb-connect .error-message,
#class-finder .result-grid-cell .name,
#ryb-connect .location-item-address {
  overflow-wrap: anywhere;
}

/* ==========================================================================
   6. Text contrast (WCAG AA)
   --------------------------------------------------------------------------
   All of the originals used !important, so these do too.
   ========================================================================== */

/* Consent disclosure with the Privacy Policy / Terms links — was rgba(0,0,0,.4)
   at 12px (~2.9:1), the worst size+contrast combination in the funnel.
   NOTE: design.css also ships `#ryb-connect.custom-funnel .wizard-stage
   .privacy-policy p` (specificity 0,1,3,1), so the plain `#ryb-connect
   .wizard-stage ...` form (0,1,2,1) loses regardless of source order. Match
   that selector explicitly. */
#ryb-connect .wizard-stage .privacy-policy p,
#ryb-connect.custom-funnel .wizard-stage .privacy-policy p,
#ryb-connect .privacy-policy p,
#ryb-connect .privacy-policy {
  color: var(--ryb-text-muted) !important;
  font-size: 14px !important;
  line-height: 1.5 !important;
}

/* The Privacy Policy / Terms links measured 78x17 and 83x17 — below even the
   24px mouse minimum (WCAG 2.5.8). Inline links can't be forced to 44px
   without wrecking the paragraph, so they get vertical padding to clear 24px
   comfortably and gain touch slop. */
#ryb-connect .wizard-stage .privacy-policy p a,
#ryb-connect.custom-funnel .wizard-stage .privacy-policy p a,
#ryb-connect .privacy-policy a {
  color: var(--ryb-text-muted) !important;
  text-decoration: underline !important;
  display: inline-block;
  padding: 5px 2px;
}

/* SMS-consent label — informed consent copy. 14px for the same reason as the
   relocated consent above: it is body copy, not metadata. */
#ryb-connect #registration-form .checkbox-wrapper label,
#ryb-connect .checkbox-wrapper label,
.ryb-connect-modal .checkbox-wrapper label {
  color: var(--ryb-text-muted) !important;
  font-size: 14px !important;
  line-height: 1.5 !important;
}

/* Header phone number measured 99x19. It is a standalone conversion path, so
   unlike the inline legal links it can take a real 44px target. */
#ryb-connect header .top-right-contacts a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}

/* Placeholders double as the visible field label. */
#ryb-connect #registration-form .input-wrapper input::placeholder,
#ryb-connect .input-wrapper input::placeholder,
#ryb-connect .input-wrapper textarea::placeholder {
  color: var(--ryb-text-subtle) !important;
  opacity: 1 !important;
}

/* Structural labels and status text that were #b0b0b0 / #9ca3af / #999. */
#ryb-connect .filter-top .dropdown-group-header,
#class-finder .filter-top .dropdown-group-header {
  color: var(--ryb-text-muted) !important;
  font-size: 11px !important;
}

#class-finder .camp-status.waitlist,
#class-finder .camps-pill-label,
#class-finder .session-info-label,
#ryb-connect .result-grid-cell .classes-per-week {
  color: var(--ryb-text-muted) !important;
}

/* Small-print labels below 12px are hard to read regardless of contrast. */
#class-finder .session-info-label {
  font-size: 12px !important;
}

/* Background scroll lock while a dialog is open. Applied by
   common/utils/modal-a11y.util.ts. Without this the page scrolled behind the
   modal on touch, so users lost their place in the results list. */
body.ryb-modal-open {
  overflow: hidden !important;
  touch-action: none;
}

/* ==========================================================================
   6b. Results step (class finder)
   ========================================================================== */

/* "Not sure? Choose all classes  See Schedules"
   Below 500px design.css turns this row into a flex container
   (`#ryb-connect .box.classes.show .controls div { display: flex }`). Its
   children are a bare text node, a <span>, and a <button> — and whitespace
   between flex items is not rendered, so the text ran together as
   "Not sure?Choose all classes". Measured gap was 0px. */
#ryb-connect .box.classes.show .controls div {
  gap: 6px;
}

/* NOTE on result-card thumbnails: the awkward crops (heads cut off, fragments
   of overlaid text visible) are NOT a CSS problem — these images already
   compute to `object-fit: cover; object-position: center`. The cause is the
   source photography (off-centre subjects, text baked into the image), so it
   has to be fixed by replacing the uploaded images, not here. */

/* Programmatic scrollIntoView() during step transitions could park a heading
   under the top edge of the scroll container (the results count was seen
   half-cut). Reserve clearance so scrolled-to content always lands clear. */
#ryb-connect .bg-white {
  scroll-padding-top: 24px;
}

/* ==========================================================================
   7. Third-party chat-widget safe area
   --------------------------------------------------------------------------
   The site chat bubble is fixed bottom-right and was covering the phone field
   and the SMS-consent row on mobile, making them untappable without first
   dismissing the widget.
   ========================================================================== */

@media only screen and (max-width: 768px) {
  #ryb-connect .bg-white .container-wrapper,
  #ryb-connect.custom-funnel .bg-white .container-wrapper {
    padding-bottom: 96px;
  }

  /* The results step has its own pinned action bar ("See Schedules"), which
     the chat bubble was sitting on top of. Keep the bar clear of it. */
  #ryb-connect .box.classes.show .controls,
  #class-finder .filter-bottom-controls {
    padding-right: 72px;
  }
}

/* ==========================================================================
   8. Reduced motion
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  #ryb-connect *,
  #class-finder * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  /* Progress indicators are exempt. The blanket rule above sets
     `animation-iteration-count: 1` on an `infinite` spinner, which stops it
     after 0.01ms — so the one animation that carries information (the page is
     working, wait) was the one animation this rule broke. Someone who asked
     for less motion got a frozen ring and no sign anything was happening.

     A small ring rotating in place is not a vestibular trigger — it does not
     travel, zoom or parallax — so it stays. Slowed slightly, which reads as
     calmer without losing the signal. */
  #ryb-connect .loader,
  #ryb-connect .loader::before,
  #ryb-connect .loader::after,
  #ryb-connect .form-submit.loading::after,
  #class-finder .loader,
  #class-finder .loader::before,
  #class-finder .loader::after {
    animation-duration: 1.4s !important;
    animation-iteration-count: infinite !important;
    animation-timing-function: linear !important;
  }
}
