/* ═════════════════════════════════════════════════════════════════════════
   MIRROR — CUSTOMER PROFILE SURFACE
   Mirror Web UX/UI Reconstruction (MWU), pilot 1.  2026-08-31.
   Plan: docs/mirror-web-ux-rebuild-2026-08-31/05-integration-plan.md

   ── WHAT THIS FILE IS ────────────────────────────────────────────────────
   The ONE owner of `mirror-profile-*`. It styles exactly two surfaces:
     resources/views/livewire/customer/customer-profile-hub-component.blade.php
     resources/views/livewire/account/account-profile-component.blade.php

   ── WHY IT IS A NEW FILE AND NOT AN EDIT TO portal.css ───────────────────
   portal.css declares `.portal-account-summary` THREE times (lines 1402,
   2095, 2595) from three design generations; the shipped card is an emergent
   composite nobody designed — runtime-confirmed 2026-08-31, including a
   `grid-template-columns` that still computes under `display:flex`.
   See 02-current-web-analysis-and-gap.md §3.

   The fix is NOT a heavier override. The two blades stop using `portal-*`
   classes entirely, so this layer and portal.css NEVER SELECT THE SAME
   ELEMENT and a fourth generation cannot form.
   portal.css is SHA-256 pinned and is not edited.
   Enforced by MwuProfileSurfaceIsolationTest.

   ── HOUSE RULES (guarded) ────────────────────────────────────────────────
   1. No `portal-` selector anywhere in this file.
   2. No raw hex in a DECLARATION — every colour resolves through a
      `--mirror-*` custom property.
   3. Logical properties only — `inset-inline-*`, `margin-inline-*`,
      `text-align: start/end`. No physical `left`/`right` positioning, so
      Arabic mirrors for free.

   ── EVIDENCE LABELS ──────────────────────────────────────────────────────
   [SOURCE]     a Flutter declaration, cited file:line
   [ADAPTATION] a deliberate web divergence, with the reason stated
   ═════════════════════════════════════════════════════════════════════════ */


/* ═════════════════════════════════════════════════════════════════════════
   1 · SCOPE
   Everything is nested under .mirror-profile so nothing can leak onto a
   surface this file does not own.
   ═════════════════════════════════════════════════════════════════════════ */
/* Scoped with :has() so ONLY a page carrying this surface is affected — every
   other page sharing the customer/portal shell is untouched. This absorbs the
   scrollbar-width rounding of the 100vw bleed above; without it the page gains
   a horizontal scrollbar. */
body:has(.mirror-profile) { overflow-x: clip; }

/* ⚠️ THE ONE SELECTOR IN THIS FILE THAT TOUCHES A SHELL ELEMENT, and it is a
   BEHAVIOURAL fix, not a restyle. Disclosed here and pinned by
   MwuProfileSurfaceIsolationTest::test_the_single_shell_override_is_exactly_one_property.

   Found by driving the runtime, not by reading it: with the Saving state on
   screen at 390 the shell grew a HORIZONTAL SCROLLBAR, while
   `document.documentElement.scrollWidth === clientWidth` still reported clean.
   The document was clean. `main.page-body` was not — it measured
   scrollWidth 383 against clientWidth 375.

   Cause: portal.css:344 declares `.page-body { overflow-x: auto }` — one of the
   THREE generations that file carries for this element (02-…§3; the later two,
   :1935 and :3761, do not re-declare it, so generation 1's value survives).
   That makes the shell's content area a SECOND horizontal scrollport, INSIDE
   the <body> the rule above already clips. The surface's bleed then overshoots
   it by half the scrollbar width — `50vw` counts the scrollbar and the layout
   width does not — and `auto` turns 7.5px of deliberate bleed into a real,
   draggable scrollbar.

   ⛔ `hidden`, NOT `clip`, and the difference matters. `overflow-x: clip` would
   stop .page-body being a scroll container at all; `overflow-y: visible` would
   then stay visible instead of computing to auto, and the sticky submit bar
   (profile_view.dart:38-42) would re-resolve against the viewport. `hidden`
   clips the overflow and removes the scrollbar while leaving the scrollport —
   and therefore the sticky behaviour — exactly as it was. */
body:has(.mirror-profile) .page-body { overflow-x: hidden; }

.mirror-profile {
    /* The white ground is carried by the SURFACE (.mirror-profile-standalone),
       not by this outer wrapper: the wrapper spans the shell's full content
       width, so painting it white left a bare white band beside the card at
       desktop. [SOURCE] theme.dart:11 scaffoldBackgroundColor: Colors.white —
       declared explicitly on the surface because <body> carries the legacy
       canvas (#F4F6FA) from portal.css, which is not this product's ground. */
    background: transparent;
    color: var(--mirror-body);
    /* FUP-3: portal.css's `html, body` Sora rule is dead (overridden at its
       own index 204 by Inter), so this surface declares its own stack rather
       than inheriting an accident. mirror-tokens-mwu.css explains the order. */
    font-family: var(--mirror-font-profile);
    font-size: var(--mirror-text-sm);
    line-height: var(--mirror-leading-body);
    min-height: 100%;
}

/* ⛔ `:is(.mirror-profile, .mirror-profile-overlay)` throughout this SCOPE
   section. The language overlay is TELEPORTED to <body> (see §5.4), so it is
   not a descendant of .mirror-profile and would otherwise inherit none of the
   surface's base: no box-sizing, no button reset, Bootstrap's reboot showing
   through, portal.css's Inter instead of the profile stack, and no
   reduced-motion honouring. Listing it beside the surface keeps ONE definition
   of the base rather than a second copy that can drift.
   `:is()` takes the specificity of its most specific argument — both are
   single classes — so nothing here changes weight. */
:is(.mirror-profile, .mirror-profile-overlay) *,
:is(.mirror-profile, .mirror-profile-overlay) *::before,
:is(.mirror-profile, .mirror-profile-overlay) *::after { box-sizing: border-box; }

/* ── Shells ──────────────────────────────────────────────────────────────
   `standalone` is the hub at /profile. `layout` is the edit surface, which
   becomes two panes at >=1024 and is a plain column below that.

   ⛔ The identity RAIL is DESKTOP-ONLY. Below 1024 the app pushes a
   full-screen form and pops back (profile_view.dart:132), so a rail there
   would contradict the navigation model the back button implements. */
.mirror-profile-standalone,
.mirror-profile-layout {
    display: flex;
    flex-direction: column;
    min-height: 100%;
}
.mirror-profile-rail { display: none; }

/* The edit surface's detail pane. Below 1024 it is the whole screen and simply
   carries the white ground; at 1024+ the >=1024 block turns it into the card
   that sits beside the rail. */
.mirror-profile-detail { background: var(--mirror-surface); }

/* [SOURCE] settings_view.dart:15 — the maroon is the PAGE GROUND, edge to
   edge, not a panel inset from one. Both shells wrap the slot in a padded
   `.page-body`, so the surface must escape that padding.

   ⛔ THE BLEED GOES ON THE WRAPPER, AND THE CLIP GOES ON <body>.
   The first attempt put `margin-inline: calc(50% - 50vw)` on the surface and
   `overflow-x: clip` on THIS element — which clipped the very thing that was
   trying to escape, and the surface rendered as an inset card with gutters at
   both 390 and 768. Measured: the surface landed at x=-7 w=768 inside a
   753-wide layout and was then clipped back to the parent's padded box.

   `50vw` counts the vertical scrollbar while the layout width does not, so the
   bleed overshoots by half the scrollbar (~7.5px) each side. The clip must
   therefore sit on an ancestor that spans the FULL viewport — <body> — and is
   scoped with :has() so no other page sharing this shell is affected.
   `calc(50% - 50vw)` is direction-agnostic, so RTL needs no special case. */
.mirror-profile { margin-inline: calc(50% - 50vw); }
.mirror-profile-standalone {
    /* The header and the sheet carry their own 24/32px gutters, so the bleed
       needs no padding added back. */
    background: var(--mirror-surface);
}

.mirror-profile h1,
.mirror-profile h2,
.mirror-profile p,
.mirror-profile ul { margin: 0; padding: 0; }
.mirror-profile ul { list-style: none; }

/* Bootstrap's reboot loads before this file and resets form controls and
   headings. These re-assert rather than inherit. [ADAPTATION]
   ⛔ Wrapped in :where() so they contribute ZERO specificity. Without it
   `.mirror-profile button` (0,1,1) out-specifies `.mirror-profile-button`
   (0,1,0) and silently strips the submit button back to plain text — which is
   exactly what happened, and was caught in runtime verification. Every
   component class below must be able to win against these defaults. */
:where(.mirror-profile, .mirror-profile-overlay) :where(button) {
    font: inherit;
    color: inherit;
    background: none;
    border: 0;
    padding: 0;
    cursor: pointer;
}
:where(.mirror-profile, .mirror-profile-overlay) :where(a) { color: inherit; text-decoration: none; }
:where(.mirror-profile, .mirror-profile-overlay) :where(input, select) { font: inherit; color: inherit; }
:where(.mirror-profile, .mirror-profile-overlay) :where(svg) { display: block; }

/* [ADAPTATION] Flutter draws no focus indicator (custom_text_form_field.dart
   :152-176, every border BorderSide.none). WCAG 2.4.7 requires one.
   :focus-visible so a pointer user sees nothing — matching the app. */
:is(.mirror-profile, .mirror-profile-overlay) :focus-visible {
    outline: 2px solid var(--mirror-brand);
    outline-offset: 2px;
    border-radius: var(--mirror-radius-sm);
}
.mirror-profile-onbrand :focus-visible { outline-color: var(--mirror-text-on-brand); }

.mirror-profile-sronly {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

/* [SOURCE] BounceIt — every tappable surface in the app scales down on press. */
.mirror-profile-bounce {
    transition: transform var(--mirror-motion), background-color var(--mirror-motion);
}
.mirror-profile-bounce:active { transform: scale(var(--mirror-tap-scale)); }

@media (prefers-reduced-motion: reduce) {
    :is(.mirror-profile, .mirror-profile-overlay) *,
    :is(.mirror-profile, .mirror-profile-overlay) *::before,
    :is(.mirror-profile, .mirror-profile-overlay) *::after,
    .mirror-profile-dialog,
    .mirror-profile-scrim {
        animation-duration: .01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .01ms !important;
    }
}


/* ═════════════════════════════════════════════════════════════════════════
   2 · ICONS
   The app's own SVGs, rendered as MASKS tinted with currentColor.
   [SOURCE] svg_widget.dart:16 applies a flat srcIn tint that DISCARDS each
   file's own colours, so an <img> would ship raw black/white artwork. The
   files' partial alpha (Hugeicons uses opacity="0.4" on secondary shapes)
   survives the mask exactly as srcIn preserves it in Flutter.
   Same technique already registered on the live nav (nav.blade.php:57-70).
   ═════════════════════════════════════════════════════════════════════════ */
.mirror-profile-icon {
    display: inline-block;
    flex: none;
    width: var(--mirror-icon-size, 20px);
    height: var(--mirror-icon-size, 20px);
    background-color: currentColor;
    -webkit-mask-image: var(--mirror-icon-url);
            mask-image: var(--mirror-icon-url);
    -webkit-mask-repeat: no-repeat;
            mask-repeat: no-repeat;
    -webkit-mask-position: center;
            mask-position: center;
    -webkit-mask-size: contain;
            mask-size: contain;
}

.mirror-profile-icon--settings  { --mirror-icon-url: url('/assets/account/svgs/settings.svg'); }
.mirror-profile-icon--email     { --mirror-icon-url: url('/assets/account/svgs/email.svg'); }
.mirror-profile-icon--phone     { --mirror-icon-url: url('/assets/account/svgs/phone.svg'); }
.mirror-profile-icon--bookings  { --mirror-icon-url: url('/assets/account/svgs/bookings.svg'); }
.mirror-profile-icon--favourite { --mirror-icon-url: url('/assets/account/svgs/favourite.svg'); }
/* ⛔ `--reviews` (rate.svg) stood HERE and is DELETED, together with the file
   it pointed at. My Reviews left the hub on 2026-08-31 by owner ruling, and a
   declaration for a row that no longer exists is exactly the dead weight this
   layer was created to stop accumulating (02-…§3 · FUP-3). Nothing else
   referenced either. */
/* [SOURCE] settings_view.dart:75 — Assets.svgsTr is the LIST-ROW mark for
   Language. translation.svg is a DIFFERENT glyph and belongs to the sheet
   header (language_changer.dart:25-29); both are carried, byte-identical. */
.mirror-profile-icon--language  { --mirror-icon-url: url('/assets/account/svgs/tr.svg'); }
.mirror-profile-icon--translation { --mirror-icon-url: url('/assets/account/svgs/translation.svg'); }
.mirror-profile-icon--policy    { --mirror-icon-url: url('/assets/account/svgs/policy.svg'); }
.mirror-profile-icon--terms     { --mirror-icon-url: url('/assets/account/svgs/terms.svg'); }
.mirror-profile-icon--about     { --mirror-icon-url: url('/assets/account/svgs/about.svg'); }
.mirror-profile-icon--logout    { --mirror-icon-url: url('/assets/account/svgs/logout.svg'); }
.mirror-profile-icon--login     { --mirror-icon-url: url('/assets/account/svgs/login.svg'); }
.mirror-profile-icon--back      { --mirror-icon-url: url('/assets/account/svgs/backBtn.svg'); }
.mirror-profile-icon--images    { --mirror-icon-url: url('/assets/account/svgs/images.svg'); }

/* [SOURCE] settings_view.dart:195-196 Icon(Icons.arrow_forward_ios, size 16,
   color gray1Color). Drawn in CSS rather than shipped as an asset — Material
   glyphs are not in the app's SVG set. */
.mirror-profile-chevron {
    flex: none;
    width: var(--mirror-icon-chevron, 16px);
    height: var(--mirror-icon-chevron, 16px);
    color: var(--mirror-hint);
    position: relative;
}
.mirror-profile-chevron::before {
    content: "";
    position: absolute;
    inset-block-start: 50%;
    inset-inline-start: 25%;
    width: 40%;
    height: 40%;
    border-top: 2px solid currentColor;
    border-right: 2px solid currentColor;
    transform: translateY(-50%) rotate(45deg);
    transform-origin: center;
}
/* ✅ OQ-1 APPROVED 2026-08-31 — mirror the forward chevron in RTL.
   Material's Icons.arrow_forward_ios does NOT auto-mirror; a "forward"
   affordance pointing backwards in an RTL reading order is simply wrong. */
[dir="rtl"] .mirror-profile-chevron::before {
    inset-inline-start: auto;
    inset-inline-end: 25%;
    transform: translateY(-50%) rotate(-135deg);
}


/* ═════════════════════════════════════════════════════════════════════════
   3 · IDENTITY RAIL — the hub header + account rows
   [SOURCE] settings_view.dart + user_info_section.dart
   Rendered by the shared partial _identity-rail.blade.php, so /profile and
   (at >=1024) /account/profile show the same identity context.
   ═════════════════════════════════════════════════════════════════════════ */

/* ── 3.1 Header ──────────────────────────────────────────────────────────
   [SOURCE] settings_view.dart:15 — the SCAFFOLD ITSELF is Constants.mainColor;
   :26-32 a SliverAppBar of the same colour carries UserInfoSection. */
.mirror-profile-header {
    position: relative;
    background: var(--mirror-brand);
    color: var(--mirror-text-on-brand);
    padding:
        calc(var(--mirror-space-5) + env(safe-area-inset-top, 0px))
        var(--mirror-space-5)
        calc(var(--mirror-space-6) + var(--mirror-radius-sheet));
    overflow: hidden;
    isolation: isolate;
}

/* [SOURCE] user_info_section.dart:16-30 — bg_pattern.svg tinted
   Colors.black.withOpacity(0.3), BoxFit.cover, deliberately over-scaled so no
   seam shows. [ADAPTATION] cover -> repeat; see mirror-tokens-mwu.css. */
.mirror-profile-header::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background-image: url('/assets/account/img/mirror-pattern-tile.png');
    background-repeat: repeat;
    background-size: var(--mirror-pattern-tile);
    opacity: .30;                       /* :26 withOpacity(0.3) */
}

/* [SOURCE] :36-38 — a GUEST header centres its single row, because there is
   no email/phone block beneath it. */
.mirror-profile-header--guest {
    display: flex;
    align-items: center;
    min-height: 148px;                  /* :27 shortestSide * .38 on a 390 phone */
}

.mirror-profile-identity {
    display: flex;
    align-items: center;
    gap: var(--mirror-space-3);         /* :62 */
}

/* [SOURCE] :50-58 — circle, Clip.antiAlias, 2px WHITE ring drawn OUTSIDE the
   box (BorderSide.strokeAlignOutside) — hence box-shadow, not border. */
.mirror-profile-avatar {
    flex: none;
    width: var(--mirror-avatar-hub);
    height: var(--mirror-avatar-hub);
    border-radius: 50%;
    overflow: hidden;
    background: var(--mirror-field-fill);
    box-shadow: 0 0 0 var(--mirror-avatar-ring) var(--mirror-text-on-brand);
    display: grid;
    place-items: center;
}
/* The photo LAYERS OVER the no-photo mark, which is always in the DOM. When the
   file 404s, `onerror` removes the <img> and the mark is simply revealed — the
   web analogue of ImageWidget's errorWidget (image_widget.dart:87-91), and the
   reason a stored-but-missing photo can no longer render a broken-image glyph. */
.mirror-profile-avatar { position: relative; }
.mirror-profile-avatar-img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    background: var(--mirror-field-fill);
}
/* [ADAPTATION] A signed-in user with NO photo hits ImageWidget's network
   branch with an empty url, which falls through to errorWidget ->
   notFound.png (image_widget.dart:87-91). Reproducing a broken-image asset
   would be porting a bug. The app's OWN no-photo language on the sibling
   screen — a lightGray circle holding a brand-tinted images.svg
   (profile_view.dart:52-63) — is used instead. */
.mirror-profile-avatar .mirror-profile-icon {
    --mirror-icon-size: 45%;
    color: var(--mirror-brand);
}

/* [SOURCE] :64-69 — 16sp / w700 / white.
   `color` is declared EXPLICITLY, not inherited: portal.css styles bare `h1`
   with a darker ink at a specificity that beats inheritance, which rendered
   the name near-black on the maroon header. Caught in runtime verification. */
.mirror-profile-name {
    flex: 1 1 auto;
    min-width: 0;
    margin: 0;
    font-family: inherit;
    font-size: var(--mirror-text-md);
    font-weight: var(--mirror-weight-bold);
    line-height: 1.3;
    color: var(--mirror-text-on-brand);
    overflow-wrap: anywhere;
}

/* [SOURCE] :71-79 — the gear. Signed in only. The ONLY route to Edit Profile.
   [ADAPTATION] Flutter's is a bare 24px SvgWidget with no accessible name and
   no padding. Here it is a real <a> with an accessible name and a 44px hit
   target; the glyph stays 24px so the visual is unchanged. */
.mirror-profile-gear {
    flex: none;
    display: grid;
    place-items: center;
    width: var(--mirror-hit-min);
    height: var(--mirror-hit-min);
    margin: calc((var(--mirror-hit-min) - 24px) / -2);
    border-radius: 50%;
    color: var(--mirror-text-on-brand);
}
.mirror-profile-gear:hover { background: rgba(255, 255, 255, .14); }
.mirror-profile-gear .mirror-profile-icon { --mirror-icon-size: 24px; }

/* The edit surface's summary card keeps its "Welcome back" eyebrow
   (portal.account_summary_greeting_title), which
   PortalDataSurfaceProfessionalizationTest pins. Restyled onto the brand
   header rather than the old admin-skin panel. Web-only — the app's Edit
   Profile screen has no identity card. */
.mirror-profile-greeting {
    font-size: var(--mirror-text-xs);
    font-weight: var(--mirror-weight-bold);
    letter-spacing: .12em;
    text-transform: uppercase;
    opacity: .72;
}
/* "Member since" — web-only, and likewise pinned by the existing guards. */
.mirror-profile-meta-label {
    flex: none;
    opacity: .8;
}

/* [SOURCE] :83-93 — email then phone; 16 above, 12 between, icon 20 + gap 8. */
.mirror-profile-meta {
    margin-top: var(--mirror-space-4);
    display: grid;
    gap: var(--mirror-space-3);
}
.mirror-profile-meta-row {
    display: flex;
    align-items: center;
    gap: var(--mirror-space-2);
    font-size: var(--mirror-text-sm);
}
.mirror-profile-meta-row .mirror-profile-icon { --mirror-icon-size: 20px; }
.mirror-profile-meta-row bdi {
    min-width: 0;
    overflow-wrap: anywhere;
}

/* ── 3.2 Sheet + rows ────────────────────────────────────────────────────
   [SOURCE] settings_view.dart:41-47 — white, top radius 28, 24 gutter. */
.mirror-profile-sheet {
    position: relative;
    margin-top: calc(var(--mirror-radius-sheet) * -1);
    background: var(--mirror-surface);
    border-radius: var(--mirror-radius-sheet) var(--mirror-radius-sheet) 0 0;
    padding-inline: var(--mirror-space-5);
    padding-bottom: var(--mirror-space-5);
    flex: 1;
}

/* [SOURCE] :53 — SizedBox(height: mqSize.height * 0.02) before row 1. */
.mirror-profile-rows { padding-top: var(--mirror-space-4); }

/* [SOURCE] :180-198 — icon 20 brand · gap 16 · label 14/500 brand ·
   chevron 16 gray1Color · 1px #F4F4F5 bottom rule · padding v16 h4. */
.mirror-profile-row {
    display: flex;
    align-items: center;
    gap: var(--mirror-space-4);
    width: 100%;
    min-height: var(--mirror-hit-min);
    padding: var(--mirror-space-4) var(--mirror-space-1);
    border-bottom: 1px solid var(--mirror-hairline);
    color: var(--mirror-brand);
    text-align: start;
}
.mirror-profile-row:hover { background: rgba(var(--mirror-brand-rgb), .04); }
.mirror-profile-row .mirror-profile-icon {
    --mirror-icon-size: 20px;
    color: var(--mirror-brand);
}
.mirror-profile-row-label {
    flex: 1 1 auto;
    font-size: var(--mirror-text-sm);
    font-weight: var(--mirror-weight-medium);
}
/* The language row shows its current value; [ADAPTATION] — the app's row
   shows only a label, but a web row that opens a picker conventionally
   previews the selection, and the app's own picker shows it via the radio. */
.mirror-profile-row-value {
    flex: none;
    font-size: var(--mirror-text-xs);
    color: var(--mirror-hint-strong);
}
/* The logout row is a POST form; its button must look like every other row. */
.mirror-profile-row-form { margin: 0; }


/* ═════════════════════════════════════════════════════════════════════════
   4 · EDIT PROFILE
   [SOURCE] profile_view.dart
   ═════════════════════════════════════════════════════════════════════════ */

/* ── 4.1 App bar ─────────────────────────────────────────────────────────
   [SOURCE] custom_appbar.dart:158-197 — transparent, centred title,
   22sp w500 brand, preferredSize height 48, leadingWidth 96. */
.mirror-profile-appbar {
    display: grid;
    grid-template-columns: 48px 1fr 48px;
    align-items: center;
    gap: var(--mirror-space-2);
    min-height: 48px;
    padding:
        calc(var(--mirror-space-2) + env(safe-area-inset-top, 0px))
        var(--mirror-space-5)
        var(--mirror-space-2);
}
.mirror-profile-title {
    grid-column: 2;
    text-align: center;
    /* `font-family` is declared EXPLICITLY: portal.css styles headings with a
       display SERIF (Georgia before Marcellus, :138/146/153), which rendered
       this title in a serif that appears nowhere in the app. The app bar uses
       the body face (custom_appbar.dart:178-183 sets only size/weight/colour).
       Caught in runtime verification. */
    font-family: inherit;
    font-size: var(--mirror-text-title, 22px);
    font-weight: var(--mirror-weight-medium);
    color: var(--mirror-brand);
    line-height: 1.25;
    margin: 0;
}

/* [SOURCE] :202-227 — a 48px Material(color: Colors.white, CircleBorder) on a
   white scaffold: the circle is invisible and only the arrow reads.
   Reproduced; the hover tint is the web's affordance for a pointer. */
.mirror-profile-back {
    grid-column: 1;
    display: grid;
    place-items: center;
    width: 48px;
    height: 48px;
    border-radius: 50%;
    background: var(--mirror-surface);
    color: var(--mirror-brand);
}
.mirror-profile-back:hover { background: var(--mirror-field-fill); }
.mirror-profile-back .mirror-profile-icon { --mirror-icon-size: 24px; }
/* [SOURCE] :212-215 Transform.flip(flipX: isAr) — the arrow IS mirrored. */
[dir="rtl"] .mirror-profile-back .mirror-profile-icon { transform: scaleX(-1); }

/* ── 4.2 Body ────────────────────────────────────────────────────────────  */
/* ⛔ NO `padding-inline` HERE. The enclosing `.mirror-profile-sheet` already
   carries the 24px gutter, and declaring it again DOUBLED it: the edit form's
   content measured 294 wide inside a 390 viewport where the approved prototype
   measures 342 — a 48px gutter against the prototype's 24.

   ⚠️ Found on 2026-08-31 while measuring the Saving state, and it had been
   HIDING BEHIND A STALE NOTE: 07-…§2 recorded the 342→294 delta as *expected*,
   "the two surfaces sit in different shells with different gutters — see gap
   G1". Ruling G1 then put both halves of Profile in the SAME shell, which
   dissolved that explanation, and nobody re-checked the number the explanation
   had excused. The hub was always right; only this form was inset twice. */
.mirror-profile-form {
    display: flex;
    flex-direction: column;
    min-width: 0;
    flex: 1;
}
/* [SOURCE] :44 — 0.05.sh before the avatar. */
.mirror-profile-form-scroll { flex: 1; padding-top: 5vh; }

/* [SOURCE] :45-71 ImageSelector — a circle of mq.size.width * .25
   (107px on the 428 design frame), tap to pick. */
.mirror-profile-photo-field { display: grid; justify-items: center; }
.mirror-profile-photo-wrap { position: relative; }
.mirror-profile-photo {
    display: grid;
    place-items: center;
    width: var(--mirror-avatar-profile);
    height: var(--mirror-avatar-profile);
    border-radius: 50%;
    overflow: hidden;
    background: var(--mirror-field-fill);   /* :55-57 lightGrayColor */
    position: relative;
    cursor: pointer;
}
/* Same layering on the form's avatar control — see .mirror-profile-avatar-img. */
.mirror-profile-photo .mirror-profile-avatar-img {
    position: absolute;
    inset: 0;
    width: 100%; height: 100%;
    object-fit: cover;
    display: block;
    background: var(--mirror-field-fill);
    z-index: 1;
}
/* [SOURCE] :58-63 images.svg at mq.size.width * .1 (~43px), brand-tinted by
   SvgWidget's default colour (svg_widget.dart:16). */
.mirror-profile-photo .mirror-profile-icon {
    --mirror-icon-size: 43px;
    color: var(--mirror-brand);
}
/* [ADAPTATION] A hover/focus affordance. Flutter needs none — a finger has no
   hover — but a pointer user must be told the circle is actionable. */
.mirror-profile-photo::after {
    content: "";
    position: absolute;
    inset: 0;
    background: rgba(var(--mirror-brand-rgb), .45);
    opacity: 0;
    transition: opacity var(--mirror-motion);
    z-index: 2;   /* above the photo layer, which sits at z-index 1 */
}
.mirror-profile-photo:hover::after { opacity: 1; }
.mirror-profile-photo-input:focus-visible + .mirror-profile-photo::after { opacity: 1; }
.mirror-profile-photo-badge {
    position: absolute;
    inset-block-end: 2px;
    inset-inline-end: 2px;
    width: 32px; height: 32px;
    border-radius: 50%;
    background: var(--mirror-brand);
    color: var(--mirror-text-on-brand);
    display: grid;
    place-items: center;
    box-shadow: 0 0 0 3px var(--mirror-surface);
    pointer-events: none;
}
.mirror-profile-photo-badge .mirror-profile-icon { --mirror-icon-size: 16px; }
/* The real control: visually clipped but still focusable and still in the tab
   order — never display:none. Carried from the existing surface (ruling P7). */
.mirror-profile-photo-input {
    position: absolute;
    width: 1px; height: 1px;
    opacity: 0;
    overflow: hidden;
}
.mirror-profile-photo-status {
    display: block;
    margin-top: var(--mirror-space-2);
    font-size: var(--mirror-text-xs);
    color: var(--mirror-hint-strong);
    text-align: center;
}

/* [SOURCE] :72 — 64px between the avatar and the first field. A deliberate,
   large separation of identity from data. Preserved exactly. */
.mirror-profile-fields {
    margin-top: var(--mirror-space-8);
    display: grid;
    gap: var(--mirror-space-3);         /* :94,:105 */
}

/* ── 4.3 Fields ──────────────────────────────────────────────────────────
   [SOURCE] custom_text_form_field.dart:149-193 + input_decoration_theme.dart
     fill lightGrayColor · radius 12 · NO border in ANY state ·
     padding v16 h12 · text 14 in BRAND colour · hint 14/400.

   [ADAPTATION — floating label, owner-approved OQ-4 2026-08-31]
   The Flutter form is PLACEHOLDER-ONLY: once a field is filled its purpose is
   invisible, and CustomTextFormField's `label` is never passed (and is styled
   Colors.white, i.e. invisible on white anyway). AT REST this renders
   identically to Flutter — same fill, same radius, same hint text in the hint
   position. The label lifts only on focus or fill, a state the app's design
   never had to answer. See 03-…§6. */
.mirror-profile-field { position: relative; }

.mirror-profile-field-box {
    position: relative;
    background: var(--mirror-field-fill);
    border-radius: var(--mirror-radius-input);
    transition: box-shadow var(--mirror-motion);
}
.mirror-profile-field-box:focus-within { box-shadow: var(--mirror-focus-ring); }

.mirror-profile-input {
    width: 100%;
    height: 56px;
    padding: 22px var(--mirror-space-3) 8px;
    border: 0;                          /* [SOURCE] BorderSide.none */
    border-radius: var(--mirror-radius-input);
    background: transparent;
    font-size: var(--mirror-text-sm);
    /* ✅ OQ-6 APPROVED — brand maroon, consistently, including the phone
       field. custom_text_form_field.dart:190 uses mainColor while
       custom_phone_textformfield.dart:132 uses Colors.black; that is an
       app-internal inconsistency and the web normalises it. */
    color: var(--mirror-brand);
    outline: none;
}
.mirror-profile-input::placeholder { color: transparent; }
.mirror-profile-input:-webkit-autofill { -webkit-text-fill-color: var(--mirror-brand); }

.mirror-profile-label {
    position: absolute;
    inset-inline-start: var(--mirror-space-3);
    inset-block-start: 50%;
    transform: translateY(-50%);
    font-size: var(--mirror-text-sm);
    color: var(--mirror-hint-strong);
    pointer-events: none;
    max-width: calc(100% - var(--mirror-space-5));
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    transition:
        inset-block-start var(--mirror-motion),
        font-size var(--mirror-motion),
        transform var(--mirror-motion),
        color var(--mirror-motion);
}
.mirror-profile-field-box:focus-within .mirror-profile-label,
.mirror-profile-input:not(:placeholder-shown) + .mirror-profile-label {
    inset-block-start: 8px;
    transform: none;
    font-size: 11px;
}
.mirror-profile-field-box:focus-within .mirror-profile-label { color: var(--mirror-brand); }

/* [SOURCE] input_decoration_theme.dart:37 errorStyle: Colors.red.
   [ADAPTATION] 12px so the message does not outweigh the value it describes,
   plus aria wiring in the blade. */
.mirror-profile-error {
    display: block;
    margin: 6px var(--mirror-space-3) 0;
    font-size: var(--mirror-text-xs);
    line-height: 1.4;
    color: var(--mirror-error);
}
.mirror-profile-field-box--invalid { box-shadow: inset 0 0 0 1px var(--mirror-error); }
.mirror-profile-field-box--invalid:focus-within {
    box-shadow: inset 0 0 0 1px var(--mirror-error), var(--mirror-focus-ring);
}

/* ── 4.4 Phone ───────────────────────────────────────────────────────────
   [SOURCE] custom_phone_textformfield.dart:88-147 — IntlPhoneField with a
   country selector and a trailing dropdown icon, initialCountryCode 'AE'.
   [ADAPTATION] Flag imagery is not portable offline and Chrome on Windows
   renders regional-indicator pairs as letters; ISO + dial code carry the same
   information deterministically. */
.mirror-profile-phone { display: flex; align-items: stretch; }
.mirror-profile-phone-country {
    display: flex;
    align-items: center;
    gap: 6px;
    padding-inline: var(--mirror-space-3) 10px;
    border-start-start-radius: var(--mirror-radius-input);
    border-end-start-radius: var(--mirror-radius-input);
    color: var(--mirror-brand);
    font-size: var(--mirror-text-sm);
    white-space: nowrap;
    border: 0;
    background: none;
}
.mirror-profile-phone-country:hover { background: rgba(var(--mirror-brand-rgb), .06); }
/* ⛔ MANDATORY — the dial code is a bidi hazard. A leading `+` is
   bidi-NEUTRAL, so the Unicode Bidi Algorithm resolves it to the PARAGRAPH
   direction and renders `+971` as `971+` inside an Arabic page. Caught in the
   prototype review; the same class of defect as OD-19 / ruling B5.
   `unicode-bidi: isolate` is what <bdi> does, applied here because the value
   is emitted by Blade into a <span>. */
.mirror-profile-phone-dial {
    direction: ltr;
    unicode-bidi: isolate;
    font-variant-numeric: tabular-nums;
}
.mirror-profile-phone-caret {
    flex: none;
    width: 14px; height: 14px;
    position: relative;
}
.mirror-profile-phone-caret::before {
    content: "";
    position: absolute;
    inset-block-start: 30%;
    inset-inline-start: 20%;
    width: 55%; height: 55%;
    border-bottom: 2px solid currentColor;
    border-right: 2px solid currentColor;
    transform: rotate(45deg);
}
.mirror-profile-phone-sep {
    align-self: center;
    width: 1px;
    height: 22px;
    background: var(--mirror-line);
}
.mirror-profile-phone-number {
    position: relative;
    flex: 1;
    min-width: 0;
}
.mirror-profile-phone-number .mirror-profile-input { padding-inline-start: 10px; }
.mirror-profile-phone-number .mirror-profile-label { inset-inline-start: 10px; }
/* The national number is always LTR digits, but it must sit on the field's
   logical start edge in both directions. */
.mirror-profile-phone-number .mirror-profile-input {
    direction: ltr;
    text-align: start;
}
[dir="rtl"] .mirror-profile-phone-number .mirror-profile-input { text-align: end; }

/* Single-field passthrough — used when the stored mobile_number cannot be
   split against a known dial code. ⛔ Load-bearing: AuthController.php:216
   writes Str::random(10) into mobile_number for Google sign-ups, so
   non-numeric values can exist. Such a user must see their value intact.
   See 05-integration-plan.md §6.3 / IR-10. */
.mirror-profile-phone--raw .mirror-profile-phone-country,
.mirror-profile-phone--raw .mirror-profile-phone-sep { display: none; }
.mirror-profile-phone--raw .mirror-profile-input { padding-inline-start: var(--mirror-space-3); }
.mirror-profile-phone--raw .mirror-profile-label { inset-inline-start: var(--mirror-space-3); }


/* ═════════════════════════════════════════════════════════════════════════
   5 · BUTTONS, FEEDBACK, STATES
   ═════════════════════════════════════════════════════════════════════════ */

/* [SOURCE] custom_button.dart — a PILL (radius 50), brand fill, white bold
   label, maxLines 1; disabled fill Constants.grayColor (:229-230).
   Height/label step 44/14 -> 50/16 -> 56/18 at the app's OWN 600 / 1024
   thresholds (:180-198). profile_view.dart:144 overrides height to 48 here. */
.mirror-profile-button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    width: 100%;
    min-height: 48px;
    padding-inline: var(--mirror-space-5);
    border-radius: var(--mirror-radius-pill);
    background: var(--mirror-brand);
    color: var(--mirror-text-on-brand);
    font-size: var(--mirror-text-sm);
    font-weight: var(--mirror-weight-bold);
    line-height: var(--mirror-leading-tight);
    white-space: nowrap;
    transition: background-color var(--mirror-motion), transform var(--mirror-motion);
}
.mirror-profile-button:hover { background: var(--mirror-brand-dark); }
.mirror-profile-button[disabled] {
    background: var(--mirror-line);
    cursor: not-allowed;
}

/* [SOURCE] profile_view.dart:38-42,128-152 — the ListView sits inside
   Expanded and the button OUTSIDE it, so the button is pinned to the bottom
   of the viewport rather than scrolling with the fields. */
.mirror-profile-submit {
    position: sticky;
    bottom: 0;
    padding-block: var(--mirror-space-5);
    background: linear-gradient(to top, var(--mirror-surface) 72%, transparent);
}

/* ── 5.1 Saving indicator ────────────────────────────────────────────────
   [SOURCE] profile_view.dart:139-142 — while the save is in flight the button
   is REPLACED by `Center(child: LogoLoadingWidget())`, and
   loading_widgets.dart:6-8,53-73 is the Mirror WORDMARK, tinted
   Constants.mainColor in a 72x72 box, with a repeating WHITE shimmer at
   angle 100, duration 2s, Curves.easeOut. The approved prototype renders
   exactly that (`#submit-loading > .m-logoload`).

   ⚠️ CORRECTED 2026-08-31, owner ruling: "Do not infer the saving state from
   the existing implementation. Re-open the prototype and inspect the exact
   visual state shown while saving."
   This surface first shipped the app's OTHER loading widget — LoadingWidget's
   28px CircularProgressIndicator — beside a VISIBLE "Working…" label. That was
   a substitution, and the note justifying it MIS-CITED its own guard:
   S5CustomerAppIconsTest::test_the_mirror_wordmark_exists_but_no_consumer_adopted_it_yet
   does not forbid every consumer. It pins three named CHROME views —
   site/header, site/footer, customer/master — because adopting a 3.54:1
   wordmark into a square brand lockup shared with /salons and /freelancers is
   an open layout decision. A loading indicator inside this surface is not that
   decision, and none of those three views is touched here.

   The mark is a CSS mask of the wordmark ALREADY in the tree and already
   SHA-pinned, so nothing new lands; the sweep is a moving gradient beneath it.
   See mirror-tokens-mwu.css --mirror-logo-mark. */
.mirror-profile-logoload {
    /* [SOURCE] :7,55-57 — SizedBox(72 x 72), BoxFit.contain, so the wordmark
       renders 72 wide and ~20 tall. */
    width: 72px;
    height: 72px;
    margin-inline: auto;
    /* [SOURCE] :64-72 — .shimmer(color: Colors.white, angle: 100). */
    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 that carries intrinsic
       dimensions, and this wordmark is 3518x994. The shorthand left it masked
       at full size with only a centre fragment visible. */
    -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;
    /* [SOURCE] :66-67 — duration 2s, Curves.easeOut. */
    animation: mirror-profile-shimmer 2s cubic-bezier(0, 0, .58, 1) infinite;
}
@keyframes mirror-profile-shimmer {
    from { background-position: 150% 0; }
    to   { background-position: -150% 0; }
}

/* ⛔ `grid` + `place-items: center`, matching the prototype's `#submit-loading`.
   The status text is SCREEN-READER ONLY: the app shows no label at all here,
   and the prototype keeps it that way while still announcing the state. The
   first build rendered it as visible copy beside a spinner, which is neither
   the app's composition nor the prototype's. */
.mirror-profile-saving {
    place-items: center;
    color: var(--mirror-brand);
}

/* [SOURCE] the app cannot double-submit — the button is gone while saving.
   [ADAPTATION] a browser lets you keep typing into the fields underneath, so
   the prototype dims and freezes them (`html[data-state="saving"] #edit-form
   fieldset { opacity: .55; pointer-events: none }`). Reproduced with a real
   `<fieldset disabled>`, which also removes the controls from the tab order —
   the accessible equivalent of what the prototype does visually. */
.mirror-profile-fieldset {
    border: 0;
    padding: 0;
    margin: 0;
    min-width: 0;
    transition: opacity var(--mirror-motion);
}
.mirror-profile-fieldset[disabled] {
    opacity: .55;
    pointer-events: none;
}

/* ── 5.2 Inline banner ───────────────────────────────────────────────────
   ✅ OQ-2 APPROVED — the web provides proper error feedback.
   [ADAPTATION] Flutter renders NOTHING on error, on either path — a confirmed
   defect (01-…§3.1, logged as FUP-1; Flutter is NOT modified in this
   workstream). A failed save must not look like a successful one. */
.mirror-profile-banner {
    display: flex;
    align-items: flex-start;
    gap: var(--mirror-space-2);
    margin-bottom: var(--mirror-space-4);
    padding: var(--mirror-space-3) var(--mirror-space-4);
    border-radius: var(--mirror-radius-lg);
    background: var(--mirror-error-bg);
    color: var(--mirror-error-fg);
    font-size: var(--mirror-text-sm);
}
.mirror-profile-banner::before {
    content: "!";
    flex: none;
    display: grid;
    place-items: center;
    width: 20px; height: 20px;
    border-radius: 50%;
    background: var(--mirror-error);
    color: var(--mirror-surface);
    font-weight: var(--mirror-weight-bold);
    font-size: var(--mirror-text-xs);
}

/* ── 5.3 Guest pane ──────────────────────────────────────────────────────
   The app has NO route to Edit Profile for a guest (the gear is hidden), so
   this is reached only by typing the URL. Copy is the app's own go_to_login
   string; no product copy is invented. */
.mirror-profile-guest {
    display: grid;
    place-items: center;
    padding: var(--mirror-space-8) var(--mirror-space-5);
    text-align: center;
}
.mirror-profile-guest-inner {
    display: grid;
    justify-items: center;
    gap: var(--mirror-space-4);
    max-width: 340px;
}
.mirror-profile-guest .mirror-profile-icon {
    --mirror-icon-size: 48px;
    color: var(--mirror-brand);
    opacity: .5;
}
.mirror-profile-guest .mirror-profile-button { width: auto; min-width: 200px; }


/* ── 5.3b The success toast must clear the shell's bottom bar ────────────
   ⛔ SECOND AND LAST declared shell override, pinned alongside the first by
   MwuProfileSurfaceIsolationTest::test_the_shell_overrides_are_declared_and_bounded.

   A REGRESSION THIS SESSION'S OWN G1 RULING INTRODUCED, found by driving the
   runtime: /account/profile used to render in the PORTAL shell, which has no
   bottom bar. On the CUSTOMER shell it does. `.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` and 72px tall (nav.blade.php:138-145).

   The result: every successful save raised a toast that was in the DOM,
   measurably 358x46 at the right colours — and completely INVISIBLE, painted
   under the navigation bar. Confirmed with elementsFromPoint: the paint order
   at the toast's own centre read cnav > toast. A screenshot of the state
   showed nothing, which is exactly how it would have shipped.

   Both values come from the APPROVED PROTOTYPE, which already specified this:
   `.m-toastwrap { bottom: calc(var(--mirror-nav-h) + safe-area + 12px);
   z-index: 60 }` — "bottom-anchored, ABOVE the nav bar". 1250 sits above the
   bar (1200) and below the language sheet (1300), so a sheet still covers a
   toast rather than the other way round.

   ⚠️ FUP-6: the primitive itself is wrong for EVERY customer-shell surface
   that raises a toast, not just this one. mirror-primitives.css is SHA-256
   pinned, and moving that pin is an owner-ruled action (plan §4.3), so the fix
   is scoped here and the general case is registered. */
body:has(.mirror-profile) .mirror-toast-region {
    z-index: 1250;
    inset-block-end: calc(var(--mirror-nav-h) + env(safe-area-inset-bottom, 0px) + 12px);
}


/* ── 5.4 Language sheet ──────────────────────────────────────────────────
   [SOURCE] language_changer.dart:11-107, opened by settings_view.dart:74-77.
   The app's Language ROW opens a modal bottom sheet; the row itself is the
   whole target (`_buildSettingsOptionSvg` wraps the entire ListTile in the
   onTap), which is why the web row is now a full-width button and not a label
   hosting a small link.

   THE ADAPTATION RULE (03-…§5.4, prototype-validated):
     Flutter bottom sheet -> phone: bottom sheet · >=600: centred modal
   Preserved in both: the scrim, the content order, and the app's OWN explicit
   Cancel action (:76-85), which is kept rather than replaced by a close X.
   Esc and scrim-click are ADDED, because a pointer/keyboard medium expects
   them and the app's sheet is dismissible by drag, which a mouse cannot do. */
/* ⛔ TELEPORTED TO <body> by Alpine (`x-teleport`), and it has to be.
   At >=1024 the row lives inside `.mirror-profile-rail`, which is
   `position: sticky` — and a sticky box ALWAYS creates a stacking context, so
   a z-index declared inside it is capped by the rail's own place in the paint
   order and the sheet would have rendered UNDER the shell's fixed bottom bar
   (z-index 1200). Teleporting also frees it from the `overflow: hidden` the
   rail carries at that width. It therefore carries the surface's typography
   itself; §1's shared rules already list it beside `.mirror-profile`. */
.mirror-profile-overlay {
    position: fixed;
    inset: 0;
    /* ⛔ THE ELEVATION LIVES HERE, NOT ON THE SCRIM AND THE SHEET.
       A `position: fixed` box creates a stacking context EVEN AT
       `z-index: auto`, so the 1300/1301 declared on its children were sealed
       inside a context that itself sat at level 0 — and the shell's fixed
       bottom bar (z-index 1200, layouts/customer/nav.blade.php:145) painted
       straight over the sheet. Measured with elementsFromPoint, not guessed:
       the paint order at the bar read cnav > dialog. The children now only
       need to order relative to EACH OTHER. */
    z-index: 1300;
    font-family: var(--mirror-font-profile);
    font-size: var(--mirror-text-sm);
    line-height: var(--mirror-leading-body);
    color: var(--mirror-body);
}

.mirror-profile-scrim {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, .45);
    /* ⚠️ DEVIATION FROM THE PROTOTYPE, measured not guessed. The prototype
       uses z-index 50/51 because its own mock nav bar sits at 40. The real
       customer shell pins its bottom bar at `z-index: 1200`
       (layouts/customer/nav.blade.php:145), and the app's sheet covers the
       bottom bar completely — showModalBottomSheet draws above everything.
       A prototype z-index of 51 would have put the sheet UNDER the nav. */
    z-index: 1;
    animation: mirror-profile-fade 200ms;
}
@keyframes mirror-profile-fade { from { opacity: 0; } }

.mirror-profile-dialog {
    position: fixed;
    inset-inline: 0;
    bottom: 0;
    z-index: 2;
    background: var(--mirror-surface);
    /* [SOURCE] :97-99 — RoundedRectangleBorder vertical top Radius 20. */
    border-radius: var(--mirror-radius-sheet-sm) var(--mirror-radius-sheet-sm) 0 0;
    /* [SOURCE] :12-15 — EdgeInsets.symmetric(horizontal: 25, vertical: 10). */
    padding: var(--mirror-space-3) 25px calc(var(--mirror-space-5) + env(safe-area-inset-bottom, 0px));
    max-height: 85vh;
    overflow: auto;
    animation: mirror-profile-sheetup var(--mirror-motion-sheet);
}
@keyframes mirror-profile-sheetup { from { transform: translateY(100%); } }

/* [SOURCE] :24-35 — translation.svg @18 then the word "Language" in
   titleLarge. ⚠️ NOTE the row uses tr.svg and the sheet uses translation.svg:
   two different assets in the app, and both are carried byte-identical. */
.mirror-profile-dialog-head {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 0;
    /* Declared for the same reason .mirror-profile-title is: portal.css styles
       headings with a display serif that appears nowhere in the app. */
    font-family: inherit;
    font-size: var(--mirror-text-lg);
    font-weight: var(--mirror-weight-semibold);
    color: var(--mirror-ink);
    margin: 0;
}
.mirror-profile-dialog-head .mirror-profile-icon {
    --mirror-icon-size: 18px;
    color: var(--mirror-brand);
}
.mirror-profile-dialog-body { padding: 10px 0; display: grid; gap: 2px; }
.mirror-profile-dialog-foot { padding-top: var(--mirror-space-3); }

/* [SOURCE] :48-70 — RadioListTile, dense, controlAffinity TRAILING,
   activeColor Constants.mainColor, title w600. */
.mirror-profile-option {
    display: flex;
    align-items: center;
    gap: var(--mirror-space-3);
    width: 100%;
    padding: var(--mirror-space-3) var(--mirror-space-2);
    border-radius: var(--mirror-radius-md);
    font-size: var(--mirror-text-sm);
    font-weight: var(--mirror-weight-semibold);
    color: var(--mirror-ink);
    min-height: var(--mirror-hit-min);
}
.mirror-profile-option:hover { background: var(--mirror-field-fill); }
.mirror-profile-option-label { flex: 1 1 auto; text-align: start; }
.mirror-profile-option-mark {
    flex: none;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    box-shadow: inset 0 0 0 2px var(--mirror-line-strong);
    display: grid;
    place-items: center;
}
.mirror-profile-option[aria-checked="true"] .mirror-profile-option-mark {
    box-shadow: inset 0 0 0 2px var(--mirror-brand);
}
.mirror-profile-option[aria-checked="true"] .mirror-profile-option-mark::after {
    content: "";
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--mirror-brand);
}


/* ═════════════════════════════════════════════════════════════════════════
   6 · RESPONSIVE
   Thresholds are the APP'S OWN — custom_button.dart:180-198 (600 / 1024) and
   dash_ext.dart:20 (isTablet => shortestSide > 600). Nothing is invented.
   ═════════════════════════════════════════════════════════════════════════ */

/* ── >=600 · TABLET ──────────────────────────────────────────────────────
   One centred column. The maroon still bleeds edge to edge — it is the page
   ground in the app (settings_view.dart:15), not a card. */
@media (min-width: 600px) {
    .mirror-profile { --mirror-avatar-hub: 72px; }

    /* [SOURCE] custom_button.dart:186-188 — tablet: label 16, height 50. */
    .mirror-profile-button {
        font-size: var(--mirror-text-md);
        min-height: 50px;
    }

    .mirror-profile-measure {
        max-width: var(--mirror-content-max);
        margin-inline: auto;
        width: 100%;
    }
    /* ⛔ `.mirror-profile-form` is deliberately NOT in this list — the sheet is
       its gutter. See §4.2. */
    .mirror-profile-header,
    .mirror-profile-sheet { padding-inline: var(--mirror-space-6); }
    .mirror-profile-form-scroll { padding-top: var(--mirror-space-7); }

    /* A Flutter bottom sheet becomes a CENTRED MODAL. Same content, same 20px
       radius — now on all four corners. Copied from the approved prototype's
       own >=600 block, value for value. */
    .mirror-profile-dialog {
        inset: 50% auto auto 50%;
        transform: translate(-50%, -50%);
        width: min(460px, calc(100vw - 48px));
        border-radius: var(--mirror-radius-sheet-sm);
        padding: var(--mirror-space-4) var(--mirror-space-5) var(--mirror-space-5);
        box-shadow: var(--mirror-overlay-shadow);
        animation: mirror-profile-modalin var(--mirror-motion-sheet);
        max-height: 80vh;
    }
    @keyframes mirror-profile-modalin {
        from { opacity: 0; transform: translate(-50%, -46%) scale(.97); }
    }
}

/* ── >=1024 · DESKTOP — MASTER–DETAIL ────────────────────────────────────
   ✅ B-3 / OQ-7 APPROVED 2026-08-31 — "use the Master–Detail design exactly
   as established in the approved prototype."

   [ADAPTATION] Flutter has NO desktop: main.dart:12 locks the app to
   portrait, so anything here is a web design decision. Master–detail
   preserves the hierarchy the app expresses through push/pop — identity and
   navigation are the parent context, the form is the child — instead of
   discarding it, and it removes the desktop's worst failure mode: a
   three-field form alone on a 1440px canvas.

   ⚠️ ONE REFINEMENT the prototype could not express. The prototype was a
   SINGLE document simulating two screens, so it showed rail+form at every
   desktop width. The web has TWO ROUTES. Embedding the edit form into
   /profile would duplicate a surface at two URLs and change the IA, which
   §0.1 of the plan forbids. So:
       /account/profile  >=1024 -> RAIL + FORM   (master–detail, as prototyped)
       /profile          >=1024 -> the hub as a centred column
   The form is never alone on a wide canvas, and identity+navigation are
   always beside it while editing — which is the pattern's whole purpose.
   Recorded in 06-integration-verification.md. */
@media (min-width: 1024px) {
    /* [SOURCE] custom_button.dart:191-193 — desktop: label 18, height 56. */
    .mirror-profile-button {
        font-size: 18px;
        min-height: 56px;
    }

    .mirror-profile-layout {
        display: grid;
        grid-template-columns: var(--mirror-rail-width) minmax(0, 1fr);
        gap: var(--mirror-space-6);
        align-items: start;
        max-width: 1200px;
        margin-inline: auto;
        padding: var(--mirror-space-6);
    }

    /* The hub keeps its own centred column at desktop — see the note above:
       /profile is a distinct route and must not embed the edit form.

       Above 1024 the full-bleed maroon stops being the page ground and becomes
       a CARD: a 1840px-wide brand band with a 704px column of content inside it
       reads as a mistake, not as identity. Rounded and clipped, it is the same
       object the desktop rail is — which is what the approved prototype shows
       (evidence/17,18,19). [ADAPTATION, registered.] */
    .mirror-profile-standalone {
        max-width: calc(var(--mirror-content-max) + var(--mirror-space-6) * 2);
        margin-inline: auto;
        width: 100%;
        border-radius: var(--mirror-radius-xl);
        overflow: hidden;
        box-shadow: 0 1px 0 var(--mirror-line-soft), 0 10px 40px rgba(43, 22, 35, .08);
        min-height: 0;
    }
    /* The sheet's own top curve is what separates it from the maroon; the
       card's bottom corners come from the wrapper. */
    .mirror-profile-standalone .mirror-profile-sheet { padding-bottom: var(--mirror-space-6); }

    .mirror-profile-rail {
        display: flex;
        position: sticky;
        top: var(--mirror-space-6);
        border-radius: var(--mirror-radius-xl);
        overflow: hidden;
        box-shadow: 0 1px 0 var(--mirror-line-soft), 0 10px 40px rgba(43, 22, 35, .08);
        display: flex;
        flex-direction: column;
    }
    .mirror-profile-rail .mirror-profile-header {
        padding:
            var(--mirror-space-5)
            var(--mirror-space-5)
            calc(var(--mirror-space-5) + var(--mirror-radius-sheet));
    }
    .mirror-profile-rail .mirror-profile-identity { flex-wrap: wrap; }
    .mirror-profile-rail .mirror-profile-measure { max-width: none; }
    .mirror-profile-rail .mirror-profile-sheet { padding-inline: var(--mirror-space-5); }

    .mirror-profile-detail {
        background: var(--mirror-surface);
        border: 1px solid var(--mirror-line-soft);
        border-radius: var(--mirror-radius-xl);
        padding-bottom: var(--mirror-space-6);
        overflow: hidden;
    }

    /* ⛔ The detail pane's own identity summary is HIDDEN once the rail supplies
       identity, or the same avatar, name, email and phone appear twice side by
       side — which is what it looked like on first render, and is not what the
       approved prototype shows (its detail pane is app bar + avatar + fields).

       Hidden in CSS, not removed from the markup: `id="account-summary-card"`,
       the greeting and the <bdi>-isolated values are pinned by
       PortalDataSurfaceProfessionalizationTest and AccountProfileParityTest, and
       below 1024 — where there is NO rail — the summary is the only identity the
       screen has. This is a responsive presentation decision, not a deletion. */
    .mirror-profile-detail > .mirror-profile-header { display: none; }
    /* Nothing stretches in a pane that is not the scroll container: the
       submit follows the fields instead of being pushed to a pane floor. */
    .mirror-profile-detail .mirror-profile-form,
    .mirror-profile-detail .mirror-profile-form-scroll,
    .mirror-profile-detail .mirror-profile-guest { flex: 0 0 auto; }

    /* The rail IS the way back, so the 48px circular back button — which
       exists only to pop a pushed route — is removed. Its accessible name
       would otherwise promise a navigation that no longer happens. */
    .mirror-profile-detail .mirror-profile-back { display: none; }
    .mirror-profile-detail .mirror-profile-appbar {
        grid-template-columns: 1fr;
        padding: var(--mirror-space-5) var(--mirror-space-6) var(--mirror-space-4);
        border-bottom: 1px solid var(--mirror-hairline);
    }
    .mirror-profile-detail .mirror-profile-title {
        grid-column: 1;
        text-align: start;
    }
    .mirror-profile-detail .mirror-profile-form { padding-inline: var(--mirror-space-6); }
    .mirror-profile-detail .mirror-profile-form-scroll { padding-top: var(--mirror-space-6); }
    .mirror-profile-detail .mirror-profile-measure {
        max-width: 520px;
        margin-inline: 0;
    }
    .mirror-profile-detail .mirror-profile-photo-field { justify-items: start; }
    .mirror-profile-detail .mirror-profile-submit {
        position: static;
        background: none;
        padding-bottom: 0;
    }
}
