/* MoxC.carousel — image/content carousel.
   Two sizes: --card (tight contexts, arrows on hover) and
   --gallery (detail pages, arrows always on).
   Consumers set container width and aspect-ratio via the host wrapper;
   the carousel fills whatever it's given. */

.mox-carousel {
   /* Animation params — inherit from global carousel tokens defined in
      ui-core.css. Override --mox-carousel-* on the element (or a parent)
      to tune a single carousel without affecting the global token.
         --mox-carousel-duration    total track-slide duration
         --mox-carousel-ease        bezier curve for every settle
         --mox-carousel-zoom        active-slide scale factor (1 = off)
         --mox-carousel-zoom-delay  when the zoom kicks in (fires
                                    between zoom-delay and duration) */
   --mox-carousel-duration:   var( --carousel-duration );
   --mox-carousel-ease:       var( --carousel-ease );
   --mox-carousel-zoom:       var( --carousel-zoom );
   --mox-carousel-zoom-delay: var( --carousel-zoom-delay );

   position: relative;
   overflow: hidden;          /* arrow overlap doesn't escape */
   outline:  none;
   width:    100%;
   /* Fill the consumer's host element. The host chooses the height
      (concrete or via aspect-ratio); images crop to fit via object-fit
      on the slide img. Without this, the first-loaded image's natural
      height drives the carousel height and any fixed-aspect wrapper
      around it appears to clip overlays like dots. */
   height:   100%;
}

.mox-carousel:focus-visible {
   outline:        2px solid var( --theme-interaction-default-normal, #1876dc );
   outline-offset: -2px;
}

.mox-carousel__viewport {
   overflow: hidden;
   width:    100%;
   height:   100%;
   touch-action: pan-y;       /* horizontal swipe belongs to us */
}

.mox-carousel__track {
   display:   flex;
   width:     100%;
   height:    100%;
   transition: transform var( --mox-carousel-duration ) var( --mox-carousel-ease );
   will-change: transform;
}

.mox-carousel__slide {
   flex:        0 0 100%;
   min-width:   0;
   user-select: none;
   /* Zoom fires between --zoom-delay and --duration (its animation
      duration = duration − delay). Total elapsed matches the track so
      both land together. */
   transform:   scale( 1 );
   transition:  transform
      calc( var( --mox-carousel-duration ) - var( --mox-carousel-zoom-delay ) )
      var( --mox-carousel-ease )
      var( --mox-carousel-zoom-delay );
}
.mox-carousel__slide.is-active {
   transform:   scale( var( --mox-carousel-zoom ) );
}
@media ( prefers-reduced-motion: reduce ) {
   .mox-carousel__slide,
   .mox-carousel__slide.is-active { transform: none; transition: none; }
}

/* Slides that contain an <img> let the image fill the slide by default. */
.mox-carousel__slide > img {
   display:    block;
   width:      100%;
   height:     100%;
   object-fit: cover;
   pointer-events: none;      /* swipe shouldn't be eaten by the image */
   -webkit-user-drag: none;
   user-drag: none;
}

/* -------- Arrows -------- */
/* Composed transform: the position already uses translateY(-50%) to
   vertical-center the arrow; scale on hover must compose with that or
   the arrow drops. Carry scale in a custom property so the hover rule
   only bumps the number — the transform expression stays one source of
   truth. */
.mox-carousel__arrow {
   --mox-arrow-scale: 1;
   position:  absolute;
   top:       50%;
   transform: translateY( -50% ) scale( var( --mox-arrow-scale ) );
   transition: transform var( --duration-snap ) var( --ease-snap ),
               opacity   var( --duration-snap ) var( --ease-snap );
   z-index:   2;
}
.mox-carousel__arrow:hover:not([disabled]) {
   --mox-arrow-scale: 1.05;
   /* Inside the carousel the chip stays pure white on hover — the
      scale is the feedback. Overrides nav-arrow.css's generic
      currentColor tint which reads as muddy gray over imagery. */
   background: #fff;
}
.mox-carousel__arrow--left  { left:  8px; }
.mox-carousel__arrow--right { right: 8px; }

/* nav-arrow.css sets display:inline-flex on .mox-nav-arrow at the same
   specificity as the UA [hidden] rule, which makes the hidden attribute
   a no-op on arrows. Re-assert display:none with enough specificity to
   win so JS can hide edge arrows via .hidden = true. */
.mox-carousel__arrow[hidden] { display: none; }

/* Card size: arrows AND dots hidden until hover. Both fade in
   together so the card reads as pure imagery at rest. Keyboard users
   rely on arrow keys (carousel root is tabindex=0); tabbing directly
   to an arrow or dot still reveals the specific element via
   :focus-visible. */
.mox-carousel--card .mox-carousel__arrow,
.mox-carousel--card .mox-carousel__dots {
   opacity:    0;
   transition: opacity var( --duration-snap ) var( --ease-snap );
}
.mox-carousel--card:hover .mox-carousel__arrow,
.mox-carousel--card:hover .mox-carousel__dots {
   opacity: 1;
}
/* Keyboard reveal:
   - Arrows are direct children of .mox-carousel so :focus-visible on
     the button itself overrides the opacity:0.
   - Dots are inside .mox-carousel__dots which carries the opacity:0
     on --card. Opacity cascades, so a focused dot inside a transparent
     container still reads as invisible. Bring the container opaque
     when any dot inside has focus (narrow-scoped :focus-within — does
     NOT match when only the carousel root has focus). */
.mox-carousel__arrow:focus-visible {
   opacity: 1;
}
.mox-carousel--card .mox-carousel__dots:focus-within {
   opacity: 1;
}

/* Gallery size: arrows always visible, slightly larger offset. */
.mox-carousel--gallery .mox-carousel__arrow--left  { left:  16px; }
.mox-carousel--gallery .mox-carousel__arrow--right { right: 16px; }

/* -------- Dots -------- */
.mox-carousel__dots {
   position:        absolute;
   left:            0;
   right:           0;
   bottom:          10px;
   display:         flex;
   gap:             6px;
   justify-content: center;
   align-items:     center;
   z-index:         2;
   pointer-events:  none;     /* container passes through, dot buttons re-enable */
}

.mox-carousel__dot {
   pointer-events: auto;
   width:          6px;
   height:         6px;
   padding:        0;
   border:         none;
   border-radius:  50%;
   background:     rgba( 255, 255, 255, 0.55 );
   /* Card-elevation token — small dot indicator lifts off the
      photo background. Lighter than the previous bespoke 0.25-alpha. */
   box-shadow:     var(
                      --theme-shadow-card,
                      0 0 2px 0 rgba( 26, 35, 43, 0.04 ),
                      0 1px 3px 0 rgba( 26, 35, 43, 0.12 )
                   );
   cursor:         pointer;
   /* Dots: background/transform are hover state flips (snap). Width and
      height animate alongside the slide track when the dot window
      paginates, so they ride the carousel curve. */
   transition:     background var( --duration-snap ) var( --ease-snap ),
                   transform  var( --duration-snap ) var( --ease-snap ),
                   width      var( --mox-carousel-duration ) var( --mox-carousel-ease ),
                   height     var( --mox-carousel-duration ) var( --mox-carousel-ease );
}
.mox-carousel__dot:hover  { background: rgba( 255, 255, 255, 0.85 ); }
.mox-carousel__dot.is-active {
   background: #fff;
   transform:  scale( 1.25 );
}
.mox-carousel__dot:focus-visible {
   outline:        2px solid var( --theme-interaction-default-normal, #1876dc );
   outline-offset: 2px;
}

/* Pagination — when a carousel has more than DOT_WINDOW slides, only
   DOT_WINDOW dots are visible at a time (set by carousel.js). The
   visible window slides as the active slide moves; the dots at the
   visible edges shrink to suggest there are more dots beyond.
     - is-hidden     full-out, removed from layout
     - is-edge       outermost visible dot when more exist beyond → 4px
     - is-mid-edge   second-from-edge → 6px (intermediate step) */
/* Pagination — when n > DOT_WINDOW (=5), only DOT_WINDOW dots visible
   (set by carousel.js). Visible-edge dots shrink to suggest more dots
   beyond. Gentle 6→5→4 step so the dot strip reads as a coherent line
   without an aggressive size scale. */
.mox-carousel__dot.is-hidden   { display: none; }
.mox-carousel__dot.is-mid-edge { width: 5px; height: 5px; }
.mox-carousel__dot.is-edge     { width: 4px; height: 4px; }

/* Gallery dots: larger, higher from the bottom, darker track so they
   read over any image. */
.mox-carousel--gallery .mox-carousel__dots { bottom: 16px; gap: 8px; }
.mox-carousel--gallery .mox-carousel__dot  { width: 8px; height: 8px; }
.mox-carousel--gallery .mox-carousel__dot.is-mid-edge { width: 6px; height: 6px; }
.mox-carousel--gallery .mox-carousel__dot.is-edge     { width: 5px; height: 5px; }

/* Reduced-motion: disable the transform transition. JS already disables
   mid-drag; this covers passive slide changes. */
@media ( prefers-reduced-motion: reduce ) {
   .mox-carousel__track { transition: none; }
   .mox-carousel__dot   { transition: none; }
}
/* MoxC.toggle — pill-shape on/off slider.
   Two sizes: md (44x24, default) for brand/privacy contexts; sm (36x20)
   for filter widget select-all switches.

   Track colors are exposed as CSS custom properties so host CSS can
   override per-context (e.g. .as-flt-toggle .mox-toggle__track { … })
   without forking the component:
     --mox-toggle-track-off  default #ccc
     --mox-toggle-track-on   default brand-primary cyan */

.mox-toggle {
   position:    relative;
   display:     inline-block;
   flex-shrink: 0;
}

.mox-toggle input {
   opacity:  0;
   width:    0;
   height:   0;
   position: absolute;
}

.mox-toggle__track {
   --mox-toggle-track-off: #ccc;
   --mox-toggle-track-on:  var( --colors-bg-brand-primary, #39CEFE );

   position:      absolute;
   inset:         0;
   background:    var( --mox-toggle-track-off );
   border-radius: 9999px;
   cursor:        pointer;
   transition:    background var( --duration-snap ) var( --ease-snap );
}

.mox-toggle__track::before {
   content:       '';
   position:      absolute;
   border-radius: 50%;
   background:    #fff;
   transition:    transform var( --duration-snap ) var( --ease-snap );
   /* Card-elevation token — the knob sits on top of the track, same
      relationship as a card on a surface. Lighter than the previous
      bespoke 0.2-alpha shadow at both md and sm sizes; the size-
      specific softer override below is removed (token is already
      light enough at small sizes). */
   box-shadow:    var(
                     --theme-shadow-card,
                     0 0 2px 0 rgba( 26, 35, 43, 0.04 ),
                     0 1px 3px 0 rgba( 26, 35, 43, 0.12 )
                  );
}

.mox-toggle input:checked + .mox-toggle__track {
   background: var( --mox-toggle-track-on );
}

.mox-toggle input:focus-visible + .mox-toggle__track {
   outline:        2px solid var( --mox-toggle-track-on );
   outline-offset: 2px;
}

.mox-toggle--disabled .mox-toggle__track {
   opacity: 0.5;
   cursor:  not-allowed;
}

/* One-shot transition suppression — set by host JS during initial state
   sync (e.g. after innerHTML rebuild) so the toggle doesn't animate from
   off→on while reflecting the controlling model. */
.mox-toggle--no-transition .mox-toggle__track,
.mox-toggle--no-transition .mox-toggle__track::before {
   transition: none;
}

/* --- Size: md (default, 44x24, knob 20px) — privacy / brand --- */

.mox-toggle--md             { width: 44px; height: 24px; }
.mox-toggle--md .mox-toggle__track::before {
   left:   2px;
   top:    2px;
   width:  20px;
   height: 20px;
}
.mox-toggle--md input:checked + .mox-toggle__track::before {
   transform: translateX( 20px );
}

/* --- Size: sm (36x20, knob 16px) — filter widget select-all --- */

.mox-toggle--sm             { width: 36px; height: 20px; }
.mox-toggle--sm .mox-toggle__track::before {
   left:   2px;
   top:    2px;
   width:  16px;
   height: 16px;
}
.mox-toggle--sm input:checked + .mox-toggle__track::before {
   transform: translateX( 16px );
}
/* MoxC.input — labelled text field.
   Two sizes (sm 36px, md 44px) matching the existing login/checkout
   input height. Focus ring uses the theme's interaction-default token. */

.mox-input {
   display:        flex;
   flex-direction: column;
   gap:            0;
   min-width:      0;
}

.mox-input__label {
   font-size:     13px;
   font-weight:   500;
   color:         var( --theme-content-secondary, #6b7280 );
   margin-bottom: 6px;
}

.mox-input__error {
   color:       var( --color-error, #ef4444 );
   font-weight: 500;
   margin-left: 4px;
}

.mox-input__field {
   box-sizing:  border-box;
   width:       100%;
   padding:     0 12px;
   border:      1px solid var( --theme-border-medium, #d1d5db );
   border-radius: 8px;
   background:  #fff;
   color:       var( --theme-content-primary, #111827 );
   font-family: inherit;
   outline:     none;
   transition:  border-color var( --duration-snap ) var( --ease-snap ),
                box-shadow   var( --duration-snap ) var( --ease-snap );
}

.mox-input__field::placeholder {
   color: var( --theme-content-tertiary, #9ca3af );
}

.mox-input__field:focus {
   border-color: var( --theme-interaction-default-normal, #1876dc );
   box-shadow:   0 0 0 1px var( --theme-interaction-default-normal, #1876dc );
}

.mox-input__field:disabled {
   opacity: 0.5;
   cursor:  not-allowed;
   background: var( --theme-bg-secondary, #f3f4f6 );
}

.mox-input__hint {
   font-size:  12px;
   color:      var( --theme-content-tertiary, #9ca3af );
   margin-top: 4px;
}

/* --- Size: md (default, 44px) --- */
.mox-input--md .mox-input__field {
   height:    44px;
   font-size: 14px;
}

/* --- Size: sm (36px) — for narrow cells like day/year in a date row --- */
.mox-input--sm .mox-input__field {
   height:    36px;
   font-size: 13px;
   padding:   0 10px;
}
/* MoxC.primaryBtn — pill primary button.
   Visual (blue fill, pill radius, glow via ::after) is inherited from the
   shared .as-btn-primary class defined in search-widget.css (loaded app-wide).
   This sheet only adds layout, slot spans, and size variants. */

.mox-btn-primary {
   display:         inline-flex;
   align-items:     center;
   justify-content: center;
   gap:             8px;
   flex-shrink:     0;
   white-space:     nowrap;
}

.mox-btn-primary:focus-visible {
   outline:        2px solid var( --theme-interaction-default-normal, #1876dc );
   outline-offset: 2px;
}

.mox-btn-primary:disabled {
   opacity: 0.5;
   cursor:  not-allowed;
}

.mox-btn-primary__icon {
   display:     inline-flex;
   align-items: center;
}

/* --- Size: md (default, 40px tall) --- */
.mox-btn-primary--md {
   height:    40px;
   padding:   0 20px;
   font-size: 14px;
}
.mox-btn-primary--md .mox-btn-primary__icon svg { width: 18px; height: 18px; }
.mox-btn-primary--md.mox-btn-primary--icon-only {
   width:   40px;
   padding: 0;
}

/* --- Size: sm (32px, pairs with mox-close-btn / mox-btn-secondary--sm) --- */
.mox-btn-primary--sm {
   height:    32px;
   padding:   0 14px;
   font-size: 13px;
}
.mox-btn-primary--sm .mox-btn-primary__icon svg { width: 16px; height: 16px; }
.mox-btn-primary--sm.mox-btn-primary--icon-only {
   width:   32px;
   padding: 0;
}

/* --- Size: lg (48px — hero / mobile full-width CTA) --- */
.mox-btn-primary--lg {
   height:    48px;
   padding:   0 28px;
   font-size: 16px;
}
.mox-btn-primary--lg .mox-btn-primary__icon svg { width: 20px; height: 20px; }
.mox-btn-primary--lg.mox-btn-primary--icon-only {
   width:   48px;
   padding: 0;
}
/* MoxC.radio — round radio button with a unified custom mark.
   Two sizes: md (22px, default — consent / standalone) and sm (20px,
   filter rows). The native <input> is hidden; the visible "mark" is a
   <span class=mox-radio__mark> styled as a circle whose border thickens
   on the checked state — box-sizing math leaves the white background
   showing through as a small round center, which gives the standard
   radio-button look without an extra inner-dot element.

   Mark colors are CSS custom properties so host CSS can override
   per-context without forking the component:
     --mox-radio-mark-border-off  default theme-bg-strong
     --mox-radio-mark-border-on   default theme-interaction-default-normal
     --mox-radio-mark-bg          default #fff — set to transparent on
                                  dark-chrome contexts where the active
                                  border color (--mox-radio-mark-border-on)
                                  collides with white and the disc would
                                  otherwise render solid in the checked
                                  state. Token escape hatch so consumers
                                  don't have to override structurally. */

.mox-radio {
   display:     inline-flex;
   align-items: center;
   gap:         8px;
   cursor:      pointer;
   font-family: inherit;
}

.mox-radio input[type="radio"] {
   position:       absolute;
   opacity:        0;
   width:          0;
   height:         0;
   pointer-events: none;
}

.mox-radio__mark {
   --mox-radio-mark-border-off: var( --theme-bg-strong, #c3ccd5 );
   --mox-radio-mark-border-on:  var( --theme-interaction-default-normal, #1876dc );
   --mox-radio-mark-bg:         #fff;

   display:       block;
   border:        2px solid var( --mox-radio-mark-border-off );
   border-radius: 50%;
   background:    var( --mox-radio-mark-bg );
   transition:    border-color var( --duration-snap ) var( --ease-snap ),
                  border-width var( --duration-snap ) var( --ease-snap );
   flex-shrink:   0;
   box-sizing:    border-box;
}

.mox-radio input:checked + .mox-radio__mark {
   border-color: var( --mox-radio-mark-border-on );
}

.mox-radio input:focus-visible + .mox-radio__mark {
   outline:        2px solid var( --mox-radio-mark-border-on );
   outline-offset: 2px;
}

.mox-radio__label {
   line-height: 1.3;
}

.mox-radio--disabled {
   opacity: 0.5;
   cursor:  not-allowed;
}

/* --- Size: md (default, 22px) — consent / standalone --- */

.mox-radio--md .mox-radio__mark {
   width:     22px;
   height:    22px;
   min-width: 22px;
}
.mox-radio--md input:checked + .mox-radio__mark {
   border-width: 6px;
}

/* --- Size: sm (20px) — filter rows --- */

.mox-radio--sm .mox-radio__mark {
   width:     20px;
   height:    20px;
   min-width: 20px;
}
.mox-radio--sm input:checked + .mox-radio__mark {
   border-width: 5px;
}
/* MoxC.closeBtn — shared close (×) button.
   Matches the existing login-modal close-button visual so pages that
   add `mox-close-btn` alongside their current class get an identical result. */

.mox-close-btn {
   width:           32px;
   height:          32px;
   display:         flex;
   align-items:     center;
   justify-content: center;
   background:      none;
   border:          none;
   cursor:          pointer;
   border-radius:   50%;
   padding:         0;
   color:           var( --theme-fg-primary, #3a424a );
   flex-shrink:     0;
   /* Reset user-agent <button> font-size (13.33px) so nested svgs
      sized in em resolve against the surrounding content. */
   font:            inherit;
}

/* Hover tint uses currentColor at 10% alpha so it tracks whatever the
   surrounding surface's text color is — dark tint on a light surface,
   light tint on a dark surface. Fixed --theme-bg-secondary used to
   erase the icon on dark cards (close-btn #1 in the showcase). */
.mox-close-btn:hover:not([disabled]) {
   background: color-mix( in srgb, currentColor 10%, transparent );
}

.mox-close-btn:focus-visible {
   outline:        2px solid var( --theme-interaction-default-normal, #1876dc );
   outline-offset: 2px;
}

.mox-close-btn:disabled {
   opacity: 0.5;
   cursor:  not-allowed;
}

.mox-close-btn svg {
   width:  20px;
   height: 20px;
}
/* MoxC.navArrow — circular chevron button for carousels & scroll
   affordances. Base chevron points RIGHT; direction modifiers
   rotate via transform so consumers only need one icon in their
   sprite. */

.mox-nav-arrow {
   display:         inline-flex;
   align-items:     center;
   justify-content: center;
   border:          none;
   border-radius:   50%;
   padding:         0;
   cursor:          pointer;
   color:           var( --theme-content-secondary, #3a424a );
   flex-shrink:     0;
   font:            inherit;
   transition:      background var( --duration-snap ) var( --ease-snap ),
                    opacity    var( --duration-snap ) var( --ease-snap );
}

/* Hover tint tracks surrounding text color so it works on both
   light and dark surfaces (same trick as close-btn). Ghost arrows pick
   up a translucent tint; solid arrows keep their opaque surface and
   darken it by mixing currentColor into the white background, so the
   chip never thins out over imagery. */
.mox-nav-arrow:hover:not([disabled]) {
   background: color-mix( in srgb, currentColor 12%, transparent );
}

.mox-nav-arrow--solid:hover:not([disabled]) {
   background: color-mix( in srgb, currentColor 12%, #fff );
}

.mox-nav-arrow:focus-visible {
   outline:        2px solid var( --theme-interaction-default-normal, #1876dc );
   outline-offset: 2px;
}

.mox-nav-arrow:disabled {
   opacity: 0.4;
   cursor:  not-allowed;
}

.mox-nav-arrow__icon {
   display:         inline-flex;
   align-items:     center;
   justify-content: center;
   pointer-events:  none;
}

/* --- Size: md (default, 36px) --- */
.mox-nav-arrow.mox-nav-arrow--md {
   width:  36px;
   height: 36px;
}
.mox-nav-arrow.mox-nav-arrow--md .mox-nav-arrow__icon svg {
   width:  20px;
   height: 20px;
}

/* --- Size: sm (28px) --- */
.mox-nav-arrow.mox-nav-arrow--sm {
   width:  28px;
   height: 28px;
}
.mox-nav-arrow.mox-nav-arrow--sm .mox-nav-arrow__icon svg {
   width:  16px;
   height: 16px;
}

/* --- Surface: solid (default) --- white circle over imagery/chrome --- */
.mox-nav-arrow--solid {
   background: #fff;
   /* Card-elevation token — same shadow ladder as toggle, segmented,
      and carousel-dot. */
   box-shadow: var(
                  --theme-shadow-card,
                  0 0 2px 0 rgba( 26, 35, 43, 0.04 ),
                  0 1px 3px 0 rgba( 26, 35, 43, 0.12 )
               );
}

/* --- Surface: ghost --- transparent (e.g. chip-scroll arrow that
   sits on a fading gradient, not its own chip). The host provides
   its own chip/halo via the surrounding element. */
.mox-nav-arrow--ghost {
   background: transparent;
   box-shadow: none;
}

/* --- Direction --- chevron-right is canonical (0deg); others rotate --- */
.mox-nav-arrow--right .mox-nav-arrow__icon { transform: rotate(   0deg ); }
.mox-nav-arrow--left  .mox-nav-arrow__icon { transform: rotate( 180deg ); }
.mox-nav-arrow--down  .mox-nav-arrow__icon { transform: rotate(  90deg ); }
.mox-nav-arrow--up    .mox-nav-arrow__icon { transform: rotate( -90deg ); }
/* MoxC.checkbox — squared checkbox with a unified custom mark.
   Two sizes: md (20px, default — consent / standalone) and sm (18px,
   filter rows). The native <input> is hidden; the visible "mark" is a
   <span class=mox-cb__mark> styled with a CSS-only checkmark
   (rotated borders ::after) so the visual is identical across browsers
   and OSes. Mark colors are CSS custom properties so host CSS can
   override per-context without forking the component.

   Default mode emits <label class=mox-cb>; bare mode (when the page
   wraps with its own <label> for row-level click ergonomics) emits
   <span class=mox-cb> instead. Layout (gap, padding, alignment) is
   the page's concern; this stylesheet only owns the mark + label
   typography + hidden-input + state. */

.mox-cb {
   display:     inline-flex;
   align-items: center;
   gap:         8px;
   cursor:      pointer;
   font-family: inherit;
}

.mox-cb input[type="checkbox"] {
   position:      absolute;
   opacity:       0;
   width:         0;
   height:        0;
   pointer-events: none;
}

.mox-cb__mark {
   --mox-cb-mark-border-off: var( --theme-bg-strong, #c3ccd5 );
   --mox-cb-mark-bg-on:      var( --theme-interaction-default-normal, #1876dc );
   --mox-cb-mark-tick:       #fff;

   display:         flex;
   align-items:     center;
   justify-content: center;
   border:          2px solid var( --mox-cb-mark-border-off );
   border-radius:   4px;
   background:      #fff;
   transition:      background   var( --duration-snap ) var( --ease-snap ),
                    border-color var( --duration-snap ) var( --ease-snap );
   flex-shrink:     0;
   box-sizing:      border-box;
}

.mox-cb input:checked + .mox-cb__mark {
   background:   var( --mox-cb-mark-bg-on );
   border-color: var( --mox-cb-mark-bg-on );
}

.mox-cb input:checked + .mox-cb__mark::after {
   content:      '';
   display:      block;
   border:       solid var( --mox-cb-mark-tick );
   border-width: 0 2px 2px 0;
   transform:    rotate( 45deg );
}

.mox-cb input:focus-visible + .mox-cb__mark {
   outline:        2px solid var( --mox-cb-mark-bg-on );
   outline-offset: 2px;
}

.mox-cb__label {
   line-height: 1.3;
}

.mox-cb--disabled {
   opacity: 0.5;
   cursor:  not-allowed;
}

/* --- Size: md (default, 20px) — consent / standalone --- */

.mox-cb--md .mox-cb__mark {
   width:     20px;
   height:    20px;
   min-width: 20px;
}
.mox-cb--md input:checked + .mox-cb__mark::after {
   width:      6px;
   height:     10px;
   margin-top: -2px;
}

/* --- Size: sm (18px) — filter rows --- */

.mox-cb--sm .mox-cb__mark {
   width:     18px;
   height:    18px;
   min-width: 18px;
}
.mox-cb--sm input:checked + .mox-cb__mark::after {
   width:      5px;
   height:     9px;
   margin-top: -2px;
}
/* MoxC.segmented — pill-shape radio group (iOS-style).
   Track is a neutral pill; selected segment gets a white surface with
   a soft shadow. Radios are hidden off-screen but still receive focus. */

.mox-segmented {
   display:       inline-flex;
   background:    var( --theme-bg-secondary, #eef0f3 );
   border-radius: 999px;
   padding:       4px;
   gap:           2px;
   /* Max-content keeps the control sized to its contents; put the
      root inside a flex/grid container to stretch if needed. */
   max-width:     max-content;
}

.mox-segmented__opt {
   position:        relative;
   display:         inline-flex;
   align-items:     center;
   justify-content: center;
   gap:             6px;
   border-radius:   999px;
   cursor:          pointer;
   color:           var( --theme-content-secondary, #555b6d );
   transition:      background var( --duration-snap ) var( --ease-snap ),
                    color      var( --duration-snap ) var( --ease-snap ),
                    box-shadow var( --duration-snap ) var( --ease-snap );
   user-select:     none;
   flex-shrink:     0;
   white-space:     nowrap;
}

.mox-segmented__opt:hover:not(.is-active):not(.is-disabled) {
   color: var( --theme-content-primary, #1a232b );
}

.mox-segmented__opt.is-active {
   background:  var( --theme-bg-primary, #fff );
   color:       var( --theme-content-primary, #1a232b );
   /* Card-elevation shadow — active option lifts off the segmented
      control's track. Two-layer (crispness + depth) per the shared
      shadow ladder. */
   box-shadow:  var(
                   --theme-shadow-card,
                   0 0 2px 0 rgba( 26, 35, 43, 0.04 ),
                   0 1px 3px 0 rgba( 26, 35, 43, 0.12 )
                );
   font-weight: 500;
}

.mox-segmented__opt.is-disabled {
   opacity: 0.4;
   cursor:  not-allowed;
}

.mox-segmented__input {
   /* Off-screen but focusable — clicking the label (or arrow-keys while
      any radio is focused) still toggles state natively. */
   position: absolute;
   opacity:  0;
   width:    1px;
   height:   1px;
   margin:   0;
   padding:  0;
   pointer-events: none;
}

/* Focus ring paints on the segment (the visible target), not the
   invisible input. Uses :has() so focus on the hidden input lights
   up the label. */
.mox-segmented__opt:has( .mox-segmented__input:focus-visible ) {
   outline:        2px solid var( --theme-interaction-default-normal, #1876dc );
   outline-offset: 2px;
}

.mox-segmented__icon {
   display:        inline-flex;
   align-items:    center;
   justify-content:center;
   pointer-events: none;
}

.mox-segmented__icon svg {
   width:  16px;
   height: 16px;
}

/* --- Size: md (32px default) --- */
.mox-segmented.mox-segmented--md .mox-segmented__opt {
   height:    32px;
   padding:   0 14px;
   font-size: 14px;
}

/* --- Size: sm (26px) --- */
.mox-segmented.mox-segmented--sm .mox-segmented__opt {
   height:    26px;
   padding:   0 10px;
   font-size: 13px;
}
.mox-segmented.mox-segmented--sm .mox-segmented__icon svg {
   width:  14px;
   height: 14px;
}
/* MoxC.tabs — horizontal tab strip with a sliding underline indicator.
   Ships the tablist only; panel visibility is the caller's concern. */

.mox-tabs {
   position:       relative;
   /* Overflow management so the indicator never bleeds past the track
      and long labels can scroll horizontally on narrow viewports. */
   overflow-x:     auto;
   overflow-y:     visible;
   /* Shared bottom rule every tab sits on; the indicator paints over it. */
   border-bottom:  1px solid var( --theme-border-subtle, rgba( 0, 0, 0, 0.08 ) );
   /* Hide the default horizontal scrollbar — tabs are navigable via
      arrow keys; scrolling is a last-resort overflow, not a chrome. */
   scrollbar-width: none;
}
.mox-tabs::-webkit-scrollbar { display: none; }

.mox-tabs__track {
   display:     inline-flex;
   min-width:   100%;
   /* relative so the absolute indicator positions against the track,
      not the scroll viewport — otherwise it'd drift when scrolled. */
   position:    relative;
}

.mox-tabs__tab {
   display:        inline-flex;
   align-items:    center;
   justify-content:center;
   gap:            6px;
   border:         none;
   background:     transparent;
   cursor:         pointer;
   color:          var( --theme-content-secondary, #555b6d );
   font:           inherit;
   font-weight:    500;
   white-space:    nowrap;
   padding:        0 16px;
   transition:     color      var( --duration-snap ) var( --ease-snap ),
                   background var( --duration-snap ) var( --ease-snap );
   /* Room above/below for the indicator underline and focus-visible ring. */
   flex-shrink:    0;
}

.mox-tabs__tab:hover:not([disabled]):not(.is-active) {
   color:      var( --theme-content-primary, #1a232b );
   background: color-mix( in srgb, currentColor 6%, transparent );
}

.mox-tabs__tab.is-active {
   color:       var( --theme-content-primary, #1a232b );
   font-weight: 600;
}

.mox-tabs__tab:focus-visible {
   outline:        2px solid var( --theme-interaction-default-normal, #1876dc );
   outline-offset: -2px;
}

.mox-tabs__tab[disabled] {
   opacity: 0.4;
   cursor:  not-allowed;
}

.mox-tabs__icon {
   display:        inline-flex;
   align-items:    center;
   justify-content:center;
   pointer-events: none;
}

/* Specificity bump so this wins over host 'svg.icon { width: 1em }' rules. */
.mox-tabs .mox-tabs__icon svg {
   width:  16px;
   height: 16px;
}

.mox-tabs__indicator {
   position:   absolute;
   bottom:     0;
   left:       0;
   height:     2px;
   background: var( --theme-interaction-default-normal, #1876dc );
   border-radius: 2px 2px 0 0;
   transform-origin: 0 0;
   opacity:    0;
   transition: transform var( --duration-snap ) var( --ease-snap ),
               width     var( --duration-snap ) var( --ease-snap ),
               opacity   var( --duration-snap ) var( --ease-snap );
   pointer-events: none;
}

/* --- Size: md (default, 40px) --- */
.mox-tabs.mox-tabs--md .mox-tabs__tab {
   height:    40px;
   font-size: 15px;
}

/* --- Size: sm (32px) --- */
.mox-tabs.mox-tabs--sm .mox-tabs__tab {
   height:    32px;
   font-size: 13px;
   padding:   0 12px;
}
.mox-tabs.mox-tabs--sm .mox-tabs__icon svg {
   width:  14px;
   height: 14px;
}
/* MoxC.price — currency-aware price display.

   Three variants share the same internal markup; the variant suffix
   on the root tunes size + weight only:

     mini    — search-widget calendar mini-prices, abbreviated
     normal  — hotel/flight card prices, the common case
     large   — flight detail header, sale-page hero prices

   All sub-elements (sign, currency, whole, decimal, label) compose
   inline so the price reads as one block. Currency-symbol position
   is driven by markup (priceParts.symbolPosition resolves before/after
   per locale); CSS provides the visual offset only.

   Color, weight, and gap are exposed as CSS custom properties so host
   pages can theme without forking the component:
     --mox-price-fg            text color (default theme-content-primary)
     --mox-price-fg-muted      strikethrough / label color
     --mox-price-strike-color  strikethrough rule color
     --mox-price-gap           horizontal gap between sub-blocks */

.mox-price {
   --mox-price-fg:           var( --theme-content-primary, #1a232b );
   --mox-price-fg-muted:     var( --theme-content-tertiary, #8b909a );
   --mox-price-strike-color: var( --theme-content-tertiary, #8b909a );

   /* No flex gap — sub-elements abut by default. The few places that
      need separation (after-locale currency symbol, strikethrough)
      apply a targeted margin instead. This keeps "$1,234", "-$50",
      "$210/night" all rendering tight, matching native price displays. */
   display:        inline-flex;
   align-items:    baseline;
   color:          var( --mox-price-fg );
   font-weight:    var( --theme-text-weight-body-strong, 600 );
   line-height:    1.1;
   white-space:    nowrap;
}

/* ── Variant sizing ──────────────────────────────────────────────── */

.mox-price--mini   { font-size: var( --theme-text-size-body-sm, 12px ); }
.mox-price--normal { font-size: var( --theme-text-size-body-lg, 14px ); }
.mox-price--large  { font-size: var( --theme-text-size-heading-md, 22px ); }

/* ── Sub-elements ────────────────────────────────────────────────── */

/* Strikethrough — original price shown crossed out before the sale.
   Slightly smaller and de-emphasized; the live price stays dominant. */
.mox-price__strike {
   color:           var( --mox-price-fg-muted );
   font-weight:     400;
   text-decoration: line-through;
   text-decoration-color: var( --mox-price-strike-color );
   font-size:       0.85em;
   margin-right:    0.4em;   /* visible gap before the live price */
}

/* Sign — '-' for negatives. Inherits color from root. */
.mox-price__sign {
   font-weight: inherit;
}

/* Currency symbol — three rendering modes (markup-driven):
     --before  inline before the number
     --after   inline after the number (fr-FR, de-DE behavior)
     --super   superscript variant (flight-detail hero)
   Default appearance is the same color/weight as the number; super
   shrinks and lifts. */
.mox-price__cur {
   font-weight: inherit;
}
.mox-price__cur--super {
   font-size:    0.6em;
   font-weight:  inherit;
   align-self:   flex-start;
   line-height:  1;
}
/* After-locale currency (fr-FR "1 234 €", de-DE "1.234,56 €"): a small
   left margin reproduces the natural space between number and symbol,
   since the literal NBSP from Intl is dropped at the priceParts layer. */
.mox-price__cur--after {
   margin-left: 0.25em;
}

/* Numeric block — whole + decimal grouped so they stay together as one
   visual unit. Whole is full-size; decimal is slightly smaller (a common
   typographic convention for price displays). */
.mox-price__num {
   display:       inline-flex;
   align-items:   baseline;
}
.mox-price__whole   { font-weight: inherit; }
.mox-price__decimal {
   font-size:    0.75em;
   font-weight:  inherit;
}
/* Compact suffix ('K', 'M') for abbreviated prices — matches the
   whole's weight; sized between whole and decimal so '1.2K' reads
   naturally with K visually anchored to the magnitude. */
.mox-price__compact {
   font-weight:  inherit;
}

/* Mini variant — abbreviation case ('$23K', '$1.2K') reads tight; the
   decimal stays full-size so .2 in $1.2K isn't shrunken. */
.mox-price--mini .mox-price__decimal { font-size: 1em; }

/* Label — '/night', 'total', etc. De-emphasized vs the price itself.

   Spacing rule: by default a small gap separates the price from the
   label so "$210 total" reads naturally. Labels that start with '/'
   ("/night") get no gap — the component drops the gap modifier so
   the slash visually joins to the price. labelPosition='before' uses
   margin-right; labelPosition='after' (default) uses margin-left. */
.mox-price__label {
   color:        var( --mox-price-fg-muted );
   font-weight:  400;
   font-size:    0.8em;
}
.mox-price__label--gap-after  { margin-left:  0.25em; }
.mox-price__label--gap-before { margin-right: 0.25em; }

/* ── Slot-machine ticker (bind() animation) ───────────────────────
   Active only while bind() is animating between values. Each digit
   becomes a `digit-col` (1em-tall window) containing a `digit-strip`
   of 10 stacked spans (digits 0-9). `translateY(-Nem)` selects digit
   N. Comma / period separators stay as static `mox-price__sep` spans
   so column alignment matches the plain text rendering.

   bind() collapses the slot-machine DOM back to plain html() output
   on transitionend, so these classes are short-lived.

   The structure is em-based so the ticker scales with each variant's
   font-size without per-variant tuning. */

.mox-price__digit-col {
   display:     inline-block;
   overflow:    hidden;
   height:      1em;
   line-height: 1;
   vertical-align: baseline;
   /* baseline alignment is necessary so the ticker sits on the same
      baseline as a static span — otherwise the column floats because
      its inline-block baseline defaults to its bottom edge. */
}

.mox-price__digit-strip {
   display:   inline-block;
   transform: translateY( 0 );
   /* per-strip transition is set by bind() so duration/easing can be
      tuned per controller without touching CSS. */
}

.mox-price__digit-strip > span {
   display:     block;
   height:      1em;
   line-height: 1;
   text-align:  center;
}

.mox-price__sep {
   display:        inline-block;
   vertical-align: baseline;
}
/* MoxC.modal — overlay + centered panel shell.
   Works on light and dark surfaces; panel paints its own surface so
   content stays readable regardless of what the backdrop reveals. */

/* The overlay is the panel positioning + click-catcher layer. Its visual
   dim is provided by the shell-managed scrim (MOX.shell.activate('full')
   in modal.js bind()), which paints behind this element at a lower
   z-index. Hosts without MOX.shell get an inline fallback background
   (see .mox-modal-overlay--scrim-fallback below) so the modal still
   looks like a modal at the edge / in tests. */
.mox-modal-overlay {
   position:        fixed;
   inset:           0;
   z-index:         var( --theme-z-index-modal, 1500 );
   background:      transparent;
   display:         flex;
   align-items:     center;
   justify-content: center;
   padding:         24px;
   /* Prevents a tall modal from being clipped when the viewport is shorter
      than the panel; panel scrolls its own body internally. */
   overflow:        auto;
}
.mox-modal-overlay--scrim-fallback {
   background:      rgba( 20, 24, 30, 0.56 );
}

.mox-modal {
   position:        relative;
   background:      var( --theme-bg-primary, #fff );
   color:           var( --theme-fg-primary, #3a424a );
   border-radius:   16px;
   /* Two-layer modal elevation. Token is the source of truth on
      skymonde; the fallback mirrors the token shape exactly so hosts
      without a brand layer get the same render. */
   box-shadow:      var(
                       --theme-shadow-modal,
                       0 0 2px 0 rgba( 26, 35, 43, 0.04 ),
                       0 8px 16px 0 rgba( 26, 35, 43, 0.12 )
                    );
   width:           100%;
   max-height:      calc( 100vh - 48px );
   display:         flex;
   flex-direction:  column;
   overflow:        hidden;
   outline:         none;
}

/* --- Size variants --- */
.mox-modal.mox-modal--sm { max-width: 320px; }
.mox-modal.mox-modal--md { max-width: 580px; }
.mox-modal.mox-modal--lg { max-width: 720px; }
.mox-modal.mox-modal--xl { max-width: 960px; }

/* --- Header --- */
.mox-modal__header {
   display:         flex;
   align-items:     center;
   justify-content: space-between;
   gap:             16px;
   padding:         16px 20px;
   border-bottom:   1px solid var( --theme-border-subtle, rgba( 0, 0, 0, 0.08 ) );
   flex-shrink:     0;
}

.mox-modal__title {
   margin:          0;
   font-size:       18px;
   font-weight:     600;
   line-height:     1.3;
   color:           inherit;
   /* Header can contain a long title + close btn; let title wrap or
      ellipsize rather than push the close off-panel. */
   min-width:       0;
   flex:            1 1 auto;
}

/* If there's a close button but no title, still push it to the right edge. */
.mox-modal__header:not(:has(.mox-modal__title)) {
   justify-content: flex-end;
}

.mox-modal__close {
   flex-shrink:     0;
   margin-right:    -6px;  /* optical — pull × toward visual edge of header */
}

/* --- Body --- */
.mox-modal__body {
   padding:         20px;
   overflow:        auto;
   flex:            1 1 auto;
   -webkit-overflow-scrolling: touch;
   /* Hide the scrollbar — content still scrolls. Without this, the
      scrollbar reserves a vertical strip that pushes the close button
      visually leftward inside the header. The scroll-shadow on the
      header (toggled by bind() below) is the affordance that signals
      "there's more content to scroll" instead of a visible scrollbar.
      Mirrors the .as-dialog-body pattern in skymonde.css. */
   scrollbar-width:    none;     /* Firefox */
}
.mox-modal__body::-webkit-scrollbar {
   display:         none;        /* WebKit / Blink */
}

/* When the body is scrolled, the header gains a subtle bottom shadow
   so content tucking under it reads as "scrolled past." Same token
   the skymonde mobile static-page header (.as-dialog-header) uses;
   fallback gives a reasonable default for hosts without the token. */
.mox-modal__header--scrolled {
   /* Fallback mirrors the skymonde --theme-shadow-overlay-top token
      shape (two stacked low-alpha layers — tight crispness + soft
      depth) so hosts without a brand token layer still get the same
      subtle effect, not a rougher single-layer blur. */
   box-shadow: var(
      --theme-shadow-overlay-top,
      0 0 2px 0 rgba( 26, 35, 43, 0.04 ),
      0 3px 6px 0 rgba( 26, 35, 43, 0.12 )
   );
}

/* --- Footer ---
   Optional sticky-bottom row for action buttons (Apply / Confirm /
   Reset / etc). Sits as a sibling of __body so it pins below the
   scrolling content. Styled symmetrically with the header — same
   border + padding so the chrome feels balanced top and bottom. */
.mox-modal__footer {
   display:         flex;
   align-items:     center;
   gap:             12px;
   padding:         16px 20px;
   border-top:      1px solid var( --theme-border-subtle, rgba( 0, 0, 0, 0.08 ) );
   flex-shrink:     0;
   /* Establish a stacking context so the upward scroll-shadow paints
      above the body's scrolled content rather than under it. */
   position:        relative;
   z-index:         1;
}

/* When body has more content scrollable below, the footer gains a
   subtle top shadow — mirror of the header's --scrolled affordance. */
.mox-modal__footer--scrolled {
   box-shadow: var(
      --theme-shadow-overlay-bottom,
      0 0 2px 0 rgba( 26, 35, 43, 0.04 ),
      0 -3px 6px 0 rgba( 26, 35, 43, 0.12 )
   );
}

/* --- Mobile: every modal is a bottom sheet. --------------------------
   Pinned to the bottom edge, max-height 85vh so a 15vh strip of the
   underlying page peeks above — drawer-over-content affordance instead
   of took-over-the-screen. Top corners rounded; bottom flush with the
   viewport.

   Animation pattern mirrors the skymonde mobile filter bank
   (apps/skymonde/data/files/app/hotels.css:1799-1810): off-screen
   default state (translateY(100%) + visibility:hidden) plus a
   `--open` modifier that returns the panel to translateY(0). A
   transition on transform + visibility makes both open AND close
   animate — bind() adds the modifier on the next frame after mount
   (slide up) and removes it before DOM removal (slide down). The
   visibility transition's directional flip (immediate when going
   visible, deferred to end-of-transition when going hidden) keeps
   the panel painted throughout both legs. Backdrop fade runs in
   parallel via the matching --open modifier on the overlay. */
@media ( max-width: 520px ) {
   .mox-modal-overlay {
      padding:      0;
      align-items:  flex-end;
      opacity:      0;
      transition:   opacity var( --sheet-duration-exit ) var( --sheet-ease-exit );
   }
   .mox-modal-overlay.mox-modal-overlay--open {
      opacity:      1;
      transition:   opacity var( --sheet-duration-enter ) var( --sheet-ease-enter );
   }
   .mox-modal.mox-modal--sm,
   .mox-modal.mox-modal--md,
   .mox-modal.mox-modal--lg,
   .mox-modal.mox-modal--xl {
      max-width:     100%;
      max-height:    85vh;
      border-radius: 16px 16px 0 0;
      transform:     translateY( 100% );
      visibility:    hidden;
      transition:    transform  var( --sheet-duration-exit ) var( --sheet-ease-exit ),
                     visibility var( --sheet-duration-exit );
      /* Surface extends ~100vh below the viewport edge via a solid
         box-shadow. The shadow's spread/offset paints the panel's
         own background color far below where the panel ends, so an
         over-pull (rubber-band scroll on iOS) or a drag-to-dismiss
         past the viewport reveals the panel surface, not the scrim
         backdrop. First layer is the bottom-sheet elevation token
         (intentionally single-layer per the brand — a silhouette
         rather than a stacked card); second is the surface extension. */
      box-shadow:    var(
                        --theme-shadow-bottom-sheet,
                        0 -16px 24px -8px rgba( 26, 35, 43, 0.12 )
                     ),
                     0 100vh 0 0 var( --theme-bg-primary, #fff );
   }
   .mox-modal.mox-modal--open {
      transform:     translateY( 0 );
      visibility:    visible;
      transition:    transform  var( --sheet-duration-enter ) var( --sheet-ease-enter ),
                     visibility var( --sheet-duration-enter );
   }
}
/* MoxC.secondaryBtn — pill secondary button.
   Two sizes: sm (32px), md (40px, default). Supports text-only,
   icon-only, and icon+text with leading or trailing icon. */

.mox-btn-secondary {
   display:         inline-flex;
   align-items:     center;
   justify-content: center;
   gap:             4px;
   background:      #fff;
   border:          1px solid var( --theme-content-secondary, #8b909a );
   color:           var( --theme-content-secondary, #555b6d );
   border-radius:   1000px;
   font-family:     inherit;
   font-weight:     600;
   cursor:          pointer;
   white-space:     nowrap;
   flex-shrink:     0;
   transition:      background var( --duration-snap ) var( --ease-snap );
}

/* Icon + text: pull the icon toward the pill's curved end so its center
   aligns with the midpoint of the end-semicircle (as if the semicircle
   closed back into a full circle, the icon would be centered inside it).
   Negative margin = end-radius - icon-radius - padding. Excluded for
   icon-only buttons which are square with padding: 0. */
.mox-btn-secondary--md:not(.mox-btn-secondary--icon-only) > .mox-btn-secondary__icon:first-child { margin-left:  -5px; }
.mox-btn-secondary--md:not(.mox-btn-secondary--icon-only) > .mox-btn-secondary__icon:last-child  { margin-right: -5px; }
.mox-btn-secondary--sm:not(.mox-btn-secondary--icon-only) > .mox-btn-secondary__icon:first-child { margin-left:  -4px; }
.mox-btn-secondary--sm:not(.mox-btn-secondary--icon-only) > .mox-btn-secondary__icon:last-child  { margin-right: -4px; }

.mox-btn-secondary:hover:not([disabled]) {
   background: var( --theme-bg-secondary, #f3f4f6 );
}

.mox-btn-secondary:focus-visible {
   outline:        2px solid var( --theme-interaction-default-normal, #1876dc );
   outline-offset: 2px;
}

.mox-btn-secondary:disabled {
   opacity: 0.5;
   cursor:  not-allowed;
}

.mox-btn-secondary__icon {
   display:     inline-flex;
   align-items: center;
}

/* --- Size: md (default, 40px tall) --- */
.mox-btn-secondary--md {
   height:    40px;
   padding:   0 16px;
   font-size: 14px;
}
.mox-btn-secondary--md .mox-btn-secondary__icon svg { width: 18px; height: 18px; }
.mox-btn-secondary--md.mox-btn-secondary--icon-only {
   width:   40px;
   padding: 0;
}

/* --- Size: sm (32px tall — pairs with mox-close-btn) --- */
.mox-btn-secondary--sm {
   height:    32px;
   padding:   0 12px;
   font-size: 13px;
}
.mox-btn-secondary--sm .mox-btn-secondary__icon svg { width: 16px; height: 16px; }
.mox-btn-secondary--sm.mox-btn-secondary--icon-only {
   width:   32px;
   padding: 0;
}
/* MoxC.accordion — expand/collapse container.
   Standard direction: chevron points RIGHT when closed, rotates 90° to
   point DOWN when open. Matches the skymonde / filter-bank idiom that
   callers expect. Host CSS can override the rotation if a different
   convention is needed (e.g. chevron-down rotating to up). */

.mox-acc__trigger {
   display:         flex;
   align-items:     center;
   justify-content: space-between;
   width:           100%;
   padding:         0;
   background:      none;
   border:          none;
   cursor:          pointer;
   font:            inherit;
   color:           inherit;
   text-align:      left;
}

.mox-acc__trigger:focus-visible {
   outline:        2px solid var( --theme-interaction-default-normal, #1876dc );
   outline-offset: 2px;
}

.mox-acc__trigger:disabled {
   opacity: 0.5;
   cursor:  not-allowed;
}

.mox-acc__title {
   flex: 1;
}

.mox-acc__chevron {
   display:         flex;
   align-items:     center;
   justify-content: center;
   width:           16px;
   height:          16px;
   color:           var( --theme-content-tertiary, #8b909a );
   transition:      transform var( --accordion-duration-exit ) var( --accordion-ease-exit );
   flex-shrink:     0;
}

/* Specificity bumped (.mox-acc prefix) so we win over any host
   sheet's generic 'svg.icon { width: 1em }' rule. Without this the
   chevron <svg> falls back to its intrinsic 300×150 viewport, spills
   out of the 16×16 parent, and steals click hits from the items
   below it (overlapping ~150px of vertical space). */
.mox-acc .mox-acc__chevron svg {
   width:  16px;
   height: 16px;
}

.mox-acc--open .mox-acc__chevron {
   transform:  rotate( 90deg );
   transition: transform var( --accordion-duration-enter ) var( --accordion-ease-enter );
}

/* Body: host controls height (max-height animation or grid-rows); the
   component just hides/shows via max-height:0 when closed if no caller
   JS is wired. Callers that manage body height themselves (e.g. filter
   bank with measured scrollHeight) override these defaults via bodyCls.
   Default behaviour is strict: a closed accordion shows ONLY its
   header. The body is fully collapsed — zero height, zero vertical
   padding — so any host padding on bodyCls doesn't leak content into
   view. Hosts that want padding while closed can override these rules
   on their bodyCls. */
.mox-acc__body {
   overflow:    hidden;
   max-height:  none;
   box-sizing:  border-box;
   transition:  max-height     var( --accordion-duration-exit ) var( --accordion-ease-exit ),
                padding-top    var( --accordion-duration-exit ) var( --accordion-ease-exit ),
                padding-bottom var( --accordion-duration-exit ) var( --accordion-ease-exit );
}

.mox-acc--open .mox-acc__body {
   transition:  max-height     var( --accordion-duration-enter ) var( --accordion-ease-enter ),
                padding-top    var( --accordion-duration-enter ) var( --accordion-ease-enter ),
                padding-bottom var( --accordion-duration-enter ) var( --accordion-ease-enter );
}

.mox-acc:not(.mox-acc--open) .mox-acc__body {
   max-height:     0;
   padding-top:    0;
   padding-bottom: 0;
}
/* MoxC.badge — small inline counter pill.
   Single size: 16px tall, full pill (border-radius 100px), brand fill,
   white bold xxs text. Per-site height variants ride on cls (e.g. an
   .as-ht-fb-badge override that bumps height/min-width to 18px).

   Colors are CSS custom properties so host CSS can override per-context
   without forking the component:
     --mox-badge-bg  default theme-content-brand
     --mox-badge-fg  default #fff */

.mox-badge {
   --mox-badge-bg: var( --theme-content-brand, #1876dc );
   --mox-badge-fg: #fff;

   display:         inline-flex;
   align-items:     center;
   justify-content: center;
   min-width:       16px;
   height:          16px;
   padding:         3px 5px;
   border-radius:   100px;
   background:      var( --mox-badge-bg );
   color:           var( --mox-badge-fg );
   font-size:       var( --theme-text-size-body-xxs, 11px );
   font-weight:     var( --theme-text-weight-body-strong, 600 );
   line-height:     var( --theme-text-height-body-xxs, 1 );
   box-sizing:      border-box;
}
/* MoxC.stepper — +/- number counter.
   Three sizes. Buttons are circular with hover bg; disabled state dims
   opacity and disables cursor. Value display has a min-width so single-
   and double-digit values don't shift the button spacing. */

.mox-stepper {
   display:     inline-flex;
   align-items: center;
   gap:         8px;
   flex-shrink: 0;
}

.mox-stepper__dec,
.mox-stepper__inc {
   display:         inline-flex;
   align-items:     center;
   justify-content: center;
   background:      #fff;
   border:          1px solid var( --theme-border-medium, #d1d5db );
   border-radius:   50%;
   padding:         0;
   cursor:          pointer;
   color:           var( --theme-content-primary, #1a232b );
   transition:      background   var( --duration-snap ) var( --ease-snap ),
                    border-color var( --duration-snap ) var( --ease-snap ),
                    opacity      var( --duration-snap ) var( --ease-snap );
   flex-shrink:     0;
}
.mox-stepper__dec:hover:not([disabled]),
.mox-stepper__inc:hover:not([disabled]) {
   background: var( --theme-bg-secondary, #f3f4f6 );
}
.mox-stepper__dec:focus-visible,
.mox-stepper__inc:focus-visible {
   outline:        2px solid var( --theme-interaction-default-normal, #1876dc );
   outline-offset: 2px;
}
.mox-stepper__dec[disabled],
.mox-stepper__inc[disabled],
.mox-stepper__dec[data-disabled],
.mox-stepper__inc[data-disabled] {
   opacity: 0.4;
   cursor:  not-allowed;
}

.mox-stepper__val {
   display:     inline-block;
   text-align:  center;
   font-weight: 600;
   font-family: inherit;
   color:       var( --theme-content-primary, #1a232b );
}

/* --- Size: md (default, 32px button, 16px glyph) --- */
.mox-stepper--md .mox-stepper__dec,
.mox-stepper--md .mox-stepper__inc {
   width:  32px;
   height: 32px;
}
.mox-stepper--md .mox-stepper__dec svg,
.mox-stepper--md .mox-stepper__inc svg {
   width:  16px;
   height: 16px;
}
.mox-stepper--md .mox-stepper__val {
   min-width: 24px;
   font-size: 15px;
}

/* --- Size: sm (28px button, 14px glyph) --- */
.mox-stepper--sm .mox-stepper__dec,
.mox-stepper--sm .mox-stepper__inc {
   width:  28px;
   height: 28px;
}
.mox-stepper--sm .mox-stepper__dec svg,
.mox-stepper--sm .mox-stepper__inc svg {
   width:  14px;
   height: 14px;
}
.mox-stepper--sm .mox-stepper__val {
   min-width: 20px;
   font-size: 14px;
}

/* --- Size: lg (40px button, 20px glyph) --- */
.mox-stepper--lg .mox-stepper__dec,
.mox-stepper--lg .mox-stepper__inc {
   width:  40px;
   height: 40px;
}
.mox-stepper--lg .mox-stepper__dec svg,
.mox-stepper--lg .mox-stepper__inc svg {
   width:  20px;
   height: 20px;
}
.mox-stepper--lg .mox-stepper__val {
   min-width: 32px;
   font-size: 16px;
}
