/*
 * BNESIM Admin — UI utility components (P2)
 *
 * Small set of token-powered building blocks. Namespaced `ui-*` to avoid
 * colliding with Bootstrap 3 classes and with the page-level flex/gap
 * utilities that many Alpine pages ship in their own inline <style> blocks.
 *
 * Use these for new UI. Legacy markup (`.white-box`, `.badge`, `.panel`,
 * Bootstrap alerts) keeps working — the migration to these utilities happens
 * as we touch each page.
 */

/* ─── Card ──────────────────────────────────────────────────────────── */

.ui-card {
    background-color: var(--bg-surface);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
    padding: 20px;
    transition: border-color var(--motion-fast) var(--ease-out),
        box-shadow var(--motion-fast) var(--ease-out);
}

.ui-card:hover {
    border-color: var(--border-default);
}

.ui-card--flush {
    padding: 0;
}

.ui-card__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 16px 20px;
    border-bottom: 1px solid var(--border-subtle);
}

.ui-card__body {
    padding: 20px;
}

.ui-card__title {
    font-size: 15px;
    font-weight: 600;
    color: var(--text-primary);
    margin: 0;
}

.ui-card__subtitle {
    font-size: 13px;
    color: var(--text-secondary);
    margin: 2px 0 0;
}

.ui-card__overline {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-tertiary);
    margin: 0 0 6px;
}

/* ─── Badge (pill) ──────────────────────────────────────────────────── */

.ui-badge {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 10px;
    font-size: 11px;
    font-weight: 600;
    line-height: 1.4;
    letter-spacing: 0.02em;
    border-radius: var(--radius-pill);
    border: 1px solid transparent;
    white-space: nowrap;
}

.ui-badge i {
    font-size: 10px;
}

.ui-badge--success {
    color: var(--success);
    background-color: var(--success-muted);
    border-color: var(--success);
}

.ui-badge--warning {
    color: var(--warning);
    background-color: var(--warning-muted);
    border-color: var(--warning);
}

.ui-badge--danger {
    color: var(--danger);
    background-color: var(--danger-muted);
    border-color: var(--danger);
}

.ui-badge--info {
    color: var(--info);
    background-color: var(--info-muted);
    border-color: var(--info);
}

.ui-badge--accent {
    color: var(--accent);
    background-color: var(--accent-muted);
    border-color: var(--accent);
}

.ui-badge--neutral {
    color: var(--text-secondary);
    background-color: var(--bg-subtle);
    border-color: var(--border-subtle);
}

/* ─── Status dot ────────────────────────────────────────────────────── */
/* Leading indicator for status labels so they read without color alone. */

.ui-status-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: var(--radius-pill);
    margin-right: 6px;
    vertical-align: middle;
    background-color: currentColor;
}

.ui-status--success {
    color: var(--success);
}

.ui-status--warning {
    color: var(--warning);
}

.ui-status--danger {
    color: var(--danger);
}

.ui-status--info {
    color: var(--info);
}

.ui-status--accent {
    color: var(--accent);
}

.ui-status--muted {
    color: var(--text-tertiary);
}

/* ─── Empty state ───────────────────────────────────────────────────── */

.ui-empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    padding: 48px 24px;
    text-align: center;
    color: var(--text-secondary);
}

.ui-empty-state__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 56px;
    height: 56px;
    border-radius: var(--radius-pill);
    background-color: var(--accent-muted);
    color: var(--accent);
    font-size: 22px;
}

.ui-empty-state__title {
    font-size: 16px;
    font-weight: 600;
    color: var(--text-primary);
    margin: 6px 0 0;
}

.ui-empty-state__desc {
    font-size: 13px;
    color: var(--text-secondary);
    margin: 0;
    max-width: 360px;
}

.ui-empty-state__cta {
    margin-top: 12px;
}

/* ─── Divider ───────────────────────────────────────────────────────── */

.ui-divider {
    border: 0;
    border-top: 1px solid var(--border-subtle);
    margin: 16px 0;
}

/* ═════════════════════════════════════════════════════════════════════
   Legacy pattern refresh (P4)
   ─────────────────────────────────────────────────────────────────────
   Promotes the patterns I used on the dashboard + customers pilot pages
   to the global CSS, so every page consuming them is refreshed for free:

     - `.white-box`                         — card surface with border + shadow
     - `.white-box > .box-title.*-background` — overline + accent-bar header
     - `.pagination-button`                 — Alpine-driven pagination pills
     - `.sorting` / `.sorting-asc` / `.sorting-desc` — sortable column headers
     - `.loading-container`                 — centered spinner overlay
     - `.skeleton` / `.chart-skeleton`      — loading placeholders

   The existing `style.css` + `custom.css` rules for these classes are
   overridden here with `!important` where needed so this layer wins.
   ═════════════════════════════════════════════════════════════════════ */

/* ─── White-box card ───────────────────────────────────────────────── */

.white-box {
    background-color: var(--bg-surface);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
    padding: 24px;
    color: var(--text-primary);
    transition: border-color var(--motion-fast) var(--ease-out),
        box-shadow var(--motion-fast) var(--ease-out);
}

/* ─── Box-title — overline + accent bar (kills the legacy ribbon) ──── */
/* The `.service-N-background` + `.standard-background` classes used to fill
   the h3 with a colored background. We re-read them as a *category color*
   for the left-side accent bar, preserving visual differentiation without
   the heavy ribbon look. */

.white-box>.box-title.service-1-background {
    --title-accent: #5DC1CC;
}

.white-box>.box-title.service-2-background {
    --title-accent: #DF7158;
}

.white-box>.box-title.service-3-background {
    --title-accent: #EF994E;
}

.white-box>.box-title.service-4-background {
    --title-accent: #6ABEE9;
}

.white-box>.box-title.service-5-background {
    --title-accent: #EFB950;
}

.white-box>.box-title.service-6-background {
    --title-accent: #244C7E;
}

.white-box>.box-title.service-7-background {
    --title-accent: #9723E9;
}

.white-box>.box-title.service-8-background {
    --title-accent: #D5537C;
}

.white-box>.box-title.service-9-background {
    --title-accent: #0099A8;
}

.white-box>.box-title.service-10-background {
    --title-accent: #40818c;
}

.white-box>.box-title.service-11-background {
    --title-accent: #990d36;
}

.white-box>.box-title.service-12-background {
    --title-accent: #c59f70;
}

.white-box>.box-title.service-help-background {
    --title-accent: #6B03BA;
}

.white-box>.box-title.service-halo-background {
    --title-accent: #676cd5;
}

.white-box>.box-title.standard-background {
    --title-accent: var(--accent-secondary);
}

.white-box>.box-title {
    background: transparent !important;
    color: var(--text-primary) !important;
    border-radius: 0 !important;
    padding: 0 !important;
    margin: 0 0 16px !important;
    font-size: 11px !important;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    display: flex;
    align-items: center;
    gap: 10px;
}

.white-box>.box-title::before {
    content: "";
    display: inline-block;
    width: 3px;
    height: 14px;
    flex: 0 0 auto;
    border-radius: var(--radius-pill);
    background: var(--title-accent, var(--accent));
}

/* Inline "UPDATE" or similar action buttons inside a box-title (dashboard,
   customer-feedbacks use this pattern with baked-in inline styles). */
.white-box>.box-title .btn {
    margin-left: auto !important;
    border-radius: var(--radius-pill) !important;
    padding: 2px 12px !important;
    font-size: 10px !important;
    min-width: 0 !important;
    letter-spacing: 0.06em;
    float: none !important;
}

/* ─── Pagination-button pattern (Alpine pages define these inline) ─── */
/* `!important` on colors so these rules beat per-page inline <style>
   redefinitions (which load later and would otherwise win by source order). */

.pagination-button {
    color: var(--text-primary) !important;
    background: var(--bg-elevated) !important;
    border: 1px solid var(--border-subtle) !important;
    padding: 4px 12px;
    font-size: 13px;
    font-weight: 600;
    border-radius: var(--radius-pill) !important;
    box-sizing: border-box;
    display: inline-block;
    min-width: 1.5em;
    text-align: center;
    cursor: pointer;
    transition: background-color var(--motion-fast) var(--ease-out),
        color var(--motion-fast) var(--ease-out),
        border-color var(--motion-fast) var(--ease-out);
}

.pagination-button:hover {
    background: var(--bg-subtle) !important;
    border-color: var(--border-default) !important;
}

.pagination-button.current,
.pagination-button.current:hover {
    cursor: default;
    color: var(--text-on-accent) !important;
    background: var(--accent) !important;
    border-color: var(--accent) !important;
}

.pagination-button:disabled {
    color: var(--text-tertiary) !important;
    border: 1px solid transparent !important;
    background: transparent !important;
    box-shadow: none;
    cursor: not-allowed;
}

.pagination-ellipsis {
    padding: 0 0.75rem;
    color: var(--text-tertiary) !important;
}

/* ─── Sortable column headers (custom Alpine-driven, not DataTables) ── */

.sorting,
.sorting-asc,
.sorting-desc {
    cursor: pointer;
    user-select: none;
    box-sizing: content-box;
}

.sorting::after,
.sorting-asc::after,
.sorting-desc::after {
    float: right;
    font-family: fontawesome;
    margin-left: 6px;
}

.sorting::after {
    content: "\f0dc";
    color: var(--text-tertiary) !important;
    opacity: 0.6;
}

.sorting-asc::after {
    content: "\f0de";
    color: var(--accent) !important;
}

.sorting-desc::after {
    content: "\f0dd";
    color: var(--accent) !important;
}

/* `.loading-container` isn't promoted globally — it means different things
   on different pages (absolute overlay vs. inline centered spinner). Each
   page keeps its own definition; we just ensure any absolute-positioned
   variant that uses a white backdrop picks up `--bg-overlay` via the
   per-page tokenization. */

/* ─── Skeleton / chart-skeleton placeholder ────────────────────────── */

.skeleton {
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    z-index: 1;
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: row;
    flex-wrap: wrap;
    gap: 1rem;
    overflow: hidden;
    padding: 1rem;
    background: var(--bg-overlay) !important;
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
    border-radius: var(--radius-lg);
}

.chart-skeleton>i,
.chart-skeleton>svg {
    color: var(--text-tertiary) !important;
    opacity: 0.5;
    font-size: clamp(5rem, 12vw, 10rem);
    width: 1em;
    animation: tw-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}

.bg-skeleton {
    background-color: var(--bg-subtle) !important;
}

/* ─── Form-check / Form-switch (Bootstrap-4 polyfill used on many pages) ─ */
/* `profile.html`, `customer-activation.html`, `customer-details.html` each
   ship this same block of CSS inline with hardcoded blues. Promoting it
   here (tokenized) lets the per-page duplicates remain harmless while
   ensuring theme-aware styling on any page using the classes. */

.form-check {
    display: block;
    min-height: 1.5rem;
    padding-left: 1.5em;
    margin-bottom: 0.125rem;
}

.form-check .form-check-input {
    float: left;
    margin-left: -1.5em;
}

.form-check .form-check-label {
    margin-bottom: 0;
    color: var(--text-primary);
}

.form-check-input {
    width: 18px;
    height: 18px;
    margin-top: 0;
    vertical-align: middle;
    background-color: var(--bg-surface) !important;
    background-repeat: no-repeat;
    background-position: center;
    background-size: 12px 12px;
    border: 1.5px solid var(--border-default) !important;
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
    -webkit-print-color-adjust: exact;
    color-adjust: exact;
    cursor: pointer;
    transition: background-color var(--motion-fast) var(--ease-out),
        border-color var(--motion-fast) var(--ease-out);
}

.form-check-input[type="checkbox"] {
    border-radius: 4px;
}

.form-check-input[type="radio"] {
    border-radius: 50%;
}

.form-check-input:active {
    filter: brightness(90%);
}

.form-check-input:focus {
    border-color: var(--accent) !important;
    outline: 0;
    box-shadow: var(--focus-ring) !important;
}

.form-check-input:checked {
    background-color: var(--accent) !important;
    border-color: var(--accent) !important;
}

.form-check-input:checked[type="checkbox"] {
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3e%3cpath d='M3 8.5l3.5 3.5L13 5' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3e%3c/svg%3e") !important;
}

.form-check-input:checked[type="radio"] {
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e") !important;
}

.form-check-input[type="checkbox"]:indeterminate {
    background-color: var(--accent) !important;
    border-color: var(--accent) !important;
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3e%3cpath d='M4 8h8' stroke='%23fff' stroke-width='2' stroke-linecap='round'/%3e%3c/svg%3e") !important;
}

/* Global checkbox style — mirrors the "Remember me" login checkbox.
   Targets every native <input type="checkbox"> so bare inputs (no class)
   adopt the design too. Plugin-rendered widgets (Treeselect, Tagify,
   daterangepicker) don't use real checkboxes, so no exclusion needed.
   .login-checkbox visually hides its own input (position: absolute, 1px,
   opacity: 0) and renders an SVG via a sibling <span>, so this rule's
   visuals don't surface there.
   .form-switch keeps its 2em pill shape because its own descendant
   selector (`.form-switch .form-check-input`) outranks ours on width /
   border-radius and uses `!important` for its thumb background image. */
input[type="checkbox"] {
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
    width: 18px;
    height: 18px;
    vertical-align: middle;
    background-color: var(--bg-surface);
    background-repeat: no-repeat;
    background-position: center;
    background-size: 12px 12px;
    border: 1.5px solid var(--border-default);
    border-radius: 4px;
    cursor: pointer;
    margin: 0;
    transition: background-color var(--motion-fast) var(--ease-out),
        border-color var(--motion-fast) var(--ease-out);
}

input[type="checkbox"]:hover {
    border-color: var(--accent);
}

input[type="checkbox"]:checked {
    background-color: var(--accent);
    border-color: var(--accent);
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3e%3cpath d='M3 8.5l3.5 3.5L13 5' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3e%3c/svg%3e");
}

input[type="checkbox"]:indeterminate {
    background-color: var(--accent);
    border-color: var(--accent);
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3e%3cpath d='M4 8h8' stroke='%23fff' stroke-width='2' stroke-linecap='round'/%3e%3c/svg%3e");
}

input[type="checkbox"]:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

input[type="checkbox"]:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

.form-check-input:disabled {
    pointer-events: none;
    filter: none;
    opacity: 0.5;
}

.form-check-input:disabled~.form-check-label,
.form-check-input[disabled]~.form-check-label {
    opacity: 0.5;
}

.form-switch {
    padding-left: 2.5em;
    display: inline-flex;
    align-items: center;
}

.form-switch .form-check-input {
    width: 2em;
    margin-left: -2.5em;
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%28148, 163, 184, 0.6%29'/%3e%3c/svg%3e") !important;
    background-position: left center;
    border-radius: 2em;
    transition: background-position 0.15s ease-in-out,
        background-color var(--motion-fast) var(--ease-out),
        border-color var(--motion-fast) var(--ease-out);
}

@media (prefers-reduced-motion: reduce) {
    .form-switch .form-check-input {
        transition: none;
    }
}

.form-switch .form-check-input:checked {
    background-position: right center;
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e") !important;
}

/* Any <label> wrapping a radio gets aligned input + text out of the box,
   so pages don't need to repeat `flex items-center gap-2 cursor-pointer
   m-0 font-normal` (or Bootstrap's legacy `.radio-inline`) on every
   radio. inline-flex (not flex) keeps multiple radios flowing side-by-side
   when they sit in normal block context. */
input[type="radio"] {
    margin: 0 !important;
}

label:has(> input[type="radio"]) {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    cursor: pointer;
    margin: 0;
    font-weight: 400;
}

/* ─── Textarea ─────────────────────────────────────────────────────────
   Global tokenized textarea theme. Bootstrap 3 `.form-control` ships flat
   greys + a hardcoded blue focus glow that doesn't track our accent / dark
   mode. This block re-skins every <textarea> on the admin (including the
   ~30 .form-control textareas across customer-details, faq, coupons,
   campaigns, promo-details, esim-compatibility, etc.) using design tokens.
   ───────────────────────────────────────────────────────────────────── */

textarea,
textarea.form-control {
    display: block;
    width: 100%;
    min-height: 80px;
    padding: 8px 12px;
    font-family: inherit;
    font-size: 14px;
    line-height: 1.6;
    color: var(--text-primary);
    background-color: var(--bg-surface);
    background-image: none;
    border: 1px solid var(--border-default);
    border-radius: var(--radius-md);
    box-shadow: none;
    resize: vertical;
    transition: border-color var(--motion-fast) var(--ease-out),
        box-shadow var(--motion-fast) var(--ease-out),
        background-color var(--motion-fast) var(--ease-out);
}

textarea::placeholder {
    color: var(--text-tertiary);
    opacity: 1;
}

textarea:hover:not(:disabled):not([readonly]):not(:focus) {
    border-color: var(--border-strong);
}

textarea:focus,
textarea.form-control:focus {
    outline: 0;
    border-color: var(--accent);
    box-shadow: var(--focus-ring);
    background-color: var(--bg-surface);
}

textarea:disabled,
textarea[readonly] {
    background-color: var(--bg-subtle);
    color: var(--text-secondary);
    cursor: not-allowed;
}

/* ═════════════════════════════════════════════════════════════════════
   Stat card — compact neutral-surface card with a colored accent rail,
   small uppercase label, large bold value, optional muted unit suffix.
   Used on customer-details, enterprise, and other summary pages.
   ═════════════════════════════════════════════════════════════════════ */

.stat-card {
    position: relative;
    background: var(--bg-surface);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-sm);
    padding: 18px 22px;
    margin-bottom: 16px;
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-height: 96px;
    overflow: hidden;
    transition: transform var(--motion-fast) var(--ease-out),
        box-shadow var(--motion-fast) var(--ease-out),
        border-color var(--motion-fast) var(--ease-out);
}

.stat-card::before {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    width: 4px;
    border-radius: var(--radius-pill);
    background: var(--text-tertiary);
}

.stat-card[data-accent="coral"]::before {
    background: #F97C50;
}

.stat-card[data-accent="sage"]::before {
    background: #8DC6BF;
}

.stat-card[data-accent="teal"]::before {
    background: #5DC1CC;
}

.stat-card[data-accent="navy"]::before {
    background: #244C7E;
}

.stat-card[data-accent="sky"]::before {
    background: #6ABEE9;
}

.stat-card[data-accent="amber"]::before {
    background: #EFB950;
}

.stat-card[data-accent="indigo"]::before {
    background: #676CD5;
}

.stat-card[data-accent="gold"]::before {
    background: #C59F70;
}

.stat-card[data-accent="rose"]::before {
    background: #D5537C;
}

.stat-card.is-clickable {
    cursor: pointer;
}

.stat-card.is-clickable:hover {
    transform: translateY(-1px);
    box-shadow: var(--shadow-md);
    border-color: var(--border-default);
}

.stat-label {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.stat-value {
    margin: 0 !important;
    font-size: 22px;
    font-weight: 700;
    color: var(--text-primary) !important;
    letter-spacing: -0.01em;
    line-height: 1.2;
    display: flex;
    align-items: baseline;
    gap: 8px;
    flex-wrap: wrap;
}

.stat-unit {
    font-size: 12px;
    font-weight: 500;
    color: var(--text-tertiary);
    letter-spacing: 0.02em;
    text-transform: none;
}

/* ─── Status badges ─ */
/* Muted-bg + solid-token-color pills for state indicators on the
   agency activation / provisioning history pages. */
.badge-active {
    background: var(--success-muted);
    color: var(--success);
}

.badge-failed {
    background: var(--danger-muted);
    color: var(--danger);
}

.badge-processing {
    background: var(--warning-muted);
    color: var(--warning);
}

.badge-scheduled {
    background: var(--info-muted);
    color: var(--info);
}

/* ─── Label / value detail row ─ */
/* Compact "label above, value below" pair used on detail pages
   (sim-card-details, sim-card-statistics). Pair the classes with
   utility-class layout (e.g. `flex flex-col gap-1`) on the wrapper. */
.detail-label {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.detail-value {
    font-size: 14px;
    color: var(--text-primary);
    font-weight: 500;
    word-break: break-word;
}

/* ─── Semantic text-color utilities ─ */
/* Override Bootstrap 3's hardcoded .text-* colors with the project's
   design tokens so these classes stay theme-aware. !important wins over
   Bootstrap's own declarations without needing a more specific selector. */
.text-success {
    color: var(--success) !important;
}

.text-warning {
    color: var(--warning) !important;
}

.text-danger {
    color: var(--danger) !important;
}

.text-muted {
    color: var(--text-tertiary) !important;
}

.text-accent {
    color: var(--accent) !important;
}

/* ─── Global scroll-to-top floating button ─
   Auto-injected into every page that loads `js/scroll-to-top.js` (via
   foot.html). Fades in once the viewport has scrolled more than 200px.
   The `.hidden-top-btn` class is toggled from JS — define it alongside
   the button so both states live in one place. */
#scroll-to-top-button {
    display: block;
    position: fixed;
    bottom: 16px;
    right: 16px;
    z-index: 99;
    padding: 10px;
    min-width: 40px;
    border-radius: var(--radius-pill);
    background-color: var(--bg-elevated);
    border: 1px solid var(--border-subtle);
    color: var(--text-primary);
    box-shadow: var(--shadow-md);
    transition: opacity var(--motion-base) var(--ease-out),
        background-color var(--motion-fast) var(--ease-out),
        transform var(--motion-fast) var(--ease-out);
}

#scroll-to-top-button:hover {
    background-color: var(--bg-subtle);
    transform: translateY(-2px);
}

.hidden-top-btn {
    opacity: 0;
    cursor: default;
    pointer-events: none;
}

/* ─── Refresh-prompt banner stack ─
   Shared bottom-left container that holds any "refresh to apply" banners
   the watchdog scripts inject (access-watchdog, inactivity-watchdog, and
   any future siblings). Lets multiple prompts stack vertically with
   consistent spacing instead of overlapping. Pinned bottom-left so it
   doesn't fight the scroll-to-top FAB at bottom-right. Sits above the
   FAB (z-index 99) but below Bootstrap modals (z-index 1040+) so
   dialogs still take focus when open.

   `column-reverse` keeps the newest banner closest to the corner so the
   most recent alert is the most visually prominent — older ones get
   nudged upward as new ones arrive. The container itself ignores pointer
   events (so the page below stays clickable in the gap between banners)
   and each banner re-enables them. */
.refresh-banners-stack {
    position: fixed;
    bottom: 16px;
    left: 16px;
    z-index: 100;
    display: flex;
    flex-direction: column-reverse;
    gap: 12px;
    max-width: 420px;
    pointer-events: none;
}

.refresh-banners-stack > * {
    pointer-events: auto;
}

/* ─── Access-level watchdog banner ─
   Visual styling for an individual banner inside .refresh-banners-stack.
   The container handles positioning; the banner only owns its own look. */
.access-watchdog-banner {
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 14px 16px;
    border-radius: var(--radius-md, 8px);
    background-color: var(--bg-elevated);
    border: 1px solid var(--border-subtle);
    color: var(--text-primary);
    box-shadow: var(--shadow-lg, var(--shadow-md));
    animation: access-watchdog-in 220ms var(--ease-out, ease-out);
}

.access-watchdog-content {
    display: flex;
    align-items: center;
    gap: 12px;
    flex: 1;
    min-width: 0;
}

.access-watchdog-icon {
    font-size: 22px;
    color: var(--warning, #f0a500);
    flex-shrink: 0;
}

.access-watchdog-text {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.access-watchdog-title {
    font-size: 14px;
    line-height: 1.3;
    color: var(--text-primary);
}

.access-watchdog-subtitle {
    font-size: 12px;
    line-height: 1.3;
    color: var(--text-secondary);
}

.access-watchdog-actions {
    display: flex;
    gap: 8px;
    flex-shrink: 0;
}

.access-watchdog-leaving {
    opacity: 0;
    transform: translateY(8px);
    transition: opacity 180ms var(--ease-out, ease-out),
                transform 180ms var(--ease-out, ease-out);
}

@keyframes access-watchdog-in {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: translateY(0); }
}

@media (max-width: 600px) {
    .refresh-banners-stack {
        left: 12px;
        right: 12px;
        bottom: 12px;
        max-width: none;
    }
    .access-watchdog-banner {
        flex-direction: column;
        align-items: stretch;
    }
    .access-watchdog-actions {
        justify-content: flex-end;
    }
}

/* ─── Branded loading spinner ─
   A unique SVG-arc spinner scoped to one class so updating the look
   (stroke width, dash pattern, speed) only touches this rule.

   Uses CSS `mask` + inline SVG so the stroke follows `currentColor` —
   drops into buttons, alerts, labels without needing a color override.
   Spins at 600ms (≈2× faster than Tailwind's `.animate-spin`) for a
   snappier feedback feel.

   Usage:  <span class="spinner"></span>           (generic inline)
           <i class="spinner"></i>                 (inside a button or icon slot)
           <span class="spinner" style="font-size: 24px"></span>   (larger)

   To redesign the glyph: edit `--spinner-svg` to a different inline
   SVG — see https://yoksel.github.io/url-encoder/ for a URL-safe encoder.
   The SVG's `stroke` color must be any literal (black works) since the
   mask only uses the alpha channel; the visible color comes from
   `background-color: currentColor`. */

.spinner {
    --spinner-svg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='black'%3e%3cpath opacity='.2' fill-rule='evenodd' clip-rule='evenodd' d='M12 19C15.866 19 19 15.866 19 12C19 8.13401 15.866 5 12 5C8.13401 5 5 8.13401 5 12C5 15.866 8.13401 19 12 19ZM12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z'/%3e%3cpath d='M2 12C2 6.47715 6.47715 2 12 2V5C8.13401 5 5 8.13401 5 12H2Z'/%3e%3c/svg%3e");
    display: inline-block;
    width: 1em;
    height: 1em;
    vertical-align: -0.125em;
    background-color: currentColor;
    -webkit-mask: var(--spinner-svg) no-repeat center / contain;
    mask: var(--spinner-svg) no-repeat center / contain;
    animation: tw-spin 0.6s linear infinite;
}

/* ─── Alpine.js initial-load flash guard ─ */
/* Alpine resolves x-cloak attributes only after it boots. Without this
   rule, Alpine-driven elements flash visible for a frame before being
   hidden. Safe to include project-wide since no non-Alpine code sets
   x-cloak. */
[x-cloak] {
    display: none !important;
}

/* ─── Empty-state section ─ */
/* Centered placeholder shown in cards/tables when a query returns no
   rows. Pages that need a tighter or more spacious variant should add a
   modifier class locally rather than overriding globally. */
.empty-state {
    text-align: center;
    padding: 48px 16px;
    color: var(--text-tertiary);
}

.empty-state i {
    font-size: 42px;
    margin-bottom: 16px;
    display: block;
}

/* ─── Horizontally-scrollable small-table wrapper ─ */
/* Mirrors Bootstrap 4's .table-responsive-sm behavior for tables that
   overflow narrow viewports. Keep outside the Bootstrap breakpoint
   scope so it applies at every width. */
.table-responsive-sm {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}

/* ─── Image placeholder / upload preview frame ──────────────────────── */
/* Themed chrome around a preview <img> or an empty-state icon. Used by
   affiliate-edit and profile logo uploaders; set the size inline (width
   and height) — everything else is token-driven so it adapts to theme. */
.ui-image-placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-sm);
    background: var(--bg-subtle);
}

.ui-image-placeholder>img {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
}

.ui-image-placeholder>.fa {
    color: var(--text-tertiary);
}

/* ─── Box-title action button ───────────────────────────────────────── */
/* Compact circular icon-only action button (Update / Explorer / etc.) that
   floats on the right of a `.box-title` heading. Pair with `.btn .btn-info`
   for the base colour; this class handles size + position only.
   This is the one explicit exception to the project's icon+text rule —
   labels go in a tooltip (`data-toggle="tooltip" title="..."`) so the button
   stays flush in the header without competing with the title. */
.btn-box-action {
    float: right;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    min-width: 0;
    padding: 0;
    margin-top: 0;
    margin-right: -4px;
    font-size: 13px;
    line-height: 1;
    border-radius: 50%;
}

/* ─── Inline SVG icon ───────────────────────────────────────────────── */
/* Sits in the same visual cell as a Font Awesome glyph: ~0.9em square,
   baseline-aligned to match FA's font metrics so SVG icons align with FA
   siblings on the same line (dropdown rows, nav links). Inherits text
   colour via `currentColor`. Combine with `.fa-fw` when you need FA's
   fixed-width column behaviour (e.g. dropdown-menu rows that mix FA +
   SVG icons). */
.svg-icon {
    display: inline-block;
    width: 0.9em;
    height: 0.9em;
    vertical-align: -0.15em;
    fill: currentColor;
}

/* When combined with `.fa-fw`, expand to FA's fixed-width column (1.28em)
   so the icon cell aligns with FA siblings — the SVG content stays its
   natural ~0.9em via viewBox aspect-ratio preservation, centred in the
   wider cell exactly like an FA glyph. Compound selector beats the plain
   `.svg-icon` width above. */
.svg-icon.fa-fw {
    width: 1.28571429em;
}

/* ─── <bne-icon> custom-element host ────────────────────────────────
   Defined in /js/icons.js, renders an inner <svg.svg-icon>. The host
   itself acts as a flex centring slot so the SVG can use its natural
   aspect ratio (`width: auto`, height driven by font-size) without
   getting squeezed inside square tile containers. */
bne-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
    color: inherit;
}

bne-icon>svg {
    width: auto;
    height: 1em;
    fill: currentColor;
}

/* Larger glyph inside coloured icon tiles (cmdk results, search-result
   list). Tile contexts scale font-size up so the SVG fills more of the
   cell and matches FA siblings visually. */
.cmdk-item-icon bne-icon>svg,
.search-page-item-icon bne-icon>svg {
    height: 1.4em;
}

/* ─── App logo chip ─────────────────────────────────────────────────
   32px square that frames an app icon — used in promo/customer rows
   and in the targeted-apps multi-select. Accent-tinted background so
   the chip reads as a brand surface rather than a plain card. */
.custom-select-logo-container {
    width: 32px;
    height: 32px;
    border-radius: var(--radius-sm);
    border: 2px solid var(--accent-muted);
    box-shadow: var(--shadow-sm);
    overflow: hidden;
    transition: border-color var(--motion-fast) var(--ease-out),
        box-shadow var(--motion-fast) var(--ease-out);
}

.custom-select-logo {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* ─── Custom multi-select dropdown ──────────────────────────────────
   Popup container used by Alpine-driven multi-select pickers (target
   apps in promo-details, target-apps filter in promos advanced search,
   …). The trigger is a plain <button> styled with `.form-control`;
   this popup is an absolutely-positioned listbox rendered relative to
   the trigger's positioned ancestor. Width is intentionally driven by
   content + `min-w-[…]` utility on the markup, so callers can size it
   to fit their longest option without changing this base.

   Pair `.custom-select-container` (wraps the option list) with
   `.custom-select-item` (each `<button role="option">`). Highlight
   state (keyboard navigation) is toggled by the caller via the
   `.option-highlighted` class. */
.custom-select-container {
    border-radius: var(--radius-sm);
    top: calc(100% + 4px);
    /* High enough to overlap sibling form content (e.g. the promos language
       tab strip rendered just below the advanced search panel) while still
       staying under the global scroll-to-top FAB (z-index 99) and Bootstrap
       modals (z-index 1040+). */
    z-index: 50;
    display: flex;
    flex-direction: column;
    max-height: 300px;
    overflow-y: auto;
    box-shadow: var(--shadow-lg);
    padding: 0 8px;
    background-color: var(--bg-elevated);
    border: 1px solid var(--border-subtle);
}

.custom-select-item {
    padding: 8px 12px;
    background-color: transparent;
    color: var(--text-primary);
    border-bottom: 1px solid var(--border-subtle);
    cursor: pointer;
    border-radius: 0;
    transition: background-color var(--motion-fast) var(--ease-out);
    box-shadow: none;
}

.custom-select-item:hover,
.custom-select-item.option-highlighted {
    background-color: var(--accent);
    color: var(--text-on-accent);
}

.custom-select-item:last-child {
    border-bottom: none;
}

/* ─── Input group ───────────────────────────────────────────────────
   Bootstrap 3's `.input-group` was `display: table` with child cells.
   The design refresh added `.form-control { border-radius: var(--radius-md)
   !important }` in chrome.css, which broke the visual continuation —
   children rounded on all four corners instead of meeting flush. We
   override here, switching the layout to flex so the input + addon/
   button reads as one unified control. components.css loads after
   chrome.css, so these rules win the cascade. */
.input-group {
    display: flex !important;
    flex-wrap: nowrap;
    align-items: stretch;
    width: 100%;
    max-width: 100%;
    box-sizing: border-box;
}

/* Default: input fills the remaining row space.
   `flex: 1 1 auto` makes flex-basis follow the `width` property —
   so an inline `style="width: calc(100% - 150px)"` (coupons reward
   value field) or a utility `w-4/5` (coupons discount field) becomes
   the basis directly. For the plain search case where no width is
   declared, our `width: auto` (same specificity as Bootstrap's
   `.input-group .form-control { width: 100% }` but later in source
   order, so it wins) keeps basis intrinsic and `flex-grow: 1` then
   fills the remaining space beside the button. `min-width: 0` lets
   it shrink below intrinsic when the row gets tight. */
.input-group > .form-control {
    width: auto;
    flex: 1 1 auto;
    min-width: 0;
}

/* Page-level width-utility classes on a form-control inside `.input-group`
   need (0,3,0) specificity to win over Bootstrap's `.input-group
   .form-control { width: 100% }` (0,2,0) and our `width: auto` above.
   Only fractions actually used on `.form-control` inside an input-group
   today are promoted; add the matching pair if a new split is needed. */
.input-group > .form-control.w-1\/5 { width: 20%; flex: 0 0 auto; }
.input-group > .form-control.w-4\/5 { width: 80%; flex: 0 0 auto; }

/* Inline `style="width: ..."` (e.g. coupons.html extra-reward
   `calc(100% - 150px)` + `width: 150px`) is already (1,0,0,0)
   specificity, but our default rule above keeps `flex-grow: 1` which
   would stretch it past the declared width. Cap grow when an inline
   width attribute is present. */
.input-group > .form-control[style*="width"] {
    flex: 0 0 auto;
}

/* Flatten every child's radius, then re-round the outer corners on
   the first/last child. `!important` is required to beat
   `.form-control { border-radius … !important }` from chrome.css. */
.input-group > *,
.input-group > .input-group-btn > .btn {
    border-radius: 0 !important;
}

.input-group > :first-child,
.input-group > .input-group-btn:first-child > .btn {
    border-top-left-radius: var(--radius-md) !important;
    border-bottom-left-radius: var(--radius-md) !important;
}

.input-group > :last-child,
.input-group > .input-group-btn:last-child > .btn {
    border-top-right-radius: var(--radius-md) !important;
    border-bottom-right-radius: var(--radius-md) !important;
}

/* Addon / prefix label (e.g. `€`, currency selector). */
.input-group-text,
.input-group-addon {
    display: flex;
    align-items: center;
    padding: 6px 12px;
    margin-bottom: 0;
    white-space: nowrap;
    background-color: var(--bg-subtle);
    color: var(--text-secondary);
    border: 1px solid var(--border-subtle);
}

/* Icon-only button slot — search-style input-groups put a compact
   square button next to the input. The button keeps its colour class
   (`.btn-info`, etc.) and gets a tooltip/aria-label for the action.
   `.input-group-btn` is a `<span>`; force flex so the inner button
   stretches to the row height instead of inheriting the span's
   inline natural height. */
.input-group > .input-group-btn {
    display: flex;
    align-items: stretch;
    flex: 0 0 auto;
    width: auto;
}

.input-group-btn > .btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0 14px;
    min-width: 42px;
    height: auto;
    line-height: 1;
}

/* ─── Language tab (pane chooser) ───────────────────────────────────────
   Used by multi-language editors (campaigns, coupons, faq) where the
   left rail lists language codes and the right pane shows the editable
   translation. Three pages used to ship near-identical inline copies of
   these rules; this is the consolidated source. Variations are handled
   with utilities on the markup: `h-full` if the strip should grow to the
   sibling pane height, `text-start` for left-aligned codes. */
.language-item {
    position: relative;
    padding: 4px;
    text-align: center;
    color: var(--text-primary);
    border: 1px solid var(--border-subtle);
    border-bottom-color: transparent;
    cursor: pointer;
    overflow: hidden;
    box-sizing: border-box;
    transition: all 300ms ease-in-out;
}

.language-item:first-of-type {
    border-top-left-radius: 3px;
}

.language-item:last-of-type {
    border-bottom-left-radius: 3px;
    border-bottom-color: var(--border-subtle);
}

.language-item:hover {
    color: var(--accent-secondary);
}

.language-item.active {
    border-right-color: transparent;
    color: var(--accent);
}

.language-item-empty-box {
    position: absolute;
    top: 90%;
    left: 80%;
    width: 20px;
    height: 20px;
    font-size: 20px;
    line-height: 1;
    opacity: 0.2;
    transform: translate(-50%, -50%) rotate(-15deg);
    transition: all 300ms ease-in-out;
}

.language-item.active .language-item-empty-box {
    color: var(--text-primary);
}

.language-item:hover .language-item-empty-box {
    color: var(--accent-secondary);
}
/* ── Segmented tab-pill control (e.g. ALL / eSIM / SIM CARD) ────────────
   Reusable pill tab group shared across views (customers, dashboard stock).
   The active pill is highlighted by `.tab-pill-bg` — a single absolutely
   positioned element that animates its `left` + `width` to slide under the
   active pill (driven by `slide_tab_pill_bg()` in utility.js). This mirrors
   the animated language-tabs slider but adapts to variable-width labels. */
.tab-pills {
    position: relative;
    display: flex;
    flex-direction: row;
    width: max-content;
    gap: 2px;
    padding: 3px;
    border-radius: var(--radius-pill);
    background: var(--bg-subtle);
    border: 1px solid var(--border-subtle);
}

/* Sliding highlight — JS sets left/width to match the active pill. */
.tab-pills > .tab-pill-bg {
    position: absolute;
    top: 3px;
    left: 3px;
    width: 0;
    height: calc(100% - 6px);
    border-radius: var(--radius-pill);
    background: var(--accent);
    box-shadow: var(--shadow-sm);
    pointer-events: none;
    z-index: 0;
    transition: left var(--motion-slow) var(--ease-out),
                width var(--motion-slow) var(--ease-out);
}

.tab-pills > .tab-pill {
    position: relative;
    z-index: 1;
    padding: 6px 14px;
    border-radius: var(--radius-pill);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-secondary);
    cursor: pointer;
    user-select: none;
    transition: color var(--motion-fast) var(--ease-out);
}

.tab-pills > .tab-pill:hover {
    color: var(--text-primary);
}

.tab-pills > .tab-pill.active {
    color: var(--text-on-accent);
}
