/* =============================================================================
   nxcart-components.css  —  NxCart admin component library (Phase 2)

   The .nx-* component classes. Every value derives from nxcart-tokens.css —
   no raw hex, no hardcoded spacing/radius/shadow here. Components consume
   tokens; rebrand by editing the token file's BRAND CORE block.

   Render functions for these components live in
   views/admin/_components/nx-components.php.

   These classes do not collide with Tailwind utilities, so they are
   unaffected by the admin-brand.css override layer. As pages adopt .nx-*
   they stop depending on that layer (see plan-admin-redesign.md Phase 10).
============================================================================= */

/* ---- Page header ---------------------------------------------------------- */
.nx-page-header {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--nx-space-3);
    margin-bottom: var(--nx-space-5);
}
.nx-page-header__main { min-width: 0; }
.nx-page-header__back {
    display: inline-flex;
    align-items: center;
    gap: var(--nx-space-1);
    margin-bottom: var(--nx-space-2);
    font-size: var(--nx-text-xs);
    font-weight: var(--nx-weight-semibold);
    color: var(--nx-color-link);
    text-decoration: none;
}
.nx-page-header__back:hover { color: var(--nx-color-link-hover); }
.nx-page-header__eyebrow {
    margin: 0 0 var(--nx-space-1);
    font-size: var(--nx-text-xs);
    font-weight: var(--nx-weight-bold);
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--nx-color-text-muted);
}
.nx-page-header__title {
    margin: 0;
    font-size: var(--nx-text-xl);
    font-weight: var(--nx-weight-bold);
    letter-spacing: -.01em;
    color: var(--nx-color-heading);
}
/* Title + count-badge row — used when nx_page_header(['count' => N]) is
   set so the resource total sits inline with the title without breaking
   the typographic baseline.
   `display: flex` (NOT inline-flex) so the row stays on its own line
   below the back link / eyebrow — inline-flex made the row collapse
   alongside the back link's <a> on pages like orders-abandoned.
   align-items:center so the small pill sits visually centered against
   the bold heading instead of dropping to the title's text baseline. */
.nx-page-header__title-row {
    display: flex;
    align-items: center;
    gap: var(--nx-space-2);
    flex-wrap: wrap;
}
.nx-page-header__lede {
    margin: var(--nx-space-1) 0 0;
    max-width: 70ch;
    font-size: var(--nx-text-base);
    line-height: var(--nx-leading-normal);
    color: var(--nx-color-text-muted);
}
.nx-page-header__actions { display: flex; gap: var(--nx-space-2); flex-shrink: 0; }

/* ---- Admin sidebar -------------------------------------------------------
   Main left-side navigation. Two row types: top-level links (Home, Settings,
   SEO audit, Documentation) and collapsible groups (<details>/<summary>) for
   the lettered sections (Sales / Catalog / Customers / Marketing / Online
   store / Insights). Active state mirrors .nx-settings-nav for cross-nav
   consistency: brand 8% tint background + brand-coloured text/icon. */
.nx-sidebar {
    display: flex;
    flex: 1;
    flex-direction: column;
    padding: var(--nx-space-3);
    gap: var(--nx-space-1);
}
.nx-sidebar__list {
    display: flex;
    flex-direction: column;
    flex: 1;
    gap: var(--nx-space-1);
    list-style: none;
    margin: 0;
    padding: 0;
}
.nx-sidebar__link {
    display: flex;
    align-items: center;
    gap: var(--nx-space-3);
    padding: 8px 12px;
    border-radius: var(--nx-radius-md);
    font-size: var(--nx-text-sm);
    font-weight: 500;
    /* Premium-nav pattern: idle label sits at full body text (dark, easily
       readable), icon stays muted so the two don't compete. Hover lifts
       both to heading colour; active lifts both to brand. Original used
       --nx-color-text-muted on the label — that's tuned for paragraph
       sub-text and read as a wash of light gray across 20+ nav rows. */
    color: var(--nx-color-text);
    text-decoration: none;
    transition:
        background-color .12s ease,
        color           .12s ease;
}
.nx-sidebar__link svg {
    width: 18px;
    height: 18px;
    flex-shrink: 0;
    color: var(--nx-color-text-muted);
    transition: color .12s ease;
}
.nx-sidebar__link:not(.is-active):hover {
    background: color-mix(in srgb, var(--nx-color-cta) 5%, transparent);
    color: var(--nx-color-heading);
}
.nx-sidebar__link:not(.is-active):hover svg {
    color: var(--nx-color-heading);
}
.nx-sidebar__link.is-active {
    background: color-mix(in srgb, var(--nx-color-cta) 8%, transparent);
    color: var(--nx-color-cta);
    font-weight: 600;
}
.nx-sidebar__link.is-active svg {
    color: var(--nx-color-cta);
}

/* Collapsible group (Sales / Catalog / etc) — native <details>/<summary>.
   Caller stamps `open` on <details> when the current page belongs inside,
   so navigating to a sub-page keeps the group expanded. */
.nx-sidebar__group {
    list-style: none;
}
.nx-sidebar__group > summary {
    list-style: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--nx-space-2);
    padding: 8px 12px;
    margin-top: 6px;
    /* Group-label colour: was --nx-color-text-subtle (38%) which was barely
       distinguishable from the chevron background. Bumped to muted (55%) so
       the uppercase section labels actually read as labels, not ghost text. */
    color: var(--nx-color-text-muted);
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .07em;
    user-select: none;
    border-radius: var(--nx-radius-md);
    transition: background-color .12s ease, color .12s ease;
}
.nx-sidebar__group > summary::-webkit-details-marker { display: none; }
.nx-sidebar__group > summary::marker { content: ''; }
.nx-sidebar__group > summary:hover {
    background: color-mix(in srgb, var(--nx-color-cta) 4%, transparent);
    color: var(--nx-color-heading);
}
.nx-sidebar__group-label {
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
}
.nx-sidebar__group-icon {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
    color: var(--nx-color-text-muted);
    transition: color .12s ease;
}
.nx-sidebar__group > summary:hover .nx-sidebar__group-icon {
    color: var(--nx-color-heading);
}
.nx-sidebar__chevron {
    width: 12px;
    height: 12px;
    color: var(--nx-color-text-muted);
    transition: transform .15s ease;
    flex-shrink: 0;
}
.nx-sidebar__group[open] .nx-sidebar__chevron {
    transform: rotate(180deg);
}
.nx-sidebar__group-list {
    list-style: none;
    margin: 2px 0 4px;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 1px;
}
/* Nested rows are indented to show parent/child + use smaller icons. */
.nx-sidebar__group-list .nx-sidebar__link {
    padding-left: 38px;
    font-size: 13px;
}
.nx-sidebar__group-list .nx-sidebar__link svg {
    width: 14px;
    height: 14px;
}
/* Pinned-bottom section (Settings / SEO audit / Documentation) — gets a top
   border so it visually anchors to the bottom of the sidebar. */
.nx-sidebar__pinned {
    margin-top: auto;
    padding-top: var(--nx-space-3);
    border-top: 1px solid var(--nx-border);
    display: flex;
    flex-direction: column;
    gap: var(--nx-space-1);
}

/* ---- Settings shell + sidebar nav ----------------------------------------
   2-column shell that puts the settings sub-route picker next to the form
   content. Replaces the legacy .scy-settings__shell / .scy-settings__nav
   pair (which is still in admin-brand.css under tenant-named classes — see
   the structure-class rule in CLAUDE.md: nxcrt__ / nx-* on new platform
   structure, scy-* tokens stay tenant-named). Use the new classes on any
   new settings sub-route. */
.nx-settings-shell {
    display: grid;
    gap: var(--nx-space-5);
    grid-template-columns: 240px minmax(0, 1fr);
}
@media (max-width: 960px) {
    .nx-settings-shell { grid-template-columns: 1fr; }
}
/* Main column of the settings shell. Flex-column so multiple stacked
   <form>s / cards space themselves; min-width:0 is the grid-child
   overflow fix (a long token in a card otherwise blows out the column).
   Mirrors the legacy .scy-settings__main rule in admin-brand.css:1350. */
.nx-settings-shell__main {
    display: flex;
    flex-direction: column;
    min-width: 0;
}
.nx-settings-nav {
    position: sticky;
    top: 5rem;
    align-self: start;
    background: var(--nx-surface-card);
    border: 1px solid var(--nx-border);
    border-radius: var(--nx-radius-lg);
    padding: 6px;
    display: flex;
    flex-direction: column;
    gap: 1px;
    box-shadow:
        0 1px 2px rgba(15, 23, 42, .04),
        0 4px 12px -4px rgba(15, 23, 42, .06);
}
@media (max-width: 960px) {
    .nx-settings-nav {
        position: static;
        flex-direction: row;
        flex-wrap: wrap;
        overflow-x: auto;
    }
}
.nx-settings-nav__link {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    border-radius: var(--nx-radius-md);
    font-size: var(--nx-text-sm);
    font-weight: 500;
    color: var(--nx-color-text-muted);
    text-decoration: none;
    white-space: nowrap;
    transition:
        background-color .12s ease,
        color           .12s ease;
}
.nx-settings-nav__link i {
    width: 16px;
    text-align: center;
    font-size: 13px;
    color: var(--nx-color-text-muted);
    transition: color .12s ease;
}
.nx-settings-nav__link:hover {
    background: color-mix(in srgb, var(--nx-color-cta) 5%, transparent);
    color: var(--nx-color-heading);
}
.nx-settings-nav__link:hover i {
    color: var(--nx-color-heading);
}
.nx-settings-nav__link.is-active {
    background: color-mix(in srgb, var(--nx-color-cta) 8%, transparent);
    color: var(--nx-color-cta);
    font-weight: 600;
}
.nx-settings-nav__link.is-active i {
    color: var(--nx-color-cta);
}
.nx-settings-nav__group-label {
    margin: 8px 6px 4px;
    padding: 0;
    font-size: 10.5px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--nx-color-text-muted);
}
.nx-settings-nav__link-text {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 6px;
}

/* ---- Card ----------------------------------------------------------------- */
.nx-card {
    background: var(--nx-surface-card);
    border: 1px solid var(--nx-border);
    border-radius: var(--nx-radius-lg);
    box-shadow: var(--nx-shadow-sm);
}
.nx-card__section { padding: var(--nx-space-5); }
/* Apply --divided to every section after the first to get a hairline rule.
   Explicit (not :first-child) so it survives <form>-nested sections. */
.nx-card__section--divided { border-top: 1px solid var(--nx-border); }
/* Dedicated card header — title + subtitle in their own bordered section
   above the body. Tighter vertical padding than a regular body section
   so the header doesn't dominate. Use this on any list/CRUD card or any
   card whose body is a form — the merchant should land on "what is this
   card for" before scanning the fields. The plain in-body title pattern
   (used by settings-tax / settings-general) is still fine for short,
   self-explanatory cards. */
.nx-card__section--head {
    padding: var(--nx-space-4) var(--nx-space-5);
    border-bottom: 1px solid var(--nx-border);
}
/* In a head section the title + subtitle are the only content — collapse
   the body-context margins so they sit as a tight unit and the section
   padding owns the spacing. */
.nx-card__section--head .nx-card__title    { margin-bottom: var(--nx-space-1); }
.nx-card__section--head .nx-card__subtitle { margin: 0; }
/* Sibling of --head — the card's action footer. Tighter padding than the
   body, hairline border-top for visual separation, right-aligned actions
   inside. Mirrors --head: no background tint, just the border + the
   white card surface — head and foot read as the same kind of frame. */
.nx-card__section--foot {
    padding: var(--nx-space-3) var(--nx-space-5);
    border-top: 1px solid var(--nx-border);
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--nx-space-2);
}
.nx-card__title {
    margin: 0 0 var(--nx-space-4);
    font-size: var(--nx-text-lg);
    font-weight: var(--nx-weight-semibold);
    color: var(--nx-color-heading);
}
.nx-card__subtitle {
    margin: calc(-1 * var(--nx-space-3)) 0 var(--nx-space-4);
    font-size: var(--nx-text-base);
    color: var(--nx-color-text-muted);
}

/* ---- Button ---------------------------------------------------------------
   Shopify-style depth: filled buttons sit slightly raised (soft drop
   shadow + a 1px darker bottom "lip"); hover lifts 1px and deepens the
   shadow; :active presses in — the button drops and the shadow inverts
   to an inset, so a click feels physical. Focus ring is an offset
   outline so it never fights the elevation shadow. */
.nx-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--nx-space-2);
    padding: .55rem 1rem;
    min-height: 2.25rem;   /* 36px — matches .nx-input so a default button lines up beside a field/select */
    border: 1px solid transparent;
    border-radius: var(--nx-radius-md);
    font-family: inherit;
    font-size: var(--nx-text-base);
    font-weight: var(--nx-weight-semibold);
    line-height: 1;
    cursor: pointer;
    text-decoration: none;
    /* Button labels aren't selectable text — dragging across a button
       shouldn't highlight "Save". Standard button affordance. */
    user-select: none;
    -webkit-user-select: none;
    transition: background-color var(--nx-transition-base),
                border-color var(--nx-transition-base),
                box-shadow var(--nx-transition-base),
                transform var(--nx-transition-fast);
}
.nx-btn:focus-visible {
    outline: 2px solid var(--nx-color-heading);
    outline-offset: 2px;
}
/* Disabled — dimmed to 0.5 opacity + fully inert. Keeps the variant's
   own colour (just faded) so it reads as "this same button, off"
   rather than a different grey button. Flattens the gradient/gloss/
   lip shadows so the faded surface stays clean, and pointer-events:
   none blocks all interaction (no hover lift, no click). `!important`
   is deliberate: the variant rules + their :hover set background-image
   + box-shadow at equal specificity, so a plain rule here loses the
   cascade. Covers native [disabled] + the ARIA-disabled pattern. */
.nx-btn:disabled,
.nx-btn[disabled],
.nx-btn[aria-disabled="true"] {
    opacity: .5;
    cursor: not-allowed;
    pointer-events: none;       /* no hover, no click — fully inert */
    transform: none !important;
    background-image: none !important;
    box-shadow: none !important;
}
.nx-btn:disabled:hover,
.nx-btn[disabled]:hover,
.nx-btn[aria-disabled="true"]:hover {
    transform: none !important;
    background-image: none !important;
    box-shadow: none !important;
}
.nx-btn--sm { padding: .35rem .7rem;  font-size: var(--nx-text-sm); min-height: 1.875rem; }
.nx-btn--lg { padding: .75rem 1.25rem; font-size: var(--nx-text-base);  gap: var(--nx-space-2); border-radius: var(--nx-radius-lg); }
.nx-btn--xl { padding: .9rem 1.6rem;   font-size: var(--nx-text-lg);    gap: var(--nx-space-2); border-radius: var(--nx-radius-lg); }
.nx-btn--lg svg, .nx-btn--xl svg { width: 18px; height: 18px; }

/* Icon-only button — auto-applied by nx_button when label is empty +
   icon is set. Square aspect (equal padding both axes) so the button
   reads as an icon target rather than a skinny rectangle; min-width
   matches the height so the icon centers on both axes; the internal
   gap is irrelevant (no neighbor) but zeroing it removes baseline
   shift on browsers that still allocate it. Sized per the parent size
   modifier so default / --sm / --lg / --xl each match the matching
   text-button height for clean alignment in a button row. */
.nx-btn--icon {
    padding: .55rem;
    gap: 0;
    min-width: 2.25rem;     /* matches default-size text-button height */
    line-height: 0;          /* drop the line-box; icon is a glyph not text */
}
.nx-btn--icon.nx-btn--sm { padding: .35rem; min-width: 1.875rem; }
.nx-btn--icon.nx-btn--lg { padding: .75rem; min-width: 2.875rem; }
.nx-btn--icon.nx-btn--xl { padding: .9rem;  min-width: 3.5rem;   }
.nx-btn--icon > i { line-height: 1; }

/* Filled variants — true 3D feel: subtle vertical gradient (lighter top,
   darker bottom = a "rounded" surface), a hairline border one step
   darker than the bg for definition, plus four-layer shadows — inner
   top white "gloss", inner bottom dark "lip", close drop, ambient depth. */
.nx-btn--primary,
.nx-btn--danger {
    color: #fff;
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .22),
        inset 0 -1px 0 rgba(0, 0, 0, .28),
        0 1px 2px rgba(0, 0, 0, .12),
        0 4px 10px -2px rgba(0, 0, 0, .16);
}
.nx-btn--primary {
    background-color: var(--nx-color-cta);
    background-image: linear-gradient(180deg,
        color-mix(in srgb, var(--nx-color-cta) 86%, #fff),
        var(--nx-color-cta));
    border-color: color-mix(in srgb, var(--nx-color-cta) 70%, #000);
}
.nx-btn--danger {
    background-color: var(--nx-danger);
    background-image: linear-gradient(180deg,
        color-mix(in srgb, var(--nx-danger) 86%, #fff),
        var(--nx-danger));
    border-color: color-mix(in srgb, var(--nx-danger) 70%, #000);
}
.nx-btn--primary:hover,
.nx-btn--danger:hover {
    transform: translateY(-1px);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .24),
        inset 0 -1px 0 rgba(0, 0, 0, .28),
        0 2px 4px rgba(0, 0, 0, .14),
        0 8px 18px -3px rgba(0, 0, 0, .20);
}
.nx-btn--primary:hover {
    background-color: var(--nx-color-cta-hover);
    background-image: linear-gradient(180deg,
        color-mix(in srgb, var(--nx-color-cta-hover) 86%, #fff),
        var(--nx-color-cta-hover));
}
.nx-btn--danger:hover {
    background-color: var(--nx-danger-hover);
    background-image: linear-gradient(180deg,
        color-mix(in srgb, var(--nx-danger-hover) 86%, #fff),
        var(--nx-danger-hover));
}

/* Ghost — flatter, but a subtle top-inner highlight makes it sit on the
   surface instead of looking glued flat. Lifts on hover. */
/* Ghost (secondary) — same 3D architecture as the filled variants so it
   sits at the same visual height: gloss inset on top, dark "lip" inset
   on bottom, close drop + ambient depth. Subtle vertical gradient
   (#fff → #f6f7f8) gives it the same "rounded surface" feel without
   stealing emphasis from the primary. */
.nx-btn--ghost {
    background-color: #fff;
    background-image: linear-gradient(180deg, #ffffff, #f3f4f6);
    color: var(--nx-color-heading);
    /* Slightly darker border so the white button has a real edge on
       white surfaces — was --nx-border (17% near-black) which read
       as a ghost line; bumped to a defined neutral so the chip
       reads as a button at a glance. */
    border-color: color-mix(in srgb, var(--nx-border) 75%, #0f172a);
    /* Rewritten 2026-05-27 (Sat): "add little dark border bottom or
       shadow, shadow not spread too much. so it looks clean and
       differentiated".
       - Bottom inset bumped from 1px @ .08 → 2px @ .14 — that's the
         "dark border bottom" lip; gives the button visible weight
         below without changing height.
       - Wide ambient `0 4px 10px -2px` dropped. Kept the close
         `0 1px 2px` drop only — shadow no longer puddles past the
         button edge.
       - Top gloss preserved so the surface still reads as a
         rounded chip, not a flat rect. */
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .9),
        inset 0 -2px 0 rgba(15, 23, 42, .14),
        0 1px 2px rgba(15, 23, 42, .08);
}
.nx-btn--ghost:hover {
    background-image: linear-gradient(180deg, #ffffff, #eceef1);
    border-color: color-mix(in srgb, var(--nx-border) 55%, #0f172a);
    transform: translateY(-1px);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .95),
        inset 0 -2px 0 rgba(15, 23, 42, .16),
        0 2px 4px rgba(15, 23, 42, .10);
}

/* Pressed — all variants. Gradient flattens, the inset shadow deepens,
   button drops back down. The lost gloss + the dark inset = a real
   "pressed" feel. Last in source so it wins over the variant :hover. */
.nx-btn:active {
    transform: translateY(0);
    background-image: none;
    box-shadow:
        inset 0 2px 4px rgba(0, 0, 0, .30),
        inset 0 -1px 0 rgba(255, 255, 255, .05);
}

/* Loading state — JS adds .is-loading on form submit / async action.
   Hides the label/icon, shows a centered spinner, locks pointer.
   Spinner color matches button text (white on filled, dark on ghost). */
.nx-btn.is-loading {
    position: relative;
    cursor: wait;
    pointer-events: none;
}
.nx-btn.is-loading > * { opacity: 0; }
.nx-btn.is-loading::after {
    content: "";
    position: absolute;
    top: 50%; left: 50%;
    width: 16px; height: 16px;
    margin: -8px 0 0 -8px;
    border-radius: 50%;
    border: 2px solid currentColor;
    border-top-color: transparent;
    opacity: .8;
    animation: nx-btn-spin .65s linear infinite;
}
.nx-btn--lg.is-loading::after,
.nx-btn--xl.is-loading::after { width: 18px; height: 18px; margin: -9px 0 0 -9px; }
@keyframes nx-btn-spin { to { transform: rotate(360deg); } }

/* ---- Alert ---------------------------------------------------------------- */
/* ---- Alert — Stripe/Linear premium inline notification --------------
   White card (always) + thin colored left-accent + saturated icon +
   dark high-contrast text. The full-tint background look (colored bg
   + colored border + colored text) reads as a stock Bootstrap alert —
   replaced with this card-with-accent pattern so it always sits
   clearly above whatever surface it lives on. Color shows up in
   just two places: the 3px left edge and the icon. */
.nx-alert {
    position: relative;
    display: flex;
    align-items: center;
    gap: var(--nx-space-3);
    margin-bottom: var(--nx-space-4);
    padding: .65rem var(--nx-space-4) .65rem calc(var(--nx-space-4) + 4px);
    background: var(--nx-surface-card);
    border: 1px solid var(--nx-border);
    border-radius: var(--nx-radius-md);
    color: var(--nx-color-text);
    font-size: var(--nx-text-sm);
    line-height: 1.45;
    overflow: hidden;     /* keeps the left-accent inside the rounded corner */
    box-shadow:
        0 1px 2px rgba(15, 23, 42, .06),
        0 4px 12px -4px rgba(15, 23, 42, .10);
}
/* 3px colored left edge — the only large area of the tone color. */
.nx-alert::before {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    width: 3px;
    background: var(--nx-color-text-muted);  /* overridden per tone below */
}
.nx-alert__icon {
    flex-shrink: 0;
    width: 20px;
    height: 20px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 1rem;
    /* Icon color = tone color (set per-variant below). */
}
.nx-alert > span:not(.nx-alert__icon) {
    color: var(--nx-color-heading);
    font-weight: var(--nx-weight-medium);
}

.nx-alert--success::before        { background: var(--nx-success); }
.nx-alert--success .nx-alert__icon { color: var(--nx-success); }
.nx-alert--info::before           { background: var(--nx-color-text-muted); }
.nx-alert--info    .nx-alert__icon { color: var(--nx-color-text-muted); }
.nx-alert--danger::before         { background: var(--nx-danger); }
.nx-alert--danger  .nx-alert__icon { color: var(--nx-danger); }
.nx-alert--warning::before        { background: var(--nx-warning); }
.nx-alert--warning .nx-alert__icon { color: var(--nx-warning); }

/* ---- Badge ---------------------------------------------------------------- */
/* ---- Badge — Stripe/Linear-style status pill -------------------------
   Tiny status dot on the left + soft tinted bg + saturated text/dot
   color. Polished by an inset top highlight (white) + faint drop shadow
   for a card-like lift. The dot uses `currentColor` so it always
   matches the text, with a soft ring so it stays visible even when bg
   and dot are close in hue. */
.nx-badge {
    display: inline-flex;
    align-items: center;
    gap: .4em;
    padding: .22rem .55rem .22rem .5rem;
    border-radius: var(--nx-radius-pill);
    font-size: var(--nx-text-xs);
    font-weight: var(--nx-weight-semibold);
    letter-spacing: .005em;
    line-height: 1.3;
    white-space: nowrap;
    /* Flat tinted pill — NO border, NO outer shadow, NO gradient. Buttons
       have chrome; badges read as labels. Body is a soft tint of the tone
       (14% currentColor + white) so it stands off white cards without
       looking pressable. Adding a new tone = one line of CSS below. */
    background: color-mix(in srgb, currentColor 14%, #fff);
}
.nx-badge::before {
    content: "";
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
    /* Soft ring keeps the dot from blending with its own tinted bg. */
    box-shadow: 0 0 0 2px color-mix(in srgb, currentColor 22%, #fff);
    flex-shrink: 0;
}
/* `.nx-badge--no-dot` opts out for cases where the badge is purely a
   label (e.g. plan tier, count chip) and the dot would be noise. */
.nx-badge--no-dot::before { display: none; }

/* Per-tone classes — ONLY set color. Background derives from currentColor
   in .nx-badge above, so adding a new tone = one line of CSS. */
.nx-badge--neutral { color: #475569; }
.nx-badge--success { color: var(--nx-success); }
.nx-badge--info    { color: var(--nx-color-text-muted); }
.nx-badge--danger  { color: var(--nx-danger); }
.nx-badge--warning { color: var(--nx-warning); }

/* ---- Field ---------------------------------------------------------------- */
.nx-field { display: flex; flex-direction: column; gap: var(--nx-space-1); }
.nx-field__label {
    font-size: var(--nx-text-sm);
    font-weight: var(--nx-weight-semibold);
    color: var(--nx-color-heading);
}
.nx-field__req { margin-left: 2px; color: var(--nx-brand-primary); }
.nx-input {
    width: 100%;
    min-height: 2.25rem;   /* 36px — shared control height; a default .nx-btn matches this so buttons align beside inputs */
    /* Pin the line-height: the browser default `normal` (~1.5) makes the field
       render ~38–39px, overflowing the 36px floor and leaving the button 2-3px
       shorter. 1.25 keeps the field's natural height under 36px so the
       min-height governs and field == button exactly. */
    line-height: 1.25;
    padding: .5rem .7rem;
    font-family: inherit;
    font-size: var(--nx-text-base);
    color: var(--nx-color-text);
    background: var(--nx-surface-card);
    border: 1px solid var(--nx-border);
    border-radius: var(--nx-radius-sm);
    transition: border-color var(--nx-transition-base), box-shadow var(--nx-transition-base);
}
.nx-input:focus {
    outline: none;
    border-color: var(--nx-border);
    box-shadow: 0 0 0 3px var(--nx-focus-ring);
}
.nx-input:disabled { background: var(--nx-surface-tint); cursor: not-allowed; }
/* Error state — coral border + ring. Red here is correct: it only ever
   means "this field is invalid", and never collides with normal focus. */
.nx-input--error,
.nx-input[aria-invalid="true"] { border-color: var(--nx-brand-primary); }
.nx-input--error:focus,
.nx-input[aria-invalid="true"]:focus {
    border-color: var(--nx-brand-primary);
    box-shadow: 0 0 0 3px var(--nx-focus-ring-error);
}
.nx-field__hint { font-size: var(--nx-text-xs); color: var(--nx-color-text-muted); }

/* ---- Input group — joins controls (input / select / button) with
   optional leading + trailing addon pills, sharing borders + focus.
   Emitted by `nx_input_group`. The inner `.nx-field` wrapper from
   `nx_field` collapses to a flex item; the actual `<input class="nx-input">`
   is the focusable surface and the CSS targets it by descendant. */
.nx-input-group {
    display: flex;
    align-items: stretch;
    width: 100%;
}
/* The .nx-field wrapper around nx_field's input fills the remaining
   space of the group; it has no border/bg of its own (those live on
   the input). */
.nx-input-group > .nx-field { flex: 1 1 auto; min-width: 0; }
.nx-input-group > .nx-field > .nx-input { width: 100%; }

.nx-input-group__addon {
    display: inline-flex;
    align-items: center;
    padding: .5rem .7rem;
    font-size: var(--nx-text-base);
    color: var(--nx-color-text-muted);
    background: var(--nx-surface-tint);
    border: 1px solid var(--nx-border);
    flex: 0 0 auto;
    white-space: nowrap;
}
.nx-input-group__addon--prefix {
    border-top-left-radius:    var(--nx-radius-sm);
    border-bottom-left-radius: var(--nx-radius-sm);
    border-right: 0;
}
.nx-input-group__addon--suffix {
    border-top-right-radius:    var(--nx-radius-sm);
    border-bottom-right-radius: var(--nx-radius-sm);
    border-left: 0;
}
/* Flatten the input's adjacent-side radii via descendant selectors —
   the input lives inside `.nx-field` inside the group, so we walk down. */
.nx-input-group--has-prefix .nx-input {
    border-top-left-radius: 0;
    border-bottom-left-radius: 0;
}
.nx-input-group--has-suffix .nx-input {
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
}
/* Focused state — sync the addon border to the input's focus colour so
   the joined border reads as one ring. The input's box-shadow focus
   ring still handles the actual glow on the input itself. */
.nx-input-group:focus-within .nx-input-group__addon {
    border-color: var(--nx-color-text-muted);
}

/* ---- Date picker — single field with a leading calendar icon. JS in
   nxcart-admin.js upgrades any `[data-nx-datepicker]` to flatpickr on
   DOMContentLoaded (when flatpickr is on the page); the input degrades
   to a plain text field if flatpickr isn't loaded. */
.nx-datepicker {
    position: relative;
    display: block;
}
.nx-datepicker__icon {
    position: absolute;
    top: 50%;
    left: var(--nx-space-3);
    transform: translateY(-50%);
    color: var(--nx-color-text-muted);
    font-size: var(--nx-text-sm);
    pointer-events: none;
}
.nx-datepicker__input {
    padding-left: 2.2rem;
    cursor: pointer;
    /* `readonly` is forced by the helper so users don't try to type a
       date — the input is a click-to-open trigger, not editable. The
       overrides below restore "normal" input chrome since readonly
       inherits some browser styling that hints at "disabled". */
    background: var(--nx-surface-card);
    color: var(--nx-color-text);
    caret-color: transparent;
    user-select: none;
}
.nx-datepicker__input:hover {
    background: var(--nx-surface-tint);
    border-color: color-mix(in srgb, var(--nx-color-text-muted) 70%, #fff);
}
.nx-datepicker:hover .nx-datepicker__icon { color: var(--nx-color-heading); }
.nx-datepicker__input:focus { caret-color: transparent; }
.nx-datepicker__input[readonly] {
    background: var(--nx-surface-card);  /* override the default :disabled/[readonly] tinted look */
}
.nx-datepicker__input[readonly]:hover {
    background: var(--nx-surface-tint);
}
.nx-date-range {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--nx-space-3);
}
@media (max-width: 480px) {
    .nx-date-range { grid-template-columns: 1fr; }
}

/* ---- Popover ---------------------------------------------------------------
   Generic popover container. Currently used by nx_date_range_popover; could
   host nx_filter_popover etc. later. The trigger sits relative; the panel is
   absolutely positioned beneath it, top-right-aligned. Multiple instances per
   page are supported because every popover scopes itself by data-nx-popover.
   Chrome matches the rest of the design-system popups (notification dropdown,
   profile menu): white card, hairline border, layered drop shadow. */
.nx-popover {
    position: relative;
    display: block;
}
.nx-popover__panel {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    z-index: 30;
    width: 22rem;
    max-width: calc(100vw - 2rem);
    background: var(--nx-surface-card);
    border: 1px solid rgba(15, 23, 42, .08);
    border-radius: var(--nx-radius-lg);
    box-shadow:
        0 4px 12px rgba(15, 23, 42, .10),
        0 16px 40px -8px rgba(15, 23, 42, .18);
    animation: nx-popover-in var(--nx-transition-base, .15s) ease-out both;
}
.nx-popover__panel[hidden] { display: none; }
.nx-popover__body {
    padding: var(--nx-space-4);
}
.nx-popover__title {
    margin: 0 0 var(--nx-space-3);
    font-size: var(--nx-text-base);
    font-weight: var(--nx-weight-semibold);
    color: var(--nx-color-heading);
}
.nx-popover__actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--nx-space-2);
    margin-top: var(--nx-space-3);
}
@keyframes nx-popover-in {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* ---- Empty state ---------------------------------------------------------- */
.nx-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding: var(--nx-space-7) var(--nx-space-4);
}
.nx-empty__icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 48px;
    height: 48px;
    margin-bottom: var(--nx-space-3);
    border-radius: var(--nx-radius-pill);
    background: var(--nx-surface-tint);
    color: var(--nx-brand-secondary);
    font-size: 1.25rem;
}
.nx-empty__title {
    margin: 0 0 var(--nx-space-1);
    font-size: var(--nx-text-lg);
    font-weight: var(--nx-weight-semibold);
    color: var(--nx-color-heading);
}
.nx-empty__message {
    margin: 0 0 var(--nx-space-4);
    max-width: 42ch;
    font-size: var(--nx-text-base);
    color: var(--nx-color-text-muted);
}

/* ===========================================================================
   COMPOSITE COMPONENTS (Phase 3)
=========================================================================== */

/* ---- Filter toolbar — search + filters above a table -------------------- */
.nx-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--nx-space-2);
    margin-bottom: var(--nx-space-4);
}
.nx-toolbar__search { flex: 1 1 240px; min-width: 0; }
.nx-toolbar__spacer { flex: 1 1 auto; }

/* ---- Data table --------------------------------------------------------- */
.nx-table-wrap {
    background: var(--nx-surface-card);
    border: 1px solid var(--nx-border);
    border-radius: var(--nx-radius-lg);
    box-shadow: var(--nx-shadow-sm);
    overflow: hidden;
}
.nx-table-scroll { overflow-x: auto; }
.nx-table { width: 100%; border-collapse: collapse; font-size: var(--nx-text-sm); }
.nx-table thead th {
    padding: var(--nx-space-3) var(--nx-space-4);
    text-align: left;
    font-size: var(--nx-text-xs);
    font-weight: var(--nx-weight-bold);
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--nx-color-heading);
    background: var(--nx-surface-tint);
    border-bottom: 1px solid var(--nx-border);
    white-space: nowrap;
}
.nx-table tbody td {
    padding: var(--nx-space-3) var(--nx-space-4);
    color: var(--nx-color-text);
    vertical-align: middle;
}
.nx-table tbody tr { transition: background-color var(--nx-transition-fast); }
.nx-table tbody tr + tr td { border-top: 1px solid var(--nx-border); }
.nx-table tbody tr:hover { background: var(--nx-surface-hover); }
.nx-table__right  { text-align: right; white-space: nowrap; }

/* Cell-content helpers — applied via <span class="..."> inside a row cell
   so callers can style individual values without bespoke utility chains. */
.nx-table__strong { font-weight: var(--nx-weight-semibold); color: var(--nx-color-heading); }
.nx-table__muted  { color: var(--nx-color-text-muted); }
.nx-table__link   { font-weight: var(--nx-weight-medium); color: var(--nx-color-link); text-decoration: none; }
.nx-table__link:hover { color: var(--nx-color-link-hover); text-decoration: underline; }

/* ---- Sticky form action bar — Cancel / Save pinned to viewport bottom --- */
.nx-form-actions {
    position: sticky;
    bottom: 0;
    z-index: 20;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--nx-space-2);
    margin-top: var(--nx-space-5);
    padding: var(--nx-space-3) var(--nx-space-4);
    background: color-mix(in srgb, var(--nx-surface-card) 88%, transparent);
    backdrop-filter: blur(8px);
    border: 1px solid var(--nx-border);
    border-radius: var(--nx-radius-lg);
    box-shadow: var(--nx-shadow-md);
}
.nx-form-actions__note {
    margin-right: auto;
    font-size: var(--nx-text-sm);
    color: var(--nx-color-text-muted);
}

/* ---- Modal -------------------------------------------------------------- */
.nx-modal {
    position: fixed;
    inset: 0;
    z-index: 100;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--nx-space-4);
    background: rgba(31, 31, 31, .55);
}
.nx-modal[hidden] { display: none; }

/* Lock body scroll while any modal is open. :has() detects an open modal
   anywhere in the document and freezes the page underneath so the user
   can't scroll the form behind the modal. Covers three modal idioms:
     - .nx-modal               — global primitive, hidden via the [hidden] attr
     - .product-modal.flex     — add-product Options modal, JS toggles .flex
     - #media-lightbox.flex    — product media lightbox, same JS toggle */
/* Body scroll-lock when a modal is open. The :not(.hidden) is part of
   the test because most modals across the admin (edit-product options
   modal, edit-product shop-change confirmation, edit-customer modals
   etc.) toggle visibility via Tailwind's `.hidden` CLASS, not the
   HTML `[hidden]` attribute. Without the class check, a modal that's
   class="nx-modal hidden" still matches :not([hidden]) (because the
   attribute isn't set) and the page silently loses scroll. */
html:has(.nx-modal:not([hidden]):not(.hidden)),
html:has(.product-modal.flex),
html:has(#media-lightbox.flex) {
    overflow: hidden;
}

/* `.hidden` utility — Tailwind's `.hidden { display: none }` lives earlier
   in the cascade than nx-* components, so at equal specificity any
   component selector that defines `display` later (e.g.
   `.nx-btn { display: inline-flex }`) silently wins and `.hidden` stops
   hiding the element. We can't bump `.hidden` itself to !important — the
   sidebar (and other places) use `.hidden lg:block` responsive pairs and
   !important would freeze the responsive override.
   Instead, raise specificity for `.hidden` on each nx-* component that
   defines its own display. Single-component listings — extend the list
   when a new primitive ships with a display value. */
.nx-btn.hidden,
.nx-input-group.hidden,
.nx-stack.hidden,
.nx-card.hidden,
.nx-modal.hidden,
.nx-segmented.hidden,
.nx-field.hidden,
.nx-toggle.hidden,
.nx-form-actions.hidden,
.nx-page-header.hidden {
    display: none;
}
.nx-modal__card {
    width: 100%;
    max-width: 520px;
    max-height: 90vh;
    display: flex;
    flex-direction: column;
    background: var(--nx-surface-card);
    border-radius: var(--nx-radius-lg);
    box-shadow: var(--nx-shadow-lg);
    overflow: hidden;
}
.nx-modal__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--nx-space-3);
    padding: var(--nx-space-4) var(--nx-space-5);
    border-bottom: 1px solid var(--nx-border);
}
.nx-modal__title {
    margin: 0;
    font-size: var(--nx-text-lg);
    font-weight: var(--nx-weight-semibold);
    color: var(--nx-color-heading);
}
.nx-modal__close {
    padding: var(--nx-space-1);
    background: none;
    border: none;
    cursor: pointer;
    line-height: 1;
    font-size: var(--nx-text-lg);
    color: var(--nx-color-text-muted);
}
.nx-modal__close:hover { color: var(--nx-color-text); }
.nx-modal__body { padding: var(--nx-space-5); overflow-y: auto; }
.nx-modal__foot {
    display: flex;
    justify-content: flex-end;
    gap: var(--nx-space-2);
    padding: var(--nx-space-4) var(--nx-space-5);
    border-top: 1px solid var(--nx-border);
}

/* ===========================================================================
   PREMIUM POLISH — micro-interactions, depth, motion
   ---------------------------------------------------------------------------
   Design intent: Shopify Admin clarity · Stripe restraint · Linear motion ·
   Vercel depth. Motion is tasteful and fast (<320ms), entrances decelerate,
   nothing bounces. All of it is disabled under prefers-reduced-motion.
=========================================================================== */

@keyframes nx-fade-in   { from { opacity: 0; } to { opacity: 1; } }
@keyframes nx-modal-in  {
    from { opacity: 0; transform: translateY(8px) scale(.98); }
    to   { opacity: 1; transform: translateY(0)   scale(1); }
}
@keyframes nx-shimmer   { from { background-position: 200% 0; } to { background-position: -200% 0; } }
@keyframes nx-rise      {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* ---- Modal entrance — overlay fades, card lifts + scales in ------------- */
.nx-modal { animation: nx-fade-in var(--nx-transition-base) both; }
.nx-modal__card { animation: nx-modal-in var(--nx-transition-slow) both; }

/* ---- Toggle switch — paired with <input type="checkbox"> -----------------
   Used by settings rows (notifications, COD enabled, etc.). The native
   input is screen-reader friendly + posts the field for free; the track
   + thumb are pure CSS driven off :checked. Focus ring on the track
   matches the rest of the system; reduced-motion users still get the
   thumb shift, just without the transition. */
.nx-toggle {
    display: flex;
    /* align-items: center so the label text reads vertically centered
       against the track instead of hanging from the top edge. For
       toggles with both a label and a hint, the body is centered as a
       group — the track ends up between label and hint lines, which
       reads cleanly because the hint is small. */
    align-items: center;
    gap: var(--nx-space-3);
    padding: var(--nx-space-3) 0;
    cursor: pointer;
    user-select: none;
}
.nx-toggle--disabled { cursor: not-allowed; opacity: .55; }

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

.nx-toggle__track {
    flex-shrink: 0;
    position: relative;
    display: inline-block;
    width: 36px;
    height: 20px;
    /* margin-top:2px removed — was a hack to align with the first text
       line when .nx-toggle used align-items:flex-start. Now that the
       parent uses align-items:center the track is centered against
       the label naturally; the 2px offset would push it visually
       below center. */
    background: var(--nx-border);
    border-radius: var(--nx-radius-pill);
    transition: background-color var(--nx-transition-base);
}
.nx-toggle__thumb {
    position: absolute;
    top: 2px;
    left: 2px;
    width: 16px;
    height: 16px;
    background: #fff;
    border-radius: 50%;
    box-shadow: var(--nx-shadow-sm);
    transition: transform var(--nx-transition-base);
}
.nx-toggle__input:checked + .nx-toggle__track {
    background: var(--nx-color-cta);
}
.nx-toggle__input:checked + .nx-toggle__track .nx-toggle__thumb {
    transform: translateX(16px);
}
.nx-toggle__input:focus-visible + .nx-toggle__track {
    box-shadow: 0 0 0 3px var(--nx-focus-ring);
}

.nx-toggle__body {
    display: flex;
    flex-direction: column;
    gap: var(--nx-space-1);
    min-width: 0;
}
.nx-toggle__label {
    font-size: var(--nx-text-base);
    font-weight: var(--nx-weight-semibold);
    color: var(--nx-color-text);
    line-height: var(--nx-leading-tight);
}
.nx-toggle__hint {
    font-size: var(--nx-text-sm);
    color: var(--nx-color-text-muted);
    line-height: var(--nx-leading-normal);
}

/* Hairline between stacked toggles inside a card section — Stripe-style. */
.nx-toggle + .nx-toggle {
    border-top: 1px solid var(--nx-border);
}
/* Horizontal toggle row — opt out of the adjacent-sibling separator
   border when toggles sit side-by-side (e.g. Inventory section's
   Track inventory · Continue selling pair). Apply `.nx-toggle-row` to
   the flex parent. */
.nx-toggle-row .nx-toggle + .nx-toggle {
    border-top: 0;
}

/* ---- Select (extends .nx-input) ---------------------------------------
   Custom chevron via data-URI; padding adjusted to avoid the chevron
   overlapping the value. Otherwise inherits .nx-input chrome so the
   field aligns with sibling text inputs in a grid. */
.nx-input--select {
    appearance: none;
    -webkit-appearance: none;
    padding-right: 2rem;
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'><path fill='%23475569' d='M6 8L0 0h12z'/></svg>");
    background-repeat: no-repeat;
    background-position: right 0.75rem center;
    background-size: 10px 7px;
    cursor: pointer;
}

/* ---- Suggestions dropdown (product type / vendor / collections / etc.)
   Shared chrome for the autocomplete popups rendered by add-products.js
   + edit-products.js into .suggestions-data. The container itself is
   styled inline on each template (positioned dropdown card); these
   rules cover the structured children the JS now emits. */
.suggestions-data__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--nx-space-2);
    padding: 0 var(--nx-space-1) var(--nx-space-2);
    border-bottom: 1px solid var(--nx-border);
    margin-bottom: var(--nx-space-2);
}
.suggestions-data__title {
    font-size: var(--nx-text-xs);
    font-weight: var(--nx-weight-semibold);
    color: var(--nx-color-text-muted);
    letter-spacing: .04em;
    text-transform: uppercase;
}
.suggestions-data__close {
    background: none; border: 0; cursor: pointer;
    width: 24px; height: 24px;
    display: inline-flex; align-items: center; justify-content: center;
    color: var(--nx-color-text-muted);
    border-radius: var(--nx-radius-sm);
    font-size: var(--nx-text-base); line-height: 1;
}
.suggestions-data__close:hover {
    background: var(--nx-surface-hover);
    color: var(--nx-color-text);
}
.suggestions-data__list {
    display: flex; flex-direction: column;
    max-height: 280px; overflow-y: auto;
}
.suggestions-data__row {
    display: flex; align-items: center; gap: var(--nx-space-3);
    padding: var(--nx-space-2) var(--nx-space-2);
    border-radius: var(--nx-radius-sm);
    cursor: pointer;
    font-size: var(--nx-text-sm);
    color: var(--nx-color-text);
}
.suggestions-data__row:hover {
    background: var(--nx-surface-hover);
}
.suggestions-data__row input[type="checkbox"] {
    accent-color: var(--nx-color-cta);
    width: 16px; height: 16px;
    flex-shrink: 0;
    cursor: pointer;
}
.suggestions-data__row:has(input[type="checkbox"]:checked) {
    background: var(--nx-surface-tint);
    color: var(--nx-color-heading);
    font-weight: var(--nx-weight-medium);
}
.suggestions-data__empty {
    padding: var(--nx-space-3);
    font-size: var(--nx-text-sm);
    color: var(--nx-color-text-muted);
    text-align: center;
}

/* ---- Callout — persistent banner (Polaris Banner pattern) ------------
   Larger than nx_alert: title + body + optional CTA + optional dismiss.
   Tones share the layout; only the left border + icon colour + soft
   surface change. Use for "do this next" messages that should stick. */
/* ---- Callout — premium directional banner ---------------------------
   Same family as nx_alert: WHITE card (always), colored 3px left edge
   accent, colored icon, dark text. The earlier `--*-soft` tints on the
   bg blended into the admin's bg-gray-50 body — info-soft is ~6%
   navy on white, near-identical to gray-50. White + accent reads
   clearly above any page bg. Real elevation shadow lifts it off
   the surface. */
.nx-callout {
    position: relative;
    display: flex;
    align-items: flex-start;
    gap: var(--nx-space-3);
    padding: var(--nx-space-4);
    padding-left: calc(var(--nx-space-4) + 4px);  /* room for the left accent */
    background: var(--nx-surface-card);
    border: 1px solid var(--nx-border);
    border-radius: var(--nx-radius-md);
    margin-bottom: var(--nx-space-4);
    overflow: hidden;
    box-shadow:
        0 1px 2px rgba(15, 23, 42, .06),
        0 4px 12px -4px rgba(15, 23, 42, .10);
}
.nx-callout::before {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    width: 3px;
    background: var(--nx-color-text-muted);  /* per-tone below */
}
.nx-callout__icon { font-size: var(--nx-text-lg); flex-shrink: 0; line-height: 1.4; }
.nx-callout__body { flex: 1; min-width: 0; }
.nx-callout__title {
    margin: 0;
    font-size: var(--nx-text-base);
    font-weight: var(--nx-weight-semibold);
    color: var(--nx-color-heading);
    line-height: var(--nx-leading-tight);
}
.nx-callout__text {
    margin: var(--nx-space-1) 0 0;
    font-size: var(--nx-text-sm);
    color: var(--nx-color-text-muted);
    line-height: var(--nx-leading-normal);
}
.nx-callout__text p:first-child { margin-top: 0; }
.nx-callout__text p:last-child  { margin-bottom: 0; }
.nx-callout__actions { margin-top: var(--nx-space-3); }
.nx-callout__dismiss {
    flex-shrink: 0;
    background: transparent;
    border: 0;
    color: var(--nx-color-text-muted);
    cursor: pointer;
    padding: 4px;
    border-radius: var(--nx-radius-sm);
    font-size: var(--nx-text-base);
    line-height: 1;
}
.nx-callout__dismiss:hover { color: var(--nx-color-text); background: var(--nx-surface-hover); }

/* Per-tone — only the 3px left accent and the icon carry the color now. */
.nx-callout--info::before    { background: var(--nx-color-text-muted); }
.nx-callout--info    .nx-callout__icon { color: var(--nx-color-text-muted); }
.nx-callout--success::before { background: var(--nx-success); }
.nx-callout--success .nx-callout__icon { color: var(--nx-success); }
.nx-callout--warning::before { background: var(--nx-warning); }
.nx-callout--warning .nx-callout__icon { color: var(--nx-warning); }
.nx-callout--danger::before  { background: var(--nx-danger); }
.nx-callout--danger  .nx-callout__icon { color: var(--nx-danger); }
/* `brand` tone — papaya-accent left bar + brick icon. Use when the
   callout describes a brand-specific state (e.g. "Fulfillment type
   locked after creation") rather than a status. Same papaya + brick
   token pair the section-head icon chips use, so brand callouts read
   as part of the same design language across the admin. */
.nx-callout--brand::before   { background: var(--nx-brand-secondary); }
.nx-callout--brand   .nx-callout__icon { color: var(--nx-brand-secondary); }

/* =====================================================================
   nx-activity-feed — timeline / event-feed primitive
   ---------------------------------------------------------------------
   Stripe / Linear–style chronological list. Each item: 28px tonal
   icon-dot on a hairline rail, premium two-line body (text + muted
   meta), generous vertical rhythm. The rail's connector segment lives
   on `.nx-activity-feed__rail::after` and is hidden on the last item
   so the line never dangles past the final event.

   Used today: order detail timeline (view-order-tpl.php). Designed
   to also serve inventory_movements, customer activity, refund
   history without further CSS work.
   ===================================================================== */
.nx-activity-feed {
    list-style: none;
    margin: 0;
    padding: 0;
}
.nx-activity-feed__item {
    display: grid;
    grid-template-columns: 28px 1fr;
    gap: var(--nx-space-3);
    align-items: flex-start;
    /* Negative-margin trick lets the hover background bleed beyond the
       text container, more obvious without changing the layout box. */
    margin: 0 calc(var(--nx-space-2) * -1);
    padding: var(--nx-space-2);
    border-radius: var(--nx-radius-sm);
    transition: background-color var(--nx-transition-fast);
}
.nx-activity-feed__item:hover { background: var(--nx-surface-tint); }

/* Rail column carries the dot + the connector segment. Dots stack
   exactly on top of the line so the visual is one continuous timeline. */
.nx-activity-feed__rail {
    position: relative;
    width: 28px;
    height: 100%;
    display: flex;
    justify-content: center;
    padding-top: 2px; /* aligns dot centre with text baseline */
}
.nx-activity-feed__rail::after {
    content: "";
    position: absolute;
    top: 32px;   /* immediately below the 28px dot */
    bottom: calc(var(--nx-space-2) * -1); /* extend through item padding into the gap */
    left: 50%;
    transform: translateX(-50%);
    width: 1px;
    background: var(--nx-border);
}
.nx-activity-feed__item.is-last .nx-activity-feed__rail::after { display: none; }

/* The dot — tonal background + matching icon colour. Borders give it
   the same lift the section-head icon chips have without being loud. */
.nx-activity-feed__dot {
    position: relative;
    z-index: 1;
    width: 28px;
    height: 28px;
    border-radius: 999px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--nx-surface-tint);
    color: var(--nx-color-text-muted);
    border: 1px solid var(--nx-border);
    /* Tiny ring of card background so the connector visually clips
       at the dot's edge rather than running through it. */
    box-shadow: 0 0 0 3px var(--nx-surface-card);
    flex-shrink: 0;
}
.nx-activity-feed__dot i {
    font-size: 11px;
    line-height: 1;
}

/* Body — two-line layout. Text leads (default heading colour, medium
   weight). Meta line is small + muted with a hair of letter-spacing. */
.nx-activity-feed__body { min-width: 0; }
.nx-activity-feed__text {
    margin: 0;
    font-size: 13.5px;
    line-height: 1.45;
    color: var(--nx-color-heading);
    font-weight: var(--nx-weight-medium);
    /* Truncate single-line events; full text shows on hover via the
       <p>'s title attr if the caller wires one up. Multi-line callers
       can wrap freely — height grows. */
    overflow-wrap: anywhere;
}
.nx-activity-feed__meta {
    margin: 2px 0 0;
    font-size: 11px;
    line-height: 1.4;
    color: var(--nx-color-text-muted);
    letter-spacing: 0.01em;
}

/* Tones — bg + icon colour pair from the same source token. color-mix
   keeps the bg light (12-15% mix toward white) so the dot reads as
   "tinted" rather than "filled". Border bumps to a 28-32% mix of the
   tone so the dot has a clean ring against white surfaces. */
.nx-activity-feed__item--success .nx-activity-feed__dot {
    background: color-mix(in srgb, var(--nx-success) 14%, #fff);
    color: var(--nx-success);
    border-color: color-mix(in srgb, var(--nx-success) 30%, var(--nx-border));
}
.nx-activity-feed__item--info .nx-activity-feed__dot {
    background: color-mix(in srgb, var(--nx-color-cta) 8%, #fff);
    color: var(--nx-color-cta);
    border-color: color-mix(in srgb, var(--nx-color-cta) 24%, var(--nx-border));
}
.nx-activity-feed__item--warning .nx-activity-feed__dot {
    background: color-mix(in srgb, var(--nx-warning) 16%, #fff);
    color: var(--nx-warning);
    border-color: color-mix(in srgb, var(--nx-warning) 32%, var(--nx-border));
}
.nx-activity-feed__item--danger .nx-activity-feed__dot {
    background: color-mix(in srgb, var(--nx-danger) 12%, #fff);
    color: var(--nx-danger);
    border-color: color-mix(in srgb, var(--nx-danger) 30%, var(--nx-border));
}
.nx-activity-feed__item--brand .nx-activity-feed__dot {
    background: var(--nx-brand-accent);
    color: var(--nx-brand-secondary);
    border-color: color-mix(in srgb, var(--nx-brand-secondary) 20%, var(--nx-border));
}

/* Empty state — rendered as a list item INSIDE the <ol> so the
   container is always the same root (lets JS append-handlers
   `.remove()` the empty row then append the first real event
   without re-creating the container). Centered, dashed border,
   compact vertical rhythm matching nx_empty_state's variant. */
.nx-activity-feed__empty {
    list-style: none;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--nx-space-2);
    padding: var(--nx-space-5) var(--nx-space-4);
    text-align: center;
    color: var(--nx-color-text-muted);
    font-size: var(--nx-text-sm);
    background: var(--nx-surface-tint);
    border: 1px dashed var(--nx-border);
    border-radius: var(--nx-radius-md);
}
.nx-activity-feed__empty-icon {
    font-size: 18px;
    color: var(--nx-color-text-muted);
    opacity: 0.7;
}

/* =====================================================================
   nx-note-composer — textarea composite with a footer-bar action.
   ---------------------------------------------------------------------
   Single composite card: textarea on top, inline footer-bar with a
   hint on the left + a small submit button on the right. Used on the
   order-detail timeline ("Add a note (visible to staff only)").
   The focus ring sits on the wrapper so the textarea drops both the
   browser-default outline AND any tailwind ring — no doubled outline
   when the textarea is focused.
   ===================================================================== */
.nx-note-composer {
    margin-top: var(--nx-space-4);
    background: var(--nx-surface-card);
    border: 1px solid var(--nx-border);
    border-radius: var(--nx-radius-md);
    transition: border-color var(--nx-transition-base),
                box-shadow var(--nx-transition-base);
}
.nx-note-composer:focus-within {
    border-color: var(--nx-color-heading);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--nx-color-heading) 12%, transparent);
}
.nx-note-composer__textarea {
    display: block;
    width: 100%;
    padding: var(--nx-space-3) var(--nx-space-4);
    border: 0;
    outline: 0;
    resize: none;
    font-family: inherit;
    font-size: var(--nx-text-sm);
    line-height: 1.45;
    color: var(--nx-color-heading);
    background: transparent;
    border-top-left-radius: var(--nx-radius-md);
    border-top-right-radius: var(--nx-radius-md);
}
.nx-note-composer__textarea::placeholder {
    color: var(--nx-color-text-muted);
}
.nx-note-composer__textarea:focus { outline: 0; box-shadow: none; }
.nx-note-composer__foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--nx-space-3);
    padding: 6px var(--nx-space-3);
    background: var(--nx-surface-tint);
    border-top: 1px solid var(--nx-border);
    border-bottom-left-radius: var(--nx-radius-md);
    border-bottom-right-radius: var(--nx-radius-md);
}
.nx-note-composer__hint {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: var(--nx-text-xs);
    color: var(--nx-color-text-muted);
}
.nx-note-composer__hint i { font-size: 9px; }

/* =====================================================================
   nx-option-row — icon-chip + title + sublabel + trailing control row.
   ---------------------------------------------------------------------
   The recurring "thing with a switch/action on the right" list row:
   pickup-location pickers, settings list rows, sales-channel toggles.
   Wrap a set in any container; rows self-separate with a top border via
   the adjacency rule so a list reads as one bordered group.
   ===================================================================== */
.nx-option-list { list-style: none; margin: var(--nx-space-2) 0 0; padding: 0; }
.nx-option-row {
    display: flex;
    align-items: center;
    gap: var(--nx-space-3);
    padding: var(--nx-space-3) 0;
}
.nx-option-row + .nx-option-row { border-top: 1px solid var(--nx-border); }
.nx-option-row__icon {
    flex-shrink: 0;
    width: 30px; height: 30px;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--nx-surface-tint);
    border-radius: var(--nx-radius-sm);
    color: var(--nx-color-text-muted);
    font-size: var(--nx-text-sm);
}
/* Raw leading slot — holds an nx_avatar() or other caller HTML instead of
   the tinted FA chip. No background/sizing of its own; the embedded
   element (avatar) brings its own. */
.nx-option-row__lead {
    flex-shrink: 0;
    display: inline-flex; align-items: center; justify-content: center;
}
/* The label half — an <a> when href is set, plain div otherwise. The
   link form stays inline-flexless so the body keeps its column flow. */
.nx-option-row__link {
    display: flex;
    align-items: center;
    gap: var(--nx-space-3);
    flex: 1;
    min-width: 0;
    text-decoration: none;
    color: inherit;
}
.nx-option-row__link:hover .nx-option-row__title { color: var(--nx-color-cta); }
.nx-option-row__body { flex: 1; min-width: 0; }
.nx-option-row__title {
    margin: 0;
    /* flex so a title_suffix pill (nx_badge / status_badge) sits inline
       and vertically centered against the title text. Wraps on narrow. */
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--nx-space-2);
    font-size: var(--nx-text-base);
    color: var(--nx-color-heading);
}
.nx-option-row__sub {
    margin: 2px 0 0;
    font-size: var(--nx-text-sm);
    color: var(--nx-color-text-muted);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.nx-option-row__control { flex-shrink: 0; display: inline-flex; align-items: center; gap: var(--nx-space-2); }

/* =====================================================================
   nx-choice-row — selectable label-card wrapping a checkbox.
   ---------------------------------------------------------------------
   Whole row is the toggle target; selected state lifts the bg with the
   brand-accent tint so checked rows visually "belong"; --disabled rows
   dim + show a "Coming soon"-style badge instead of the checkbox.
   Sales-channel pickers, feature opt-ins. Wrap a set in .nx-choice-list.
   (Promoted 2026-05-28 from the inline .channel-row CSS duplicated
   across the 3 product templates.)
   ===================================================================== */
.nx-choice-list {
    display: flex;
    flex-direction: column;
    border: 1px solid var(--nx-border);
    border-radius: var(--nx-radius-lg);
    overflow: hidden;
}
.nx-choice-row {
    display: flex;
    align-items: center;
    gap: var(--nx-space-3);
    padding: 14px 16px;
    border-top: 1px solid var(--nx-border);
    background: var(--nx-surface-card);
    cursor: pointer;
    transition: background 120ms;
}
.nx-choice-row:first-child { border-top: 0; }
.nx-choice-row:hover { background: var(--nx-surface-hover); }
.nx-choice-row:has(.nx-choice-row__input:checked) {
    background: color-mix(in srgb, var(--nx-brand-accent) 55%, var(--nx-surface-card));
}
.nx-choice-row--disabled { cursor: not-allowed; background: var(--nx-surface-tint); }
.nx-choice-row--disabled:hover { background: var(--nx-surface-tint); }
.nx-choice-row--disabled .nx-choice-row__name,
.nx-choice-row--disabled .nx-choice-row__desc { opacity: .6; }
.nx-choice-row__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px; height: 36px;
    border-radius: 8px;
    background: var(--nx-brand-accent);
    color: var(--nx-brand-secondary);
    font-size: 14px;
    flex-shrink: 0;
}
.nx-choice-row__body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.nx-choice-row__name { font-size: 14px; font-weight: 600; color: var(--nx-color-heading); }
.nx-choice-row__desc { font-size: 12.5px; color: var(--nx-color-text-muted); line-height: 1.4; }
.nx-choice-row__input { width: 18px; height: 18px; accent-color: var(--nx-color-cta); cursor: pointer; flex-shrink: 0; }
.nx-choice-row__badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 3px 10px;
    border-radius: 999px;
    background: var(--nx-surface-card);
    border: 1px solid var(--nx-border);
    color: var(--nx-color-text-muted);
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .04em;
    flex-shrink: 0;
}

/* ---- Choice tile — large clickable card for a "pick one to start"
   step (e.g. the Create-discount type picker). A whole-card <a> with an
   icon chip, title, description and an optional "e.g." example. Disabled
   variant (Phase-2 / coming-soon) renders as a dimmed div with a badge.
   Wrap a set in .nx-choice-tile-grid. */
.nx-choice-tile-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--nx-space-3);
}
@media (min-width: 640px)  { .nx-choice-tile-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1024px) { .nx-choice-tile-grid { grid-template-columns: repeat(3, 1fr); } }
.nx-choice-tile {
    display: flex;
    flex-direction: column;
    gap: var(--nx-space-2);
    padding: var(--nx-space-4);
    border: 1px solid var(--nx-border);
    border-radius: var(--nx-radius-lg);
    background: var(--nx-surface-card);
    text-decoration: none;
    transition: border-color .12s ease, box-shadow .12s ease, transform .12s ease;
}
a.nx-choice-tile:hover {
    border-color: var(--nx-color-cta);
    box-shadow: var(--nx-shadow-sm);
}
.nx-choice-tile__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px; height: 40px;
    border-radius: var(--nx-radius-md);
    background: var(--nx-brand-accent);
    color: var(--nx-brand-secondary);
    font-size: 16px;
    flex-shrink: 0;
}
.nx-choice-tile__head { display: flex; align-items: center; gap: var(--nx-space-2); }
.nx-choice-tile__title { font-size: var(--nx-text-base); font-weight: 600; color: var(--nx-color-heading); }
.nx-choice-tile__desc { font-size: var(--nx-text-sm); color: var(--nx-color-text-muted); line-height: 1.45; }
.nx-choice-tile__eg {
    font-size: var(--nx-text-xs);
    color: var(--nx-color-text-muted);
    font-style: italic;
    margin-top: auto;
}
.nx-choice-tile--disabled { cursor: not-allowed; background: var(--nx-surface-tint); }
.nx-choice-tile--disabled .nx-choice-tile__title,
.nx-choice-tile--disabled .nx-choice-tile__desc,
.nx-choice-tile--disabled .nx-choice-tile__icon { opacity: .55; }
.nx-choice-tile__soon {
    margin-left: auto;
    padding: 2px 8px;
    border-radius: 999px;
    background: var(--nx-surface-card);
    border: 1px solid var(--nx-border);
    color: var(--nx-color-text-muted);
    font-size: 10.5px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .04em;
}

/* ---- Disclosure — a styled <details>/<summary> for an "advanced" section.
   Hides the native marker; shows a title + hint with a chevron that rotates
   when open. Use on a .nx-card__section that's a <details class="nx-disclosure">. */
.nx-disclosure__summary {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--nx-space-3);
    cursor: pointer;
    list-style: none;
}
.nx-disclosure__summary::-webkit-details-marker { display: none; }
.nx-disclosure__title {
    display: block;
    font-size: var(--nx-text-base);
    font-weight: 600;
    color: var(--nx-color-heading);
}
.nx-disclosure__hint {
    display: block;
    margin-top: 2px;
    font-size: var(--nx-text-sm);
    color: var(--nx-color-text-muted);
    line-height: 1.45;
}
.nx-disclosure__chevron {
    flex-shrink: 0;
    margin-top: 3px;
    color: var(--nx-color-text-muted);
    transition: transform .15s ease;
}
.nx-disclosure[open] .nx-disclosure__chevron { transform: rotate(180deg); }

/* ---- Drawer — right-side slide-in panel ------------------------------
   Different from modal: takes full viewport height + slides in from
   right + body is scrollable. Use for filters, quick-edit, contextual
   detail. Open via [data-nx-drawer-open="<id>"] (nxcart-admin.js). */
.nx-drawer {
    position: fixed;
    inset: 0;
    z-index: 110;
    display: flex;
    justify-content: flex-end;
}
.nx-drawer[hidden] { display: none; }
.nx-drawer__backdrop {
    position: absolute;
    inset: 0;
    background: rgba(15, 23, 42, .45);
    backdrop-filter: blur(2px);
    animation: nx-fade-in var(--nx-transition-fast) both;
}
.nx-drawer__panel {
    position: relative;
    width: 100%;
    max-width: 480px;
    height: 100%;
    background: var(--nx-surface-card);
    box-shadow: -12px 0 32px -8px rgba(15, 23, 42, .25);
    display: flex;
    flex-direction: column;
    animation: nx-drawer-in var(--nx-transition-slow) both;
}
.nx-drawer--sm .nx-drawer__panel { max-width: 360px; }
.nx-drawer--lg .nx-drawer__panel { max-width: 720px; }

.nx-drawer__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--nx-space-4) var(--nx-space-5);
    border-bottom: 1px solid var(--nx-border);
    flex-shrink: 0;
}
.nx-drawer__title {
    margin: 0;
    font-size: var(--nx-text-lg);
    font-weight: var(--nx-weight-semibold);
    color: var(--nx-color-heading);
}
.nx-drawer__close {
    width: 32px;
    height: 32px;
    border: 0;
    background: transparent;
    color: var(--nx-color-text-muted);
    border-radius: var(--nx-radius-sm);
    cursor: pointer;
    font-size: var(--nx-text-lg);
}
.nx-drawer__close:hover { background: var(--nx-surface-hover); color: var(--nx-color-text); }
.nx-drawer__body { padding: var(--nx-space-5); overflow-y: auto; flex: 1; }
.nx-drawer__foot {
    padding: var(--nx-space-3) var(--nx-space-5);
    border-top: 1px solid var(--nx-border);
    background: var(--nx-surface-tint);
    display: flex;
    justify-content: flex-end;
    gap: var(--nx-space-2);
    flex-shrink: 0;
}

@keyframes nx-fade-in    { from { opacity: 0; } }
@keyframes nx-drawer-in  { from { transform: translateX(100%); } }

/* ---- Dropdown menu — click-trigger floating action list -------------
   The "⋯ More" button pattern. Open on trigger click, close on outside
   click or Escape (nxcart-admin.js). Items can be links or buttons;
   destructive items get a red accent on hover. */
.nx-dropdown { position: relative; display: inline-block; }
.nx-dropdown__trigger { display: inline-block; }
.nx-dropdown__menu {
    position: absolute;
    top: calc(100% + 4px);
    min-width: 180px;
    z-index: 90;
    background: var(--nx-surface-card);
    border: 1px solid var(--nx-border);
    border-radius: var(--nx-radius-md);
    box-shadow: var(--nx-shadow-lg);
    padding: 4px;
    animation: nx-pop-in var(--nx-transition-fast) both;
}
.nx-dropdown__menu[hidden] { display: none; }
.nx-dropdown__menu--left  { left: 0; }
.nx-dropdown__menu--right { right: 0; }

.nx-dropdown__item {
    display: flex;
    align-items: center;
    gap: var(--nx-space-2);
    width: 100%;
    padding: var(--nx-space-2) var(--nx-space-3);
    border: 0;
    background: transparent;
    border-radius: var(--nx-radius-sm);
    font-family: inherit;
    font-size: var(--nx-text-sm);
    color: var(--nx-color-text);
    text-align: left;
    text-decoration: none;
    cursor: pointer;
    white-space: nowrap;
}
.nx-dropdown__item:hover { background: var(--nx-surface-hover); }
.nx-dropdown__item:focus-visible { outline: 2px solid var(--nx-focus-ring); outline-offset: -2px; }
.nx-dropdown__item-icon { color: var(--nx-color-text-muted); width: 14px; text-align: center; }
.nx-dropdown__item--destructive { color: var(--nx-danger); }
.nx-dropdown__item--destructive .nx-dropdown__item-icon { color: var(--nx-danger); }
.nx-dropdown__item--destructive:hover { background: var(--nx-danger-soft); }

.nx-dropdown__divider {
    height: 1px;
    margin: 4px 0;
    background: var(--nx-border);
}

/* Small uppercase eyebrow for grouping dropdown items into named sections
   (e.g. "Switch store"). Sits flush with the dropdown's inner padding so
   it visually owns the items underneath. */
.nx-dropdown__section-label {
    padding: 8px 12px 4px;
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--nx-color-text-muted);
}

/* Identity block — bigger row inside a dropdown that shows who's signed
   in (avatar + name + email). Used at the top of the user menu. */
.nx-dropdown__identity {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px;
    border-radius: var(--nx-radius-sm);
}
.nx-dropdown__identity-body { min-width: 0; }
.nx-dropdown__identity-name {
    margin: 0;
    font-size: 14px;
    font-weight: 600;
    color: var(--nx-color-heading);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.nx-dropdown__identity-meta {
    margin: 2px 0 0;
    font-size: 12.5px;
    color: var(--nx-color-text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

@keyframes nx-pop-in {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* ---- Segmented control — radio-group styled as a single pill --------- */
.nx-segmented {
    display: inline-flex;
    background: var(--nx-surface-card);
    border: 1px solid var(--nx-border);
    border-radius: var(--nx-radius-md);
    padding: 2px;
    gap: 2px;
}
.nx-segmented__opt {
    display: inline-flex;
    align-items: center;
    gap: var(--nx-space-1);
    padding: var(--nx-space-1) var(--nx-space-3);
    border-radius: var(--nx-radius-sm);
    font-size: var(--nx-text-sm);
    font-weight: var(--nx-weight-semibold);
    color: var(--nx-color-text-muted);
    cursor: pointer;
    user-select: none;
    transition: background-color var(--nx-transition-fast), color var(--nx-transition-fast);
}
.nx-segmented__opt:hover:not(.is-active) { color: var(--nx-color-text); }
.nx-segmented__opt input { position: absolute; opacity: 0; pointer-events: none; }
.nx-segmented__opt.is-active {
    background: var(--nx-color-cta);
    color: var(--nx-color-on-cta);
}
.nx-segmented__opt.is-active i { color: inherit; }
.nx-segmented__opt i { font-size: var(--nx-text-xs); }
.nx-segmented__opt input:focus-visible + i,
.nx-segmented__opt input:focus-visible ~ span {
    /* Focus indication via outline on the parent label since the input is hidden. */
}
.nx-segmented__opt:focus-within,
.nx-segmented__opt:focus-visible {
    /* Covers both modes — radio (inputs use :focus-within on the label)
       and link (the <a> uses :focus-visible directly). */
    box-shadow: 0 0 0 3px var(--nx-focus-ring);
}
/* Link-mode is a real anchor — kill the underline + inherit text color. */
a.nx-segmented__opt { text-decoration: none; color: inherit; }

/* ---- Address block --------------------------------------------------- */
.nx-address {
    font-style: normal;
    font-size: var(--nx-text-sm);
    line-height: 1.55;
    color: var(--nx-color-text);
}
.nx-address strong { font-weight: var(--nx-weight-semibold); color: var(--nx-color-heading); }
.nx-address__phone { display: inline-block; margin-top: var(--nx-space-1); color: var(--nx-color-text-muted); }
.nx-address--empty { color: var(--nx-color-text-muted); font-style: italic; }

/* ---- Combobox — searchable single-select -----------------------------
   Hidden form input carries the value; visible search input filters the
   popover list as you type. assets/js/nxcart-admin.js owns open/filter/
   select + keyboard nav (Arrow up/down, Enter, Escape). */
.nx-combobox { position: relative; }
.nx-combobox__field {
    position: relative;
    display: block;
}
.nx-combobox__search {
    padding-right: 2rem;
    cursor: pointer;
}
.nx-combobox__chevron {
    position: absolute;
    right: 0.75rem;
    top: 50%;
    transform: translateY(-50%);
    color: var(--nx-color-text-muted);
    font-size: var(--nx-text-xs);
    pointer-events: none;
    transition: transform var(--nx-transition-fast);
}
.nx-combobox[data-open] .nx-combobox__chevron {
    transform: translateY(-50%) rotate(180deg);
}
.nx-combobox__menu {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    z-index: 90;
    max-height: 260px;
    overflow-y: auto;
    background: var(--nx-surface-card);
    border: 1px solid var(--nx-border);
    border-radius: var(--nx-radius-md);
    box-shadow: var(--nx-shadow-lg);
    padding: 4px;
}
.nx-combobox__menu[hidden] { display: none; }
.nx-combobox__option {
    display: flex;
    flex-direction: column;
    gap: 2px;
    width: 100%;
    padding: var(--nx-space-2) var(--nx-space-3);
    border: 0;
    background: transparent;
    border-radius: var(--nx-radius-sm);
    font-family: inherit;
    font-size: var(--nx-text-sm);
    color: var(--nx-color-text);
    text-align: left;
    cursor: pointer;
}
.nx-combobox__option:hover,
.nx-combobox__option.is-highlighted { background: var(--nx-surface-hover); }
.nx-combobox__option.is-selected {
    background: var(--nx-surface-tint);
    font-weight: var(--nx-weight-semibold);
}
.nx-combobox__option-label { color: var(--nx-color-text); }
.nx-combobox__option-sub   { font-size: var(--nx-text-xs); color: var(--nx-color-text-muted); }
.nx-combobox__option[hidden] { display: none; }
.nx-combobox__empty {
    margin: 0;
    padding: var(--nx-space-3);
    font-size: var(--nx-text-sm);
    color: var(--nx-color-text-muted);
    text-align: center;
}

/* ---- Metric card — dashboard KPI tile -------------------------------- */
.nx-metric {
    display: block;
    padding: var(--nx-space-4) var(--nx-space-5);
    background: var(--nx-surface-card);
    border: 1px solid var(--nx-border);
    border-radius: var(--nx-radius-md);
    text-decoration: none;
    color: inherit;
    transition: border-color var(--nx-transition-fast),
                box-shadow var(--nx-transition-fast),
                transform var(--nx-transition-fast);
}
.nx-metric--link { cursor: pointer; }
.nx-metric--link:hover {
    border-color: var(--nx-border);
    box-shadow: var(--nx-shadow-md);
    transform: translateY(-2px);
}
.nx-metric__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    color: var(--nx-color-text-muted);
    font-size: var(--nx-text-sm);
    margin-bottom: var(--nx-space-2);
}
.nx-metric__label { font-weight: var(--nx-weight-medium); }
.nx-metric__icon  { color: var(--nx-color-text-muted); }
.nx-metric__value {
    font-size: var(--nx-text-2xl);
    font-weight: var(--nx-weight-bold);
    color: var(--nx-color-heading);
    letter-spacing: -.01em;
    font-variant-numeric: tabular-nums;
    line-height: 1.1;
}
/* Status tone — colours the value + icon when the NUMBER signals good/bad
   (health, capacity, limits). Set via nx_metric_card(['tone' => 'success'|
   'danger'|'warning']). For period-over-period change, use `delta` instead. */
.nx-metric__value--success { color: var(--nx-success); }
.nx-metric__value--danger  { color: var(--nx-danger); }
.nx-metric__value--warning { color: var(--nx-warning); }
.nx-metric--success .nx-metric__icon { color: var(--nx-success); }
.nx-metric--danger  .nx-metric__icon { color: var(--nx-danger); }
.nx-metric--warning .nx-metric__icon { color: var(--nx-warning); }
.nx-metric__delta {
    display: inline-flex;
    align-items: baseline;
    gap: var(--nx-space-1);
    margin-top: var(--nx-space-2);
    font-size: var(--nx-text-xs);
    font-weight: var(--nx-weight-semibold);
}
.nx-metric__delta--success { color: var(--nx-success); }
.nx-metric__delta--danger  { color: var(--nx-danger); }
.nx-metric__delta--neutral { color: var(--nx-color-text-muted); }
.nx-metric__delta i { font-size: 9px; }
.nx-metric__delta-label {
    color: var(--nx-color-text-muted);
    font-weight: var(--nx-weight-normal);
    margin-left: var(--nx-space-1);
}

/* ---- Empty state — small in-list "nothing here yet" card ------------
   Centered icon → title → body → optional CTA. Use INSIDE a list view
   when the result set is empty. Whole-page hero onboarding is bespoke. */
.nx-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding: var(--nx-space-6) var(--nx-space-5);
    background: var(--nx-surface-card);
    border: 1px dashed var(--nx-border);
    border-radius: var(--nx-radius-md);
}
.nx-empty__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    margin-bottom: var(--nx-space-3);
    border-radius: 999px;
    background: var(--nx-surface-subtle);
    color: var(--nx-color-text-muted);
    font-size: 1.1rem;
}
.nx-empty__title {
    margin: 0 0 var(--nx-space-1);
    font-size: var(--nx-text-base);
    font-weight: var(--nx-weight-semibold);
    color: var(--nx-color-heading);
}
.nx-empty__body {
    margin: 0 0 var(--nx-space-4);
    max-width: 38ch;
    color: var(--nx-color-text-muted);
    font-size: var(--nx-text-sm);
    line-height: 1.5;
}
.nx-empty__body:last-child { margin-bottom: 0; }
.nx-empty__action { margin-top: 0; }

/* ---- Resource picker — searchable list-with-checkboxes modal -------
   Composes nx_modal so overlay/focus-trap/Esc live in nxcart-admin.js;
   this is just the contents (search → list → empty-fallback). */
.nx-picker__search {
    position: relative;
    margin-bottom: var(--nx-space-3);
}
.nx-picker__search-icon {
    position: absolute;
    top: 50%;
    left: var(--nx-space-3);
    transform: translateY(-50%);
    color: var(--nx-color-text-muted);
    font-size: var(--nx-text-sm);
    pointer-events: none;
}
.nx-picker__search-input {
    width: 100%;
    padding: .55rem var(--nx-space-3) .55rem 2.2rem;
    background: var(--nx-surface-card);
    color: var(--nx-color-text);
    border: 1px solid var(--nx-border);
    border-radius: var(--nx-radius-md);
    font-size: var(--nx-text-sm);
    font-family: inherit;
    transition: border-color var(--nx-transition-fast),
                box-shadow var(--nx-transition-fast);
}
.nx-picker__search-input:focus {
    outline: none;
    border-color: var(--nx-color-cta);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--nx-color-cta) 22%, transparent);
}
/* The "Selected (N)" group + the results group share the same chrome,
   but the selected group lives at the top with a subtle tint so it
   reads as a separate, pinned region. */
.nx-picker__group {
    margin-top: var(--nx-space-3);
}
.nx-picker__group:first-of-type { margin-top: 0; }
.nx-picker__group[hidden] { display: none; }
.nx-picker__group-label {
    display: flex;
    align-items: center;
    gap: var(--nx-space-2);
    margin: 0 0 var(--nx-space-1);
    padding: 0 var(--nx-space-1);
    font-size: var(--nx-text-xs);
    font-weight: var(--nx-weight-semibold);
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--nx-color-text-muted);
}
.nx-picker__count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.25rem;
    padding: 0 .4rem;
    background: var(--nx-surface-subtle);
    color: var(--nx-color-text-muted);
    font-size: var(--nx-text-xs);
    font-weight: var(--nx-weight-semibold);
    border-radius: var(--nx-radius-pill);
    text-transform: none;
    letter-spacing: 0;
}
.nx-picker__list {
    list-style: none;
    margin: 0;
    padding: 0;
    max-height: min(60vh, 480px);
    overflow-y: auto;
    border: 1px solid var(--nx-border);
    border-radius: var(--nx-radius-md);
    background: var(--nx-surface-card);
}
/* Pinned-selected list: lighter cap, brand-tinted background so the eye
   immediately tells it apart from the results list below it. */
.nx-picker__list--selected {
    max-height: 200px;
    background: var(--nx-surface-subtle);
    border-color: var(--nx-border);
}
.nx-picker__row {
    border-bottom: 1px solid var(--nx-border);
}
.nx-picker__row:last-child { border-bottom: 0; }
.nx-picker__row[hidden] { display: none; }
.nx-picker__label {
    display: flex;
    align-items: center;
    gap: var(--nx-space-3);
    padding: var(--nx-space-2) var(--nx-space-3);
    cursor: pointer;
    transition: background-color var(--nx-transition-fast);
}
.nx-picker__label:hover { background: var(--nx-surface-hover); }
.nx-picker__input { margin: 0; flex-shrink: 0; }
.nx-picker__thumb {
    width: 36px;
    height: 36px;
    border-radius: var(--nx-radius-sm);
    object-fit: cover;
    background: var(--nx-surface-subtle);
    border: 1px solid var(--nx-border);
    flex-shrink: 0;
}
/* Fallback when a row has no image AND no image_fallback was set on
   the picker — keeps row heights uniform with a neutral placeholder. */
.nx-picker__thumb--empty {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--nx-color-text-muted);
    font-size: var(--nx-text-sm);
}
.nx-picker__text {
    display: flex;
    flex-direction: column;
    min-width: 0;
    flex: 1;
}
.nx-picker__title {
    color: var(--nx-color-heading);
    font-size: var(--nx-text-sm);
    font-weight: var(--nx-weight-medium);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.nx-picker__sub {
    color: var(--nx-color-text-muted);
    font-size: var(--nx-text-xs);
    margin-top: 2px;
}
.nx-picker__empty,
.nx-picker__hint {
    margin: var(--nx-space-3) 0 0;
    padding: var(--nx-space-4);
    text-align: center;
    color: var(--nx-color-text-muted);
    font-size: var(--nx-text-sm);
    background: var(--nx-surface-subtle);
    border-radius: var(--nx-radius-md);
}
.nx-picker__loading {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--nx-space-2);
    margin-top: var(--nx-space-3);
    padding: var(--nx-space-3);
    color: var(--nx-color-text-muted);
    font-size: var(--nx-text-sm);
}
.nx-picker__spinner {
    width: 14px;
    height: 14px;
    border: 2px solid var(--nx-border);
    border-top-color: var(--nx-color-cta);
    border-radius: 50%;
    animation: nx-picker-spin .8s linear infinite;
}
@keyframes nx-picker-spin {
    to { transform: rotate(360deg); }
}
.nx-picker__more {
    width: 100%;
    margin-top: var(--nx-space-3);
}

/* ---- Divider — semantic horizontal rule with token margin ------------ */
.nx-divider {
    border: 0;
    border-top: 1px solid var(--nx-border);
    margin: var(--nx-space-4) 0;
}
.nx-divider--gap-1 { margin: var(--nx-space-1) 0; }
.nx-divider--gap-2 { margin: var(--nx-space-2) 0; }
.nx-divider--gap-3 { margin: var(--nx-space-3) 0; }
.nx-divider--gap-4 { margin: var(--nx-space-4) 0; }
.nx-divider--gap-5 { margin: var(--nx-space-5) 0; }
.nx-divider--gap-6 { margin: var(--nx-space-6) 0; }

/* ---- Keyboard chip --------------------------------------------------- */
.nx-kbd {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 20px;
    padding: 2px 6px;
    background: var(--nx-surface-card);
    color: var(--nx-color-text);
    border: 1px solid var(--nx-border);
    border-bottom-width: 2px;
    border-radius: var(--nx-radius-sm);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: var(--nx-text-xs);
    font-weight: var(--nx-weight-semibold);
    line-height: 1;
    box-shadow: 0 1px 0 rgba(0, 0, 0, .04);
}
.nx-kbd-group {
    display: inline-flex;
    align-items: center;
    gap: 4px;
}
.nx-kbd-group__sep {
    color: var(--nx-color-text-muted);
    font-size: var(--nx-text-xs);
}

/* ---- Breadcrumbs ----------------------------------------------------- */
.nx-breadcrumbs ol {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0;
}
.nx-breadcrumbs__item {
    display: inline-flex;
    align-items: center;
    font-size: var(--nx-text-sm);
    color: var(--nx-color-text-muted);
}
.nx-breadcrumbs__link {
    color: var(--nx-color-text-muted);
    text-decoration: none;
    padding: 2px 0;
}
.nx-breadcrumbs__link:hover { color: var(--nx-color-link); text-decoration: underline; }
.nx-breadcrumbs__item.is-current { color: var(--nx-color-heading); font-weight: var(--nx-weight-semibold); }
.nx-breadcrumbs__sep {
    margin: 0 var(--nx-space-2);
    font-size: var(--nx-text-xs);
    color: var(--nx-color-text-muted);
}

/* ---- Pagination — Prev / Status / Next ------------------------------- */
.nx-pagination {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--nx-space-3);
    padding: var(--nx-space-3) var(--nx-space-4);
    background: var(--nx-surface-card);
    border: 1px solid var(--nx-border);
    border-radius: var(--nx-radius-md);
}
.nx-pagination__btn {
    display: inline-flex;
    align-items: center;
    gap: var(--nx-space-2);
    padding: var(--nx-space-2) var(--nx-space-3);
    border-radius: var(--nx-radius-sm);
    font-size: var(--nx-text-sm);
    font-weight: var(--nx-weight-semibold);
    color: var(--nx-color-text);
    text-decoration: none;
    transition: background-color var(--nx-transition-fast);
}
.nx-pagination__btn:hover { background: var(--nx-surface-hover); }
.nx-pagination__btn.is-disabled {
    color: var(--nx-color-text-muted);
    pointer-events: none;
    cursor: not-allowed;
}
.nx-pagination__status {
    font-size: var(--nx-text-sm);
    color: var(--nx-color-text-muted);
}

/* ---- Progress bar — linear ------------------------------------------- */
.nx-progress { width: 100%; }
.nx-progress__head {
    display: flex;
    justify-content: space-between;
    margin-bottom: var(--nx-space-1);
    font-size: var(--nx-text-sm);
}
.nx-progress__label { color: var(--nx-color-text); font-weight: var(--nx-weight-medium); }
.nx-progress__pct   { color: var(--nx-color-text-muted); font-variant-numeric: tabular-nums; }
.nx-progress__track {
    width: 100%;
    height: 8px;
    background: var(--nx-surface-tint);
    border: 1px solid var(--nx-border);
    border-radius: var(--nx-radius-pill);
    overflow: hidden;
}
.nx-progress__fill {
    height: 100%;
    border-radius: var(--nx-radius-pill);
    transition: width var(--nx-transition-base);
}
.nx-progress--info    .nx-progress__fill { background: var(--nx-color-text-muted); }
.nx-progress--success .nx-progress__fill { background: var(--nx-success); }
.nx-progress--warning .nx-progress__fill { background: var(--nx-warning); }
.nx-progress--danger  .nx-progress__fill { background: var(--nx-danger); }
.nx-progress--indeterminate .nx-progress__fill {
    width: 35% !important;
    animation: nx-prog-march 1.4s ease-in-out infinite;
    background: var(--nx-color-text-muted);
}
@keyframes nx-prog-march {
    0%   { transform: translateX(-100%); }
    100% { transform: translateX(285%); }
}

/* ---- Toast region + cards (positioned bottom-right) ------------------
   The mount lives once in body-end-code-tpl.php; nxToast.show() in JS
   appends individual toast cards into it. Each card auto-dismisses
   after `duration` (default 3500ms) with a slide+fade. */
.nx-toast-region {
    position: fixed;
    bottom: var(--nx-space-4);
    right:  var(--nx-space-4);
    z-index: 200;
    display: flex;
    flex-direction: column;
    gap: var(--nx-space-2);
    pointer-events: none;
    max-width: calc(100vw - 2 * var(--nx-space-4));
}
.nx-toast {
    pointer-events: auto;
    position: relative;            /* for the ::before accent bar */
    overflow: hidden;              /* clips the bar to the rounded corner */
    min-width: 260px;
    max-width: 400px;
    padding: .7rem .85rem .7rem 1rem;
    background: var(--nx-brand-dark);
    color: #fff;
    border: 1px solid rgba(255, 255, 255, .06);
    border-radius: var(--nx-radius-md);
    /* Linear-style system-notification depth — heavier than card shadow
       so the toast reads as floating above page chrome. Two layers:
       close drop (definition) + ambient (distance from surface). */
    box-shadow:
        0 4px 12px rgba(0, 0, 0, .28),
        0 16px 32px -8px rgba(0, 0, 0, .35);
    display: flex;
    align-items: flex-start;
    gap: var(--nx-space-2);
    font-size: var(--nx-text-sm);
    line-height: var(--nx-leading-normal);
    animation: nx-toast-in var(--nx-transition-slow) both;
}
/* Per-tone — bg stays dark; color shows up in a 3px left accent and the
   icon only. Icon tones are color-mix'd toward white because the raw
   --nx-* tokens are tuned for light backgrounds and turn muddy on the
   dark toast surface. */
.nx-toast::before {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    width: 3px;
    background: rgba(255, 255, 255, .25);  /* neutral default for plain toasts */
}
.nx-toast--success::before        { background: color-mix(in srgb, var(--nx-success) 75%, #fff); }
.nx-toast--success .nx-toast__icon { color: color-mix(in srgb, var(--nx-success) 70%, #fff); }
.nx-toast--danger::before         { background: color-mix(in srgb, var(--nx-danger) 75%, #fff); }
.nx-toast--danger  .nx-toast__icon { color: color-mix(in srgb, var(--nx-danger) 70%, #fff); }
.nx-toast--warning::before        { background: var(--nx-warning); }
.nx-toast--warning .nx-toast__icon { color: color-mix(in srgb, var(--nx-warning) 80%, #fff); }
.nx-toast.is-leaving { animation: nx-toast-out var(--nx-transition-base) both; }
.nx-toast__icon { font-size: 1rem; margin-top: 1px; }
.nx-toast__msg  { flex: 1; min-width: 0; font-size: var(--nx-text-sm); font-weight: var(--nx-weight-medium); }
.nx-toast__close {
    background: transparent;
    border: 0;
    color: rgba(255, 255, 255, .7);
    padding: 0 2px;
    cursor: pointer;
    font-size: var(--nx-text-sm);
}
.nx-toast__close:hover { color: #fff; }
@keyframes nx-toast-in  { from { opacity: 0; transform: translateX(20px); } }
@keyframes nx-toast-out { to   { opacity: 0; transform: translateX(20px); } }

/* ---- Stack: flex container with a token-scale gap. ------------------
   Direction × gap. Replaces inline display-flex + gap variables
   scattered across views. BEWARE: do NOT write a close-comment
   sequence anywhere inside this block, even inside backticks — CSS
   comments don't respect string delimiters, the first close-comment
   ends the block and everything after gets parsed as CSS until
   recovery, which silently swallows the .nx-stack rule below. */
.nx-stack { display: flex; min-width: 0; }
.nx-stack--v { flex-direction: column; }
.nx-stack--h { flex-direction: row; }
.nx-stack--gap-1 { gap: var(--nx-space-1); }
.nx-stack--gap-2 { gap: var(--nx-space-2); }
.nx-stack--gap-3 { gap: var(--nx-space-3); }
.nx-stack--gap-4 { gap: var(--nx-space-4); }
.nx-stack--gap-5 { gap: var(--nx-space-5); }
.nx-stack--gap-6 { gap: var(--nx-space-6); }
.nx-stack--gap-7 { gap: var(--nx-space-7); }
.nx-stack--align-start   { align-items: flex-start; }
.nx-stack--align-center  { align-items: center; }
.nx-stack--align-end     { align-items: flex-end; }
.nx-stack--align-stretch { align-items: stretch; }
.nx-stack--justify-start   { justify-content: flex-start; }
.nx-stack--justify-center  { justify-content: center; }
.nx-stack--justify-end     { justify-content: flex-end; }
.nx-stack--justify-between { justify-content: space-between; }
.nx-stack--justify-around  { justify-content: space-around; }
/* Wrap modifier — opts horizontal stacks into multi-row flow. Use for
   chip rows / tag lists / quick-add button rows that would otherwise
   overflow horizontally and force the parent (e.g. a modal body) to
   scroll. Token-driven `row-gap` is the same as the column gap. */
.nx-stack--wrap { flex-wrap: wrap; }

/* ---- Checkbox — multi-select (distinct from nx_toggle) ----------------
   Custom-painted box so the check + focus ring stay on-brand. Native
   <input> visually hidden; the box reads its :checked / :focus / :disabled
   state via the sibling selector. */
.nx-checkbox {
    display: inline-flex;
    /* align-items: center — label text vertically centered against the
       box mark instead of top-aligned. Matches the .nx-toggle change. */
    align-items: center;
    gap: var(--nx-space-2);
    cursor: pointer;
    user-select: none;
}
.nx-checkbox--disabled { cursor: not-allowed; opacity: .55; }
.nx-checkbox__input {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0,0,0,0);
    white-space: nowrap;
    border: 0;
}
.nx-checkbox__box {
    flex-shrink: 0;
    width: 18px; height: 18px;
    /* margin-top:1px removed — same reason as .nx-toggle__track. With
       align-items:center on the parent .nx-checkbox the box is
       centered against the label automatically. */
    border: 1px solid var(--nx-border);
    border-radius: 4px;
    background: var(--nx-surface-card);
    color: transparent;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background-color var(--nx-transition-fast),
                border-color var(--nx-transition-fast),
                color var(--nx-transition-fast);
}
.nx-checkbox__box svg { width: 14px; height: 14px; display: block; }
.nx-checkbox__input:checked + .nx-checkbox__box {
    background: var(--nx-color-cta);
    border-color: var(--nx-color-cta);
    color: var(--nx-color-on-cta);
}
.nx-checkbox__input:focus-visible + .nx-checkbox__box {
    box-shadow: 0 0 0 3px var(--nx-focus-ring);
}
.nx-checkbox__body {
    display: flex;
    flex-direction: column;
    gap: var(--nx-space-1);
    min-width: 0;
}
.nx-checkbox__label {
    font-size: var(--nx-text-base);
    color: var(--nx-color-text);
    line-height: var(--nx-leading-tight);
}
.nx-checkbox__hint {
    font-size: var(--nx-text-sm);
    color: var(--nx-color-text-muted);
    line-height: var(--nx-leading-normal);
}

/* ---- Radio group — vertical, with per-option hint --------------------
   For choices where the option's label needs vertical breathing room +
   sometimes a sub-explanation. Different shape than nx_segmented_control
   (horizontal pill, no hints). */
.nx-radio-group {
    border: 0;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--nx-space-2);
    min-width: 0;
}
.nx-radio-group__option {
    display: flex;
    align-items: flex-start;
    gap: var(--nx-space-3);
    padding: var(--nx-space-3);
    border: 1px solid var(--nx-border);
    border-radius: var(--nx-radius-md);
    background: var(--nx-surface-card);
    cursor: pointer;
    transition: border-color var(--nx-transition-fast),
                background-color var(--nx-transition-fast);
}
.nx-radio-group__option:hover { border-color: var(--nx-border); }
/* Selected styling — driven entirely by :has(:checked) on the wrapping
   <label>. Pure CSS, follows the browser's native radio mutual-exclusion
   state without needing JS to sync.
   The server-side .is-active class IS still emitted by nx_radio_group
   (kept for any code reading/writing it programmatically) but it does
   NOT drive visuals — sticky-on-the-Physical-option after the user
   clicks Digital would otherwise highlight both at once. The :has(:
   checked) selector is the single source of truth for "which radio is
   selected". */
.nx-radio-group__option:has(.nx-radio-group__input:checked) {
    border-color: var(--nx-color-cta);
    background: var(--nx-surface-tint);
}
.nx-radio-group__input {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0,0,0,0);
}
.nx-radio-group__dot {
    flex-shrink: 0;
    width: 18px; height: 18px;
    margin-top: 1px;
    border: 1px solid var(--nx-border);
    border-radius: 50%;
    background: var(--nx-surface-card);
    position: relative;
    transition: border-color var(--nx-transition-fast);
}
.nx-radio-group__option:has(.nx-radio-group__input:checked) .nx-radio-group__dot {
    border-color: var(--nx-color-cta);
}
.nx-radio-group__option:has(.nx-radio-group__input:checked) .nx-radio-group__dot::after {
    content: "";
    position: absolute;
    inset: 4px;
    border-radius: 50%;
    background: var(--nx-color-cta);
}
.nx-radio-group__input:focus-visible + .nx-radio-group__dot {
    box-shadow: 0 0 0 3px var(--nx-focus-ring);
}
.nx-radio-group__body { flex: 1; min-width: 0; }
.nx-radio-group__label {
    display: block;
    font-size: var(--nx-text-base);
    font-weight: var(--nx-weight-semibold);
    color: var(--nx-color-text);
    line-height: var(--nx-leading-tight);
}
.nx-radio-group__hint {
    display: block;
    margin-top: var(--nx-space-1);
    font-size: var(--nx-text-sm);
    color: var(--nx-color-text-muted);
    line-height: var(--nx-leading-normal);
}

/* ---- Tooltip — pure-CSS hover/focus popover --------------------------
   Wrap any trigger inline. tabindex="-1" on the wrap lets focus-within
   show the tooltip when the inner element is keyboard-focused. */
.nx-tooltip {
    position: relative;
    display: inline-flex;
    outline: none;
}
.nx-tooltip__bubble {
    position: absolute;
    z-index: 80;
    background: var(--nx-brand-dark);
    color: #fff;
    padding: 4px 8px;
    border-radius: var(--nx-radius-sm);
    font-size: var(--nx-text-xs);
    line-height: 1.4;
    white-space: nowrap;
    max-width: 240px;
    pointer-events: none;
    opacity: 0;
    transform: translate(-50%, 0);
    transition: opacity var(--nx-transition-fast);
    left: 50%;
}
.nx-tooltip--top .nx-tooltip__bubble    { bottom: calc(100% + 6px); }
.nx-tooltip--bottom .nx-tooltip__bubble { top:    calc(100% + 6px); }
.nx-tooltip:hover .nx-tooltip__bubble,
.nx-tooltip:focus-within .nx-tooltip__bubble { opacity: 1; }

/* ---- Avatar — initials fallback when no image -----------------------
   Size scale (sm 24 / md 32 / lg 40 / xl 56) + shape (circle | square).
   Tints rotate through a 6-bucket palette so the same name always lands
   on the same colour (avatars don't flicker between renders). */
.nx-avatar {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    font-weight: var(--nx-weight-semibold);
    line-height: 1;
    background: var(--nx-surface-tint);
    color: var(--nx-color-heading);
}
.nx-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
.nx-avatar--circle { border-radius: 50%; }
.nx-avatar--square { border-radius: var(--nx-radius-sm); }
.nx-avatar--sm { width: 24px; height: 24px; font-size: 10px; }
.nx-avatar--md { width: 32px; height: 32px; font-size: 12px; }
.nx-avatar--lg { width: 40px; height: 40px; font-size: 14px; }
.nx-avatar--xl { width: 56px; height: 56px; font-size: 20px; }
.nx-avatar__initials { letter-spacing: .02em; }
/* Tint palette — six buckets. Soft surfaces, brand-adjacent. */
.nx-avatar--tint-0 { background: #fde2e1; color: #8a1c14; }
.nx-avatar--tint-1 { background: #ffe5c2; color: #7a3e00; }
.nx-avatar--tint-2 { background: #e6f0d8; color: #324d12; }
.nx-avatar--tint-3 { background: #d8eaf3; color: #0c3f5b; }
.nx-avatar--tint-4 { background: #e4dff5; color: #392b75; }
.nx-avatar--tint-5 { background: #f7d8e9; color: #6b1846; }

/* ---- Thumbnail — square product image + fallback --------------------
   Three sizes (sm 32 / md 48 / lg 72) with consistent border + radius. */
.nx-thumbnail {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--nx-surface-tint);
    border: 1px solid var(--nx-border);
    border-radius: var(--nx-radius-sm);
    overflow: hidden;
    color: var(--nx-color-text-muted);
}
.nx-thumbnail img { width: 100%; height: 100%; object-fit: cover; display: block; }
.nx-thumbnail--sm { width: 32px; height: 32px; font-size: var(--nx-text-sm); }
.nx-thumbnail--md { width: 48px; height: 48px; font-size: var(--nx-text-base); }
.nx-thumbnail--lg { width: 72px; height: 72px; font-size: var(--nx-text-lg); }
.nx-thumbnail--empty i { opacity: .55; }

/* ---- Definition list — key/value pairs ------------------------------- */
.nx-dl { margin: 0; padding: 0; }
.nx-dl__row { display: block; }
.nx-dl--stacked .nx-dl__row + .nx-dl__row { margin-top: var(--nx-space-3); }
.nx-dl--stacked .nx-dl__term {
    margin: 0;
    font-size: var(--nx-text-xs);
    font-weight: var(--nx-weight-semibold);
    color: var(--nx-color-text-muted);
    text-transform: uppercase;
    letter-spacing: .06em;
}
.nx-dl--stacked .nx-dl__desc {
    margin: var(--nx-space-1) 0 0;
    font-size: var(--nx-text-base);
    color: var(--nx-color-text);
}
/* Horizontal: term left, value right, hairline between rows. */
.nx-dl--horizontal .nx-dl__row {
    display: grid;
    grid-template-columns: minmax(140px, 30%) 1fr;
    gap: var(--nx-space-3);
    padding: var(--nx-space-2) 0;
    border-top: 1px solid var(--nx-border);
}
.nx-dl--horizontal .nx-dl__row:first-child { border-top: 0; }
.nx-dl--horizontal .nx-dl__term {
    margin: 0;
    font-size: var(--nx-text-sm);
    color: var(--nx-color-text-muted);
}
.nx-dl--horizontal .nx-dl__desc {
    margin: 0;
    font-size: var(--nx-text-sm);
    color: var(--nx-color-text);
}

/* ---- Tabs — sub-navigation within a page ----------------------------
   Underline-style. Active tab carries the CTA colour underline +
   semibold weight. Items are real <a> by default (URL-driven state).
   Wraps on narrow widths instead of horizontal-scrolling. */
.nx-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: var(--nx-space-1);
    border-bottom: 1px solid var(--nx-border);
    margin-bottom: var(--nx-space-4);
}
/* Sticky variant — nx_tabs(['sticky' => true]). Pins the tab bar just below the
   sticky admin topbar (3.5rem) as the page scrolls, so it doubles as an always-
   reachable jump-to-section TOC on long pages (e.g. the SEO audit). The solid
   page-matching background is essential: without it, content scrolling underneath
   shows through the gaps between tabs. */
.nx-tabs--sticky {
    position: sticky;
    top: 3.5rem;                       /* clear the sticky .admin-topbar (height 3.5rem) */
    z-index: 20;
    background: #f1f3f5;               /* = admin page bg (admin-brand.css body override) */
    padding-top: var(--nx-space-3);
    box-shadow: 0 6px 10px -8px rgba(15, 23, 42, .16);
}
.nx-tabs__item {
    display: inline-flex;
    align-items: center;
    gap: var(--nx-space-2);
    padding: var(--nx-space-3) var(--nx-space-3);
    margin-bottom: -1px;
    border: 0;
    background: transparent;
    color: var(--nx-color-text-muted);
    font-family: inherit;
    font-size: var(--nx-text-base);
    font-weight: var(--nx-weight-medium);
    text-decoration: none;
    border-bottom: 2px solid transparent;
    cursor: pointer;
    transition: color var(--nx-transition-fast), border-color var(--nx-transition-fast);
}
.nx-tabs__item:hover { color: var(--nx-color-text); }
.nx-tabs__item.is-active {
    color: var(--nx-color-heading);
    font-weight: var(--nx-weight-semibold);
    border-bottom-color: var(--nx-color-cta);
}
.nx-tabs__badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 20px;
    height: 20px;
    padding: 0 6px;
    border-radius: var(--nx-radius-pill);
    background: var(--nx-surface-tint);
    color: var(--nx-color-text-muted);
    font-size: var(--nx-text-xs);
    font-weight: var(--nx-weight-semibold);
}
.nx-tabs__item.is-active .nx-tabs__badge {
    background: var(--nx-color-cta);
    color: var(--nx-color-on-cta);
}

/* ---- Accordion — collapsible sections via <details>/<summary> -------- */
.nx-accordion {
    border: 1px solid var(--nx-border);
    border-radius: var(--nx-radius-md);
    background: var(--nx-surface-card);
    overflow: hidden;
}
.nx-accordion__item + .nx-accordion__item { border-top: 1px solid var(--nx-border); }
.nx-accordion__summary {
    list-style: none;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--nx-space-3);
    padding: var(--nx-space-3) var(--nx-space-4);
    cursor: pointer;
    color: var(--nx-color-text);
    user-select: none;
}
.nx-accordion__summary::-webkit-details-marker { display: none; }
.nx-accordion__summary:hover { background: var(--nx-surface-hover); }
.nx-accordion__title { font-weight: var(--nx-weight-semibold); }
.nx-accordion__chevron {
    color: var(--nx-color-text-muted);
    font-size: var(--nx-text-sm);
    transition: transform var(--nx-transition-fast);
}
.nx-accordion__item[open] .nx-accordion__chevron { transform: rotate(180deg); }
.nx-accordion__body {
    padding: 0 var(--nx-space-4) var(--nx-space-4);
    color: var(--nx-color-text);
    font-size: var(--nx-text-sm);
    line-height: var(--nx-leading-normal);
}

/* ---- File upload — styled drop zone + hidden input -------------------
   Visual only; the caller's per-module JS wires drag-drop + upload to
   the existing /admin/admin-core/media-upload chunked endpoint. */
.nx-file {
    display: block;
    cursor: pointer;
    border: 2px dashed var(--nx-border);
    border-radius: var(--nx-radius-md);
    padding: var(--nx-space-5);
    background: var(--nx-surface-card);
    text-align: center;
    transition: border-color var(--nx-transition-fast),
                background-color var(--nx-transition-fast);
}
.nx-file:hover { border-color: var(--nx-color-cta); background: var(--nx-surface-tint); }
.nx-file.is-dragover { border-color: var(--nx-color-cta); background: var(--nx-surface-tint); }
.nx-file__input { position: absolute; opacity: 0; pointer-events: none; }
.nx-file__inner {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: var(--nx-space-2);
}
.nx-file__icon {
    font-size: var(--nx-text-2xl);
    color: var(--nx-color-text-muted);
}
.nx-file__cta {
    font-size: var(--nx-text-sm);
    color: var(--nx-color-text-muted);
}
.nx-file__cta strong { color: var(--nx-color-text); font-weight: var(--nx-weight-semibold); }
.nx-file--has-value { padding: var(--nx-space-3); border-style: solid; }
.nx-file__preview {
    max-width: 120px;
    max-height: 120px;
    object-fit: contain;
    display: block;
    margin: 0 auto var(--nx-space-2);
    border-radius: var(--nx-radius-sm);
}

/* ---- Hover-lift card — for clickable cards (KPI tiles, pickers, etc.) --- */
.nx-card--interactive {
    cursor: pointer;
    transition: transform var(--nx-transition-base),
                box-shadow var(--nx-transition-base),
                border-color var(--nx-transition-base);
}
.nx-card--interactive:hover {
    transform: translateY(-2px);
    box-shadow: var(--nx-shadow-md);
    border-color: var(--nx-border);
}

/* ---- Loading skeleton — shimmering placeholder ------------------------- */
.nx-skeleton {
    display: block;
    min-height: 1em;
    border-radius: var(--nx-radius-sm);
    color: transparent !important;
    user-select: none;
    background: linear-gradient(
        90deg,
        var(--nx-surface-tint) 25%,
        color-mix(in srgb, var(--nx-surface-tint) 55%, #fff) 50%,
        var(--nx-surface-tint) 75%);
    background-size: 200% 100%;
    animation: nx-shimmer 1.4s ease-in-out infinite;
}
.nx-skeleton--text  { height: .8em; margin: .25em 0; }
.nx-skeleton--line  { width: 100%; }

/* ---- Entrance for content that streams in (cards, table on load) ------- */
.nx-rise { animation: nx-rise var(--nx-transition-slow) both; }

/* ---- Soft depth on hover for any bordered surface opting in ------------ */
.nx-elevate { transition: box-shadow var(--nx-transition-base); }
.nx-elevate:hover { box-shadow: var(--nx-shadow-md); }

/* ---- Global sleek scrollbar -------------------------------------------
   Replaces the chunky OS-default (≈16px on Windows) with a slim 8px
   pill-shaped track that hides into the page. Tokens-driven so a rebrand
   ripples here too. Applies to every scrollable element across admin,
   platform, and customizer surfaces (all three load this stylesheet).

   Already-customised scrollbars (sidebar 6px, customizer panels, etc.)
   carry higher-specificity selectors and continue to win — this is the
   default everything else falls through to.

   ----------------------------------------------------------------
   Sleek pill pattern:
     - 8px wide / 8px tall — visible enough to grab, narrow enough to
       blend in. The Windows default is ~16-17px and feels brutalist
       on a Stripe / Linear-style admin.
     - Thumb is muted at rest, lifts to full text-muted on hover.
     - 2px transparent border + background-clip:padding-box gives the
       thumb visual breathing room from the track edges (looks like a
       floating pill, not a solid bar).
     - Track stays transparent — the page bg shows through.
     - Firefox: `scrollbar-width: thin` + `scrollbar-color` on `html`
       cascades to every descendant (Firefox honours inheritance).
*/
html {
    scrollbar-width: thin;
    scrollbar-color: color-mix(in srgb, var(--nx-color-text-muted) 45%, transparent) transparent;
}
*::-webkit-scrollbar {
    width: 8px;
    height: 8px;
}
*::-webkit-scrollbar-track {
    background: transparent;
}
*::-webkit-scrollbar-thumb {
    background-color: color-mix(in srgb, var(--nx-color-text-muted) 45%, transparent);
    background-clip: padding-box;
    border: 2px solid transparent;
    border-radius: var(--nx-radius-pill);
    transition: background-color var(--nx-transition-fast);
}
*::-webkit-scrollbar-thumb:hover {
    background-color: var(--nx-color-text-muted);
    background-clip: padding-box;
}
*::-webkit-scrollbar-corner {
    background: transparent;
}

/* ---- Respect the OS "reduce motion" setting — premium products always do */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .01ms !important;
        scroll-behavior: auto !important;
    }
}
