/**
 * ALLYNOR Shared Components — written ONCE, consumed by every surface.
 * ====================================================================
 *
 * Loaded on Hyva (storefront) AND Luma (checkout + vendor dashboard).
 * Every value comes from tokens.css. There are no literal hexes in this file.
 *
 * ---------------------------------------------------------------------------
 * CASCADE STRATEGY — read this before adding a rule.
 * ---------------------------------------------------------------------------
 * 1. This file is DELIBERATELY UNLAYERED. Hyva's components live in
 *    `@layer components`; in Tailwind v4 unlayered CSS beats layered CSS
 *    regardless of specificity. So these rules win the storefront cascade by
 *    construction — not by out-specificity-ing anything.
 *
 * 2. On Luma we do not compete with the framework, we BECOME it: the selector
 *    lists below include Luma's own class names (`.action.primary`, …), at the
 *    same specificity, loaded later. Ours wins the tie.
 *
 * 3. `!important` is BANNED in this module. It is how you lose this fight
 *    slowly. If a rule seems to need it, the architecture is wrong — fix the
 *    architecture. The Token Conformance Gate will name whatever is overriding
 *    us, with its file and line.
 *
 * STEP 0 NOTE (2026-07-12): the framework selectors are commented out on
 * purpose. This file currently defines the `.al-*` components ONLY, so it can
 * ship with ZERO visible change. Steps 1-4 of the migration switch them on,
 * one surface at a time, each behind the conformance gate.
 */

/* ===========================================================================
 * BUTTONS
 * v1.1 law — HOVER RISES (fill holds at full chroma); PRESS DARKENS.
 * =========================================================================== */

.al-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    min-height: var(--al-control-height);
    padding: 12px 20px;
    border: 1px solid transparent;
    border-radius: var(--al-radius-input);
    font-family: var(--al-font-heading);
    font-size: 15px;
    font-weight: 600;
    line-height: 1;
    text-align: center;
    text-decoration: none;
    cursor: pointer;
    box-shadow: none;
    transition:
        box-shadow var(--al-t-state) var(--al-ease),
        transform var(--al-t-press) var(--al-ease),
        background-color var(--al-t-press) var(--al-ease);
}

/* HOVER: the fill is NOT repainted. The control rises. AA holds by construction. */
.al-btn:hover {
    box-shadow: var(--al-shadow-btn-hover);
    transform: translateY(var(--al-hover-lift));
}

/* PRESS: now — and only now — the fill steps down one cut in-family. */
.al-btn:active {
    box-shadow: none;
    transform: scale(var(--al-press-scale));
}

.al-btn:focus-visible {
    outline: var(--al-focus-width) solid var(--al-focus-color);
    outline-offset: var(--al-focus-offset);
}

.al-btn[disabled],
.al-btn[aria-disabled="true"],
.al-btn.is-disabled {
    opacity: var(--al-disabled-opacity);
    cursor: not-allowed;
    box-shadow: none;
    transform: none;
    transition: none;
}

/* --- Variants ------------------------------------------------------------ */

.al-btn--primary {
    background-color: var(--al-primary);
    border-color: var(--al-primary);
    color: var(--al-white);              /* 6.94 N */
}
.al-btn--primary:active {
    background-color: var(--al-primary-pressed);   /* 9.06 N */
    border-color: var(--al-primary-pressed);
}

/* CTA green. G3: the ONE go action on buyer surfaces.
 * NEVER a button on the seller surface — there, green means money-in. */
.al-btn--cta {
    background-color: var(--al-cta);
    border-color: var(--al-cta);
    color: var(--al-white);              /* 4.93 N */
}
.al-btn--cta:active {
    background-color: var(--al-cta-pressed);       /* 6.81 */
    border-color: var(--al-cta-pressed);
}

.al-btn--secondary {
    background-color: var(--al-white);
    border-color: var(--al-primary);
    color: var(--al-primary);            /* 6.94 N on white */
}
.al-btn--secondary:hover {
    background-color: var(--al-primary-50);
    box-shadow: var(--al-shadow-btn-hover);
    /* border doubles optically via an inset ring — no layout shift */
    box-shadow: var(--al-shadow-btn-hover), inset 0 0 0 1px var(--al-primary);
}
.al-btn--secondary:active {
    color: var(--al-primary-pressed);
}

.al-btn--ghost {
    background-color: transparent;
    border-color: transparent;
    color: var(--al-primary);
}
/* Ghost is flat by nature — it has no surface to lift. Wash only. */
.al-btn--ghost:hover {
    background-color: var(--al-primary-50);
    box-shadow: none;
    transform: none;
}

.al-btn--danger {
    background-color: var(--al-error-text);
    border-color: var(--al-error-text);
    color: var(--al-white);
}

/* On the navy chrome, elevation needs a darker cast. */
.al-on-chrome .al-btn:hover {
    box-shadow: var(--al-shadow-btn-hover-dark);
}
.al-on-chrome .al-btn:focus-visible {
    outline-color: var(--al-focus-color-on-dark);
}

/* ===========================================================================
 * FORM CONTROLS — LEGACY, input-level face.
 *
 * ⚠️ SUPERSEDED by "THE FORM FIELD" below, which is the locked styleguide's
 * face and applies to the markup Magento already emits. These `.al-*` classes
 * paint the INPUT; the styleguide paints the WRAPPER (see that block for why
 * the difference matters). No template consumes them — kept only so an
 * unaudited consumer cannot break — and they should be deleted in the same
 * supervised pass that removes the page-specific form CSS.
 * =========================================================================== */

.al-input,
.al-select,
.al-textarea {
    width: 100%;
    min-height: var(--al-control-height);
    padding: 12px 16px;
    background-color: var(--al-white);
    border: 1px solid var(--al-border);
    border-radius: var(--al-radius-input);
    font-family: var(--al-font-body);
    font-size: 16px;    /* iOS Safari zooms any input below 16px. Do not lower this. */
    color: var(--al-ink);
    transition:
        border-color var(--al-t-state) var(--al-ease),
        box-shadow var(--al-t-state) var(--al-ease);
}

.al-input::placeholder,
.al-textarea::placeholder {
    color: var(--al-gray-light);   /* 2.51 — placeholders only, never content */
}

.al-input:focus,
.al-select:focus,
.al-textarea:focus {
    outline: none;
    border-color: var(--al-focus-color);
    box-shadow: 0 0 0 3px var(--al-focus-halo);
}

.al-input.is-invalid,
.al-select.is-invalid,
.al-textarea.is-invalid {
    border-color: var(--al-error-text);
    background-color: var(--al-error-bg);
}

.al-input[disabled],
.al-select[disabled],
.al-textarea[disabled] {
    opacity: var(--al-disabled-opacity);
    cursor: not-allowed;
}

/* Admin-locked sensitive field (bank / PAN) — a real state in this project. */
.al-input.is-locked {
    background-color: var(--al-surface-2);
    color: var(--al-gray-medium);
    cursor: not-allowed;
}

.al-field-error {
    margin: 6px 0 0;
    font-family: var(--al-font-body);
    font-size: 13px;
    color: var(--al-error-text);   /* 5.66 */
}

/* ===========================================================================
 * THE FORM FIELD — ONE face, every surface.
 * (Styleguide §5.4: storefront.html `.ctrl`, vendor.html `.input`. The two
 *  agree on every value and differ by exactly one variable — `--form-bg`,
 *  Lever 1 — which is wired in tokens.css, not here.)
 * ===========================================================================
 *
 * WHY THIS EXISTS: five different field faces shipped, radius disagreeing four
 * ways (8 / 11 / 14 / 0.5rem) and font-size four ways, each declared in its own
 * page CSS. The styleguide has always defined exactly one. This is that one.
 *
 * THE STRUCTURE IS THE POINT — read this before changing a selector.
 * The fill, the 1px stroke and the 14px radius live on the WRAPPER. The control
 * inside is transparent and border-less. That is what makes an adornment — a
 * `+91` prefix, a "Verify" button, a "Show" toggle, a success tick — free: it is
 * just another child of the same box, so there is no seam to hide, no
 * border-radius to zero out on one side, no input-group machinery. Painting the
 * input instead is precisely what forced every composed control to invent its
 * own chrome reset, and therefore its own face.
 *
 * NO NEW MARKUP IS ASKED FOR. Magento and Hyva already emit
 *     .field > .label + .control > input | select | textarea
 * on every core form, so `.control` IS the wrapper and no template changes.
 * We do not fight the framework's markup — we BECOME it (same doctrine as the
 * message and button bridges below).
 *
 * ⚠️ THE `:has()` GUARD IS LOAD-BEARING. `.control` is not a synonym for the
 * styleguide's `.ctrl`: Magento also uses it to wrap radio groups, fieldsets and
 * bare help text. Boxing those would draw a pill around a set of radios. The
 * guard means a `.control` is only boxed when it directly holds a real control.
 *
 * Cascade: this file is unlayered, so on Hyva it beats the theme's `@layer base`
 * input styling regardless of specificity (Tailwind v4: unlayered wins). On Luma
 * it is loaded after the framework at a higher class count. No `!important`.
 * =========================================================================== */

/* --- 1. THE BOX ---------------------------------------------------------- */
/* Two things in this guard are load-bearing.
 *
 * THE TYPE EXCLUSION. `> input` alone is wrong twice over: it would box a
 * `.control` holding nothing but a checkbox, a radio or a submit button, and §2
 * would then stretch that checkbox to 100% width with 16px of padding — i.e. it
 * would silently delete the control it was meant to style. A choice is a
 * `.choice` in the styleguide, never a `.ctrl`. The list is NEGATIVE on purpose:
 * a text-ish input type added to HTML later is styled automatically, whereas a
 * positive list would quietly stop covering it.
 *
 * THE `.field-reserved` ARMS. Hyva's formValidation INJECTS a
 * `div.field.field-reserved` between `.control` and the input the first time a
 * field is validated, whenever the template's own `.field` does not already carry
 * that class (advanced-form-validation.phtml, createMessageContainer). Without
 * these arms the box would render correctly on load and vanish the moment the
 * user submitted — the worst kind of bug to find by eye. */
/* ⚠️ `:where()` AROUND THE GUARD IS LOAD-BEARING, NOT STYLE.
 * `:has()` takes the specificity of its most specific argument, so a bare
 * `:has(> input:not([type="checkbox"]…))` scores (0,1,1) and this rule totals **(0,3,1)** —
 * which OUT-RANKS every state rule below, all of which are (0,3,0). The field then never
 * hovers, never focuses, never turns red. Caught by driving /market-register/vendor: the
 * control reported `:hover` true while its border stayed at the resting #E7ECF3.
 * `:where()` contributes ZERO specificity while matching identically, so the guard still
 * excludes checkbox/radio-only controls and the states can win again.
 * Do not "simplify" this back to a bare :has(). */
.field > .control:where(:has(
    > input:not([type="checkbox"], [type="radio"], [type="hidden"], [type="submit"], [type="button"], [type="image"], [type="reset"]),
    > select,
    > textarea,
    > .field-reserved > input:not([type="checkbox"], [type="radio"], [type="hidden"], [type="submit"], [type="button"], [type="image"], [type="reset"]),
    > .field-reserved > select,
    > .field-reserved > textarea
)) {
    /* The focus ring as a SWITCH, not a literal. The state rules in §6 do not
     * restate this guard — repeating a guard is how it drifts out of sync with
     * itself. Where the box is not drawn this property is simply never set, so
     * `box-shadow: var(--al-field-ring)` there is invalid at computed-value time
     * and resolves to the initial value, `none`. A halo can therefore never appear
     * around something that has no box. border-color needs no such switch: on a
     * `border:0` element it is invisible by construction. */
    --al-field-ring: 0 0 0 3px var(--al-focus-halo);

    position: relative;              /* the .aff adornment is positioned against this */
    display: flex;
    flex-wrap: wrap;                 /* a note/error child drops to its own line, never squashes the input */
    align-items: center;
    min-height: var(--al-control-height);        /* 48 */
    background-color: var(--al-form-bg);         /* Lever 1 — tinted in a shop room, white in a work room */
    border: 1px solid var(--al-border);
    border-radius: var(--al-radius-input);       /* 14 */
    transition:
        border-color var(--al-t-state) var(--al-ease),
        box-shadow var(--al-t-state) var(--al-ease);
}

/* --- 2. THE CONTROL INSIDE THE BOX --------------------------------------- */
/* Same type exclusion as the guard above, for the same reason: a checkbox that
 * happens to share a `.control` with a text input must keep its own metrics. */
.field > .control > input:not([type="checkbox"], [type="radio"], [type="hidden"], [type="submit"], [type="button"], [type="image"], [type="reset"]),
.field > .control > select,
.field > .control > textarea,
.field > .control > .field-reserved > input:not([type="checkbox"], [type="radio"], [type="hidden"], [type="submit"], [type="button"], [type="image"], [type="reset"]),
.field > .control > .field-reserved > select,
.field > .control > .field-reserved > textarea {
    /* `flex-basis: 0`, NOT `auto`, and `width: auto`, NOT `100%`.
     * The wrapper is `flex-wrap: wrap` (so a `.note` can drop to its own row). With
     * `flex: 1 1 auto` the basis resolves from `width`, so a `width: 100%` input demands the
     * full row — and the moment an `.aff` adornment shares the box, the two no longer fit and
     * the input WRAPS BELOW IT. Measured on /market-register/affiliate: children summed 598px
     * in a 556px control, the `+91` sat on its own line and the field rendered 72px instead of
     * 48. Basis 0 lets the control take exactly the space the adornment leaves. */
    flex: 1 1 0%;
    width: auto;
    min-width: 0;                    /* a flex item refuses to shrink below its intrinsic width without this */
    height: auto;
    margin: 0;
    padding: 12px 16px;
    appearance: none;
    background: transparent;         /* the WRAPPER carries the fill */
    border: 0;
    border-radius: var(--al-radius-input);   /* only for the focus/selection highlight — the box already clips */
    box-shadow: none;
    outline: none;                   /* replaced by the wrapper's focus ring in §6 — never removed bare */
    font-family: var(--al-font-body);
    font-size: var(--al-text-body);  /* 16px. iOS Safari zooms any input below 16. Do not lower this. */
    line-height: 1.4;
    color: var(--al-ink);
}

/* Hyva's injected wrapper must not become a box of its own — it is scaffolding,
 * not structure. */
.field > .control > .field-reserved {
    display: contents;
}

/* --- 3. TEXTAREA --------------------------------------------------------- */
.field > .control > textarea,
.field > .control > .field-reserved > textarea {
    min-height: 96px;
    padding-block: 14px;
    line-height: 1.55;
    resize: vertical;
}

/* --- 4. SELECT ----------------------------------------------------------- */
/* `appearance:none` above strips the native arrow, so the chevron is drawn back
 * in. Stroke is --al-gray-medium (#5B6776), url-encoded — a data URI cannot read
 * a custom property. It is the only place in this file a hex appears as text,
 * and it is a token value, not a new colour. */
/* `:not([multiple])` — a chevron means "opens a dropdown", which a multi-select
 * does not do: it is an always-open scrolling list. Drawing one there is a false
 * affordance, and the 44px padding it reserves eats a column of the option text.
 * Caught on the affiliate product-ad form, which uses `<select multiple size="8">`. */
.field > .control > select:not([multiple]),
.field > .control > .field-reserved > select:not([multiple]) {
    padding-right: 44px;
    background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='%235B6776' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 16px center;
}

/* A multi-select is a list, so it sizes from its `size` attribute rather than the
 * single-control height, and its options need the full width. */
.field > .control > select[multiple],
.field > .control > .field-reserved > select[multiple] {
    padding-block: 8px;
    background-image: none;
}

/* --- 5. PLACEHOLDER ------------------------------------------------------ */
/* `opacity:1` is required: Firefox dims placeholders by default, which would
 * drop an AA-safe token below AA without changing a single colour value. */
.field > .control > input::placeholder,
.field > .control > textarea::placeholder,
.field > .control > .field-reserved > input::placeholder,
.field > .control > .field-reserved > textarea::placeholder {
    color: var(--al-gray-medium);    /* white 5.76 · surface-2 5.42 */
    opacity: 1;
}

/* --- 6. STATES ----------------------------------------------------------- */
/* Two hook vocabularies produce ONE result: `[data-state=…]`, which is what the
 * locked styleguides mark up, and the runtime classes the frameworks actually
 * toggle — `field-error` (Hyva's formValidation, on the field wrapper) and
 * `mage-error` (Luma's jQuery validate, on the control itself). */

.field > .control:hover,
.field > .control[data-state="hover"] {
    border-color: var(--al-gray);
}

.field > .control:focus-within,
.field > .control[data-state="focus"] {
    border-color: var(--al-primary);
    /* No fallback ON PURPOSE — see §1. Unset (i.e. not a box) must resolve to
     * `none`, and a fallback here would resolve it to a halo instead. */
    box-shadow: var(--al-field-ring);
}

.field.field-error > .control,
.field > .control:has(> .field-error),
.field > .control:has(> .mage-error),
.field > .control[data-state="error"] {
    border-color: var(--al-error-text);
}

.field.field-success > .control,
.field > .control[data-state="success"] {
    border-color: var(--al-cta);
}

/* READONLY IS NOT DISABLED, and must not be painted as if it were.
 *
 * A readonly control is focusable, in the tab order, and its VALUE is the whole
 * point of it: the vendor's `bank_name` / `bank_branch` ARE the IFSC lookup result,
 * the thing the seller reads to confirm before submitting. Folding readonly into the
 * disabled rule below would apply `opacity: .45`, which composites the TEXT as well
 * as the fill and drops it to a measured 2.71:1 in a shop room and 2.83:1 in a work
 * room — an AA failure on must-read content. WCAG's inactive-control carve-out does
 * not cover readonly, because a readonly control is not inactive.
 *
 * So readonly gets its own affordance: hairline ground, softened value, no hover, no
 * caret. Full ink is 15.17 here; `--al-gray-medium` on `--al-border` measures 4.85,
 * so the value stays legible while reading unmistakably not-yours-to-edit. Note the
 * ground is the lever that works in BOTH rooms: `--al-surface-2` IS the resting fill
 * in a shop room, so the disabled rule's fill change is invisible on registration.
 *
 * Ordered BEFORE disabled deliberately. Both are (0,3,0), so a control that is both
 * readonly and disabled resolves to disabled — the stronger, genuinely inert state. */
.field > .control:has(> [readonly]),
.field > .control:has(> .field-reserved > [readonly]),
.field > .control[data-state="readonly"] {
    background-color: var(--al-border);
}

/* Hover must not offer an affordance the field cannot honour. Re-declaring the
 * resting token is the only way to withdraw a hover in CSS; (0,4,0) so it outranks
 * the (0,3,0) hover rule above regardless of source order. */
.field > .control:has(> [readonly]):hover,
.field > .control:has(> .field-reserved > [readonly]):hover,
.field > .control[data-state="readonly"]:hover {
    border-color: var(--al-border);
}

/* The value itself. (0,3,1) — one attribute above §2's (0,3,0), which would
 * otherwise hold the text at full `--al-ink`. */
.field > .control > input[readonly],
.field > .control > textarea[readonly],
.field > .control > .field-reserved > input[readonly],
.field > .control > .field-reserved > textarea[readonly],
.field > .control[data-state="readonly"] > input,
.field > .control[data-state="readonly"] > select,
.field > .control[data-state="readonly"] > textarea {
    color: var(--al-gray-medium);
    cursor: default;
}

.field > .control:has(> :disabled),
.field > .control:has(> .field-reserved > :disabled),
.field > .control[data-state="disabled"] {
    opacity: var(--al-disabled-opacity);
    background-color: var(--al-surface-2);
    cursor: not-allowed;
    transition: none;                /* a disabled control does not animate — the point of the state is that it does nothing */
}

.field > .control:has(> :disabled) > input,
.field > .control:has(> :disabled) > select,
.field > .control:has(> :disabled) > textarea,
.field > .control[data-state="disabled"] > input,
.field > .control[data-state="disabled"] > select,
.field > .control[data-state="disabled"] > textarea {
    cursor: not-allowed;
}

/* --- 7. THE ADORNMENT SLOT ----------------------------------------------- */
/* Anything that shares the box with the control: a `+91` prefix, a "Verify"
 * button, a "Show" toggle, a success tick. `flex:none` so it keeps its size and
 * the control absorbs the remaining width. */
.field > .control > .aff {
    flex: none;
    display: grid;
    place-items: center;
    padding-right: 14px;
    color: var(--al-gray-medium);
}

.field > .control > .aff:first-child {
    padding: 0 0 0 16px;             /* a PREFIX pads on the leading edge instead */
}

.field.field-success > .control > .aff,
.field > .control[data-state="success"] > .aff {
    color: var(--al-cta);
}

/* --- 8. WHAT MAGENTO ALSO PUTS IN `.control` ----------------------------- */
/* `.control` is not the styleguide's clean `.ctrl`: Magento drops help text
 * (`.note`) into it, and both validation engines append their message nodes
 * there too. They are given the full row and the control's own horizontal
 * padding so they read as text under the field rather than as content crammed
 * beside it. The correct end state is that these live OUTSIDE `.control` as the
 * styleguide's `.help` sibling — that is a template change, and template changes
 * are out of scope for this slice. */
.field > .control > .note,
.field > .control > div.mage-error,
.field > .control > .messages,
.field > .control > .field-reserved > .messages {
    flex: 1 0 100%;
    margin: 0;
    padding: 0 16px 10px;
    font-family: var(--al-font-body);
    font-size: 13px;
    line-height: 1.5;
    color: var(--al-gray-medium);
}

.field > .control > div.mage-error,
.field > .control > .messages,
.field > .control > .field-reserved > .messages {
    color: var(--al-error-text);
}

/* ===========================================================================
 * THE FORM FIELD — SELLER SURFACE, STAGED OFF.  *** DELETE ME IN THE CLEANUP ***
 * ===========================================================================
 *
 * The marketplace dashboard already paints a CONFORMANT field — same 48px, same
 * 14px radius, same 1px --al-border, same 16px type, same focus halo — but it
 * paints it on the INPUT, from `.al-seller .column.main .fieldset .control
 * .input-text` in marketplace-reskin.less (0,6,0).
 *
 * Two consequences, and this block exists because of the first:
 *
 *   1. Turning the wrapper box on here would draw a SECOND rounded border 1px
 *      outside the one already there, on a surface the client has signed off.
 *   2. Beating (0,6,0) from this file means either an `!important` (banned in
 *      this module) or a specificity race against a file this slice is not
 *      allowed to edit. Neither is a trade worth making for a face that is
 *      already correct.
 *
 * So the seller surface keeps its input-level face until the supervised pass
 * that deletes the page-specific form CSS. At that point: delete this block, and
 * the wrapper face — plus the white work-room ground already wired in tokens.css
 * (`.al-luma.al-seller { --al-form-bg: var(--al-white) }`) — takes over with no
 * further change.
 *
 * Scope is `.al-luma.al-seller .fieldset`, the same pair tokens.css flips the
 * ground on, PLUS `.fieldset` — and that qualifier is load-bearing.
 *
 * 🔴 IT WAS MISSING, AND IT COST A REGRESSION (found by Robin, 2026-07-30).
 * Reskin's rule is `.al-seller .column.main .fieldset .control .input-text`: it
 * only repaints inputs that live INSIDE a `.fieldset`. This block was scoped to
 * the whole seller surface, so every `.control` OUTSIDE a fieldset was stripped
 * of the component's box and got nothing back — reskin never reached it. On
 * `marketplace/account/editprofile` the social-ID rows and the whole "SEO &
 * tracking" panel live in `.vdisc-body`, not a fieldset, and rendered as bare
 * text on the page: input border 0, wrapper border 0, both transparent.
 * Measured alongside `#customer-email`, which IS in a fieldset and was fine.
 *
 * The staging must therefore cover EXACTLY what reskin repaints — no more. Any
 * `.control` reskin does not reach keeps the shared component's face, which is
 * the correct fallback and what those fields now use.
 *
 * It must also repeat the guard VERBATIM: a narrower copy would leave some
 * `.control`s boxed here and is the other way this staging can silently
 * half-apply.
 * =========================================================================== */

.al-luma.al-seller .fieldset /* ⚠️ `:where()` AROUND THE GUARD IS LOAD-BEARING, NOT STYLE.
 * `:has()` takes the specificity of its most specific argument, so a bare
 * `:has(> input:not([type="checkbox"]…))` scores (0,1,1) and this rule totals **(0,3,1)** —
 * which OUT-RANKS every state rule below, all of which are (0,3,0). The field then never
 * hovers, never focuses, never turns red. Caught by driving /market-register/vendor: the
 * control reported `:hover` true while its border stayed at the resting #E7ECF3.
 * `:where()` contributes ZERO specificity while matching identically, so the guard still
 * excludes checkbox/radio-only controls and the states can win again.
 * Do not "simplify" this back to a bare :has(). */
.field > .control:where(:has(
    > input:not([type="checkbox"], [type="radio"], [type="hidden"], [type="submit"], [type="button"], [type="image"], [type="reset"]),
    > select,
    > textarea,
    > .field-reserved > input:not([type="checkbox"], [type="radio"], [type="hidden"], [type="submit"], [type="button"], [type="image"], [type="reset"]),
    > .field-reserved > select,
    > .field-reserved > textarea
)) {
    --al-field-ring: none;

    display: block;
    min-height: 0;
    background-color: transparent;
    border: 0;
    border-radius: 0;
    box-shadow: none;
}

/* ===========================================================================
 * PHASE A — THE FOUR PIECES THE FORM CONTRACT WAS MISSING
 * (Styleguide §05.4 + §09, storefront.html + vendor.html)
 * ===========================================================================
 *
 * The field itself landed above, but four things a form needs were never
 * written anywhere — verified by grepping the BUILT styles.css, not assumed:
 *
 *   .help / --error / --success ...... 0 hits   (templates hand-carried
 *                                                `text-[color:var(--al-error-text)]`
 *                                                utilities as a workaround)
 *   .file-drop ....................... 0 hits   (spec existed only in vendor.html)
 *   the OTP box ...................... 0 hits   (and no spec at all — two live sizes)
 *   .spinner / loading ............... 0 hits   (spec existed only in storefront.html)
 *
 * They live here, ONCE, because the alternative is each page inventing its own —
 * which is exactly how five different field faces happened in the first place.
 * Anything that needs one of these now inherits it instead of authoring it.
 * =========================================================================== */

/* --- A1. HELP TEXT — the `.help` sibling below a field. -------------------
 * Styleguide storefront.html:543. This is the correct home for what Magento
 * currently drops INSIDE `.control` (see §8 above): the end state is a sibling
 * of `.control`, not a child of it.
 * Measured: --al-gray-medium 5.42 on surface-2 / 5.76 on white ·
 * --al-error-text 6.57 white / 5.76 on --al-error-bg ·
 * --al-success-text 6.81 white / 6.02 on --al-success-bg. */
.help {
    font-size: var(--al-text-caption);   /* 13px — the styleguide's help size, not text-xs */
    line-height: 1.5;
    color: var(--al-gray-medium);
}

/* `display:flex` so a leading icon pins to the FIRST line of a wrapping
 * message instead of centring against the whole block. */
.help--error,
.help--success {
    display: flex;
    align-items: flex-start;
    gap: 6px;
}

.help--error { color: var(--al-error-text); }
.help--success { color: var(--al-success-text); }

/* --- A2. THE OTP BOX ------------------------------------------------------
 * NEITHER styleguide defines one — the only mentions are "OTP verified" in the
 * tick vocabulary. So it is derived from the field rather than invented: the
 * same fill, stroke, radius, focus ring and error colour, in a square-ish box
 * with the digit centred. One size, 48x56 (Robin's call 2026-07-30) — the two
 * that shipped disagreed (vendor 48x56, affiliate 44x48).
 *
 * Unlike the field, the face is on the INPUT here, not a wrapper: an OTP row is
 * N independent boxes, so there is no single control to draw a box around.
 *
 * The digit is --al-ink (15.17 on the tinted fill). What shipped was #2D2D2D,
 * the RETIRED pre-revamp `charcoal`. */
.al-otp {
    display: flex;
    gap: 10px;
}

.al-otp > input {
    flex: 0 0 auto;
    width: 48px;
    height: 56px;
    min-width: 0;
    padding: 0;
    text-align: center;
    appearance: none;
    background-color: var(--al-form-bg);         /* Lever 1 — tinted in a shop room, white in a work room */
    border: 1px solid var(--al-border);
    border-radius: var(--al-radius-input);       /* 14 */
    font-family: var(--al-font-body);
    font-size: 18px;
    font-weight: 600;
    line-height: 1;
    color: var(--al-ink);
    transition:
        border-color var(--al-t-state) var(--al-ease),
        box-shadow var(--al-t-state) var(--al-ease);
}

.al-otp > input::placeholder {
    color: var(--al-gray-medium);
    opacity: 1;
}

.al-otp > input:hover { border-color: var(--al-gray); }

/* FILLED — a box that already holds a digit. The vendor template carried this as
 * a raw `border-primary border-2` utility and the affiliate one had no filled
 * state at all; it belongs to the component so both read the same. Stays at 1px:
 * the old utility jumped to 2px, which nudges the digit as you type. */
.al-otp > input.is-filled { border-color: var(--al-primary); }

.al-otp > input:focus {
    outline: none;                               /* replaced by the ring, never removed bare */
    border-color: var(--al-primary);
    box-shadow: 0 0 0 3px var(--al-focus-halo);
}

/* The whole row turns red together — one wrong code is not six wrong fields. */
.al-otp[data-state="error"] > input,
.al-otp.field-error > input { border-color: var(--al-error-text); }

.al-otp > input:disabled,
.al-otp[data-state="disabled"] > input {
    opacity: var(--al-disabled-opacity);
    cursor: not-allowed;
    transition: none;
}

/* Mobile: six fixed 48px boxes plus gaps overflow a 360px viewport and the row
 * clips at the edges — the client reported exactly this on 2026-07-02. The boxes
 * flex down to fit instead. This behaviour used to live in page-register.css as
 * `.fab-register .fab-otp-box`; it belongs to the component, so the page rule
 * goes when the templates move over. */
@media (max-width: 639px) {
    .al-otp { gap: 8px; }

    .al-otp > input {
        flex: 1 1 0;
        width: auto;
        max-width: 52px;
        height: 52px;
    }
}

/* --- A3. THE FILE DROPZONE ------------------------------------------------
 * Styleguide vendor.html:477 (`.file-drop`). A horizontal row — icon well,
 * name + hint stack, Browse button — NOT a tall empty rectangle.
 *
 * What shipped on the vendor documents step was `2px dashed --al-border`, which
 * measures 1.19 against the fill: the dashed edge was effectively invisible, the
 * same failure this whole slice removed from the text fields. The spec's stroke
 * is 1.5px --al-gray = 4.31 on surface-2 / 4.59 on white, and it also gets a
 * fill, so the affordance no longer rests on the stroke alone.
 *
 * ⚠️ The spec's icon-well glyph is `--color-gray-dark`, which has NO token in
 * tokens.css (open item #3). Substituted --al-gray-medium: measured 5.22 on
 * --al-surface-3. Reconcile when the token lands. */
.file-drop {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 14px;
    background-color: var(--al-form-bg);
    border: 1.5px dashed var(--al-gray);
    border-radius: var(--al-radius-input);       /* 14 */
    cursor: pointer;
    transition:
        border-color var(--al-t-state) var(--al-ease),
        background-color var(--al-t-state) var(--al-ease);
}

.file-drop:hover,
.file-drop[data-state="hover"] {
    border-color: var(--al-primary);
    background-color: var(--al-primary-50);      /* primary stroke on it = 6.12 */
}

.file-drop:focus-within {
    border-color: var(--al-primary);
    box-shadow: 0 0 0 3px var(--al-focus-halo);
}

.file-drop .fic {
    flex: none;
    display: grid;
    place-items: center;
    width: 40px;
    height: 40px;
    border-radius: 10px;
    background-color: var(--al-surface-3);
    color: var(--al-gray-medium);
}

.file-drop .fmeta { flex: 1; min-width: 0; }

.file-drop .fname {
    display: block;
    font-size: var(--al-text-body-sm);
    font-weight: 600;
    color: var(--al-ink);
}

/* Single line, always. These sit in a grid — twelve of them on the vendor
 * documents step — and a hint that wraps makes its tile taller than its
 * neighbours. Measured: "JPG · JPEG · PNG · PDF" wrapped in a 102px cell and
 * produced 91px tiles beside 72px ones. */
.file-drop .fhint {
    display: block;
    font-size: var(--al-text-caption);
    color: var(--al-gray-medium);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* LOADED — the stroke goes solid (nothing is being asked for any more) and the
 * icon well turns green. Styleguide law, vendor.html:1556: "Uploaded + verified
 * is the ONE place a green icon well appears in a form." Do not reuse it. */
.file-drop[data-state="loaded"] {
    border-style: solid;
    border-color: var(--al-border);
}

.file-drop[data-state="loaded"] .fic {
    background-color: var(--al-success-bg);
    color: var(--al-success-text);               /* 6.02 on that fill */
}

.file-drop[data-state="error"] {
    border-style: solid;
    border-color: var(--al-error-text);
}

/* --- A4. THE LOADING STATE ------------------------------------------------
 * Styleguide storefront.html:463 (`.spinner`) and the law at :2338 — "buttons
 * keep their label and add an inline spinner". So a busy button does NOT swap to
 * "Loading…" and does NOT dim: the label stays readable and a 16px ring appears
 * beside it. `currentColor` means it is correct on every button variant for free.
 * This is what "Request OTP" needed. */
.spinner {
    width: 16px;
    height: 16px;
    flex: none;
    border-radius: 50%;
    border: 2px solid currentColor;
    border-right-color: transparent;
    animation: al-spin 700ms linear infinite;
}

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

/* A spinner is a status indicator, not decoration, so it must not be removed
 * outright under reduced-motion — it is slowed to a non-vestibular rate. */
@media (prefers-reduced-motion: reduce) {
    .spinner { animation-duration: 2400ms; }
}

/* Busy = inert to input, but NOT faded: the label has to stay legible, which is
 * the whole point of keeping it. Deliberately no `opacity` here. */
[aria-busy="true"] {
    cursor: progress;
    pointer-events: none;
}

/* --- A5. CHECKBOX + RADIO REACH 3:1 --------------------------------------
 * These are `appearance:none` controls, so their border IS the control boundary
 * and WCAG 1.4.11 applies at 3:1. Hyva sets `--form-stroke: var(--color-border)`
 * (tailwind-source.css:166) = #E7ECF3, which measures **1.06** against the
 * tinted field fill — a bare radio on the registration page was verified live at
 * that value, so this was never just the seller template's `border-border`
 * utility. R5-B already retargeted this variable, but only under four
 * `.customer-account-*` body classes, so every other form kept the failure.
 *
 * Declared on `body`, not `:root`, ON PURPOSE. Hyva declares it at `:root` too;
 * at equal specificity that would be decided by stylesheet order, which is
 * fragile. A custom property resolves from the nearest ancestor that declares
 * it, and every form control is inside `body` — so this wins for all of them
 * regardless of load order, and leaves Hyva's `:root` value untouched.
 *
 * Measured: --al-gray 4.31 on the tinted fill · 4.59 on white (needs 3).
 * The CHECKED state is deliberately left alone — it already fills with
 * --form-active-color (= --al-primary). */
body {
    --form-stroke: var(--al-gray);
}

/* --- A7. THE FIELD LABEL -------------------------------------------------
 * Styleguide storefront.html:509 (`.field > label, .field .lbl`).
 *
 * The earlier slice deleted `.form-label` from `components/forms.css` and NOTHING
 * replaced it, so every template has been carrying its own
 * `block text-sm font-medium text-charcoal mb-1` — which is why `text-charcoal`,
 * a RETIRED pre-revamp token, still appears ~70 times per registration template.
 * Purely additive: there is no competing rule to fight.
 *
 * TYPOGRAPHY ONLY — deliberately no `display` and no margin. The styleguide's
 * `.field > label` is `display:flex` so an icon can sit inline, but applying that
 * globally would re-flow every Magento label, including `.field.choice`, where
 * the label wraps the control itself. Templates keep their own layout utilities;
 * this owns what the label LOOKS like. Measured: --al-ink 16.13 on white, 15.17
 * on the tinted band. */
.field > label,
.field > .label {
    font-size: var(--al-text-body-sm);   /* 14px */
    font-weight: 600;
    line-height: 1.4;
    color: var(--al-ink);
}

/* --- A6. THE REQUIRED MARK -----------------------------------------------
 * `components/forms.css:128` paints it `text-red-600` — raw Tailwind, not our
 * error token, and this project retired #DC2626 for #B42318. R5-B corrected it,
 * but only under four `.customer-account-*` body classes, so every other form
 * still marks required fields in the wrong red. Same shape of gap as A5.
 * Both selectors because Magento emits `<label class="label">` in some templates
 * and a bare `<label>` in others. */
.field.required > label::after,
.field.required > .label::after,
.field._required > label::after,
.field._required > .label::after {
    color: var(--al-error-text);         /* 6.57 on white */
}

/* ===========================================================================
 * MESSAGES — the global block.  (Styleguide §09, storefront.html + vendor.html)
 *
 * Channel law (one event, ONE channel — never both):
 *   belongs to one field      -> .al-field-error (inline)
 *   transient + positive      -> toast
 *   blocks / errors / must survive a page load -> .al-msg (this block)
 *
 * Shape: THE FILL IS THE SIGNAL — no border, no left-rule. 20px icon pinned to
 * the first line, radius 14, body-sm/1.55, nothing truncates. A message the
 * user cannot finish reading is a message that was never sent.
 *
 * TWO MARKUP SHAPES render from this one definition:
 *   A. `.al-msg` — our own markup (Hyva storefront `messages.phtml`).
 *   B. Magento's own `div.message.<type>` — Luma checkout + the marketplace
 *      pages + core/3rd-party templates. See "FRAMEWORK BRIDGE" at the bottom;
 *      it shares every rule below via the per-type blocks.
 * =========================================================================== */

/* Magento can emit several at once. They stack with a 10px gap, ERRORS FIRST —
 * the blocking information is never below the congratulation. */
.al-msg-stack { display: grid; gap: 10px; }
.al-msg-stack:empty { display: none; }

.al-msg {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 14px 16px;
    margin: 0;
    border: 0;
    border-radius: var(--al-radius-lg);
    font-family: var(--al-font-body);
    font-size: 14px;
    line-height: 1.55;
}

/* The icon is a mask, not a glyph — so ONE definition serves both markup shapes
 * (a `<span>` here, Magento's `::before` on the bridge) and it always paints in
 * currentColor, i.e. the proven pair text colour. */
.al-msg__icon {
    flex: 0 0 auto;
    width: 20px;
    height: 20px;
    margin-top: 1px;
    background-color: currentColor;
    -webkit-mask: var(--al-msg-icon) center / 20px 20px no-repeat;
    mask: var(--al-msg-icon) center / 20px 20px no-repeat;
}

.al-msg__text { flex: 1 1 auto; min-width: 0; }   /* long copy wraps — never truncates */
.al-msg__text b { display: block; margin-bottom: 2px; }

/* The dismiss control: pinned top-right, 44px hit target (WCAG 2.5.5). The
 * negative margins keep the hit area off the visual box, so a one-line message
 * stays a one-line message. */
.al-msg__dismiss {
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    width: 44px;
    height: 44px;
    margin: -12px -12px -12px auto;
    padding: 0;
    background: none;
    border: 0;
    border-radius: 8px;
    color: inherit;
    opacity: .72;
    cursor: pointer;
    transition:
        opacity var(--al-t-state) var(--al-ease),
        background-color var(--al-t-state) var(--al-ease);
}
.al-msg__dismiss:hover { opacity: 1; background-color: rgba(255, 255, 255, .6); }
.al-msg__dismiss:focus-visible {
    outline: var(--al-focus-width) solid currentColor;
    outline-offset: -4px;
}

/* --- The four types. One rule each — it carries BOTH markup shapes. --------
 * Every pair below is AA-proven in tokens.css. Links inherit the pair colour,
 * so a link inside a message is AA by construction — never Magento's blue. */

.al-msg--success,
div.message.success {
    --al-msg-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
    background-color: var(--al-success-bg);
    color: var(--al-success-text);      /* 5.93 on its bg — see tokens.css */
}

.al-msg--error,
div.message.error {
    --al-msg-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='m15 9-6 6'/%3E%3Cpath d='m9 9 6 6'/%3E%3C/svg%3E");
    background-color: var(--al-error-bg);
    color: var(--al-error-text);        /* 5.66 */
}

.al-msg--warning,
div.message.warning {
    --al-msg-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 9v4'/%3E%3Cpath d='M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0Z'/%3E%3Cpath d='M12 17h.01'/%3E%3C/svg%3E");
    background-color: var(--al-warning-bg);
    color: var(--al-warning-text);      /* 4.63 */
}

.al-msg--info,
.al-msg--notice,
div.message.info,
div.message.notice {
    --al-msg-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M12 11v5'/%3E%3Cpath d='M12 8h.01'/%3E%3C/svg%3E");
    background-color: var(--al-info-bg);
    color: var(--al-info-text);         /* 6.12 */
}

/* ===========================================================================
 * BADGES — money states must read identically on every surface.
 * G3: a green badge means SETTLED. Green is never a button here.
 * =========================================================================== */

/* Shape/type come from the LOCKED styleguide (docs/design-system/styleguide/vendor.html): a badge is
 * an 8px rounded rect in 600 uppercase, NOT a 700-weight pill. The shipped component had drifted from
 * it, and the drift was invisible until the seller-forms fidelity gate measured a badge against its
 * mockup on 2026-07-13.
 *
 * ⚠️ SCOPE CHANGED 2026-07-28. This used to say ".al-badge renders only on the seller surface, so
 * this cannot reach the storefront or checkout" — that is no longer true. Customer order status now
 * uses this same set (Slice C3), which is the point: FABLE-05 §5 requires ONE badge system across
 * the seller, affiliate and customer surfaces, because a customer who sees two systems trusts
 * neither. A change here is now visible on the STOREFRONT as well. */
.al-badge {
    /* Luma sets border-box on form controls only, so a bare <span> is content-box — its padding then
     * sits OUTSIDE any height it is given, and it measures 8px shorter than it renders. */
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 10px;
    border-radius: var(--al-radius-badge);
    /* No font-family: the styleguide's badge inherits the surface's BODY face. Pinning it to the
     * heading face was part of the same drift as the pill radius. */
    font-size: 12px;
    font-weight: 600;
    line-height: 1.4;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    white-space: nowrap;
}

.al-badge--settled { background-color: var(--al-success-bg); color: var(--al-money-in); }
.al-badge--pending { background-color: var(--al-warning-bg); color: var(--al-money-pending); }
.al-badge--payable { background-color: var(--al-info-bg);    color: var(--al-money-payable); }
.al-badge--failed  { background-color: var(--al-error-bg);   color: var(--al-error-text); }

/* Finished, and neither good nor bad. Added 2026-07-28 for a fully-refunded `closed` order, which
 * the four money states could only describe by lying: red would tell the customer something went
 * wrong when nothing did, and green would claim a settlement that was reversed. No new hexes —
 * existing tokens only. Computed 5.22 on its own background. */
.al-badge--neutral { background-color: var(--al-surface-3);  color: var(--al-gray-medium); }

/* Optional leading dot. Colour alone is never the signal — the label always carries the meaning —
 * but the dot makes a badge read as a STATUS at a glance rather than as a stray chip. It inherits
 * `currentColor`, so it can never drift out of the AA-proven pair it sits in. */
.al-badge-dot {
    flex: 0 0 auto;
    width: 6px;
    height: 6px;
    border-radius: var(--al-radius-pill);
    background-color: currentColor;
    opacity: .7;
}

/* Money is always tabular so columns line up and cannot be misread. */
.al-money {
    font-variant-numeric: tabular-nums;
    color: var(--al-ink);   /* prices render in ink, never brand blue */
}

/* ===========================================================================
 * FRAMEWORK BRIDGE — MESSAGES.  *** STEP 1: LIVE. ***
 * ===========================================================================
 *
 * Magento emits its own message markup in three places we do not own:
 *
 *   Luma / checkout / marketplace (PHP + Knockout, `Magento_Ui/messages.html`):
 *       <div class="messages">
 *         <div class="message-error error message" data-ui-id="message-error">
 *           <div>…text…</div>          <- the icon hangs off THIS child's ::before
 *         </div>
 *       </div>
 *
 *   Core + 3rd-party templates:  <div class="message info empty">…</div>
 *   (Magento_Catalog product list, Magento_Sales order history, Fab_Returns,
 *    Fab_ShiprocketMarketplace, Hyva's wishlist/vault/address templates …)
 *
 * We do not fight those rules — we BECOME them. The selectors below carry the
 * SAME shape as Magento's own, one element-qualifier higher (`div.message.x` =
 * 0,2,1 vs Luma's `.message.x` = 0,2,0) and are loaded later, so they win
 * cleanly. No `!important`, anywhere.
 *
 * The `div` qualifier is load-bearing, not decoration: it deliberately EXCLUDES
 * `<p class="message error">` — the compact per-item error inside the cart
 * drawer. That is an INLINE channel, not the global block (channel law), and it
 * must not inflate into a box.
 *
 * The per-type fills/icons live in the MESSAGES section above — this block only
 * supplies the LAYOUT for Magento's markup shape.
 * =========================================================================== */

div.message.success,
div.message.error,
div.message.warning,
div.message.notice,
div.message.info {
    position: relative;          /* the ::before icon is positioned against this */
    display: block;
    margin: 0 0 10px;
    padding: 14px 16px 14px 48px;
    border: 0;
    border-radius: var(--al-radius-lg);
    font-family: var(--al-font-body);
    font-size: 14px;
    line-height: 1.55;
}

/* Magento hangs its icon-font glyph off the first child's ::before
 * (`.message.success > *:first-child:before` = 0,3,1). Ours is 0,3,2 and later:
 * the glyph becomes the same 20px masked icon the `.al-msg` component uses. */
div.message.success > *:first-child::before,
div.message.error > *:first-child::before,
div.message.warning > *:first-child::before,
div.message.notice > *:first-child::before,
div.message.info > *:first-child::before {
    content: "";
    position: absolute;
    top: 15px;
    left: 16px;
    width: 20px;
    height: 20px;
    margin: 0;
    padding: 0;
    font: inherit;               /* kills the `icons-blank-theme` glyph metrics */
    line-height: 1;
    color: inherit;
    background-color: currentColor;
    -webkit-mask: var(--al-msg-icon) center / 20px 20px no-repeat;
    mask: var(--al-msg-icon) center / 20px 20px no-repeat;
}

/* Links inherit the pair text colour and underline — the pair is already AA, so
 * the link is too. Magento paints them brand-blue on a red fill; we do not. */
.al-msg a,
div.message.success a,
div.message.error a,
div.message.warning a,
div.message.notice a,
div.message.info a {
    color: inherit;
    font-weight: 700;
    text-decoration: underline;
    text-underline-offset: 2px;
}

.al-msg a:hover,
.al-msg a:active,
div.message.success a:hover,
div.message.success a:active,
div.message.error a:hover,
div.message.error a:active,
div.message.warning a:hover,
div.message.warning a:active,
div.message.notice a:hover,
div.message.notice a:active,
div.message.info a:hover,
div.message.info a:active {
    color: inherit;
}

/* ===========================================================================
 * FRAMEWORK BRIDGE — LUMA BUTTONS.  *** STEP 2: LIVE. ***
 * ===========================================================================
 *
 * Scope: `.al-luma` — a body class set by the Fab/Luma theme (Magento_Theme/layout/default.xml).
 *
 * WHY A SCOPE, when the spec said to just list Luma's class names at equal specificity:
 * because `.action.primary` is NOT Luma-exclusive. The Hyva storefront renders it on the
 * customer-account pages, where the theme already styles it (page-customer.css). An unscoped rule
 * here would have out-specificity-ed nothing there but still *added* our transition, lift and
 * shadow to it — a silent, unreviewed storefront change shipped under a "checkout" step. The scope
 * makes the bridge inert on Hyva by construction. It also lifts us to (0,4,0), so we beat Luma's
 * own `.action.primary:hover` (0,3,0) with no !important.
 *
 * WHAT THIS BRIDGE DOES AND DOES NOT DO — this is the important part:
 *
 *   It carries the MOTION law, and no colour at all.
 *
 * Colour still comes from Luma's own variables in Fab/Luma/web/css/source/_theme.less, which is
 * correct and is not duplication: those variables ARE the framework's single knob, and they now
 * hold the token. Putting `background: var(--al-cta)` here instead would hard-wire GREEN into the
 * Luma bridge — and Luma is also the SELLER surface, where G3 says green is never a button. One
 * rule, two surfaces, two colours: so the rule must stay colour-free.
 *
 * The v1.1 law, restated: HOVER RISES — the fill holds at full chroma and the control lifts onto a
 * tight downward shadow. Nothing is repainted, so every contrast ratio is identical to rest and AA
 * holds by construction. PRESS DARKENS — darkening now means exactly one thing: you are pressing
 * this. (Luma already darkened on :active; what it also did, wrongly, was darken on :hover. That is
 * fixed at the variable, by making the hover fill equal the rest fill.)
 * =========================================================================== */

.al-luma .action.primary,
.al-luma .action.secondary,
.al-luma .action-apply,
.al-luma button.action,
.al-luma button.ask-que-to-admin {
    transition:
        box-shadow var(--al-t-state) var(--al-ease),
        transform var(--al-t-press) var(--al-ease),
        background-color var(--al-t-press) var(--al-ease);
}

.al-luma .action.primary:hover,
.al-luma .action.secondary:hover,
.al-luma .action-apply:hover,
.al-luma button.action:hover,
.al-luma button.ask-que-to-admin:hover {
    box-shadow: var(--al-shadow-btn-hover);
    transform: translateY(var(--al-hover-lift));
}

.al-luma .action.primary:active,
.al-luma .action.secondary:active,
.al-luma .action-apply:active,
.al-luma button.action:active,
.al-luma button.ask-que-to-admin:active {
    box-shadow: none;
    transform: scale(var(--al-press-scale));
}

.al-luma .action.primary:focus-visible,
.al-luma .action.secondary:focus-visible,
.al-luma .action-apply:focus-visible,
.al-luma button.action:focus-visible,
.al-luma button.ask-que-to-admin:focus-visible {
    outline: var(--al-focus-width) solid var(--al-focus-color);
    outline-offset: var(--al-focus-offset);
}

/* A disabled control neither rises nor sinks — the whole point of the state is that it does
 * nothing. Without this, Luma's disabled Place-Order button would still lift under the cursor and
 * read as pressable. */
.al-luma .action[disabled],
.al-luma .action.disabled,
.al-luma button.action[disabled] {
    box-shadow: none;
    transform: none;
    transition: none;
}

/* ===========================================================================
 * FRAMEWORK BRIDGE — THE SELLER ALLOCATION.  *** STEP 3: LIVE. ***
 * ===========================================================================
 *
 * Scope: `.al-luma.al-seller` — marketplace pages only (set by the Webkul marketplace_styles
 * handle, which loads on seller pages and nowhere else).
 *
 * This is the one place the surfaces are ALLOWED to diverge, and the divergence is ALLOCATION,
 * not palette: no new hex is introduced, the same tokens are simply spent differently.
 *
 *   G3 — GREEN IS EARNED.
 *   Buyer surface : green is the single GO action (checkout's Next / Place Order).
 *   Seller surface: green is NEVER a button. It means money-in / settled / verified.
 *                   Blue asks; green confirms.
 *
 * Both surfaces are Luma, so both inherited checkout's green `.action.primary` — meaning a vendor
 * saw the same green on "Contact Support" that they see on a settled payout. Repainting the seller's
 * primary blue is what frees green to carry money, and it is why `.al-badge--settled` can be trusted
 * to mean something.
 *
 * The rise/press motion still comes from the `.al-luma` bridge above — that law is universal, and is
 * not restated here. Only the FILL changes.
 * =========================================================================== */

.al-luma.al-seller .action.primary,
.al-luma.al-seller button.ask-que-to-admin {
    background-color: var(--al-primary);
    border-color: var(--al-primary);
    color: var(--al-white);              /* 6.94 N */
}

/* HOVER HOLDS at full chroma — it rises instead (see the .al-luma bridge). Stated explicitly
 * because Luma's own `.action.primary:hover` would otherwise repaint it. */
.al-luma.al-seller .action.primary:hover,
.al-luma.al-seller button.ask-que-to-admin:hover {
    background-color: var(--al-primary);
    border-color: var(--al-primary);
    color: var(--al-white);
}

/* PRESS DARKENS — one cut down, in-family. */
.al-luma.al-seller .action.primary:active,
.al-luma.al-seller button.ask-que-to-admin:active {
    background-color: var(--al-primary-pressed);   /* 9.06 N */
    border-color: var(--al-primary-pressed);
    color: var(--al-white);
}

/* ===========================================================================
 * FRAMEWORK BRIDGE — HYVA / TAILWIND BUTTONS.  *** STEP 4: LIVE. ***
 * ===========================================================================
 *
 * The storefront's `.btn` is a Tailwind `@utility` whose colours are driven entirely by a
 * `--btn-*` custom-property contract (--btn-bg, --btn-hover-bg, --btn-active-bg, …). That contract
 * is good, and it is kept. What was NOT good is that every VARIANT restated its colours as a second,
 * hand-typed definition of buttons the design system already owned — with stale fallback hexes
 * (`var(--color-primary, #002167)` falls back to the NAVY, not the primary blue), unowned hover
 * hexes (#026b4d, #B91C1C, #C43A2F), and one variant, the affiliate pill, whose entire palette
 * (#7C3AED / #C4B5FD / #F5F3FF) existed nowhere in the system at all.
 *
 * So the variants now live HERE — filled from tokens, once — and the storefront's @utility file
 * keeps only layout and typography. No literal hex survives on this surface.
 *
 * These rules are UNLAYERED and Tailwind's utilities are in `@layer utilities`, so they win by
 * construction — no !important, no specificity war.
 *
 * THE PURPLE PILL IS GONE. "Join as an Affiliate" is now a token-backed blue outline, the sibling
 * of the green-outline seller pill. G1 — no new hexes — finally holds on the storefront.
 * =========================================================================== */

/* The v1.1 law, applied to the storefront exactly as it is to checkout and the seller dashboard:
 * HOVER RISES (the fill holds — see each variant's --btn-hover-bg, which equals its --btn-bg),
 * PRESS DARKENS (--btn-active-bg steps one cut down, in-family). */
.btn:hover {
    box-shadow: var(--al-shadow-btn-hover);
    transform: translateY(var(--al-hover-lift));
}
.btn:active {
    box-shadow: none;
    transform: scale(var(--al-press-scale));
}
.btn:focus-visible {
    outline: var(--al-focus-width) solid var(--al-focus-color);
    outline-offset: var(--al-focus-offset);
    box-shadow: none;      /* the storefront used a focus RING; the system uses an outline */
}
.btn:is(:disabled, [aria-disabled="true"]):hover {
    box-shadow: none;
    transform: none;
}

/* --- Variants: the same colours as .al-btn--*, expressed in the --btn-* contract -------------- */

.btn-primary,
.pagebuilder-button-primary {   /* PageBuilder emits its own markup and never carries .btn-primary */
    --btn-stroke: transparent;
    --btn-bg: var(--al-primary);
    --btn-color: var(--al-white);                 /* 6.94 N */
    --btn-hover-stroke: transparent;
    --btn-hover-bg: var(--al-primary);            /* HOLDS — it rises instead */
    --btn-hover-color: var(--al-white);
    --btn-active-stroke: transparent;
    --btn-active-bg: var(--al-primary-pressed);   /* 9.06 N */
    --btn-active-color: var(--al-white);
}

/* G3: the ONE go action on the buyer surface (Add to Cart, Search, Buy Now). */
.btn-cta {
    --btn-stroke: transparent;
    --btn-bg: var(--al-cta);
    --btn-color: var(--al-white);                 /* 4.93 N */
    --btn-hover-stroke: transparent;
    --btn-hover-bg: var(--al-cta);                /* HOLDS */
    --btn-hover-color: var(--al-white);
    --btn-active-stroke: transparent;
    --btn-active-bg: var(--al-cta-pressed);       /* 6.81 */
    --btn-active-color: var(--al-white);
}

/* Outline. Hover WASHES rather than inverting to a solid fill: an outline button that turns solid
 * on hover repaints white text onto a colour it never had at rest, which is a second contrast
 * ratio to prove. The wash keeps the one that is already proven. */
.btn-secondary,
.pagebuilder-button-secondary {
    --btn-stroke: var(--al-primary);
    --btn-bg: var(--al-white);
    --btn-color: var(--al-primary);               /* 6.94 N on white */
    --btn-hover-stroke: var(--al-primary);
    --btn-hover-bg: var(--al-primary-50);
    --btn-hover-color: var(--al-primary);
    --btn-active-stroke: var(--al-primary-pressed);
    --btn-active-bg: var(--al-primary-100);
    --btn-active-color: var(--al-primary-pressed);
}

/* Text/ghost. Flat by nature — it has no surface to lift, so it washes and does not rise. */
.btn-tertiary {
    --btn-stroke: transparent;
    --btn-bg: transparent;
    --btn-color: var(--al-primary);
    --btn-hover-stroke: transparent;
    --btn-hover-bg: var(--al-primary-50);
    --btn-hover-color: var(--al-primary);
    --btn-active-stroke: transparent;
    --btn-active-bg: var(--al-primary-100);
    --btn-active-color: var(--al-primary-pressed);
}
.btn-tertiary:hover {
    box-shadow: none;
    transform: none;
}

/* Subtle bordered, white fill (PDP: Copy link / Reset / Write a review). */
.btn-ghost {
    --btn-stroke: var(--al-border);
    --btn-bg: var(--al-white);
    --btn-color: var(--al-primary);
    --btn-hover-stroke: var(--al-primary-100);
    --btn-hover-bg: var(--al-white);
    --btn-hover-color: var(--al-primary);
    --btn-active-stroke: var(--al-primary);
    --btn-active-bg: var(--al-primary-50);
    --btn-active-color: var(--al-primary-pressed);
}

/* Header "Join as a Seller" — green-tinted outline.
 * The stroke is the AA-SAFE green (--al-cta), not the #06A77D the storefront used to reach for:
 * that one is 3.07:1 and the system refuses to own it. As a border it would have squeaked past the
 * 3:1 UI floor, but there is no reason to keep an AA-failing hex alive for a border when a token
 * that clears text contrast is sitting right there. */
.btn-outline-seller {
    --btn-stroke: var(--al-cta);
    --btn-bg: var(--al-white);
    --btn-color: var(--al-cta);                   /* 4.93 N on white */
    --btn-hover-stroke: var(--al-cta);
    --btn-hover-bg: var(--al-success-bg);
    --btn-hover-color: var(--al-cta-pressed);
    --btn-active-stroke: var(--al-cta-pressed);
    --btn-active-bg: var(--al-success-bg);
    --btn-active-color: var(--al-cta-pressed);
}

/* Header "Join as an Affiliate" — THE PURPLE PILL, REPAINTED.
 *
 * It was #7C3AED text on a #C4B5FD stroke with a #F5F3FF hover — three hexes, zero token backing,
 * a whole accent colour that belonged to no palette and appeared nowhere else on the site. It is
 * now the blue sibling of the seller pill: same shape, same weight, token-backed, and it finally
 * means something in the system (blue asks; green confirms). */
.btn-outline-aff {
    --btn-stroke: var(--al-primary);
    --btn-bg: var(--al-white);
    --btn-color: var(--al-primary);               /* 6.94 N on white */
    --btn-hover-stroke: var(--al-primary);
    --btn-hover-bg: var(--al-primary-50);
    --btn-hover-color: var(--al-primary);
    --btn-active-stroke: var(--al-primary-pressed);
    --btn-active-bg: var(--al-primary-100);
    --btn-active-color: var(--al-primary-pressed);
}
