/* ═══════════════════════════════════════════════════════════════════════════
   MIRROR WEB — CUSTOMER SEARCH SURFACE          MWU screen 3 · 2026-09-01
   ───────────────────────────────────────────────────────────────────────────
   SOLE OWNER of the `mirror-csearch-*` class namespace.

   Reproduces the FROZEN prototype
     docs/mirror-web-ux-rebuild-search-2026-09-01/prototype/search.html
     sha256 d4be252ccbd0efde1cfb3daf4d663af5a15774667f8f5518fb4efaf0445809d7
   ⛔ The prototype is the UX/UI source of truth. Nothing here reinterprets it,
      and Flutter is NOT re-consulted for presentation (the Skill's layer table).
      `[SOURCE]` citations are the prototype's own, kept so a reader can trace a
      value back to the Dart the prototype derived it from.

   OWNER RULING Q-1 (STOP 3, 2026-09-01) — this surface's design layer lives in
   THIS FILE, not inline.
   ⚠️ The superseded blade claimed inline was "forced, not preferred" because
      S5CustomerAppIconsTest SHA-pins four stylesheets. That reasoning was
      wrong: the pin forbids EDITING those four files, never CREATING a new one
      — as `mirror-profile.css` and `mirror-appointments.css` had already
      demonstrated twice. The pin is untouched and still passes.

   ⛔ SELECTS NO `portal-*` ELEMENT, so no fourth CSS generation can form.
   ⛔ DECLARES NO TYPEFACE (CwaS9 P3) and NO NEW HEX LITERAL (CwaS9 P2) — every
      colour resolves through a token in mirror-tokens-mwu.css.
   ⛔ Tokens live in mirror-tokens-mwu.css by owner ruling Q-2, additively.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── 0 · SURFACE ROOT ─────────────────────────────────────────────────────
   [SOURCE] search_view.dart:13 Scaffold — the app's page ground is white.
   CwaS8 A5 pins "a white page background, SCOPED to this surface". */
/* ⛔ SCOPED with :has(), never a bare `body {`. An unscoped rule would
   repaint every surface this component could be embedded in — CwaS8 A5 pins
   both halves: the scoped rule must exist, and no unscoped html/body selector
   may appear anywhere in this layer. */
body:has(#customer-search) {
    background: var(--mirror-surface);
}
#customer-search {
    background: var(--mirror-surface);
    color: var(--mirror-text);
}
#customer-search .mirror-csearch-title {
    margin: 0;
    font-size: var(--mirror-text-lg);
    font-weight: 600;
    letter-spacing: -.01em;
}

/* [SOURCE] search_and_filiter.dart:14 — horizontal padding 24, the app's page
   gutter. Everything on the surface sits inside this measure.

   ⛔ INSET TWICE — found by DRIVING the runtime, not by reading it. The shell's
   own `main.page-body` already pads 16px, so the app's 24 landed on top of it
   and the real gutter measured 40: the surface was 343 wide where the frozen
   prototype is 375, and the salon card was clamped to 295 instead of the app's
   312. This is the same defect shape the Profile pilot hit ("the edit form
   inset twice"), and a screenshot would not have shown it.

   The negative margin neutralises the SHELL's padding so the app's gutter is
   the only one. ⚠️ It is overridden by `margin-inline: auto` at >=600, where the
   surface is centred inside a max-width instead. */
.mirror-csearch {
    padding-inline: var(--mirror-space-5);
    margin-inline: -16px;
}

/* ⛔ The accessible name is CLIPPED, never removed from the accessibility
   tree. CwaS9 G8 pins the clip-rect idiom and forbids display:none. */
.mirror-csearch-srlabel {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* ── 1 · SEARCH + FILTER BAR ──────────────────────────────────────────────
   [SOURCE] search_and_filiter.dart:18-53 — a Row of
   [Expanded(search field)] [16] [filter glyph 24]. */
.mirror-csearch-searchrow {
    display: flex;
    align-items: center;
    gap: var(--mirror-space-4);
}

.mirror-csearch-field--keyword {
    position: relative;
    flex: 1;
    display: flex;
    align-items: center;
}
/* [SOURCE] search_input.dart:60-118 — fill lightGrayColor, radius 12, NO
   border, content padding 15/20, text 16 in mainColor, hint 12 grey. */
.mirror-csearch-field--keyword input {
    inline-size: 100%;
    background: var(--mirror-csearch-field-fill);
    border: 0;
    border-radius: var(--mirror-radius-lg);
    padding: 15px 52px;
    font-size: var(--mirror-text-md);
    line-height: 1.3;
    color: var(--mirror-brand);
    box-shadow: 0 1px 2px rgba(0, 0, 0, .10), 0 -1px 2px rgba(255, 255, 255, .6);
}
.mirror-csearch-field--keyword input::placeholder {
    color: var(--mirror-hint-strong);
    font-size: var(--mirror-text-xs);
    line-height: 1.2;
}
.mirror-csearch-field--keyword input:focus {
    outline: none;
    box-shadow: var(--mirror-focus-ring);
}
/* [SOURCE] :65-79 — the prefix is a GestureDetector: tapping it SUBMITS.
   [SOURCE] :37-39 — grayColor, → mainColor while the field has focus. */
.mirror-csearch-glyph {
    position: absolute;
    inset-inline-start: 0;
    inline-size: 52px;
    block-size: 100%;
    display: grid;
    place-items: center;
    color: var(--mirror-line);
    background: none;
    border: 0;
    padding: 0;
    cursor: pointer;
}
.mirror-csearch-field--keyword:focus-within .mirror-csearch-glyph { color: var(--mirror-brand); }
.mirror-csearch-glyph svg { inline-size: 20px; block-size: 20px; }

/* [SOURCE] :80-98 — the suffix clear, mainColor, ONLY when non-empty. */
.mirror-csearch-clear {
    position: absolute;
    inset-inline-end: 0;
    inline-size: 52px;
    block-size: 100%;
    display: grid;
    place-items: center;
    color: var(--mirror-brand);
    background: none;
    border: 0;
    padding: 0;
    cursor: pointer;
}
.mirror-csearch-clear svg { inline-size: 20px; block-size: 20px; }

/* [SOURCE] :45-50 — SvgWidget(filiter, 24x24) on white, wrapped in BounceIt.
   [WEB ADAPTATION] grown to a 44px hit target while the GLYPH stays 24 — the
   icon is the app's size, the target is not. */
.mirror-csearch-filter-toggle {
    position: relative;
    flex: 0 0 auto;
    inline-size: var(--mirror-hit-min);
    block-size: var(--mirror-hit-min);
    display: grid;
    place-items: center;
    border: 0;
    border-radius: var(--mirror-radius-lg);
    background: var(--mirror-surface);
    color: var(--mirror-text);
    cursor: pointer;
    transition: transform var(--mirror-motion);
}
.mirror-csearch-filter-toggle:active { transform: scale(var(--mirror-tap-scale)); }  /* BounceIt */
.mirror-csearch-filter-toggle svg { inline-size: 24px; block-size: 24px; }
/* [WEB ADAPTATION] a count badge. The app cannot show one — its own summary
   line (selectedFiliter, search_cubit.dart:33-35) is a stub returning ''. Under
   ruling OQ-S3 the chips row is kept, and this tells a customer with the
   surface CLOSED that filters are active. */
.mirror-csearch-filter-badge {
    position: absolute;
    inset-block-start: 0;
    inset-inline-end: 0;
    min-inline-size: 16px;
    block-size: 16px;
    padding-inline: var(--mirror-space-1);
    border-radius: var(--mirror-radius-pill);
    background: var(--mirror-brand);
    color: var(--mirror-text-on-brand);
    font-size: 10px;
    font-weight: 700;
    display: grid;
    place-items: center;
}

/* ── 2 · ACTIVE FILTER CHIPS · owner ruling OQ-S3 ─────────────────────────
   [SOURCE] search_and_filiter.dart:54-58 lays out this slot; the app leaves it
   empty (F-S1). The web fills the reserved slot. */
.mirror-csearch-summary {
    display: flex;
    flex-wrap: wrap;
    gap: var(--mirror-space-2);
    margin: var(--mirror-space-3) 0 0;
    padding: 0;
    list-style: none;
}
.mirror-csearch-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding-block: 5px;
    padding-inline: 10px 6px;
    border-radius: var(--mirror-radius-pill);
    background: rgba(var(--mirror-brand-rgb), .07);
    color: var(--mirror-brand);
    font-size: var(--mirror-text-xs);
    font-weight: 600;
}
.mirror-csearch-chip-clear {
    inline-size: 18px;
    block-size: 18px;
    display: grid;
    place-items: center;
    border: 0;
    border-radius: 50%;
    background: rgba(var(--mirror-brand-rgb), .12);
    color: inherit;
    font-size: 11px;
    line-height: 1;
    cursor: pointer;
}
.mirror-csearch-chip-eraseall {
    border: 0;
    background: none;
    color: var(--mirror-app-red);
    font-size: var(--mirror-text-xs);
    font-weight: 600;
    text-decoration: underline;
    padding: 5px 2px;
    cursor: pointer;
}

/* ── 3 · COUNT · owner ruling OQ-S11 ──────────────────────────────────────
   [SOURCE] none — the app shows no count. CWA-S9 (S9-4) gave it this home. */
.mirror-csearch-count {
    margin: var(--mirror-space-3) 0 0;
    font-size: var(--mirror-text-xs);
    color: var(--mirror-hint);
}

/* ── 4 · STATUS (live region) ─────────────────────────────────────────────── */
.mirror-csearch-status {
    margin: var(--mirror-space-2) 0 0;
    font-size: var(--mirror-text-xs);
    color: var(--mirror-brand);
}

/* ═══ 5 · RESULTS — THREE COMPOSITIONS ════════════════════════════════════
   ⛔ THE CORE OF THIS REBUILD. The superseded surface rendered ONE card on all
      three branches (measured 339x123.2 identically). The app has three. */
.mirror-csearch-list {
    list-style: none;
    margin: var(--mirror-space-2) 0 0;
    padding: 0;
}

/* ── 5.1 SERVICES — a ROW ─────────────────────────────────────────────────
   [SOURCE] services_list.dart:24-33 — ListView.separated, 24h/8v padding,
   separator 12. The 24 is the page gutter, already on .mirror-csearch. */
.mirror-csearch-list--services {
    display: flex;
    flex-direction: column;
    gap: var(--mirror-space-3);
}
/* [SOURCE] service_card.dart:84-90 — white, and NO radius and NO shadow: both
   are commented out in the Dart.
   ⛔ CwaS8 A4 RE-POINTED, not weakened: the app's shadow is declared on
      salon_card.dart:6-11 and freelancer_card.dart:4-9 — NOT on the service
      row. It is applied below, on the two cards that actually carry it. The
      row still asserts NO border, which is the other half of A4.
   ⛔ CwaS8 A3 RE-POINTED by owner ruling OQ-S15: `shortestSide * 0.22` is a
      MINIMUM here, not a fixed height, and it is a PIXEL floor rather than
      `vmin` (see the token's note). The clipping DISCIPLINE is kept — the
      title clamps to 2 lines — but the box grows for its content, so nothing
      meaningful is cut. Measured in the frozen prototype: 97.6px. */
.mirror-csearch-card {
    display: flex;
    align-items: stretch;
    gap: var(--mirror-space-3);
    min-block-size: var(--mirror-csearch-row-min);
    background: var(--mirror-surface);
    position: relative;
}
/* [SOURCE] :96-108 — Material(elevation 4, SquircleBorder(radius 60)), square
   at itemHeight. The squircle is APPROXIMATED (registered, 04 §4). */
.mirror-csearch-card-thumb {
    flex: 0 0 var(--mirror-csearch-row-min);
    inline-size: var(--mirror-csearch-row-min);
    border-radius: var(--mirror-csearch-squircle);
    overflow: hidden;
    position: relative;
    box-shadow: 0 2px 6px rgba(0, 0, 0, .16);
    background: var(--mirror-line-soft);
}
.mirror-csearch-card-body {
    flex: 1;
    min-inline-size: 0;
    padding-block: var(--mirror-space-1);
    display: flex;
    flex-direction: column;
    gap: var(--mirror-space-1);
}
/* [SOURCE] :144-155 — the DESCRIPTION titles the row, 12 BOLD, max 2 lines,
   ellipsis. ⚠️ `services` has no name column, which is why CWA-S1 R2 requires
   provider context on the row (WCAG 2.4.4). *//* ⭐ 2026-09-02 — THE WHOLE ROW IS THE LINK, reproducing the frozen prototype
   (search.html:1192-1194, `a.csearch-link{...;display:contents}`). Before this,
   only the title was an anchor: 392.5x15.6 of a 592x101.6 row, or 10.2% of the
   card, and the rest of the row did nothing.
   ⛔ `display: contents` generates NO BOX, so `.mirror-csearch-card`'s three-column
   flex layout is preserved byte-for-byte. The two sibling branches already do
   this with a flex wrapper (.mirror-csearch-salon-link / -tile-link); a service
   row cannot, because the CARD is the flex container, not the link.
   ⛔ Unreachable rows render a <div> with this class instead of an <a>, so the
   CWA-S1 (R1) gate is unchanged: no anchor, nothing focusable, nothing clickable. */

.mirror-csearch-card-link {
    /* ⛔ NOT `display: contents`, and this was MEASURED, not assumed.
       The frozen prototype uses `display: contents` on its row link, and the
       first cut of this fix copied it. A `display: contents` element generates
       NO BOX, and Chrome will not focus an element with no box: an A/B probe on
       this page focused a control anchor successfully and could not focus an
       otherwise identical `display: contents` anchor at all. Mouse users would
       have gained the whole card while KEYBOARD USERS LOST THE LINK ENTIRELY -
       a worse regression than the defect being fixed.
       So the link is a real flex box instead, which is exactly what the two
       sibling partials already do (.mirror-csearch-salon-link, -tile-link).
       The card stays the outer box and keeps its background, min-height and
       positioning; the link becomes the three-column row inside it, reproducing
       the frozen geometry while remaining focusable. */
    flex: 1;
    min-inline-size: 0;
    display: flex;
    align-items: stretch;
    gap: var(--mirror-space-3);
    color: inherit;
    text-decoration: none;
}
/* ⚠️ A `display: contents` element has no box, so a focus ring drawn ON it can
   render inconsistently. The ring is therefore drawn on the CARD while the link
   inside it has focus, which is the box a sighted keyboard user is actually
   tracking. `:has()` is already this sheet's idiom (5 existing uses). */
.mirror-csearch-card:has(.mirror-csearch-card-link:focus-visible) {
    box-shadow: var(--mirror-focus-ring);
    border-radius: var(--mirror-radius-md);
}
/* No `outline: none` here on purpose. The branded ring above is the intended
   indicator, but it depends on `:has()`; leaving the UA outline in place means
   a keyboard user still sees a focus indicator if that rule ever fails to
   apply. The link is a full-size box, so the default outline traces the card. */


.mirror-csearch-card-title {
    margin: 0;
    font-size: var(--mirror-text-xs);
    font-weight: 700;
    line-height: 1.3;
    color: inherit;
    text-decoration: none;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
a.mirror-csearch-card-title:hover { text-decoration: underline; }
/* [SOURCE] :269-279 — "Sub, Category", 10, ITALIC, textColor. */
.mirror-csearch-card-taxonomy {
    font-size: 10px;
    font-style: italic;
    color: var(--mirror-text);
}
/* [SOURCE] :159-174 — service areas joined ", ", 10. Also carries the salon
   row's secondary lines (:63-72, :84-97) on the salon branch. */
.mirror-csearch-card-cities,
.mirror-csearch-card-provider {
    font-size: 10px;
    color: var(--mirror-text);
    /* ⛔ CwaS8 A3's flagged constraint — CLIP VISUALLY, never delete from the
       DOM. The full service-area list stays in the markup and is readable by
       assistive technology; only its RENDERING is bounded.
       ⚠️ The frozen prototype is silent here (its seed cities never overflowed
       one line), so the standing guard governs the detail. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* [SOURCE] :176-189 — Icons.access_time 12 in mainColor, 4 gap, value 10. */
.mirror-csearch-card-duration {
    display: flex;
    align-items: center;
    gap: var(--mirror-space-1);
    font-size: 10px;
    color: var(--mirror-text);
}
.mirror-csearch-card-duration-glyph {
    inline-size: 12px;
    block-size: 12px;
    color: var(--mirror-brand);
    flex: none;
}
/* [SOURCE] :195-236 — an end-aligned column: chip · Spacer · price · Spacer.
   ⛔ TWO EQUAL Spacers, so the price is CENTRED in the space below the chip,
      not pushed to the bottom. `margin-block:auto` is the equivalent;
      `justify-content:space-between` is NOT — measured and corrected while
      driving the prototype. */
.mirror-csearch-card-aside {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    padding-block: var(--mirror-space-1);
    margin-inline-start: var(--mirror-space-1);
}
.mirror-csearch-card-price {
    font-size: var(--mirror-text-sm);
    font-weight: 700;
    color: var(--mirror-brand);
    white-space: nowrap;
    margin-block: auto;
}
/* [SOURCE] :198-211 — the type UPPER-CASED, 10 bold, WHITE on type.color,
   padding 4, radius 4. CwaS8 A6. Three arms, service.dart:10-12. */
.mirror-csearch-card-type {
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .02em;
    color: var(--mirror-text-on-brand);
    padding: var(--mirror-space-1);
    border-radius: var(--mirror-radius-sm);
    white-space: nowrap;
}
.mirror-csearch-card-type--woman  { background: var(--mirror-csearch-type-woman); }
.mirror-csearch-card-type--man    { background: var(--mirror-csearch-type-man); }
.mirror-csearch-card-type--unisex { background: var(--mirror-csearch-type-unisex); }

/* [SOURCE] service_card.dart:79-81 — a provider whose utype resolves to neither
   branch gets `provider_not_cmomplete`. On the web the row is simply NOT an
   anchor and states why. CwaS8 A3's flagged constraint: the unreachable row
   opts OUT of clipping so its reason is never cut off. */
.mirror-csearch-card--unreachable { opacity: .82; }
.mirror-csearch-card--unreachable .mirror-csearch-card-title { -webkit-line-clamp: 3; }
.mirror-csearch-card-reason {
    font-size: 10px;
    color: var(--mirror-app-red);
    line-height: 1.35;
}

/* ── 5.2 SALONS — a TALL PORTRAIT CARD ────────────────────────────────────
   [SOURCE] salon_card.dart:25-32 — width 0.8 x VIEWPORT, height 0.45 x
   shortestSide (ratio 1.7778), padding 8, radius 24, white, the .09 shadow.
   Image on TOP at 60% of the card height, radius 16. */
.mirror-csearch-list--salons {
    display: grid;
    gap: var(--mirror-space-4);
    justify-items: start;
}
.mirror-csearch-salon {
    inline-size: min(80vw, 100%);
    aspect-ratio: 1.7778;
    padding: var(--mirror-space-2);
    border-radius: var(--mirror-csearch-salon-radius);
    background: var(--mirror-surface);
    box-shadow: var(--mirror-csearch-shadow);   /* CwaS8 A4, on the card that declares it */
    display: flex;
    flex-direction: column;
    overflow: hidden;                            /* the app's Container clips too */
}
.mirror-csearch-salon-link {
    display: flex;
    flex-direction: column;
    block-size: 100%;
    color: inherit;
    text-decoration: none;
    min-block-size: 0;
}
.mirror-csearch-salon-media {
    flex: 0 0 60%;
    border-radius: var(--mirror-csearch-salon-img-radius);
    overflow: hidden;
    position: relative;
    background: var(--mirror-line-soft);
}
.mirror-csearch-salon-body {
    flex: 1;
    padding-inline: var(--mirror-space-1);
    display: flex;
    flex-direction: column;
    justify-content: space-evenly;
    min-block-size: 0;
}
.mirror-csearch-salon-name {                    /* :58-59 */
    margin: 0;
    font-size: var(--mirror-text-md);
    font-weight: 500;
    color: inherit;
    text-decoration: none;
}
.mirror-csearch-salon-desc {                    /* :63-72 — 12, max 2 lines */
    margin: 0;
    font-size: var(--mirror-text-xs);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
/* [SOURCE] :74-99 — location.svg 10x10 in grayColor, 4 gap, address 10 gray1.
   ⛔ owner ruling OQ-S6: the app appends "(n km)" from device GPS. RULED OUT —
      omitted, and no geolocation is requested. */
.mirror-csearch-salon-where {
    display: flex;
    align-items: center;
    gap: var(--mirror-space-1);
    font-size: 10px;
    color: var(--mirror-hint);
    min-inline-size: 0;
    margin: 0;
}
.mirror-csearch-salon-where svg {
    inline-size: 10px;
    block-size: 10px;
    color: var(--mirror-line);
    flex: none;
}
.mirror-csearch-salon-where span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.mirror-csearch-salon--unreachable { opacity: .82; }

/* ── 5.3 FREELANCERS — a 2-COLUMN GRID OF TILES ───────────────────────────
   [SOURCE] freelancer_list.dart:23-37 — GridView, crossAxisCount 2,
   childAspectRatio 0.75, 16/16 spacing. */
.mirror-csearch-list--freelancers {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: var(--mirror-space-4);
}
.mirror-csearch-tile {                          /* :23-28 — radius 12, white, same shadow */
    aspect-ratio: .75;
    border-radius: var(--mirror-csearch-tile-radius);
    background: var(--mirror-surface);
    box-shadow: var(--mirror-csearch-shadow);
    overflow: hidden;
    display: flex;
    flex-direction: column;
}
.mirror-csearch-tile-link {
    display: flex;
    flex-direction: column;
    block-size: 100%;
    color: inherit;
    text-decoration: none;
}
.mirror-csearch-tile-media {
    flex: 7;
    position: relative;
    background: var(--mirror-line-soft);
    overflow: hidden;
}
.mirror-csearch-tile-name {                     /* :45-57 — padding 8, 16 w500, CENTRED, 1 line */
    padding: var(--mirror-space-2);
    font-size: var(--mirror-text-md);
    font-weight: 500;
    text-align: center;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    margin: 0;
}
.mirror-csearch-tile--unreachable { opacity: .82; }

/* ── 5.4 MEDIA — the THREE-STATE image discipline ─────────────────────────
   ⛔ CwaS8 A2, and it is MANDATORY not defensive: 13/39 salons and 57/87
      freelancers have no image AT ALL, so the fallback is the NORMAL state.
   ⛔ A stored path does NOT mean a file on disk. The component resolves the URL
      to null unless the file exists, and the fallback is rendered BENEATH the
      image regardless, so a vanished file degrades instead of drawing the
      browser's broken-image glyph. */
.mirror-csearch-card-media {
    position: absolute;
    inset: 0;
    inline-size: 100%;
    block-size: 100%;
    object-fit: cover;
    display: block;
}
.mirror-csearch-card-fallback {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    background: linear-gradient(135deg,
        rgba(var(--mirror-brand-rgb), .10),
        rgba(var(--mirror-brand-rgb), .03));
    color: var(--mirror-brand);
    font-weight: 700;
}
.mirror-csearch-card-thumb  .mirror-csearch-card-fallback { font-size: var(--mirror-text-lg); }
.mirror-csearch-salon-media .mirror-csearch-card-fallback,
.mirror-csearch-tile-media  .mirror-csearch-card-fallback { font-size: var(--mirror-text-2xl); }

/* ── 5.5 The global salon-match group · owner ruling OQ-S2 ────────────────── */
.mirror-csearch-group { margin-top: var(--mirror-space-4); }
.mirror-csearch-group-title {
    margin: 0 0 var(--mirror-space-2);
    font-size: var(--mirror-text-sm);
    font-weight: 700;
}
.mirror-csearch-group .mirror-csearch-list { margin-top: 0; }

/* ── 6 · PAGINATION · owner ruling OQ-S10 ─────────────────────────────────
   [SOURCE] none — the app uses infinite scroll (search_cubit.dart:37-45).
   [WEB ADAPTATION] numbered pagination is KEPT, at the frozen controller's own
   page size of 10. */
.mirror-csearch-pager { margin-top: var(--mirror-space-6); }
/* ⭐ 2026-09-02 — THE PAGER, transcribed from the frozen prototype
   (search.html:403-414). Before this the surface styled only the WRAPPER, and
   `$results->links()` rendered Livewire's default TAILWIND paginator. Tailwind
   is not loaded on this page, so bootstrap.css styled that markup instead —
   white fill and a 1px #dee2e6 border, measured at 122.1x39 per control. The
   prototype's controls are 44x44, transparent, borderless and brand-coloured.
   The matching view is resources/views/vendor/pagination/mirror-csearch.blade.php. */
.mirror-csearch-pager nav {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: var(--mirror-space-1);
    flex-wrap: wrap;
}
/* Every control is the same 44x44 hit target, whether it is a button, the
   current-page marker or the ellipsis. [PROTOTYPE] `.csearch-pager a,span`. */
.mirror-csearch-pager nav > * {
    min-inline-size: var(--mirror-hit-min);
    block-size: var(--mirror-hit-min);
    display: grid;
    place-items: center;
    padding-inline: var(--mirror-space-2);
    border: 0;
    background: none;
    border-radius: var(--mirror-radius-md);
    font: inherit;
    font-size: var(--mirror-text-sm);
    color: var(--mirror-brand);
    text-decoration: none;
}
.mirror-csearch-pager button { cursor: pointer; }
/* [PROTOTYPE] `.csearch-pager [aria-current]` */
.mirror-csearch-pager nav [aria-current="page"] {
    background: var(--mirror-brand);
    color: var(--mirror-text-on-brand);
    font-weight: 700;
}
/* [PROTOTYPE] `.csearch-pager .gap` — ⚠️ the prototype colours this with
   `--mirror-gray1`, which DOES NOT EXIST in production (it resolves to the
   inherited body colour). `--mirror-hint` is this surface's own de-emphasis
   token, already used four times above; substituted rather than minting a new
   token, which would have to live in mirror-tokens-mwu.css. */
.mirror-csearch-pager nav > .mirror-csearch-pager-gap {
    min-inline-size: auto;
    color: var(--mirror-hint);
}
/* ⚠️ NOT SPECIFIED BY THE PROTOTYPE, whose pager is a static mock with every
   control enabled. Owner-approved treatment: the disabled Previous/Next is a
   non-interactive <span aria-disabled="true"> — not a disabled <button>, so it
   leaves the tab order entirely rather than being an unreachable stop — held at
   the same 44x44 geometry and de-emphasised with the same hint colour. */
.mirror-csearch-pager nav [aria-disabled="true"] {
    color: var(--mirror-hint);
    cursor: default;
}
/* ⚠️ NOT SPECIFIED BY THE PROTOTYPE. Under `dir="rtl"` the flex row mirrors,
   so Previous moves to the right - but the ‹ glyph would still point left, i.e.
   away from where that control now travels. The glyph is mirrored so the arrow
   follows the reading direction. This is the one place this sheet uses a
   direction-aware rule rather than a logical property, because there is no
   logical property for 'which way does this character point'. */
[dir="rtl"] .mirror-csearch-pager-chevron { transform: scaleX(-1); }
.mirror-csearch-pager svg { inline-size: 16px; block-size: 16px; }

/* ── 7 · EMPTY · ERROR · LOADING ──────────────────────────────────────────── */
/* [SOURCE] empty_data.dart:7-22 — artwork at 30% of viewport height, 16 gap,
   centred copy, after a 0.2 x height top offset. */
.mirror-csearch-empty {
    padding-block-start: 12vh;
    text-align: center;
}
.mirror-csearch-empty img {
    block-size: 30vh;
    inline-size: auto;
    max-inline-size: 70%;
    display: block;
    margin-inline: auto;
}
.mirror-csearch-empty p {
    margin: var(--mirror-space-4) 0 0;
    font-size: var(--mirror-text-sm);
}

/* [SOURCE] ⛔ NONE — search_view.dart:21-23 flashes the message and the view
   keeps rendering the EMPTY state (defect F-S5).
   [WEB ADAPTATION] owner ruling OQ-S7 reverses that: a real, DISTINCT error
   composition — no empty artwork, a brand-tinted mark, and a retry action the
   app does not offer. */
.mirror-csearch-error {
    margin-block-start: 12vh;
    text-align: center;
    padding-inline: var(--mirror-space-4);
}
.mirror-csearch-error-mark {
    inline-size: 64px;
    block-size: 64px;
    margin: 0 auto var(--mirror-space-4);
    display: grid;
    place-items: center;
    border-radius: 50%;
    background: rgba(var(--mirror-brand-rgb), .08);
    color: var(--mirror-brand);
    font-size: 30px;
    font-weight: 700;
}
.mirror-csearch-error p {
    margin: 0 0 var(--mirror-space-4);
    font-size: var(--mirror-text-sm);
}
.mirror-csearch-retry {
    display: inline-grid;
    place-items: center;
    min-block-size: var(--mirror-hit-min);
    padding-inline: var(--mirror-space-6);
    border: 0;
    border-radius: var(--mirror-radius-pill);
    background: var(--mirror-brand);
    color: var(--mirror-text-on-brand);
    font-size: var(--mirror-text-sm);
    font-weight: 600;
    cursor: pointer;
}

/* [SOURCE] services_list.dart:16-17,36 — LoadingWidget(loadingSize: 24) BELOW
   the list, ADDITIVELY: the results stay on screen. loading_widgets.dart:76-101
   — mainColor, strokeWidth size/8 = 3, round caps, 50%-opacity track.
   ⛔ `wire:loading.flex`, never bare `wire:loading` — Livewire writes
      `display:inline-block` INLINE and beats this stylesheet (pilot lesson). */
.mirror-csearch-loading {
    display: grid;
    place-items: center;
    padding-block: var(--mirror-space-4) var(--mirror-space-2);
}
.mirror-csearch-ring {
    inline-size: 24px;
    block-size: 24px;
    border-radius: 50%;
    border: 3px solid rgba(var(--mirror-brand-rgb), .5);
    border-block-start-color: var(--mirror-brand);
    animation: mirror-csearch-spin 900ms linear infinite;
}
@keyframes mirror-csearch-spin { to { transform: rotate(360deg); } }

/* [SOURCE] filiter_bottom_sheet.dart:90-93 — the SHEET's loading state is a
   DIFFERENT widget: a centred LogoLoadingWidget. loading_widgets.dart:51-72 —
   the Mirror WORDMARK in a 72x72 box, tinted mainColor, with a repeating WHITE
   shimmer at angle 100, duration 2s, Curves.easeOut.
   ⛔ Keeping the two loaders DISTINCT is deliberate: substituting one for the
      other is exactly the R2 violation the Profile pilot shipped. */
.mirror-csearch-logoload {
    inline-size: 72px;
    block-size: 72px;
    margin-inline: auto;
    background: linear-gradient(100deg,
        var(--mirror-brand) 0 38%,
        var(--mirror-text-on-brand) 50%,
        var(--mirror-brand) 62% 100%);
    background-size: 300% 100%;
    background-repeat: no-repeat;
    /* ⛔ longhand, NOT the `mask:` shorthand — Chrome does not apply the
       position/size half of the shorthand to an SVG carrying intrinsic
       dimensions, and this wordmark is 3518x994. */
    -webkit-mask-image: var(--mirror-logo-mark);
    -webkit-mask-repeat: no-repeat;
    -webkit-mask-position: center;
    -webkit-mask-size: contain;
            mask-image: var(--mirror-logo-mark);
            mask-repeat: no-repeat;
            mask-position: center;
            mask-size: contain;
    animation: mirror-csearch-shimmer 2s cubic-bezier(0, 0, .58, 1) infinite;
}
@keyframes mirror-csearch-shimmer {
    from { background-position:  150% 0; }
    to   { background-position: -150% 0; }
}

/* ═══ 8 · THE FILTER SURFACE ══════════════════════════════════════════════
   [SOURCE] filiter_bottom_sheet.dart:16-27 — showModalBottomSheet,
   isScrollControlled, white, TOP corners 16.
   Overlay mapping: Flutter bottom sheet → phone SHEET, ≥600 CENTRED MODAL. */
.mirror-csearch-scrim {
    position: fixed;
    inset: 0;
    background: var(--mirror-header-overlay);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--mirror-motion);
    /* ⛔ the elevation lives on the FIXED element itself — `position: fixed`
       creates a stacking context even at `z-index: auto`, so a z-index declared
       on a child would be sealed inside it (pilot defect). */
    z-index: 1300;
}
.mirror-csearch-filters {
    position: fixed;
    inset-inline: 0;
    inset-block-end: 0;
    max-block-size: 88vh;
    display: flex;
    flex-direction: column;
    background: var(--mirror-surface);
    border-radius: var(--mirror-radius-xl) var(--mirror-radius-xl) 0 0;   /* :20-22 top 16 */
    box-shadow: var(--mirror-overlay-shadow);
    transform: translateY(100%);
    transition: transform var(--mirror-motion-sheet);
    z-index: 1310;                               /* above the nav bar's 1200 */
    /* ⛔ 2026-09-02 — THE PANEL MUST NOT BE HIT-TESTABLE WHILE CLOSED.
       Below 768 the sheet hides by moving off-screen, which removes it from the
       hit area for free. The desktop popover (§12) hides with `opacity: 0`
       instead, and a transparent element is still clickable: measured at 1440,
       1024 and 768, a 509.6x381 invisible box at z-index 1310 sat over the
       results and `elementFromPoint` at the first card's centre returned
       `.mirror-csearch-select` — every click on a result was swallowed.
       ⚠️ The FROZEN PROTOTYPE has the same flaw (its rows 2-4 are blocked), so
       this is an owner-approved improvement ON the contract, not a parity fix.
       ⛔ `pointer-events`, NOT `visibility: hidden` — visibility would need a
       delayed transition and would break the approved fade. */
    pointer-events: none;
}
/* ⛔ `translateY(0)`, NEVER `transform: none`. Measured on the prototype: a
   transition between `translateY(100%)` and the KEYWORD `none` does not
   interpolate — the stuck animated value kept winning over the cascade AND over
   an inline `transform:none`, so the sheet stayed parked off-screen while every
   rule resolved correctly. Two explicit transform values always interpolate. */
#customer-search[data-filter-open="true"] ~ .mirror-csearch-scrim,
.mirror-csearch-overlay[data-open="true"] .mirror-csearch-scrim {
    opacity: 1;
    pointer-events: auto;
}
.mirror-csearch-overlay[data-open="true"] .mirror-csearch-filters {
    transform: translateY(0);
    pointer-events: auto;
}
/* [WEB ADAPTATION] the drag handle a Flutter modal sheet draws for free. */
.mirror-csearch-grip {
    inline-size: 44px;
    block-size: 4px;
    border-radius: 2px;
    background: var(--mirror-line-soft);
    margin: var(--mirror-space-2) auto 0;
    flex: none;
}
/* ⚖️ owner ruling OQ-S4 — the app applies 16 TWICE (:24 and :96) so its
   controls sit 32 from the edge. RULED: use 16 once. */
.mirror-csearch-filters-scroll {
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: var(--mirror-space-4);
}
.mirror-csearch-filters-head {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--mirror-space-2);
}
/* [SOURCE] :120-129 — a right-aligned TextButton.icon carrying Icons.clear_all
   and the app's eraseAll, RED. CWA-S9 (S9-6) made it read as an action. */
.mirror-csearch-erase {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-block-size: var(--mirror-hit-min);
    padding-inline: var(--mirror-space-2);
    border: 0;
    background: none;
    color: var(--mirror-app-red);
    font-size: var(--mirror-text-sm);
    font-weight: 600;
    cursor: pointer;
}
.mirror-csearch-erase svg { inline-size: 18px; block-size: 18px; }
.mirror-csearch-erase[disabled] { opacity: .4; cursor: default; }

.mirror-csearch-grid {                           /* :101-111 — 16 above, then 8 between */
    margin-top: var(--mirror-space-4);
    display: flex;
    flex-direction: column;
    gap: var(--mirror-space-2);
}
/* [SOURCE] :61-64 — Row(crossAxisAlignment: end, [Expanded(dropdown), 12, trash]).
   ⚖️ owner ruling OQ-S16 → WEB ADAPTATION FS-1: in the app the 36px (12 gap +
      24 glyph) exists only when the filter is SET, so the control edges move as
      filters are set and cleared. The gutter is now RESERVED on every row and
      the trash renders into it only when the filter has a value. The gap moved
      INTO the slot, so a row WITH a trash is geometrically identical to the
      app's and a row without one matches it instead of stretching. */
.mirror-csearch-field {
    display: flex;
    align-items: flex-end;
    gap: 0;
}
.mirror-csearch-control {
    flex: 1;
    min-inline-size: 0;
    position: relative;
}
.mirror-csearch-trash-slot {
    flex: 0 0 36px;                              /* 12 (the app's gap) + 24 (the glyph) */
    display: flex;
    align-items: center;
    justify-content: flex-end;
    min-block-size: var(--mirror-hit-min);
}
/* [SOURCE] :65-79 — a 24x24 RED glyph, present ONLY when that filter is set,
   12 from the control. The BUTTON fills the whole reserved 36x44 slot so the
   hit target clears WCAG 2.5.8, while `justify-content: flex-end` keeps the
   24px GLYPH exactly where the app puts it. */
.mirror-csearch-trash {
    inline-size: 36px;
    block-size: var(--mirror-hit-min);
    display: flex;
    align-items: center;
    justify-content: flex-end;
    border: 0;
    background: none;
    color: var(--mirror-app-red);
    cursor: pointer;
    padding: 0;
}
.mirror-csearch-trash svg { inline-size: 24px; block-size: 24px; flex: none; }

/* [SOURCE] drop_down_search_config.dart:53-68 — the field carries hintText AND
   labelText. CWA-S9 (S9-5) removed the visible DUPLICATION while keeping every
   accessible name.
   ⚠️ The frozen prototype makes these labels VISIBLE — and there is no
      duplication, because the control shows a VALUE (or "Select"), never its
      own name. CwaS9 G8's "no control paints its name twice" therefore still
      holds; its implementation is re-pointed to assert exactly that. */
.mirror-csearch-label {
    display: block;
    font-size: var(--mirror-text-xs);
    color: var(--mirror-hint);
    margin-block-end: var(--mirror-space-1);
}
.mirror-csearch-select {
    inline-size: 100%;
    min-block-size: var(--mirror-hit-min);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--mirror-space-2);
    padding: 10px var(--mirror-space-3);
    border: 0;
    background: var(--mirror-csearch-field-fill);
    border-radius: var(--mirror-radius-md);
    font-size: var(--mirror-text-sm);
    text-align: start;
    cursor: pointer;
    color: inherit;
}
.mirror-csearch-select-value {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* [SOURCE] config:121-134 — a SELECTED value renders 14 BOLD in mainColor. */
.mirror-csearch-select[data-set="yes"] .mirror-csearch-select-value {
    color: var(--mirror-brand);
    font-weight: 700;
}
.mirror-csearch-select[data-set="no"] .mirror-csearch-select-value {
    color: var(--mirror-hint-strong);
}
/* [SOURCE] config:31-40 — keyboard_arrow_down / _up, mainColor, 24. */
.mirror-csearch-caret {
    flex: none;
    inline-size: 24px;
    block-size: 24px;
    color: var(--mirror-brand);
    transition: transform var(--mirror-motion);
}
.mirror-csearch-select[aria-expanded="true"] .mirror-csearch-caret { transform: rotate(180deg); }

/* ── 8.1 The anchored menu · owner ruling OQ-S12 ──────────────────────────
   [SOURCE] drop_down_search_config.dart:71-89 — PopupProps.menu: white,
   elevation 4, shadow black@50%, MenuAlign.bottomCenter, shape with the BOTTOM
   corners 8 only, popUpAnimationStyle.noAnimation, FlexFit.loose,
   showSelectedItems, disableFilter + searchDelay ZERO.
   ⚖️ OQ-S12 RULED: an anchored popover INSIDE the filter surface at every
      width, never a second sheet. */
.mirror-csearch-menu {
    position: absolute;
    inset-inline: 0;
    inset-block-start: 100%;
    z-index: 5;
    background: var(--mirror-surface);
    box-shadow: var(--mirror-csearch-menu-shadow);
    border-radius: var(--mirror-csearch-menu-radius);
    max-block-size: 246px;
    overflow: auto;
    overscroll-behavior: contain;
    /* :24 — AnimationStyle.noAnimation. It appears instantly, deliberately. */
}
.mirror-csearch-menu-search {
    padding: var(--mirror-space-2);
    position: sticky;
    inset-block-start: 0;
    background: var(--mirror-surface);
}
/* [SOURCE] config:43-50 — filled lightGrayColor, its own hint.
   :87 searchDelay Duration.zero — NO debounce, unlike the keyword field. */
.mirror-csearch-menu-search input {
    inline-size: 100%;
    border: 0;
    border-radius: var(--mirror-radius-md);
    background: var(--mirror-csearch-field-fill);
    padding: 10px var(--mirror-space-3);
    font-size: var(--mirror-text-sm);
}
.mirror-csearch-menu ul {
    list-style: none;
    margin: 0;
    padding: 0 0 var(--mirror-space-2);
}
.mirror-csearch-menu li button {
    inline-size: 100%;
    text-align: start;
    padding: 11px var(--mirror-space-3);
    font-size: var(--mirror-text-sm);
    border: 0;
    background: none;
    color: inherit;
    cursor: pointer;
}
.mirror-csearch-menu li button:hover { background: rgba(var(--mirror-brand-rgb), .06); }
.mirror-csearch-menu li button[aria-selected="true"] {   /* :84 showSelectedItems */
    color: var(--mirror-brand);
    font-weight: 700;
    background: rgba(var(--mirror-brand-rgb), .08);
}
.mirror-csearch-menu-none {
    padding: var(--mirror-space-3);
    color: var(--mirror-hint);
    font-size: var(--mirror-text-xs);
}

/* ── 8.2 Apply · owner ruling OQ-S9 ───────────────────────────────────────
   [SOURCE] :368-377 — CustomButton(tr.submit) inside a SafeArea; it calls
   onApply then POPS the sheet. CustomButton is a PILL.
   ⚖️ OQ-S9 RULED — the web adopts this batched model; nothing re-queries until
      Apply. OQ-S4 ruled it closes at ALL widths. */
.mirror-csearch-foot {
    padding: var(--mirror-space-4);
    padding-block-start: var(--mirror-space-3);
    border-block-start: 1px solid var(--mirror-hairline);
    background: var(--mirror-surface);
}
.mirror-csearch-apply {
    inline-size: 100%;
    min-block-size: 52px;
    border: 0;
    border-radius: var(--mirror-radius-pill);
    background: var(--mirror-brand);
    color: var(--mirror-text-on-brand);
    font-size: var(--mirror-text-md);
    font-weight: 600;
    cursor: pointer;
    transition: transform var(--mirror-motion);
}
.mirror-csearch-apply:active { transform: scale(var(--mirror-tap-scale)); }
.mirror-csearch-sheet-loading {
    display: grid;
    place-items: center;
    padding-block: var(--mirror-space-8);
}

/* ═══ 8.3 · THE TOAST MUST CLEAR THE SHELL'S BOTTOM BAR ═══════════════════
   R1C-4 (docs/mirror-web-shared-shell-2026-09-01/12-r1c-handoff-context.md)
   · FUP-6, raised by the Customer Profile pilot, met again by Appointments,
   and now met a THIRD time here. Fixed 2026-09-02; record in
   docs/mirror-web-r1c-search-toast-fix-2026-09-02.md.

   `.mirror-toast-region` is pinned at `z-index: 1000` and
   `inset-block-end: 24px` (mirror-primitives.css:132-137) — both BELOW and
   BEHIND `.mirror-cnav`, which is `z-index: 1200` (nav.blade.php:158-162)
   and measures ~75px tall. Measured on /search at 390x800: a dispatched
   toast sat at y=728-776, entirely inside the bar's band and painted under
   it. The region is rendered by layouts/customer/master.blade.php on EVERY
   customer-shell route, so it is present here whether or not this surface
   raises anything.

   ⚠️ TWO CORRECTIONS TO THE HANDOFF, recorded so they are not repeated:
   · The FROZEN SEARCH PROTOTYPE DECLARES NO TOAST — search.html has none.
     The clearance the handoff attributes to it is the APPOINTMENTS
     prototype's `.a-toast`. The value below is BORROWED from that sibling
     (mirror-appointments.css §14), not read off this surface's contract.
   · NO CODE PATH DISPATCHES `mirror-toast` ON /search TODAY (app/ and
     resources/views grepped 2026-09-02). The defect is LATENT: any toast a
     future change raises here would be invisible. Not a live symptom.

   ⛔ mirror-primitives.css is SHA-256 pinned, so the general fix is still an
   owner-ruled action; this stays scoped with :has() exactly as both
   siblings' are. FUP-6 STAYS OPEN, with THREE overrides now in place. The
   general fix has a home that is neither the pinned primitive nor a fourth
   surface sheet: the customer shell layer, keyed on the bar's presence and
   written at a specificity that loses to these three (`:where()`), so it
   reaches every customer-shell route and no portal without a bar. Proposed
   in the record above for an owner ruling; NOT implemented.

   1250 sits above the bar (1200) and below this surface's scrim (1300) and
   filter sheet (1310), so an open sheet still covers a toast, never the
   reverse. `body:has(#customer-search)` is the scope §0 already uses.

   ⚠️ SEPARATE FINDING, NOT FIXED HERE (R1C-5 in the record above): in
   `dir="rtl"` the primitive's own centring — `inset-inline-start: 50%` +
   `translateX(-50%)`, mirror-primitives.css:135-136 — parks the region a
   full width to the LEFT of centre on EVERY customer-shell surface;
   measured x = -332.5 at 390 on /search?lang=ar. This rule does not touch
   it: the general fix lives in the pinned primitive and is owner-ruled. */
body:has(#customer-search) .mirror-toast-region {
    z-index: 1250;
    inset-block-end: calc(var(--mirror-nav-h) + env(safe-area-inset-bottom, 0px) + var(--mirror-space-4));
}

/* ═══ 9 · RESPONSIVE — the app's own 600 / 1024 ═══════════════════════════
   [SOURCE] custom_button.dart:180-198 for the breakpoints themselves.
   ⛔ The app is PORTRAIT-LOCKED. Nothing below has a Flutter counterpart:
      every rule in this section is a [WEB ADAPTATION]. */
@media (min-width: 600px) {
    .mirror-csearch { max-inline-size: var(--mirror-content-max); margin-inline: auto; }

    /* the filter surface stops being a sheet and becomes a CENTRED MODAL — the
       mapping the Profile pilot established and the owner approved.
       ⛔ PHYSICAL inset, deliberately: top/left + translate(-50%,-50%) centres
          identically in LTR and RTL. An `inset-inline: auto` override unset BOTH
          left and right and pushed the modal off-screen — measured at left:-7
          (768 AR). */
    .mirror-csearch-filters {
        inset: 50% auto auto 50%;
        transform: translate(-50%, -46%) scale(.98);
        inline-size: min(520px, calc(100vw - 48px));
        max-block-size: min(660px, 86vh);
        border-radius: var(--mirror-radius-xl);
        opacity: 0;
        transition: opacity var(--mirror-motion), transform var(--mirror-motion);
    }
    .mirror-csearch-overlay[data-open="true"] .mirror-csearch-filters {
        /* symmetric function list with the closed state, so it interpolates */
        transform: translate(-50%, -50%) scale(1);
        opacity: 1;
    }
    .mirror-csearch-grip { display: none; }      /* a modal is not draggable */

    .mirror-csearch-list--salons { grid-template-columns: repeat(2, 1fr); }
    .mirror-csearch-salon { inline-size: 100%; }
    .mirror-csearch-list--freelancers { grid-template-columns: repeat(3, 1fr); }
}

@media (min-width: 1024px) {
    /* ⛔ SERVICES STAY A SINGLE COLUMN OF ROWS AND KEEP THE 640 MEASURE at every
       width. A row is a row; turning it into a grid of cards would be the
       "developer-preferred layout" R2 forbids. Widening the measure is just as
       wrong in the other direction — it is precisely what produced the
       superseded 1420px row with ~900px of empty white between the text and the
       price. Only the GRID branches widen, because a grid uses extra width for
       more cards rather than for more emptiness inside one card. */
    #customer-search[data-branch="salon"] .mirror-csearch,
    #customer-search[data-branch="freelancer"] .mirror-csearch {
        max-inline-size: 960px;
    }
    .mirror-csearch-list--salons { grid-template-columns: repeat(3, 1fr); }
    .mirror-csearch-list--freelancers { grid-template-columns: repeat(4, 1fr); }
    .mirror-csearch-card { min-block-size: 96px; }
    .mirror-csearch-card-thumb { flex-basis: 96px; inline-size: 96px; }
}

/* ═══ 10 · REDUCED MOTION ═════════════════════════════════════════════════
   [SOURCE] none — Flutter honours the OS setting through its own widgets.
   [WEB ADAPTATION] on the web it must be written. Everything suppressed here is
   decorative; the STATES are unchanged. ⛔ Without motion a spinner is just a
   shape and the tinted wordmark is just a LOGO, so the status text — screen-
   reader-only everywhere else — becomes VISIBLE. */
@media (prefers-reduced-motion: reduce) {
    .mirror-csearch-scrim,
    .mirror-csearch-filters,
    .mirror-csearch-caret,
    .mirror-csearch-apply,
    .mirror-csearch-filter-toggle { transition-duration: .001ms !important; }

    .mirror-csearch-ring,
    .mirror-csearch-logoload {
        animation-duration: .001ms !important;
        animation-iteration-count: 1 !important;
    }
    .mirror-csearch-ring { opacity: .9; }

    .mirror-csearch-loading .mirror-csearch-srlabel,
    .mirror-csearch-sheet-loading .mirror-csearch-srlabel {
        position: static;
        inline-size: auto;
        block-size: auto;
        margin: var(--mirror-space-3) 0 0;
        overflow: visible;
        clip: auto;
        clip-path: none;
        white-space: normal;
        display: block;
        text-align: center;
        font-size: var(--mirror-text-xs);
        color: var(--mirror-brand);
    }
}

/* ═══ 11 · RTL ════════════════════════════════════════════════════════════
   Everything above is written with logical properties, so direction is a
   document attribute, not a stylesheet fork. Only genuinely physical things
   need a mirror. */
[dir="rtl"] .mirror-csearch-logoload { transform: scaleX(-1); }  /* the shimmer sweeps with the text */
