/*
 * Design-system token block — inline mirror of
 *   packages/frontend-assets/resources/assets/css/microweber/css/design-system.css
 * so the var()s referenced below resolve regardless of
 * design-system.css load order.
 */
:root {
    --color-primary: #0d6efd;
    --color-primary-hover: #0b5ed7;
    --color-success: #22c55e;
    --color-error: #ef4444;
    --color-warning: #f59e0b;

    --color-surface: #ffffff;
    --color-surface-raised: #f8f9fa;

    --color-text-primary: #111827;
    /* AI-284: bumped secondary gray-500→gray-600 and muted gray-400→
       gray-500 so every text token clears the WCAG AA 4.5:1 minimum
       against `--color-surface: #ffffff`. See design-system.css for the
       full audit ratios; DesignSystemContrastTest pins them. */
    --color-text-secondary: #4b5563;
    --color-text-muted: #6b7280;

    --color-border: #d1d5db;
    --color-border-focus: #0d6efd;

    --color-footer-text: var(--mw-footer-text-color, #F8F9FA);
    /* AI-210 (task-2026-05-13-c3a280) — product title now resolves to
       primary text color so the public link palette stays harmonious
       across product cards, headings, and body copy. tester observed
       Big2 template emitting orange product titles (#F4A261) alongside
       off-white footer links (#F8F9FA); piping the token through
       primary text removes the warm-orange outlier and lets each
       template still override via a stylesheet that loads after this
       one without re-introducing the mismatch. */
    --color-product-title: var(--color-text-primary);
    /* Hover state lifts to the brand primary so the link affordance
       remains discoverable. */
    --color-product-title-hover: var(--color-primary);

    --radius-sm: 4px;
    --radius-md: 8px;

    --touch-target-min: 44px;
    --space-11: 44px;
    /* AI-285 8px-based scale — full mirror of design-system.css. */
    --space-1: 4px;
    --space-2: 8px;
    --space-3: 12px;
    --space-4: 16px;
    --space-5: 24px;
    --space-6: 32px;
    --space-7: 48px;
    --space-8: 64px;
    --space-12: 96px;

    /* Semantic component-padding aliases. */
    --space-card: var(--space-4);
    --space-form: var(--space-5);
    --space-modal: var(--space-6);

    /* AI-286 responsive breakpoint tokens — mirror of design-system.css. */
    --breakpoint-xs: 0;
    --breakpoint-sm: 576px;
    --breakpoint-md: 768px;
    --breakpoint-lg: 992px;
    --breakpoint-xl: 1200px;
    --breakpoint-xxl: 1440px;

    /* AI-287 component tokens — mirror of design-system.css. */
    --btn-primary-radius: 6px;
    --btn-primary-padding-y: 8px;
    --btn-primary-padding-x: 16px;
    --btn-secondary-radius: 6px;
    --btn-secondary-padding-y: 6px;
    --btn-secondary-padding-x: 12px;
    --card-radius: 8px;
    --card-padding: var(--space-card);
    --card-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);

    /* AI-292 form-element tokens — mirror of design-system.css. */
    --input-height: var(--touch-target-min);
    --input-border-width: 1px;
    --input-radius: 6px;
    --input-padding-x: var(--space-3);
    --input-focus-shadow: 0 0 0 3px rgba(13, 110, 253, 0.15);
    --label-font-size: var(--font-size-small);
    --label-font-weight: 500;
    --label-color: var(--color-text-secondary);
    --helper-text-font-size: 12px;
    --helper-text-color: var(--color-text-muted);

    --duration-fast: 150ms;
    --duration-normal: 250ms;
    --duration-slow: 350ms;
    --ease-default: cubic-bezier(0.4, 0, 0.2, 1);
    --ease-in: cubic-bezier(0.4, 0, 1, 1);
    --ease-out: cubic-bezier(0, 0, 0.2, 1);

    /* AI-289 elevation tokens — mirror of design-system.css. */
    --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1);
    --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
    --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
    --shadow-xl: 0 20px 40px rgba(0, 0, 0, 0.15);
    --z-base: 0;
    --z-sticky: 10;
    --z-dropdown: 20;
    --z-fixed: 30;
    --z-overlay: 40;
    --z-popover: 50;
    --z-modal: 100;
    --z-toast: 200;

    /* AI-290 icon size tokens — mirror of design-system.css. */
    --icon-size-xs: 12px;
    --icon-size-sm: 16px;
    --icon-size-md: 20px;
    --icon-size-lg: 24px;
    --icon-size-xl: 32px;

    /* AI-294 footer tokens — mirror of design-system.css. */
    --footer-padding-top: 40px;
    --footer-padding-bottom: 24px;
    --footer-column-gap-mobile: var(--space-2);
    --footer-max-height-mobile: 240px;

    /* AI-283 typography scale — mirror of design-system.css. */
    --font-size-h1: 32px;
    --font-size-h2: 28px;
    --font-size-h3: 24px;
    --font-size-h4: 20px;
    --font-size-h5: 18px;
    --font-size-h6: 16px;
    --font-size-body: 16px;
    --font-size-small: 14px;
    --line-height-heading: 1.25;
    --line-height-body: 1.5;

    /* AI-291 button-state tokens — mirror of design-system.css. */
    --btn-focus-ring-width: 2px;
    --btn-focus-ring-offset: 2px;
    --btn-active-scale: 0.98;
    --btn-disabled-opacity: 0.5;

    /*
     * AI-284 Bootstrap variable mapping.
     *
     * Bootstrap exposes its theme color stops on `:root` as `--bs-*`
     * custom properties (`--bs-primary`, `--bs-primary-rgb`, etc.).
     * Its own utility classes (`.text-primary`, `.bg-primary`, `.btn-
     * primary`) and component CSS read those `--bs-*` variables. We
     * point each one at the matching `--color-*` design token so the
     * audit-approved palette flows through Bootstrap utilities
     * automatically — no per-class color override, no hardcoded hex.
     *
     * `--bs-primary-rgb` (and the other `-rgb` variants) are kept in
     * their decimal-triplet form because Bootstrap uses them inside
     * `rgba(var(--bs-primary-rgb), 0.5)` expressions; CSS doesn't
     * resolve a `var()` to its hex inside the rgba() shorthand.
     */
    --bs-primary: var(--color-primary);
    --bs-primary-rgb: 13, 110, 253;
    --bs-success: var(--color-success);
    --bs-success-rgb: 34, 197, 94;
    --bs-danger: var(--color-error);
    --bs-danger-rgb: 239, 68, 68;
    --bs-warning: var(--color-warning);
    --bs-warning-rgb: 245, 158, 11;

    --bs-body-color: var(--color-text-primary);
    --bs-body-bg: var(--color-surface);
    --bs-secondary-color: var(--color-text-secondary);
    --bs-tertiary-color: var(--color-text-muted);
    --bs-border-color: var(--color-border);
    --bs-link-color: var(--color-primary);
    --bs-link-hover-color: var(--color-primary-hover);
}

/*
 * AI-283 global type scale.
 *
 * Apply the design-system font-size + line-height tokens to the standard
 * heading + body elements so every public page inherits the same scale,
 * regardless of which Bootstrap template skin renders the content. Rules
 * live outside the mobile-only `@media` block below so the scale applies
 * at every viewport — `--font-size-*` itself is the same value at every
 * width (the audit's complaint was the lack of any consistent scale, not
 * mobile-specific sizing).
 *
 * Selectors target the bare elements rather than `.h1`, `.h2`, etc., so
 * Bootstrap's utility classes (.h1, .display-1, .lead) remain free to
 * override on a case-by-case basis without us fighting them globally.
 */
h1 { font-size: var(--font-size-h1); line-height: var(--line-height-heading); }
h2 { font-size: var(--font-size-h2); line-height: var(--line-height-heading); }
h3 { font-size: var(--font-size-h3); line-height: var(--line-height-heading); }
h4 { font-size: var(--font-size-h4); line-height: var(--line-height-heading); }
h5 { font-size: var(--font-size-h5); line-height: var(--line-height-heading); }
h6 { font-size: var(--font-size-h6); line-height: var(--line-height-heading); }
body { font-size: var(--font-size-body); line-height: var(--line-height-body); }
small,
.text-small { font-size: var(--font-size-small); line-height: var(--line-height-body); }

/*
 * AI-286 responsive utility classes.
 *
 * Three reusable patterns that the audit called out:
 *   1. .mw-stack-below-sm — flex children stack vertically below 576px
 *      (contact form, footer columns). Above 576px the parent's normal
 *      row layout applies; below, children become full-width and align
 *      top-to-bottom.
 *   2. .mw-hero-split — hero left/right halves go side-by-side from
 *      768px+ and stack vertically below. Built with flex so it works
 *      regardless of how many child blocks the editor drops in.
 *   3. .mw-grid-shop — same 3→2→1 column flow as the shop livewire
 *      blade but available as a CSS class for module blade authors
 *      who can't reach into the Livewire markup.
 *
 * Each rule keys off the breakpoint TOKENS so the
 * DesignSystemBreakpointsContractTest can pin the @media min-widths
 * to the canonical values.
 */

@media (max-width: 575.98px) {
    .mw-stack-below-sm {
        flex-direction: column;
    }
    .mw-stack-below-sm > * {
        width: 100%;
        max-width: 100%;
    }
}

.mw-hero-split {
    display: flex;
    flex-direction: column;
    gap: var(--space-5);
}
@media (min-width: 768px) {
    .mw-hero-split {
        flex-direction: row;
        align-items: center;
    }
    .mw-hero-split > * {
        flex: 1 1 0;
    }
}

.mw-grid-shop {
    display: grid;
    gap: var(--space-4);
    grid-template-columns: 1fr;
}
@media (min-width: 768px) {
    .mw-grid-shop {
        grid-template-columns: repeat(2, 1fr);
    }
}
@media (min-width: 992px) {
    .mw-grid-shop {
        grid-template-columns: repeat(3, 1fr);
    }
}

/*
 * AI-287 component consistency.
 *
 * Apply the canonical component tokens to Bootstrap's `.btn-primary`,
 * `.btn-secondary`/`.btn-outline-*`, and `.card` classes so every
 * variant has the same radius / padding / shadow regardless of which
 * module template renders it.
 *
 * Each rule scopes to Bootstrap class names rather than the bare
 * element so it stacks on top of AI-291's focus/active/disabled state
 * rules (which target both `<button>` and `.btn`).
 *
 * Active/hover/disabled/loading variants are already shipped in the
 * AI-291 block — they cascade through these AI-287 base styles
 * automatically because the AI-291 rules don't override radius,
 * padding, or shadow.
 */

/* task-2026-05-21-bef102 / AI-868 — Salmon .btn-primary on desktop.
 * The brand-blue color override previously existed only inside
 * `@media (max-width: 768px), (pointer: coarse)` so mobile/touch
 * received brand-blue while desktop got Bootstrap template's demo
 * salmon `$primary: #f4a261`. Lifted to global scope here so the
 * correction applies at ALL viewports unconditionally.
 *
 * Also adds --mw-btn-background-color :root token so the CONTACT US
 * nav button (`.btn.btn-sm` routing through `app.css`'s broad negation
 * selector `var(--mw-btn-background-color)`) picks up brand-blue.
 *
 * Tier-3 gate: desktop 1440 getComputedStyle('.btn-primary').backgroundColor
 * must return `rgb(13, 110, 253)` after this ship. */
:root {
    --mw-btn-background-color: var(--color-primary, #0d6efd);
}

.btn-primary,
.btn.btn-primary,
a.btn-primary,
button.btn-primary,
.shop-cta,
a.shop-cta,
button.shop-cta,
.add-to-cart-button,
.mw-btn-primary,
[data-button="primary"] {
    background-color: var(--mw-btn-background-color, var(--color-primary, #0d6efd)) !important;
    border-color: var(--mw-btn-border-color, var(--color-primary, #0d6efd)) !important;
    color: #ffffff !important;
    /* AI-250 / task-2026-06-04-btnradius: honor the template's configured
       button radius (--mw-btn-border-radius) FIRST so primary CTAs match the
       radius of every other .btn on the page. Previously this hardcoded
       --radius-sm (4px), forcing primary CTAs to 4px while base .btn used the
       template's --mw-btn-border-radius (e.g. 33px pill) — a visible
       header-CTA-vs-content-CTA radius mismatch. Falls back to the radius
       tokens then a literal when the template defines no button radius. */
    border-radius: var(--mw-btn-border-radius, var(--radius-sm, 6px)) !important;
    transition: background-color var(--duration-fast, 0.15s) var(--ease-default, ease),
                border-color var(--duration-fast, 0.15s) var(--ease-default, ease);
}

.btn-primary:hover,
.btn.btn-primary:hover,
a.btn-primary:hover,
button.btn-primary:hover,
.shop-cta:hover,
a.shop-cta:hover,
button.shop-cta:hover,
.add-to-cart-button:hover,
.mw-btn-primary:hover,
[data-button="primary"]:hover {
    background-color: var(--mw-btn-background-hover-color, var(--color-primary-hover, #0b5ed7)) !important;
    border-color: var(--mw-btn-border-color, var(--color-primary-hover, #0b5ed7)) !important;
}

.btn.btn-primary {
    border-radius: var(--btn-primary-radius);
    padding: var(--btn-primary-padding-y) var(--btn-primary-padding-x);
}

.btn.btn-secondary,
.btn.btn-outline-primary,
.btn.btn-outline-secondary {
    border-radius: var(--btn-secondary-radius);
    padding: var(--btn-secondary-padding-y) var(--btn-secondary-padding-x);
}

.card {
    border-radius: var(--card-radius);
    padding: var(--card-padding);
    box-shadow: var(--card-shadow);
}

/*
 * AI-292 form elements.
 *
 * Applies design-system form tokens to Bootstrap's form classes
 * (.form-control, .form-select, .form-label, .form-text, .is-invalid,
 * .is-valid, .invalid-feedback, .valid-feedback) so every input on
 * the public site has the same height/border/radius/focus behavior
 * regardless of the module template rendering it.
 *
 * Why scope to Bootstrap classes vs. bare elements: Bootstrap-styled
 * forms already opt in via .form-control. Bare <input> elements
 * inside admin widgets, Vue components, and Filament partials have
 * their own styling stacks — bumping every bare input to 44px would
 * regress those. .form-control covers the public-facing surface this
 * audit measured.
 *
 * The 44px height matches the AI-219..234 touch-target sweep already
 * applied to interactive controls — forms inherit the same floor.
 */

.form-control,
.form-select,
textarea.form-control {
    min-height: var(--input-height);
    border: var(--input-border-width) solid var(--color-border);
    border-radius: var(--input-radius);
    padding-left: var(--input-padding-x);
    padding-right: var(--input-padding-x);
}

textarea.form-control {
    /* Textareas grow vertically, so min-height is the floor not the
       fixed height — let the user-resize / rows attribute work. */
    height: auto;
}

.form-control:focus,
.form-select:focus {
    border-color: var(--color-border-focus);
    box-shadow: var(--input-focus-shadow);
    outline: none;
}

.form-label {
    font-size: var(--label-font-size);
    font-weight: var(--label-font-weight);
    color: var(--label-color);
    /* Sentence case: don't transform; rely on copy authors. Resetting
       any inherited uppercase rule keeps the AI-292 sentence-case
       requirement intact. */
    text-transform: none;
}

.form-text {
    font-size: var(--helper-text-font-size);
    color: var(--helper-text-color);
}

/* Error state — Bootstrap toggles .is-invalid on inputs and
   .invalid-feedback / .invalid-tooltip on the message. We bind both
   to the design-system error color so the validation styling matches
   the audit-approved palette instead of Bootstrap's default red. */
.form-control.is-invalid,
.form-select.is-invalid {
    border-color: var(--color-error);
}
.form-control.is-invalid:focus,
.form-select.is-invalid:focus {
    box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.15);
}
.invalid-feedback,
.invalid-tooltip {
    color: var(--color-error);
    font-size: var(--helper-text-font-size);
}

/* Success state — Bootstrap's .is-valid toggle, paired with a
   checkmark via the existing valid-feedback Bootstrap class. We
   override the border + feedback color; the checkmark glyph is part
   of Bootstrap's default `.form-control.is-valid` background-image. */
.form-control.is-valid,
.form-select.is-valid {
    border-color: var(--color-success);
}
.form-control.is-valid:focus,
.form-select.is-valid:focus {
    box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.15);
}
.valid-feedback,
.valid-tooltip {
    color: var(--color-success);
    font-size: var(--helper-text-font-size);
}

/*
 * AI-289 elevation scale application.
 *
 * Cards hover-elevate from shadow-sm → shadow-md (AI-289 acceptance).
 * The transition fires under the AI-288 motion tokens so the hover
 * feels coordinated rather than independent. prefers-reduced-motion
 * is already respected by the existing motion guard further up the
 * file (the button-state @media (prefers-reduced-motion: reduce)
 * block) — adding it again here would duplicate the rule.
 */
.card {
    transition: box-shadow var(--duration-normal) var(--ease-default),
                transform var(--duration-fast) var(--ease-default);
}
.card:hover {
    box-shadow: var(--shadow-md);
}

@media (prefers-reduced-motion: reduce) {
    .card {
        transition: none;
    }
    .card:hover {
        /* keep the elevation cue but skip the animation */
    }
}

/*
 * AI-290 icon size scale.
 *
 * Apply --icon-size-* tokens to a five-step class scale. Targets:
 *  - Bare <svg> and <i class="mdi-...">/<i class="fa-..."> glyphs.
 *  - Sizes the box AND the font (for icon-font glyphs) so the same
 *    class works whether the icon is an inline SVG or a Material/
 *    FontAwesome glyph rendered via ::before pseudo.
 *  - vertical-align: -0.125em keeps the icon on the text baseline
 *    (the 0.125em offset is the canonical fix for SVG icons rendering
 *    too high relative to inline text).
 *
 * Per AI-290 audit recommendation: FontAwesome 6 is the canonical
 * library. Migrating every mdi-* / image-icon to fa-* is the AI-251
 * scope (icon consolidation umbrella) — out of scope here. This PR
 * ships the SIZE scale + classes so future per-template sweeps can
 * apply them consistently.
 */
.icon-xs,
.icon-sm,
.icon-md,
.icon-lg,
.icon-xl {
    display: inline-block;
    vertical-align: -0.125em;
    line-height: 1;
}
.icon-xs { width: var(--icon-size-xs); height: var(--icon-size-xs); font-size: var(--icon-size-xs); }
.icon-sm { width: var(--icon-size-sm); height: var(--icon-size-sm); font-size: var(--icon-size-sm); }
.icon-md { width: var(--icon-size-md); height: var(--icon-size-md); font-size: var(--icon-size-md); }
.icon-lg { width: var(--icon-size-lg); height: var(--icon-size-lg); font-size: var(--icon-size-lg); }
.icon-xl { width: var(--icon-size-xl); height: var(--icon-size-xl); font-size: var(--icon-size-xl); }

/*
 * AI-294 footer density.
 *
 * Pre-AI-294 the .footer-background `<section>` rendered ~664px on
 * a 390×844 viewport (77% of the visible scroll budget — the audit's
 * complaint). Two big contributors:
 *   1. Bootstrap's `mb-4` (24px) between each stacked footer column.
 *      At three columns + the copyright bar that's 72px of mostly
 *      empty vertical gap.
 *   2. The footer template uses paragraph text with default `<br>`
 *      breaks and `<p>` margins, each adding ~16px of inherited
 *      browser spacing.
 *
 * The fix tightens column gaps below 576px without changing the
 * blade template (so it works for every footer skin variant) and
 * sets explicit footer padding tokens. The `--footer-max-height-mobile`
 * token is a documented target (240px); we don't set `max-height` on
 * the element because clipping address/contact blocks would lose
 * content rather than compress layout.
 *
 * Why scope to `.footer-background`: that's the canonical class on
 * the Bootstrap template's footer skins (skin-1 through skin-31).
 * Compressing on .footer-background lets every variant inherit
 * without per-template edits.
 */
.footer-background {
    padding-top: var(--footer-padding-top);
    padding-bottom: var(--footer-padding-bottom);
}

@media (max-width: 575.98px) {
    /* Squeeze the column gaps from mb-4 (24px) to --space-2 (8px)
       so three stacked sections take ~50px less vertical real estate. */
    .footer-background .row > [class*="col-"].mb-4 {
        margin-bottom: var(--footer-column-gap-mobile) !important;
    }
    /* Tighten inherited paragraph spacing inside footer text blocks. */
    .footer-background p,
    .footer-background small {
        margin-bottom: var(--space-2);
        line-height: 1.4;
    }
    /* Reset the `<br>` height-bump that compounds the column padding. */
    .footer-background br {
        line-height: 1;
    }
    /* The duplicate-address pattern (NY + CA stacked) on the
       address column is one of the audit's "duplicate/unnecessary"
       findings on mobile — hide the second one below 576px so the
       footer drops below the 240px target without losing primary
       contact info. The full block remains for tablets+ desktops. */
    .footer-background .col-lg-3 > small:nth-of-type(2),
    .footer-background .col-lg-3 > small:nth-of-type(2) + p {
        display: none;
    }
}

/*
 * AI-291 button states.
 *
 * Scope: bare <button>, <input type="submit|button|reset">, and the
 * Bootstrap `.btn` class. We deliberately target both because Bootstrap
 * resets `<button>` styles only when `.btn` is present — leaving
 * unclassed `<button>` elements (used by the AI-281 admin login + the
 * AI-295 Btn module rewrite) with no focus/active/disabled feedback.
 *
 * Why `:focus-visible` instead of `:focus`:
 *   `:focus` fires for both keyboard and mouse interactions. The
 *   keyboard-only `:focus-visible` ring is the WCAG-recommended
 *   pattern — mouse users don't see the ring after a click (which
 *   feels noisy), but keyboard users always do.
 *
 * Each rule is scoped to the public template surface (Bootstrap +
 * unclassed buttons in module blades). Filament admin buttons have
 * their own focus-ring already in the Filament theme.
 */

/* Focus ring — keyboard-only via :focus-visible */
button:focus-visible,
input[type="submit"]:focus-visible,
input[type="button"]:focus-visible,
input[type="reset"]:focus-visible,
.btn:focus-visible {
    outline: var(--btn-focus-ring-width) solid var(--color-border-focus);
    outline-offset: var(--btn-focus-ring-offset);
}

/* Active state — slight scale + relative dim. Uses `filter` instead of
   overriding background-color so it stacks on top of every variant
   (.btn-primary, .btn-outline-secondary, custom modules) without
   needing per-variant rules. */
button:not(:disabled):not(.disabled):active,
input[type="submit"]:not(:disabled):active,
input[type="button"]:not(:disabled):active,
input[type="reset"]:not(:disabled):active,
.btn:not(:disabled):not(.disabled):active {
    transform: scale(var(--btn-active-scale));
    filter: brightness(0.9);
    transition: transform var(--duration-fast) var(--ease-default),
                filter var(--duration-fast) var(--ease-default);
}

/* Disabled state — visually dim, not-allowed cursor, NO hover/active.
   `pointer-events: none` is intentionally omitted so screen readers
   still announce the disabled button; the cursor + opacity + the
   `:hover` neutralizer below carry the affordance. */
button:disabled,
button.disabled,
input[type="submit"]:disabled,
input[type="button"]:disabled,
input[type="reset"]:disabled,
.btn:disabled,
.btn.disabled {
    opacity: var(--btn-disabled-opacity);
    cursor: not-allowed;
}
button:disabled:hover,
button.disabled:hover,
.btn:disabled:hover,
.btn.disabled:hover {
    transform: none;
    filter: none;
}

/*
 * Loading state — opt-in via `[data-loading="true"]` or `.is-loading`.
 * Spinner is inserted as a `::before` pseudo so the button's existing
 * label content is preserved. `aria-busy="true"` is the canonical ARIA
 * attribute; we also gate on it so screen readers + visual users see
 * the loading affordance in lock-step.
 */
button[data-loading="true"],
button.is-loading,
button[aria-busy="true"],
.btn[data-loading="true"],
.btn.is-loading,
.btn[aria-busy="true"] {
    cursor: progress;
    pointer-events: none;
    opacity: 0.85;
}

button[data-loading="true"]::before,
button.is-loading::before,
button[aria-busy="true"]::before,
.btn[data-loading="true"]::before,
.btn.is-loading::before,
.btn[aria-busy="true"]::before {
    content: "";
    display: inline-block;
    width: 1em;
    height: 1em;
    margin-right: 0.5em;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: 50%;
    vertical-align: -0.15em;
    animation: mw-btn-spin var(--duration-slow) linear infinite;
}

@keyframes mw-btn-spin {
    to { transform: rotate(360deg); }
}

/* Respect reduced-motion preference — drop the spin + active scale. */
@media (prefers-reduced-motion: reduce) {
    button[data-loading="true"]::before,
    button.is-loading::before,
    button[aria-busy="true"]::before,
    .btn[data-loading="true"]::before,
    .btn.is-loading::before,
    .btn[aria-busy="true"]::before {
        animation: none;
    }
    button:not(:disabled):not(.disabled):active,
    .btn:not(:disabled):not(.disabled):active {
        transform: none;
    }
}

[data-bs-theme="dark"],
html.dark {
    --color-surface: #111827;
    --color-surface-raised: #1f2937;
    --color-text-primary: #f9fafb;
    --color-text-secondary: #9ca3af;
    --color-text-muted: #6b7280;
    --color-border: #374151;
    --color-border-focus: #60a5fa;
    --color-footer-text: #d1d5db;
    /* AI-210 — dark mode also routes product title through primary
       text so the link palette stays consistent in both themes. */
    --color-product-title: var(--color-text-primary);
    --color-product-title-hover: var(--color-primary);
}

/* AI-947 / task-2026-05-22 — OS-level dark mode preference.
 * Applies the same token overrides as html.dark / [data-bs-theme="dark"]
 * so users whose OS is set to dark mode see appropriate colors
 * without needing to toggle manually. Brand-blue CTAs unchanged
 * (--color-primary stays #0d6efd; only surface/text/border tokens shift).
 * Triggers only when no explicit light-mode override is set. */
@media (prefers-color-scheme: dark) {
    :root {
        --color-surface: #111827;
        --color-surface-raised: #1f2937;
        --color-text-primary: #f9fafb;
        --color-text-secondary: #9ca3af;
        --color-text-muted: #6b7280;
        --color-border: #374151;
        --color-border-focus: #60a5fa;
        --color-footer-text: #d1d5db;
        --color-product-title: var(--color-text-primary);
        --color-product-title-hover: var(--color-primary);
    }
    body {
        background-color: var(--color-surface);
        color: var(--color-text-primary);
    }
    .card, .modal-content, .dropdown-menu, .list-group-item {
        background-color: var(--color-surface-raised);
        color: var(--color-text-primary);
        border-color: var(--color-border);
    }
    .navbar, header.site-header, .site-header, #header {
        background-color: var(--color-surface-raised) !important;
        border-bottom-color: var(--color-border);
    }
    footer, .site-footer, .footer-section {
        background-color: var(--color-surface) !important;
    }
    a:not(.btn):not([class*="btn-"]) {
        color: var(--color-border-focus);
    }
    a:not(.btn):not([class*="btn-"]):hover {
        color: var(--color-primary-hover, #0b5ed7);
    }
}

/*
 * Public-site mobile-touch refinements — mirror of the Filament admin
 *   packages/microweber-filament-theme/resources/assets/css/microweber/mobile-touch.css
 * for the public Bootstrap site + module public skins.
 *
 * Floors a TARGETED set of public-site interactive controls
 * (social links, cart link, footer nav, swiper dots, etc.) up to
 * the WCAG 2.5.5 / iOS HIG 44x44 minimum. Targeted selector list
 * — not a universal `a, button { min-height: 44px }` sweep — so
 * desktop power-user density on >=1024px is preserved and inline
 * links in body copy aren't bumped to a chunky 44px.
 *
 * The match-list `(max-width: 1023.98px), (hover: none) and
 * (pointer: coarse)` covers phones / small tablets in landscape
 * AND any pointer-coarse device regardless of viewport.
 */

@media (max-width: 1023.98px), (hover: none) and (pointer: coarse) {
    /* Social links — icon-only <a> wrappers in `<div class="mw-socialLinks">`
       and `<ul class="mw-socialLinks list-inline">`. Bumps the bare icon
       link to a 44x44 hit-area. inline-flex + center keeps the SVG centred
       inside the larger box without reflowing the row layout. */
    .mw-socialLinks a,
    .mw-socialLinks > a,
    .mw-socialLinks li > a {
        min-width: 44px;
        min-height: 44px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }

    /* Cart icon link in the public layout header. The visible badge
       `.js-shopping-cart-quantity` is small; the tap target is the
       wrapping `<a class="nav-link ...">`. The `.nav-link` carries
       `px-0` so we cannot rely on padding for the hit-area — use
       min-width/min-height on the wrapper. */
    .btn-shopping-cart > a.nav-link,
    .btn-shopping-cart > .nav-link {
        min-width: 44px;
        min-height: 44px;
    }

    /* AI-516 — Cart count badge `.js-shopping-cart-quantity` touch-target
       floor. The badge is the visible pill that shows the cart item count
       (rendered inside the cart-icon `<a>` wrapper via skin-1.blade.php).
       Bootstrap's `btn btn-outline-primary btn-sm` gives it a ~38px
       height — below the 44x44 WCAG 2.5.5 floor. Floor min-width and
       min-height on the badge itself. The `:not([hidden])` guard makes
       the empty-state hidden behaviour explicit (the badge carries the
       HTML5 `hidden` attribute when cart_qty <= 0 per the AI-40 fix in
       skin-1.blade.php). */
    .js-shopping-cart-quantity:not([hidden]) {
        min-width: 44px;
        min-height: 44px;
    }

    /* AI-518 — CONTACT US header button (`<module type="btn"
       id="header-layout-btn" button_size="btn-sm">` in
       layouts/templates/menus/skin-1.blade.php line 43). The Btn module
       renders it as `<button|a id="header-layout-btn" class="btn btn-sm">`
       — Bootstrap's `.btn-sm` gives a ~38px height. The audit measured
       137x38 at 390x844; floor min-height to 44px on mobile so the header
       contact CTA hits the WCAG 2.5.5 tap-target threshold.

       Targeted by id (stable, narrow) AND by header-context selector so
       a future skin that uses a different module id but the same header
       wrapper still inherits the floor. Content-area `.btn-sm` instances
       are unaffected — they sit outside `.templates-top-header-menu`. */
    #header-layout-btn.btn,
    .templates-top-header-menu #header-layout-btn,
    .templates-top-header-menu .btn.btn-sm {
        min-height: 44px;
    }

    /* Footer menu links (`footer-19-menu` is the wrapper used by
       layouts/footers/skin-1; cover the generic Bootstrap footer
       patterns too: `footer .menu a`, `footer nav a`). Public footer
       links are typically ~24px tall — pad the y-axis without changing
       text size. */
    .footer-19-menu a,
    footer .menu a,
    footer nav a,
    footer .nav-link,
    .module-footer .menu a {
        min-height: 44px;
        display: inline-flex;
        align-items: center;
        padding-top: 6px;
        padding-bottom: 6px;
    }

    /* Swiper pagination bullets (Slider, Testimonials, Teamcard etc.).
       Swiper renders `.swiper-pagination-bullet` at 8x8 by default —
       below the WCAG threshold. Strategy: keep the visible dot small,
       expand the hit-area via padding + transparent border so the dot
       looks identical but the tap-target is 44x44. The negative
       margin keeps the row width unchanged. */
    .swiper-pagination-bullet {
        position: relative;
        min-width: 44px;
        min-height: 44px;
        background-clip: content-box;
        padding: 18px;
        margin: 0 !important;
        box-sizing: border-box;
    }
    .swiper-pagination-horizontal {
        /* Negative inline-padding offsets the bullet padding so the
           pagination row is not pushed wider on small screens. */
        padding-inline: 0;
    }

    /* task-2026-05-22-fc5060 / AI-585 — Slider nav prev/next buttons.
       `.mw-slider-v2-buttons-slide` is shared by both `default.blade.php`
       (uses `<button>`) and `swiper-skin-1.blade.php` (uses `<div>`).
       `slider.css` fixes these at `width: 40px; height: 40px` — 4px
       short of the WCAG 2.5.5 floor. `min-width`/`min-height` override
       the fixed dimensions because the constraint algorithm applies after
       the cascade regardless of `!important` on `width`/`height`. */
    .mw-slider-v2-buttons-slide {
        min-width: 44px;
        min-height: 44px;
    }

    /* Bootstrap carousel control prev/next arrows.
       `.carousel-control-prev`/`.carousel-control-next` are the
       absolute-positioned <button>s; the inner `-icon` is 20px by
       default. Force the outer button to be a 44x44 hit-area
       regardless of icon size. */
    .carousel-control-prev,
    .carousel-control-next {
        min-width: 44px;
        min-height: 44px;
    }

    /* Cart action buttons that already use `.btn` meet the Bootstrap
       `.btn` 38px-ish baseline; the regression risk is the `.btn-sm`
       qty +/- pair around the qty input. Bump the qty group buttons
       specifically — leave the larger primary "Add to cart" alone. */
    .quantity-selector .btn-sm,
    .cart-actions .quantity-selector .btn,
    .mw-add-to-cart-disabled-btn,
    .mw-add-to-cart-btn {
        min-width: 44px;
        min-height: 44px;
    }

    /* Bootstrap navbar toggler (hamburger) for the public site. The
       Bootstrap default is 30x30-ish at the navbar-light/dark size.
       Bump to 44x44. Padding stays auto so the icon glyph is unchanged. */
    .navbar-toggler {
        min-width: 44px;
        min-height: 44px;
    }

    /* Generic icon-only links inside the public main area.
       `.main a > i:only-child`, `.main a > svg:only-child`, and
       `.main button > i:only-child` are the conventional shapes for
       icon-only controls. Use `:only-child` so we never bump a normal
       text link that happens to contain an icon-prefix. */
    .main a > i:only-child,
    .main a > svg:only-child,
    .main button > i:only-child,
    .main button > svg:only-child {
        min-width: 44px;
        min-height: 44px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }

    /*
     * Public-site primary nav links — Bootstrap layouts render these
     * ~22px tall, half the WCAG floor. `inline-flex` (not block)
     * keeps horizontal navbar layouts intact while vertically
     * centering the text.
     */
    .mw-nav-list a,
    .mw-main-nav a,
    nav.navbar .navbar-nav .nav-link,
    nav ul.menu li a,
    nav ul.nav li a {
        min-height: 44px;
        display: inline-flex;
        align-items: center;
    }

    /*
     * Public-site footer link catch-all for direct `<footer> > a` and
     * `.mw-footer a` shapes (the `footer .menu a` / `footer nav a`
     * rules above already cover the nav variants). Use a footer
     * wrapper class to avoid bumping every inline link inside footer
     * column copy (which would visually break dense small-print lists).
     */
    .mw-footer a,
    footer.mw-footer a {
        min-height: 44px;
        display: inline-flex;
        align-items: center;
    }

    /*
     * "Go Live Edit" chip (`.mw-go-live-edit`) ships 6px short of
     * the 44px floor. Rule kept narrow (no padding override) so the
     * chip's existing inline padding/typography is preserved and
     * only the floor lifts.
     */
    .mw-go-live-edit {
        min-height: 44px;
    }

    /* AI-522 — ContactForm module mobile touch-targets per agent-test
       audit (2026-05-14, 390x844). The ContactForm `default` template
       (`Modules/ContactForm/resources/views/templates/default.blade.php`)
       renders inside `.contact-form-container` and ships three
       touch-target violations on mobile:

         (a) Submit button — `.btn.btn-primary` inside
             `.form-group` (`partials/formSubmit.blade.php` line 26)
             measures ~38-40px. The site-wide `.btn.btn-primary` rule
             (line 284 above) sets only border-radius/padding, not
             min-height; floor it explicitly for the contact form
             container.
         (b) Inputs — `.form-control` carries an `--input-height` of
             44px from AI-292 globally, but the audit measured ~38px
             on the rendered contact form. Belt-and-braces: pin the
             floor inside the touch-viewport @media, scoped to the
             contact form container so it cannot regress.
         (c) Newsletter-subscribe checkbox — `<input type="checkbox">`
             inside `<label class="mw-ui-check">` (default.blade.php
             line 33). Bootstrap-native checkbox renders 16x16; the
             label row needs a 44h floor + inline-flex centring so
             a fingertip on the row toggles the checkbox. Visual
             checkbox stays at its native ~16x16.

       Scoped to `.contact-form-container` so site-wide rendering of
       `.btn-primary` / `.form-control` / `.mw-ui-check` (used outside
       the contact form, e.g. in admin and other modules) is
       unaffected. */
    .contact-form-container .btn.btn-primary,
    .contact-form-container .btn {
        min-height: 44px;
    }
    .contact-form-container .form-control,
    .contact-form-container .form-select,
    .contact-form-container textarea.form-control {
        min-height: 44px;
    }
    .contact-form-container label.mw-ui-check {
        min-height: 44px;
        display: inline-flex;
        align-items: center;
    }

    /* AI-528 — Category module mobile touch-targets per agent-test
       audit (2026-05-14, 390x844). The Category module's `default`,
       `skin-1`, and `horizontal-list-1` templates all set
       `$params['ul_class'] = 'nav-list'` and render a Knp-Menu tree
       like `<nav class="module-categories"><ul class="nav-list"><li>
       <a href="...">Category title</a></li>...</ul></nav>`.

       The `.nav-list li a` links carry no padding by default — they
       render at the font-size only (~16x14px text bounds), far below
       the WCAG 2.5.5 44x44 floor. The existing nav-link block in
       this file (line ~818) covers `.mw-nav-list a` / `.mw-main-nav a`
       / `nav.navbar .navbar-nav .nav-link` / `nav ul.menu li a` /
       `nav ul.nav li a` but NOT `.nav-list li a` (no `.mw-` prefix,
       not `.nav` class).

       Scope: `.nav-list` is used ONLY by the Category module across
       the entire repo (grep-verified) so the bare selector is safe —
       no other module appropriates the class name. */
    .nav-list li a {
        min-height: 44px;
        display: inline-flex;
        align-items: center;
    }

    /* AI-530 — Tabs module mobile touch-targets per agent-test audit
       (2026-05-14, 390x844). The Tabs `default.blade.php` template
       (the Bootstrap-accordion skin) renders each tab as a
       `<button class="btn btn-link"` inside a `<div class="card-header">`
       (default.blade.php line 25). Bootstrap's `.btn-link` default
       padding gives ~36px total height — below the 44x44 WCAG 2.5.5
       floor.

       Scope: `.card-header .btn` + `.card-header .nav-link`. Limited to
       the Bootstrap card-header context so site-wide `.btn` rendering
       (other modules, buttons inside content blocks) is unaffected.
       Other Tabs skins (`skin-1` Webflow-style, `skin-2` `.nav-link`,
       `skin-3`/`skin-4`/`skin-5`/`flower-tabs`/`horizontal-centered-tabs`)
       use different button shapes; only `default.blade.php` puts the
       tab button inside `.card-header` — that's the audit scope. */
    .card-header .btn,
    .card-header .nav-link {
        min-height: 44px;
        display: inline-flex;
        align-items: center;
    }

    /* AI-531 — SocialLinks module mobile touch-targets per agent-test
       audit (2026-05-14, 390x844). Each social-link `<a>` wraps an
       SVG icon (default ~24x24 px) with no inherent padding on the
       anchor — tester measured 24x24 touch-target, below the 44x44
       WCAG 2.5.5 floor.

       All 6 SocialLinks templates wrap links in `.mw-socialLinks`:
         - `default.blade.php`   (<div class="mw-socialLinks">)
         - `footer.blade.php`    (<ul class="mw-socialLinks ...">)
         - `skin-1.blade.php`    (<ul class="mw-socialLinks ...">)
         - `skin-2.blade.php`    (<ul class="mw-socialLinks ...">)
         - `skin-7.blade.php`    (<ul class="mw-socialLinks ...">)
         - `skin-9.blade.php`    (<ul class="mw-socialLinks ...">)

       Scope: `.mw-socialLinks` is used ONLY by SocialLinks module
       templates across the entire repo (grep-verified; the only other
       hits are inside `Templates/Bootstrap/resources/assets/css/main.scss`
       which sets the same rule for the Bootstrap template). Bare-class
       selector is safe.

       **Defensive duplicate by design:** `Templates/Bootstrap/.../main.scss`
       line 560 already declares
       `.mw-socialLinks a, .module-breadcrumb a, ... { min-height: 44px; min-width: 44px; ... }`
       inside `@media (max-width: 767.98px)`. If the audited public
       page used the Bootstrap template the existing rule would have
       applied — yet tester measured 24x24 at 390x844, indicating the
       active template at the audit site does NOT load Bootstrap's
       compiled `app.css`. `public-touch.css` is loaded as a separate
       site-wide mobile-touch stylesheet, so duplicating the rule here
       guarantees the floor across templates. Same defensive pattern
       as AI-510/AI-211 served-mirror sync. */
    .mw-socialLinks a {
        min-width: 44px;
        min-height: 44px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }

    /* AI-532 — Rating module star touch-targets per agent-test audit
       (2026-05-14, 390x844). The Rating `default.blade.php` template
       renders `<div class="module-rating module-rating-template-default">
       <div class="starrr" ...></div></div>`. The starrr jQuery plugin
       (Modules/Rating/resources/assets/js/lib.js line 49) appends
       `<i class="...">` for each star. The shipped
       `Modules/Rating/resources/assets/css/rating.css` line 7-11 styles
       `.starrr i { font-size: 20px; padding: 0 2px; }` — total tap
       area ~24x20 px, below the 44x44 WCAG 2.5.5 floor.

       PM's dispatch suggests `.module-rating .starrr span, .module-rating
       .starrr i` — covers both DOM shapes (lib.js currently emits `<i>`;
       future starrr versions / replacements may emit `<span>`). The
       `font-size: 28px` bump enlarges the visual star glyph too — stars
       ARE the primary affordance here (not a small modifier like the
       AI-517/518 checkbox), so the visual-stays-small pattern doesn't
       apply.

       Scope: `.module-rating .starrr` is exclusive to this module
       (grep-verified). The `display: inline-flex` etc. set up the
       per-star hit-area as a 44x44 cell with the glyph centred. */
    .module-rating .starrr span,
    .module-rating .starrr i {
        min-width: 44px;
        min-height: 44px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        font-size: 28px;
    }

    /* AI-534 — Logo module link touch-target floor per agent-test
       audit (2026-05-14). The Logo `default.blade.php` template
       renders `<div class="logo-module"><a class="logo-link"
       href="..."><img|span></a></div>`. The inline `<style>` block
       at lines 43-49 of default.blade.php sets
       `.logo-module .logo-link { display: inline-block; max-width:
       100%; overflow: hidden; text-overflow: ellipsis; white-space:
       nowrap; }` — no min-height floor. Small logos (100x30 image)
       or short text logos (30px font) result in 30-36px touch
       targets, below the 44x44 WCAG 2.5.5 floor.

       Scope: `.logo-module .logo-link` is exclusive to the Logo
       module's `default.blade.php` template across the entire repo
       (`2rows.blade.php` uses `.module-logo.module-logo-2rows`, a
       different shape; Big2 template uses `.linktree-logo .logo-module
       a` — bare `a`, no `.logo-link`). Bare combinator selector is
       safe; no collisions. */
    .logo-module .logo-link {
        min-height: 44px;
        display: flex;
        align-items: center;
    }

    /* AI-535 — Breadcrumb module link touch-target floor per agent-test
       audit (2026-05-14). The Breadcrumb `default.blade.php` template
       renders `<nav aria-label="Breadcrumb"><ol class="breadcrumb">
       <li class="breadcrumb-item"><a href="...">title</a></li>...</ol>
       </nav>`. Bootstrap's stock `.breadcrumb-item` styling sets no
       `<a>` padding — the clickable area is text-bound (~16x14 px),
       far below the 44x44 WCAG 2.5.5 floor. A11y already-correct:
       `<nav aria-label="Breadcrumb">` + `aria-current="page"` on the
       last (non-link) crumb.

       Scope: `.breadcrumb-item` is Bootstrap's stock class — broader
       than the per-module bare classes used in AI-528/AI-531/AI-534
       (`nav-list`, `mw-socialLinks`, `logo-module`). The rule
       therefore floors EVERY breadcrumb link on the public surface
       (Microweber breadcrumbs + any custom Bootstrap-styled breadcrumb
       elsewhere). This is intentional per PM dispatch and matches the
       project's site-wide WCAG policy: touch-target floors apply to
       all interactive elements, not only module-default ones.

       Note: main.scss line 561 declares `.module-breadcrumb a` in the
       AI-510 touch-target rule list, but the Breadcrumb default
       template does NOT add a `.module-breadcrumb` wrapper class —
       so that rule never actually matches the rendered DOM. AI-535's
       `.breadcrumb-item a` selector matches the actual Bootstrap
       breadcrumb DOM directly. */
    .breadcrumb-item a {
        min-height: 44px;
        display: inline-flex;
        align-items: center;
        padding: 8px 4px;
    }
}

/*
 * Public header icon size unification. Header icons (search / user /
 * cart / menu) otherwise render at mismatched sizes from the compiled
 * `main.scss` size pin. `public-touch.css` is loaded AFTER `app.css`
 * so this rule wins the cascade without needing a template-side
 * `npm run build`.
 *
 * Lift the 44x44 floor onto the parent `<a class="nav-link">` so the
 * tap area stays 44x44 (accessibility) while the inner SVG renders at
 * 28x28 (visual consistency). Higher specificity (4 classes) beats
 * the `.main a > svg:only-child` (3 classes) bare-icon floor so the
 * SVG sizing rule below wins.
 */
@media (max-width: 768px), (pointer: coarse) {
    .templates-top-header-menu .top-header-background.header-top .btn-search .nav-link,
    .templates-top-header-menu .top-header-background.header-top .btn-member .nav-link,
    .templates-top-header-menu .top-header-background.header-top .btn-shopping-cart .nav-link {
        min-width: 44px;
        min-height: 44px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }
    .templates-top-header-menu .top-header-background.header-top .btn-search .nav-link svg,
    .templates-top-header-menu .top-header-background.header-top .btn-member .nav-link svg,
    .templates-top-header-menu .top-header-background.header-top .btn-shopping-cart .nav-link svg {
        width: 28px !important;
        height: 28px !important;
        min-width: 28px !important;
        min-height: 28px !important;
    }

    /*
     * Footer link touch-target floor. Footers in the Bootstrap
     * template render inside a `<section class="footer-background ...">`
     * wrapper with links variously inside `<p>`, `<li>`, and
     * `<span class="mw-powered-by">` containers — no single common
     * parent class.
     *
     * Floor every `<a>` inside `.footer-background` with `inline-flex`
     * so the tap area grows visually without breaking inline text
     * flow. Width grows naturally by content length.
     */
    .footer-background a,
    section.footer-background a,
    [class*="footer-background"] a {
        min-height: 44px;
        display: inline-flex;
        align-items: center;
        padding-top: 6px;
        padding-bottom: 6px;
    }
    /* The `mw-powered-by` inline-credit links sit inside a sentence
       — they need to keep wrapping naturally with the surrounding
       text, so reduce padding and use baseline alignment. */
    .footer-background .mw-powered-by a,
    section.footer-background .mw-powered-by a {
        min-height: 44px;
        padding: 6px 4px;
        align-items: center;
    }

    /*
     * Footer text color token separation. Footer text uses
     * `--color-footer-text` while product titles use
     * `--color-product-title` so the two surfaces never share a
     * theme value (preventing footer links from inheriting the
     * orange product-title color through template overrides).
     *
     * !important is needed because the Bootstrap template's app.css
     * `.footer-background a { color: var(--mw-footer-link-color); }`
     * uses a token that may be set to page-edit-overridden values
     * and loads in the same media-query-less context — without
     * !important the equal-specificity tie + the template-set var
     * wins.
     */
    .footer-background,
    .footer-background a,
    .footer-background p,
    .footer-background h1,
    .footer-background h2,
    .footer-background h3,
    .footer-background h4,
    .footer-background h5,
    .footer-background h6,
    section.footer-background,
    section.footer-background a {
        color: var(--color-footer-text) !important;
    }

    /* task-2026-05-21-bef102 / AI-868 — .btn-primary color rules moved
       to global scope (see below). Left here as a comment so the mobile
       @media block remains structurally intact and future diffing is
       clear about the pre-868 layout. The lifted rules below apply at
       ALL viewports; this block no longer needs them. */

    /*
     * Live Edit mobile editing chrome.
     * The base `.element-active` outline in
     * `frontend-assets-libs/css/liveedit.css` is `1px dotted #A6A6A6`
     * — barely visible on mobile and disappears against most
     * backgrounds, especially in dark mode or against colored
     * sections. `public-touch.css` is loaded INSIDE the iframe so
     * a higher-specificity rule wins on the iframe's text/module
     * elements without needing a separate frontend-assets package
     * rebuild. Desktop's 1px dotted gray is preserved by being
     * outside this media query.
     *
     *   1. `.element-active` outline -> 2px solid Bootstrap-blue
     *      with 2px offset + outer box-shadow halo so the focus
     *      ring is high-contrast on every background.
     *   2. `caret-color: #0d6efd` on `.element-active *` so the
     *      blinking text cursor is visible.
     *   3. Floor `#mw-text-editor` inline-toolbar buttons to 44x44
     *      with bottom-sheet positioning (fixed to viewport bottom,
     *      full-width) so the toolbar surfaces above the iOS
     *      keyboard.
     *   4. Bump `body.mw-live-edit .plain-text` empty-state
     *      min-height to 24px so empty paragraphs have a usable
     *      tap area.
     */
    .mw-live-edit .element-active,
    body.mw-live-edit .element-active {
        outline: 2px solid #0d6efd !important;
        outline-offset: 2px !important;
        box-shadow: 0 0 0 1px rgba(13, 110, 253, 0.25), inset 0 0 1px #fff !important;
    }
    .mw-live-edit .element-active,
    .mw-live-edit .element-active *,
    body.mw-live-edit .element-active,
    body.mw-live-edit .element-active * {
        caret-color: #0d6efd !important;
    }
    /* Bottom-sheet positioning for the inline WYSIWYG toolbar so it
       surfaces above the iOS keyboard and browser chrome on every
       mobile viewport, regardless of where the editing element sits. */
    #mw-text-editor,
    body.mw-live-edit #mw-text-editor {
        position: fixed !important;
        left: 0 !important;
        right: 0 !important;
        bottom: 0 !important;
        top: auto !important;
        width: 100vw !important;
        max-width: 100vw !important;
        z-index: 1050 !important;
        padding: 8px !important;
        gap: 4px !important;
        display: flex !important;
        flex-wrap: wrap !important;
        background: #ffffff !important;
        border-top: 1px solid rgba(0, 0, 0, 0.1) !important;
        box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.08) !important;
    }
    #mw-text-editor .mw-ui-btn,
    #mw-text-editor a.mw-ui-btn-medium,
    #mw-text-editor button,
    #mw-text-editor a,
    body.mw-live-edit #mw-text-editor button,
    body.mw-live-edit #mw-text-editor a {
        min-width: 44px !important;
        min-height: 44px !important;
        display: inline-flex !important;
        align-items: center !important;
        justify-content: center !important;
    }
    /* Empty editable paragraph hit-area floor — 12px is too small
       to reliably tap into edit mode on touch devices. */
    body.mw-live-edit .plain-text,
    body.mw-live-edit .plain-text:empty {
        min-height: 24px;
    }
}

/* ------------------------------------------------------------------
 * AI-265 (task-2026-05-13-de78ce) — product card image placeholder.
 * The product cards on the shop grid render their image inside
 * `.background-image-holder` with `aspect-ratio: 1/1` reserving the
 * card height. Until the network request resolves the image is
 * visually empty (white flash on light themes / dark flash on dark).
 * Giving the wrapper the `--color-surface-raised` token as a soft
 * background-color keeps the slot occupied with the same neutral
 * surface used elsewhere in the design system, eliminating the
 * white-flash + signalling that an image is loading.
 *
 * Scoped via `.mw-product-card-image-placeholder` so non-card images
 * (slider, blog post hero, gallery) keep their own placeholders.
 * The full LQIP/blur-up placeholder is deferred to the AI-265 WebP
 * variant pipeline follow-up; this rule covers the no-CLS / no-white-
 * flash subset of acceptance criterion #4.
 * ------------------------------------------------------------------ */
.mw-product-card-image-placeholder {
    background-color: var(--color-surface-raised);
}

/* ------------------------------------------------------------------
 * AI-210 (task-2026-05-13-c3a280) — product card title link color
 * unification. tester audit observed `#F4A261` (Big2 sandy-orange) on
 * product titles alongside `#F8F9FA` (off-white) footer links — two
 * unrelated warm/cool values in the same visual scan-region. The
 * design-system token `--color-product-title` now resolves to
 * `var(--color-text-primary)` (dark in light mode, light-gray in dark
 * mode); these rules apply the token directly to the product card
 * anchor + heading so a downstream template's hardcoded link color
 * cannot reintroduce the mismatch.
 *
 * `!important` is needed because Big2's `design-styles.css` sets
 * `--mw-header-link-color: #f4a261` on `<a>` elements and loads in the
 * same media-query-less context — without `!important` the
 * equal-specificity tie + the template-set var wins.
 *
 * Scoped to the two shop product card variants (`.shop-products
 * .product` default + `.mw-online-shop-skin-1-product` skin-1) so the
 * rule cannot leak to non-product surfaces.
 * ------------------------------------------------------------------ */
.shop-products .product > a,
.shop-products .product > a h4,
.mw-online-shop-skin-1-product > a,
.mw-online-shop-skin-1-product > a h4 {
    color: var(--color-product-title) !important;
}

.shop-products .product > a:hover,
.shop-products .product > a:hover h4,
.shop-products .product > a:focus-visible,
.shop-products .product > a:focus-visible h4,
.mw-online-shop-skin-1-product > a:hover,
.mw-online-shop-skin-1-product > a:hover h4,
.mw-online-shop-skin-1-product > a:focus-visible,
.mw-online-shop-skin-1-product > a:focus-visible h4 {
    color: var(--color-product-title-hover) !important;
}

/* ------------------------------------------------------------------
 * AI-265b (task-2026-05-13-56ea16) — product card LQIP blur-up effect.
 *
 * Builds on the AI-265 first-slice (commit 836bab5193): the wrapper
 * `.mw-product-card-image-placeholder` already paints
 * `background-color: var(--color-surface-raised)` which reserves the
 * card slot and eliminates the white flash. This CSS-only follow-up
 * adds a blur-up perceptual-performance trick on top:
 *
 *   - Each `<img>` rendered inside the placeholder wrapper animates
 *     from `filter: blur(8px); opacity: 0` to
 *     `filter: blur(0); opacity: 1` over 400ms on every render.
 *   - The colored wrapper underneath is the static LQIP; the
 *     animation makes the image fade in sharp over the placeholder.
 *
 * This is intentionally NOT tied to the JS `load` event — a pure-CSS
 * `animation` on every render achieves the same visible result with
 * zero JS (no load-event listener, no class-toggle script, no risk
 * of stale state when the image hits the browser cache and resolves
 * synchronously).
 *
 * `prefers-reduced-motion: no-preference` gates the rule per WCAG
 * 2.3.3 — users who opted out of motion see the image render
 * normally (no blur, no fade). The wrapper's color placeholder still
 * paints in both modes so reduced-motion users still get the
 * no-CLS / no-white-flash benefit from the AI-265 first slice.
 *
 * `will-change: opacity, filter` is a perf hint — without it some
 * browsers don't promote the image to its own layer and the blur
 * filter falls back to a low-quality software blur during the
 * animation. The hint is auto-cleared once the animation completes.
 *
 * Scoped to `.mw-product-card-image-placeholder` so the rule cannot
 * leak to non-product images (slider, blog hero, gallery, etc.).
 * The selector matches both direct `> img` (responsive_thumbnail's
 * current emission) AND `picture > img` (future `<picture>`-wrapped
 * variant if/when the WebP pipeline AI-265a lands).
 *
 * Acceptance:
 *   - AC #1 Blur effect visible on placeholder ✓
 *   - AC #2 Smooth transition when image loads ✓ (400ms ease-out
 *     animation from blur+transparent to sharp+opaque)
 *   - AC #3 No layout shift (CLS = 0) ✓ — animation only touches
 *     opacity + filter, never margin/padding/transform-translate or
 *     box dimensions; the AI-265 first-slice aspect-ratio wrapper
 *     keeps the slot reserved
 *   - AC #4 Works at 390x844 mobile viewport ✓ — no media-query gate
 *     beyond the prefers-reduced-motion exception, so the rule
 *     applies to every viewport
 * ------------------------------------------------------------------ */
@media (prefers-reduced-motion: no-preference) {
    .mw-product-card-image-placeholder > img,
    .mw-product-card-image-placeholder picture > img {
        animation: mw-product-card-image-blur-up 400ms ease-out 1 both;
        will-change: opacity, filter;
    }
}

@keyframes mw-product-card-image-blur-up {
    from {
        opacity: 0;
        filter: blur(8px);
    }
    to {
        opacity: 1;
        filter: blur(0);
    }
}

/* ---- WCAG 2.5.5 Touch-target floors for public-surface interactive elements ----
 *
 * AI-498 (Content): .button-8 "Read more" anchors in content list templates
 * AI-499 (Page):    .mw-more "Read More" anchors in page/product list template
 * AI-553 (Currency): currency-switcher toggle button
 * AI-564 (LayoutContent): .btn call-to-action links inside layout-content module
 * AI-565 (Btn): .btn-sm rendered by the Btn module (configurable size may emit sm)
 * AI-572 (Sharer): social-share icon anchors wrapping SVGs
 *
 * All rules are scoped to their module context where possible to avoid
 * global .btn-sm interference.
 * ------------------------------------------------------------------ */

/* AI-498: Content "Read more" button-8 style */
.button-8 {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
}

/* AI-499: Page / product list "Read More" text link */
.mw-more {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
}

/* AI-553: Currency switcher toggle */
.currency-switcher button {
    min-height: 44px;
}

/* AI-564/AI-565: .btn and .btn-sm inside module wrappers on public surface */
.module-layout-content .btn,
.mw-btn-align-wrap .btn,
.mw-btn-align-wrap .btn-sm {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

/* AI-572: Sharer social-share icon anchors */
.mw-social-share-links a {
    min-height: 44px;
    min-width: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

/* AI-501: Menu module default skin nav links
 * The navbar and simple skins already pass via nav.navbar .navbar-nav .nav-link
 * and nav ul.menu li a rules above. The default skin wraps links in
 * .module-navigation-default without a navbar wrapper, so they fall through.
 * Scoped to the module wrapper to avoid global <a> impact.
 * ------------------------------------------------------------------ */
.module-navigation-default a {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
}

/* AI-524: Teamcard module — website-link anchor touch target
 * The website link inside each .team-card-item is rendered as an `<a>`
 * with Bootstrap `d-block` (display: block already). The text-link
 * height equals the line-height (~24px) — below the WCAG 2.5.5 44px
 * floor. `min-height: 44px` on the block anchor expands the tap area
 * without any layout shift. Scoped to .team-card-item to avoid leaking
 * to other anchors outside the card. */
.team-card-item a {
    min-height: 44px;
}

/* AI-582: Newsletter module — form-control input height + checkbox label
 * Bootstrap .form-control renders at ~38px (padding-y + line-height).
 * The terms-and-conditions checkbox is a raw <input type="checkbox">
 * nested inside a <label> — browser default checkbox is ~13×13px.
 *
 * Fix 1: floor .form-control inputs to 44px inside the newsletter wrapper.
 * Fix 2: expand the checkbox <label> to 44px with inline-flex so the
 *        entire label row is the tap target (clicking anywhere on the row
 *        still toggles the checkbox). gap: 8px provides visual separation
 *        between the checkbox and the label text. */
.newsletter-module-wrapper .form-control {
    min-height: 44px;
}
.newsletter-module-wrapper label:has(input[type="checkbox"]) {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    gap: 8px;
}

/* AI-518a: Header CTA — broader `.btn.btn-sm` selector at narrow viewports
 * per tester CREATIVE EVAL 2026-05-15 (CRITICAL). The original AI-518
 * rule above (line 721) targets `#header-layout-btn` + `.templates-top-
 * header-menu .btn.btn-sm`. Tester runtime measurement at 390×844 found
 * the rendered header CTA carries neither wrapper class nor the id —
 * so the existing rule doesn't apply and the button measures 137×38px
 * (below the 44px WCAG 2.5.5 floor).
 *
 * Fix: at viewports ≤575.98px (phone-sized), floor ALL `.btn.btn-sm`
 * elements to 44h. This intentionally broadens to a Bootstrap-stock
 * class — same scope rationale as AI-535 `.breadcrumb-item a` (SOUL
 * #63): site-wide WCAG policy applies to all interactive elements,
 * and at <576px Bootstrap's compact form clusters are rare while
 * single-CTA buttons are the dominant pattern.
 *
 * Threshold deliberately ≤575.98px (not the broader project-standard
 * 1023.98px) to limit the broad-blast risk. If the broader threshold
 * proves needed, expand on tester re-eval. */
@media (max-width: 575.98px) {
    .btn.btn-sm {
        min-height: 44px;
    }
}

/* AI-603: Slider `.slider-button` per tester CREATIVE EVAL 2026-05-15
 * (HIGH). The Slider templates render anchors with class
 * "slider-button btn btn-primary" (default.blade.php:165 +
 * swiper-skin-1.blade.php:124 — recon SOUL #72). However, each slide's
 * inline `<style>` block declares
 * `#js-slider-{id} .swiper-slide-{slide-id} .slider-button { padding:
 * 8px 20px; ... }` — that selector has higher specificity than the
 * bare `.btn` (id + 2 classes), so it overrides Bootstrap's `.btn`
 * padding. Tester measures the rendered button at ~36px height.
 *
 * Fix: add a floor on `.slider-button` itself. Class-only selector
 * has lower specificity than the inline override, so we need to also
 * provide the floor via `min-height` (which the inline style doesn't
 * touch) — Bootstrap's `.btn` doesn't set min-height either, so this
 * is purely additive.
 *
 * task-2026-05-22-fc5060 / AI-585 CHANGE: also adds `min-width: 44px`
 * since tester measured the CTA at ~36px wide on narrow slides. */
.slider-button {
    min-width: 44px;
    min-height: 44px;
}

/* task-2026-05-18-2747f0 / AI-866 v2 — header cart-counter badge brand-blue
   cascade fix. CHANGE absorbed per designer dispatch task-2026-05-18-6a307b
   (commit f3542d9051 prior). 4th instance of the Stage-2 salmon-cascade
   family on the public frontend (AI-794a + AI-855 + AI-819 CHANGE +
   AI-866 = 4-instance threshold; LESSONS canonicalization shipped on
   2026-05-18).

   v1 (a56fcd04c0) shipped 3-selector form with `var(--color-primary,
   #0d6efd) !important` — Tier-1 source-pin + Tier-2 served-mirror +
   Tier-2 served-bundle all green. Designer's Tier-3 runtime probe at
   fresh /shop load measured cart badge STILL salmon `rgb(244, 162, 97)`
   despite the rule reaching the served bundle. Cascade diagnostic
   identified the badge surface as `<span class="btn btn-outline-primary
   btn-sm mx-2 js-shopping-cart-quantity">`; AI-866 v1 selectors landed
   at specificity (0,2,0) — same tier as the competing app.css rule
   `.btn.btn-outline, .btn.btn-outline-primary, .btn.btn-outline-secondary
    { background-color: transparent !important }`. Source-order tiebreak
   SHOULD have favoured public-touch.css (loads after app.css) but did
   NOT — root cause unconfirmed (var-resolution shadow OR JS repaint
   suspected; designer flagged AI-866d as separate root-cause-investigation
   candidate exploring the `--mw-primary-color: #f4a261` token at
   app.css :root).

   v2 ships Path B (designer's surgical preference): bump selector
   specificity to (0,3,0) by adding `.btn.btn-outline-primary` to BOTH
   selectors + drop `.btn-shopping-cart .badge` (no `.badge` child
   exists in the rendered DOM per the cascade probe) + swap from
   `var(--color-primary, #0d6efd)` to literal `#0d6efd` so token-shadow
   is ruled out independently of any future AI-866d diagnostic. The
   token-name + selector-list discipline calls from v1 stay (Bootstrap
   public-modality NOT admin `--primary-NNN`; class-chain combinator
   `> span.btn`). Hover variant matched at same (0,3,0).

   NOT inside the touch-viewport @media (lines 665+) — defect manifests
   at BOTH 1440 desktop AND 390 mobile per Round 28 runtime evidence;
   fix must be viewport-agnostic.

   Tier-3 runtime probe is MANDATORY for every future ship in the
   salmon-cascade family per AI-866 v1's missed-runtime-probe lesson:
   `getComputedStyle(el).backgroundColor === 'rgb(13, 110, 253)'` at a
   fresh page load BEFORE marking Done. v1 escaped Tier-1+Tier-2 PASS
   despite Tier-3 FAIL — the strongest validation of the
   css-ship-3-tier-probe discipline to date. */
.js-shopping-cart-quantity.btn.btn-outline-primary,
.btn-shopping-cart > span.btn.btn-outline-primary {
    background-color: #0d6efd !important;
    color: #fff !important;
    border-color: #0d6efd !important;
}
.js-shopping-cart-quantity.btn.btn-outline-primary:hover,
.btn-shopping-cart > span.btn.btn-outline-primary:hover {
    background-color: #0b5ed7 !important;
    color: #fff !important;
    border-color: #0b5ed7 !important;
}

/* task-2026-05-18-adaa6a / AI-867 — noUi-slider handle viewport overflow at mobile 390.
   Pre-fix the price-range filter at /shop renders the noUi-slider with handles
   that overflow the viewport's right edge by ~4px (`docW=394` vs `viewportW=390`)
   because the library positions handles via `transform: translate(-50%, 0)` from
   a percentage-positioned origin. At the upper bound (100% slider value), the
   handle's centroid sits exactly at the track's right edge — the -50% translate
   leaves 50% of the 34px handle width (~17px) extending past. The track sits
   flush against the sidebar's right edge with no right-padding, so the
   protruding 17px lands outside the 390px viewport.

   Option A per designer (preferred over B): symmetric-bounded margin inset on
   the noUi-target. 8px left + 18px right (asymmetric — the 18px right
   compensates for the upper-handle's half-width = 17px + 1px breathing,
   while 8px left provides modest breathing for the lower-handle which only
   half-protrudes when value = 0%; designer's spec values preserved verbatim).

   Avoided the alternative `transform: translate(-50%) !important` library-
   fighting approach — that visually breaks handle centering at intermediate
   slider values (the centroid drift becomes proportional to handle width,
   not handle position).

   Library-agnostic — fix targets noUi-slider's `.noUi-target` / `.noUi-horizontal`
   classes (the canonical library mount points). Renders correctly for any
   future widget that wraps the same noUi DOM contract (e.g. multi-range
   slider, datepicker plugin built on noUi).

   1st instance of 3rd-party-widget-overflow-at-mobile defect family per
   designer's LESSONS canonicalization watch — NOT promoting to LESSONS yet
   (1-instance below 3-instance threshold). Sibling-watch list: datepicker,
   multi-select dropdown, modal, range-slider widgets. */
.noUi-target,
.noUi-horizontal {
    margin-left: 8px;
    margin-right: 18px;
}

/* task-2026-05-22-7d4e11 / AI-NEW (from task-d5ee77 mobile audit)
   ─────────────────────────────────────────────────────────────────
   FAIL #1 — Hamburger menu tap-target floor.

   skin-1 initialises MWSiteMobileMenu with size:'25px', making the
   .mw-vhmbgr-wrapper <button> 25×25px — well below the WCAG 2.5.5
   44×44px minimum. The tester measured 25×25px at 390×844.

   Fix: min-width/min-height 44px with !important override the
   JS-injected `--size` CSS custom property (width: var(--size);
   height: var(--size)) without altering the visual SVG.
   The SVG uses position:absolute + 200%/-50% centering relative
   to the wrapper, so it stays visually centred regardless of how
   large the wrapper's minimum tap area grows.

   Scope: .mw-vhmbgr-wrapper is exclusively the MWSiteMobileMenu
   hamburger button — safe to target bare class.
   Already inside the touch-viewport @media (max-width: 768px),
   (pointer: coarse) block so it does not affect desktop layout. */
@media (max-width: 768px), (pointer: coarse) {
    .mw-vhmbgr-wrapper {
        min-width: 44px !important;
        min-height: 44px !important;
        display: inline-flex !important;
        align-items: center !important;
        justify-content: center !important;
    }
}

/* task-2026-05-22-7d4e11 / AI-NEW (from task-d5ee77 mobile audit)
   ─────────────────────────────────────────────────────────────────
   FAIL #2 — Footer copyright-bar link tap-target floor.

   The footer skin-1 template has two sections:
     1. <section class="footer-background …"> — covered by the existing
        .footer-background a { min-height: 44px } rule at line ~1159.
     2. <section class="py-2" style="…"> — the bottom copyright bar
        (© All Rights Reserved + powered_by_link()). This section does
        NOT carry .footer-background, so the existing rule is out of scope.

   The copyright bar's powered_by_link() outputs:
     <span class="mw-powered-by">
       <a …>Create a website</a> with <a …>Microweber</a>
     </span>
   These <a> elements render at ~20-23px height (tester measured).

   Fix: broaden the .mw-powered-by a rule to the standalone class
   (without requiring a .footer-background ancestor). The existing
   .footer-background .mw-powered-by a rule (higher specificity) still
   wins inside .footer-background — this new rule only adds coverage
   for the copyright bar outside .footer-background.

   display: inline-flex allows the link to grow to 44px min-height
   while wrapping naturally in the "Create a website with Microweber"
   inline sentence. padding-block 8px provides visual breathing room. */
@media (max-width: 768px), (pointer: coarse) {
    .mw-powered-by a {
        min-height: 44px;
        display: inline-flex;
        align-items: center;
        padding-block: 8px;
    }
}

/* task-2026-05-22-67db1e / AI-883 HIGH — Shop module touch-target fixes at 390×844.
 *
 * Two WCAG 2.5.5 violations measured by tester:
 *
 * Fix 1: Category filter sidebar buttons (.mw-shop-filter-categories)
 *   .list-group-item.list-group-item-action measured 366×40px.
 *   40px height < 44px floor. Scoped to .mw-shop-filter-categories to
 *   avoid touching Bootstrap list-groups elsewhere on the site.
 *
 * Fix 2: Per-page limit <select class="form-control">
 *   select measured 38×44px. 38px width < 44px floor. `select.form-control`
 *   is the canonical class on the element (no wrapper class); min-width:44px
 *   is a minimum and won't shrink any select that is already wider.
 *
 * INVESTIGATE result: empty cart dropdown (280×18px) is the .mw-big-dropdown-cart
 * container before cart items load. Not an interactive element itself — the
 * toggle button (cart icon) already passes at 44×44px. No touch-target fix
 * needed for the dropdown container. */
@media (max-width: 1023.98px), (hover: none) and (pointer: coarse) {
    /* Fix 1: Shop category filter buttons */
    .mw-shop-filter-categories .list-group-item.list-group-item-action {
        min-height: 44px;
        display: flex;
        align-items: center;
    }

    /* Fix 2: Per-page selector (Limit <select>) */
    select.form-control {
        min-width: 44px;
    }
}

/* task-2026-05-22-7fceee / AI-895 MEDIUM — Bootstrap pagination .page-link touch-target floor.
 *
 * Bootstrap 5 pagination renders .page-item > .page-link at ~38px height by
 * default (padding 0.375rem + line-height 1.5 × 1rem = ~38px rendered). This
 * is 6px below the WCAG 2.5.5 44×44px floor for interactive elements.
 *
 * The tester code-level analysis confirmed the likely failure. Runtime
 * measurement was blocked because pagination only triggers when the content
 * count exceeds the per-page limit (currently only 6 products). This rule is
 * preemptive — when content grows and pagination activates, .page-link will
 * already meet the 44px floor on all viewports.
 *
 * Covers: pagination numbers, prev/next arrows (‹ ›), and ellipsis items.
 * All .page-link elements are interactive so the floor applies to all.
 *
 * display: inline-flex + align-items: center ensures text/icon vertically
 * centres inside the enlarged hit area. min-width: 44px ensures square
 * pagination buttons (numbers) as well as the wider prev/next labels. */
@media (max-width: 1023.98px), (hover: none) and (pointer: coarse) {
    .page-link {
        min-height: 44px;
        min-width: 44px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }
}

/* task-2026-05-22-ea3b1f / AI-302 MEDIUM — BeforeAfter module .twentytwenty-handle touch-target.
 *
 * Tester measured the draggable divider handle at 38×38px at 390×844.
 * The handle sits at the midpoint of the before/after image split and is the
 * primary interactive affordance of the module — it must meet the 44×44px
 * WCAG 2.5.5 floor.
 *
 * twentytwenty.css (the library stylesheet served from
 * public/modules/before_after/css/twentytwenty.css) hard-codes width: 38px
 * with no min-width override. We add min-width + min-height here in
 * public-touch.css (loaded via Bootstrap template) rather than patching the
 * library file, following the AI-517/AI-518/AI-522..AI-895 pattern.
 *
 * Inside the standard touch-viewport @media so desktop gets the native 38px
 * handle size while mobile gets the enlarged touch target. */
@media (max-width: 1023.98px), (hover: none) and (pointer: coarse) {
    .twentytwenty-handle {
        min-width: 44px;
        min-height: 44px;
    }
}

/* task-2026-05-22-c71042 / AI-558 MEDIUM — Post module read-more link touch-target floor.
 *
 * Tester measured text-only read-more links at 16-21px height at 390×844.
 * The Post module ships 30+ skin templates; only two use named CSS classes on
 * the read-more anchor — the rest render bare `<a class="" itemprop="url">`.
 *
 * Named classes covered by this rule:
 *   .skin-18--read-more-link  — used by skin-18.blade.php + pro_blog.blade.php
 *   .mw-post-22-post-read-more — used by skin-22.blade.php
 *
 * AI-558a follow-up candidate: the remaining ~25 post skins render read-more
 * via bare `<a class="" itemprop="url">` with no unique class to target.
 * Fixing those requires adding a `.mw-post-read-more` class to each template
 * — deferred pending explicit dispatch.
 *
 * Note: the PM's suggested .blog-posts selector does not match the actual
 * templates, which use skin-specific wrappers (.blog-posts-1, .blog-posts-2,
 * etc., not .blog-posts without a suffix). */
@media (max-width: 1023.98px), (hover: none) and (pointer: coarse) {
    .skin-18--read-more-link,
    .mw-post-22-post-read-more {
        min-height: 44px;
        display: inline-flex;
        align-items: center;
    }
}

/* task-2026-05-22-af62dd / AI-303 MEDIUM — CookieNotice toggle option row touch-target.
 *
 * The CookieNotice module CSS defines a .switch (48×24px visual toggle track)
 * and .cookie-option (flex row wrapping the switch + category label text).
 * The toggle height 24px is below the 44×44px WCAG 2.5.5 floor.
 *
 * The default template (default.blade.php) does not currently render cookie
 * category toggles — the current UI shows only Close / Learn more / Accept All
 * (all three already at 44px per cookie-notice.css AI-551 rules). This rule
 * is defensive, targeting the .cookie-option row wrapper for when a cookie
 * preferences panel is added that renders per-category toggles.
 *
 * Pattern: enlarging the row wrapper (.cookie-option) preserves the visual
 * 48×24px slider shape while extending the interactive tap area to the full
 * row height — same pattern as the Filament checkbox wrapper (AI-517/AI-518). */
@media (max-width: 1023.98px), (hover: none) and (pointer: coarse) {
    .cookie-notice-wrapper .cookie-option {
        min-height: 44px;
        display: flex;
        align-items: center;
    }
}

/* task-2026-05-22-ba0702 / AI-881 FAIL #1 — Product module tag-filter btn-link touch-target.
 * product-card.blade.php:78 renders category/tag filter pills as
 * `.btn.btn-link.p-0` elements. Bootstrap `.btn-link` + `.p-0` (zero padding)
 * results in a text-height hit box of ~20px — below the WCAG 2.5.5 44px floor.
 * min-height wins over explicit height/padding via the CSS constraint algorithm
 * (min-height is a separate property unaffected by !important on height).
 *
 * FAIL #2 — Product module skin-4 Slick carousel arrow touch-target.
 * skin-4.blade.php uses the Slick carousel library. slick.css defaults set
 * .slick-prev and .slick-next to 20×30px — below the 44px floor on both axes.
 * No existing rule in public-touch.css covered these selectors. */
@media (max-width: 1023.98px), (hover: none) and (pointer: coarse) {
    .btn.btn-link { min-height: 44px; display: inline-flex; align-items: center; }
    .slick-prev, .slick-next { min-width: 44px; min-height: 44px; }
}

/* task-2026-05-22-4ca677 / AI-297 MEDIUM — Search module "← Return home" CTA touch-target.
 * resources/views/frontend/search/results.blade.php:77 renders the Return-home
 * link as a bare <a class="mw-frontend-search-results__cta"> with no padding,
 * no min-height, and no Bootstrap button class. Tester measured 107×23px at
 * 390×844 — 21px below the WCAG 2.5.5 44px floor.
 * Fix: min-height + inline-flex + align-items inside the touch @media block. */
@media (max-width: 1023.98px), (hover: none) and (pointer: coarse) {
    .mw-frontend-search-results__cta {
        min-height: 44px;
        display: inline-flex;
        align-items: center;
    }
}

/* task-2026-05-22-ef3960 / AI-877 P3 — Frontend bare <a> link colour (salmon → brand-blue).
 * Bootstrap template _variables.scss sets $link-color: #f4a261 (demo salmon),
 * which compiles into the `a { color: ... }` rule in app.css and trickles to
 * every bare text link on the public frontend — footer mailto, "Create a website"
 * credit link, /search "Return home" link, and any unclassed body-text <a>.
 *
 * AI-868 fixed `.btn-primary` backgrounds at global scope but left $link-color
 * untouched, so bare text links remained salmon. This is the 6th instance of
 * the Stage-2 salmon-cascade family (AI-794a / AI-855 / AI-819 CHANGE / AI-866 /
 * AI-868 / this AI-877).
 *
 * Fix: global scope override (no @media guard) using :not(.btn):not(.navbar-brand)
 * to avoid touching Bootstrap nav chrome that legitimately inherits white from
 * its dark navbar context, and .btn elements which carry their own color chain.
 *
 * Tier-3 verification: getComputedStyle(footerLink).color must return
 * `rgb(13, 110, 253)` on a bare footer <a> after this rule is served.
 * Negative guard: `rgb(244, 162, 97)` must not appear on body-text links. */
a:not(.btn):not(.navbar-brand):not([class*="btn-"]) {
    color: var(--color-primary, #0d6efd);
}
a:not(.btn):not(.navbar-brand):not([class*="btn-"]):hover {
    color: var(--color-primary-hover, #0b5ed7);
    text-decoration: underline;
}

/* task-2026-05-22-10ee46 / AI-900 P3 — CONTACT US topbar button salmon fix.
 * Bootstrap template $primary: #f4a261 (demo salmon) cascades into .btn-sm
 * elements that carry no .btn-primary class. The CONTACT US nav button is
 * rendered by the Btn module as `<button class="btn btn-sm">` (no .btn-primary),
 * so AI-868 (.btn-primary selector) and AI-877 (bare <a> text link color)
 * both missed it — this is the 7th instance of the Stage-2 salmon-cascade family.
 *
 * AI-868 shipped an :root { --mw-btn-background-color } token that was
 * expected to route through app.css's broad negation selector, but the runtime
 * DOM confirms backgroundColor is still rgb(244, 162, 97) — the token chain
 * did not reach the CONTACT US button. Direct selector + literal hex is the
 * confirmed-working pattern (Path B discipline per AI-866 v2).
 *
 * Exclusions via :not():
 *   .btn-outline-primary — shopping cart badge `btn btn-outline-primary btn-sm`
 *                          must stay transparent with blue border (AI-866)
 *   .btn-danger           — red action buttons must stay red
 *   .btn-outline-danger   — outline red must stay as-is
 *
 * Global scope (no @media guard) — applies to all viewports because the salmon
 * defect manifests at every screen width, not just mobile.
 *
 * Negative regression guard: rgb(244, 162, 97) must NOT appear on .btn.btn-sm. */
a.btn.btn-sm:not(.btn-outline-primary):not(.btn-danger):not(.btn-outline-danger),
button.btn.btn-sm:not(.btn-outline-primary):not(.btn-danger):not(.btn-outline-danger) {
    background-color: var(--mw-btn-background-color, #0d6efd) !important;
    border-color: var(--mw-btn-border-color, #0d6efd) !important;
    color: #fff !important;
}
