:root {
    --droply-primary: 28 177 201;
    --droply-primary-dark: 23 154 175;
    --droply-orange: 249 115 22;
    --droply-violet: 139 92 246;
    --droply-success: 22 163 74;
    --droply-text-main: 15 23 42;
    --droply-text-sub: 100 116 139;
    --droply-border: 226 232 240;
    --droply-surface: 255 255 255;
    --droply-surface-soft: 248 250 252;
    --droply-page: 244 247 251;
    --droply-shadow-soft: 0 22px 55px -28px rgba(28, 177, 201, 0.22);
    --droply-shadow-card: 0 28px 80px -32px rgba(15, 23, 42, 0.18);
    --droply-shadow-glass: 0 20px 50px -35px rgba(15, 23, 42, 0.28);
    --droply-radius-xl: 1.5rem;
    --droply-radius-2xl: 2rem;
    --droply-radius-3xl: 2.5rem;
}

html,
body {
    font-family: 'Manrope', 'Segoe UI', Tahoma, Arial, sans-serif;
    background:
        radial-gradient(circle at top right, rgba(var(--droply-primary), 0.12), transparent 28%),
        radial-gradient(circle at bottom left, rgba(var(--droply-orange), 0.08), transparent 26%),
        rgb(var(--droply-page));
    color: rgb(var(--droply-text-main));
    /* Panels slide in from the right using transform: translateX(100%),
       which briefly pushes the fixed element beyond the viewport's
       right edge. Without overflow-x: hidden here, the browser adds a
       horizontal scrollbar the moment the panel mounts and removes it
       again a beat later — the appearing / disappearing scrollbar
       shifts the page's vertical layout and reads as a "shake" on
       first open. Clipping horizontally kills that jitter without
       affecting any legitimate horizontal-scroll region (which owns
       its own overflow-x:auto).

       `clip`, NOT `hidden` — and note this selector is `html, body`, which
       is the whole reason it mattered.

       `hidden` makes an element a scroll container, and CSS forces the
       other axis to `auto` when one axis is `hidden`. There IS an exemption
       that would have saved us: the root's overflow propagates to the
       viewport, so `body { overflow-x: hidden }` alone leaves body itself
       computing to `visible`. But that exemption only applies while the
       ROOT element is `visible`, and here `html` carries the declaration
       too. So the root became a scroll container, and every
       `position: sticky` in the app anchored to it instead of to the
       viewport — silently doing nothing. The product page's sticky gallery
       just scrolled away.

       Measured in Chrome: with the declaration on BOTH, `html` resolved to
       `clip` but `body` still resolved to `overflow-x: hidden` — which
       forces `overflow-y: auto` and makes BODY a scroll container. Every
       sticky element then anchored to a body that never scrolls. So the
       declaration lives on the root ALONE (see the `html` rule below);
       the root is what propagates to the viewport, which is what actually
       suppresses the scrollbar. Body needs no overflow at all.

       Do not add `body` back to that selector, and do not "simplify" it
       to `hidden`. */
}

/* Root only — see the note above. This is the declaration that clips the
   sliding panels; putting it on `body` as well is what silently disabled
   position: sticky across the entire app. */
html {
    overflow-x: clip;
}

body {
    min-height: 100vh;
}

a,
.btn-link {
    color: rgb(var(--droply-primary));
}

.btn-primary {
    color: #fff;
    background-color: rgb(var(--droply-primary));
    border-color: rgb(var(--droply-primary-dark));
}

.droply-shell {
    position: relative;
    isolation: isolate;
}

.droply-shell::before {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background:
        radial-gradient(circle at 12% 12%, rgba(var(--droply-primary), 0.07), transparent 18%),
        radial-gradient(circle at 88% 18%, rgba(var(--droply-violet), 0.06), transparent 20%),
        radial-gradient(circle at 82% 84%, rgba(var(--droply-orange), 0.07), transparent 18%);
    z-index: -1;
}

.droply-page {
    max-width: 1440px;
    margin-inline: auto;
    padding-inline: 1.5rem;
}

.droply-surface-card {
    background: rgba(var(--droply-surface), 0.88);
    border: 1px solid rgba(var(--droply-border), 0.9);
    border-radius: var(--droply-radius-2xl);
    box-shadow: var(--droply-shadow-soft);
    backdrop-filter: blur(14px);
}

.droply-surface-card-strong {
    background: linear-gradient(180deg, rgba(255, 255, 255, 0.98), rgba(248, 250, 252, 0.95));
    border: 1px solid rgba(var(--droply-border), 0.9);
    border-radius: var(--droply-radius-3xl);
    box-shadow: var(--droply-shadow-card);
}

.droply-glass-nav {
    background: rgba(255, 255, 255, 0.76);
    border-bottom: 1px solid rgba(255, 255, 255, 0.7);
    box-shadow: var(--droply-shadow-glass);
    backdrop-filter: blur(18px);
}

.droply-button-primary {
    background: linear-gradient(135deg, rgb(var(--droply-primary)), rgb(var(--droply-primary-dark)));
    color: #fff;
    box-shadow: 0 20px 45px -24px rgba(var(--droply-primary), 0.7);
}

.droply-button-secondary {
    background: rgba(255, 255, 255, 0.9);
    color: rgb(var(--droply-text-main));
    border: 1px solid rgba(var(--droply-border), 0.9);
    box-shadow: 0 18px 36px -28px rgba(15, 23, 42, 0.18);
}

.droply-button-primary,
.droply-button-secondary {
    transition: transform 180ms ease, box-shadow 180ms ease, background-color 180ms ease;
}

.droply-button-primary:hover,
.droply-button-secondary:hover {
    transform: translateY(-1px);
}

.droply-muted {
    color: rgb(var(--droply-text-sub));
}

.droply-text-gradient {
    background: linear-gradient(135deg, rgb(var(--droply-primary)), rgb(var(--droply-violet)));
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
}

.text-gradient {
    background: linear-gradient(135deg, rgb(var(--droply-primary)), rgb(var(--droply-violet)));
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
}

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
  box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}

/* ─── intl-tel-input wrapper ─────────────────────────────────────────────
   Shared styling for the phone-number widget across the app. Used by
   PartnerRegistration's owner-phone field AND the Manual Order panels
   (Add + Edit). Anything that wraps an intl-tel-input <input> in a
   `<div class="intl-tel-input-wrapper">` picks these rules up.
   Keeps the widget the full width of its container, hides the default
   country-flag chrome's background so it inherits the input's bg, and
   forces LTR rendering of the dialled number even when the page is RTL
   (phone numbers always read left-to-right). */
/* NOTE: the vendored intl-tel-input is v19+, which RENAMED these hooks —
   `iti__flag-container` → `iti__country-container` and `iti__selected-flag`
   → `iti__selected-country`. The old names were still in use here, so every
   rule below silently matched nothing and the widget fell back to raw vendor
   styling on every page that uses it. Both names are kept so the styling
   survives whichever build is served. */
.intl-tel-input-wrapper .iti { width: 100%; }

/* Room for the flag + dial-code chip — ONE rule for every surface that uses
   the widget (customer signup, partner registration, checkout, addresses).
   Logical property, so RTL mirrors without a second declaration.

   This is a FALLBACK, not a competitor. intl-tel-input measures the chip and
   writes padding-left / padding-right onto the input as an INLINE style, and
   inline always beats a stylesheet rule — so whenever the measurement lands,
   this is ignored and the gap matches the real dial-code width ("+1" and
   "+962" differ). It only takes effect when the widget measures before layout
   settles and writes a too-small value, which is what made the signup
   placeholder run underneath the "+962" chip.

   Pages previously hard-coded their own offset (partner registration used
   ltr:pl-[90px] rtl:pr-[90px]; signup used 0.5rem, which was too small and
   caused the overlap). Those are removed in favour of this.

   PHYSICAL sides, deliberately — this was `padding-inline-start`, and that was
   the bug behind the clipped Arabic placeholder. `padding-inline-start`
   resolves against the INPUT's own direction, and the rule below forces the
   input to `direction: ltr` so digits read left-to-right on an Arabic page.
   So on an RTL page the fallback landed on the LEFT while the chip sits on the
   RIGHT. autoPad then wrote its inline padding-right for the chip and cleared
   its inline padding-LEFT — which just un-hid this 5.5rem again. The input was
   squeezed from both sides at once and the placeholder had nowhere to go.
   Keyed off the PAGE direction, the fallback is always on the same side as the
   chip, and autoPad's clear of the opposite side reveals nothing. */
/* `:not(.iti__search-input)` is LOAD-BEARING.
   These rules reserve room for the country chip, and they target a bare
   `input` so they still apply before the widget boots. Once the dropdown
   stopped being teleported to <body>, the search box became a descendant of
   this wrapper too — so it inherited 5.5rem (88px) of clearance for a chip it
   doesn't have. THAT is the space inside the search box: not padding I chose,
   but the phone field's chip gutter landing on the wrong input. Excluding it
   by class keeps the pre-init fallback working and spares the search field. */
/* 6.5rem, not the old 5.5rem: the unified phone type scale (0.95rem,
   weight 500) renders the "+962" chip at ~96-100px — WIDER than the old
   88px fallback — so whenever autoPad's measurement was late or failed,
   the digits started underneath the chip's edge and read as one glued
   string ("+9627"). The fallback must clear the widest common chip at
   the unified scale; autoPad still tightens it to the exact width the
   moment its measurement lands. */
[dir="ltr"] .intl-tel-input-wrapper input:not(.iti__search-input) { padding-left: 6.5rem; }
[dir="rtl"] .intl-tel-input-wrapper input:not(.iti__search-input) { padding-right: 6.5rem; }
/* Leading inset before the flag. Kept small: the hosting field already
   supplies its own edge padding, and 12px on top of that visibly pushed the
   flag past the icons on the neighbouring inputs. The gap on the OTHER side of
   the chip — between it and the number — is owned by autoPad, not this. */
/* ONE flag inset for the whole app. The chip's real distance from the field
   edge is this gutter PLUS the vendor's own `.iti__selected-country-primary`
   inset (--iti-spacer-horizontal, 8px), so 8 + 8 = 16px — which is exactly
   the horizontal text inset both form systems already use (partner
   registration's `px-4`, customer signup's `0.95rem`). The flag therefore
   lines up with the field's own text and icons on every surface instead of
   each page landing somewhere different: partner registration sat at 4px
   while signup sat at 10px, which is what made the two fields look unrelated. */
.intl-tel-input-wrapper { --dpl-flag-gutter: 8px; }
/* The vendor writes that inner inset PHYSICALLY
   (`padding: 0 var(--iti-arrow-padding) 0 var(--iti-spacer-horizontal)`) with
   no RTL counterpart, so in Arabic the flag sat 6px in rather than 8px and the
   arrow gap swapped sides. Restated logically, it mirrors. */
.intl-tel-input-wrapper .iti__selected-country-primary {
    padding-inline: var(--iti-spacer-horizontal) var(--iti-arrow-padding);
}
[dir="ltr"] .intl-tel-input-wrapper .iti__flag-container,
[dir="ltr"] .intl-tel-input-wrapper .iti__country-container { left: 0; right: auto; padding-left: var(--dpl-flag-gutter); padding-right: 0; }
[dir="ltr"] .intl-tel-input-wrapper .iti__selected-flag,
[dir="ltr"] .intl-tel-input-wrapper .iti__selected-country { background: transparent !important; border-radius: 12px 0 0 12px; }
[dir="rtl"] .intl-tel-input-wrapper .iti__flag-container,
[dir="rtl"] .intl-tel-input-wrapper .iti__country-container { left: auto; right: 0; padding-left: 0; padding-right: var(--dpl-flag-gutter); }
[dir="rtl"] .intl-tel-input-wrapper .iti__selected-flag,
[dir="rtl"] .intl-tel-input-wrapper .iti__selected-country { background: transparent !important; border-radius: 0 12px 12px 0; }
/* The widget container stays RTL so its own absolute positioning mirrors
   naturally. This used to force `direction: ltr` here, which fought the
   `right: 0` chip placement above — logical properties resolved against the
   opposite axis, so the chip and the input's padding disagreed and the flag
   drifted away from the trailing edge. Only the INPUT is forced LTR (below),
   which is all that's needed: digits and the "+962" prefix must read
   left-to-right even in an Arabic page. */
[dir="rtl"] .intl-tel-input-wrapper .iti { direction: rtl; }
/* Same exclusion, and for the same reason: the phone NUMBER must read
   left-to-right on an Arabic page, but an Arabic search query must not — this
   rule was forcing the country search to type LTR. */
[dir="rtl"] .intl-tel-input-wrapper input:not(.iti__search-input) { direction: ltr; text-align: right; }
/* ── ONE type scale for every phone field ─────────────────────────
   The number and the dial-code chip render at the SAME size, weight
   and color on every surface (checkout, addresses, signup, partner
   registration, dashboard panels) — pinned to the /account/addresses
   field's scale. !important is deliberate and load-bearing twice over:
   page-scoped styles are injected AFTER app.css and win equal-
   specificity ties (checkout 0.875rem, signup's own scale…), and
   autoPad copies the input's COMPUTED font onto the chip as inline
   styles — pinning the input here makes both the tie-break and the
   inline copy resolve to these values, so every field matches. */
.intl-tel-input-wrapper input:not(.iti__search-input),
.intl-tel-input-wrapper .iti__selected-dial-code {
    font-size: 0.95rem !important;
    font-weight: 500 !important;
    color: #131b2e !important;
}

/* ── Country dropdown ──────────────────────────────────────────────
   The vendored build is v24.6.0 and it ALREADY ships correct [dir=rtl]
   rules for the row internals (flag margin, country-name margin, arrow,
   selected dial code). Those rules key off the `dir` attribute on <html>,
   so they match on an Arabic page without help. The job here is only the
   two things the vendor genuinely can't know: the box's width, and which
   edge it anchors to.

   Everything below is deliberately NOT a re-implementation of the vendor's
   RTL handling. An earlier attempt here re-declared `.iti__country-name`
   margins with logical properties; because `.iti` is `direction: rtl`,
   `margin-inline-start` resolved to margin-RIGHT and the accompanying
   `margin-inline-end: 0` zeroed the vendor's own margin-left spacer. That
   is what jammed the dial code against the country name ("+93Afghanistan").
   Fighting the vendor's RTL rules was the bug; deferring to them is the fix. */

/* WIDTH — an explicit box, not shrink-to-fit.
   `.iti__country-list` carries `white-space: nowrap` (the vendor's
   flexible-dropdown-width behaviour), so the list is as wide as the longest
   country name while `.iti__search-input` is `width: 100%` of the dropdown's
   own shrink-to-fit box. The two disagreed: the list overflowed to one side
   and the search field stopped short of it, which is the gap that showed to
   the right of the Arabic search box. Pinning the width makes both resolve
   against the same number. */
.intl-tel-input-wrapper .iti__dropdown-content {
    width: 20rem;
    max-width: calc(100vw - 2rem);
    border-radius: 12px;
    box-shadow: 0 10px 40px -10px rgba(0, 0, 0, 0.15);
    /* Above form chrome and drawers (~50) without competing with modals. */
    z-index: 60;
    overflow: hidden;   /* keeps the list's corners inside the radius */
}
/* Long names now wrap inside the fixed width instead of overflowing it. */
.intl-tel-input-wrapper .iti__country-list { white-space: normal; max-height: 250px; }

/* ANCHORING — the containing block is `.iti__country-container` (the chip),
   which sits at the input's leading edge. The vendor leaves both offsets
   `auto`, so the box falls at its static position and grows to the right in
   either direction; in RTL that walked it off the trailing edge of the field
   and out over the page. Anchor it explicitly per direction: the list's
   leading edge lines up with the field's, and it grows inward. */
[dir="ltr"] .intl-tel-input-wrapper .iti--inline-dropdown .iti__dropdown-content {
    left: 0;
    right: auto;
}
[dir="rtl"] .intl-tel-input-wrapper .iti--inline-dropdown .iti__dropdown-content {
    left: auto;
    right: 0;
    /* Mirror of the vendor's `margin-left: calc(var(--iti-border-width) * -1)`,
       which is physical and has no RTL counterpart upstream. */
    margin-left: 0;
    margin-right: calc(var(--iti-border-width) * -1);
}

/* Search field: match the list's direction so the caret and placeholder start
   on the reading edge, and give it the padding the vendor omits entirely. */
/* The SAME horizontal inset the vendor gives every country row
   (`.iti__country { padding: 8px var(--iti-spacer-horizontal) }`). It was a
   hard-coded 12px, which left the search text sitting 4px further from the
   edge than the list beneath it — read as a stray space before "ابحث عن دولة"
   in Arabic, where the eye starts at that edge. Sharing the variable means
   the search field and the rows can't drift apart again. */
.intl-tel-input-wrapper .iti__search-input { padding: 9px var(--iti-spacer-horizontal); }
[dir="rtl"] .intl-tel-input-wrapper .iti__search-input { direction: rtl; text-align: right; }
/* Dial codes stay LTR as text ("+962", never "962+") wherever they appear. */
[dir="rtl"] .intl-tel-input-wrapper .iti__dial-code { direction: ltr; unicode-bidi: embed; }
/* ── Chip metrics ──────────────────────────────────────────────────
   The widget's own chrome, normalised ONCE so the flag / arrow / dial-code
   group is identical on every surface. Pages legitimately differ in field
   height and text size (h-11 here, py-2 there), and that is fine — what must
   not differ is the widget itself. Previously each page inherited whatever
   the vendor shipped plus whatever its own wrapper happened to cascade, which
   is why partner registration and signup didn't match.

   Typography is NOT set here. It's copied from each field's own input by
   autoPad, so the dial code always matches the number it sits next to
   regardless of that page's classes — the one thing a fixed value here could
   never get right. */
.intl-tel-input-wrapper .iti__selected-flag,
.intl-tel-input-wrapper .iti__selected-country {
    display: flex;
    align-items: center;   /* vertical centring is the widget's job, not the page's */
    gap: 6px;
    height: 100%;
}
/* Uniform gap between the chip and the number on every field. autoPad's
   SAFE_GAP handles the input side; this owns the chip's own trailing inset. */
.intl-tel-input-wrapper .iti--separate-dial-code .iti__selected-dial-code {
    font-weight: 500;
    margin-inline-start: 2px;
    /* Always "+962", never "962+", even inside an RTL chip. */
    direction: ltr;
    unicode-bidi: embed;
}
/* The vendor sizes the caret in px and colours it near-black; both read as
   heavier than our field chrome. */
.intl-tel-input-wrapper .iti__arrow {
    margin-inline-start: 4px;
    border-top-color: rgba(19, 27, 46, 0.45);
}
.intl-tel-input-wrapper .iti__arrow.iti__arrow--up { border-bottom-color: rgba(19, 27, 46, 0.45); }

.content {
    padding-top: 1.1rem;
}

h1:focus {
    outline: none;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid #e50000;
}

.validation-message {
    color: #e50000;
}

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

.darker-border-checkbox.form-check-input {
    border-color: #929292;
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
    text-align: start;
}

[dir="rtl"] body {
    font-family: 'Segoe UI', Tahoma, Arial, sans-serif;
}

/* ── Dashboard panel / dialog animations ── */

@keyframes droply-overlay-in {
    from { opacity: 0; }
    to   { opacity: 1; }
}

@keyframes droply-overlay-out {
    from { opacity: 1; }
    to   { opacity: 0; }
}

@keyframes droply-slide-in-right {
    from { transform: translateX(100%); }
    to   { transform: translateX(0); }
}

@keyframes droply-slide-out-right {
    from { transform: translateX(0); }
    to   { transform: translateX(100%); }
}

@keyframes droply-dialog-in {
    from { opacity: 0; transform: scale(0.95) translateY(4px); }
    to   { opacity: 1; transform: scale(1) translateY(0); }
}

@keyframes droply-dialog-out {
    from { opacity: 1; transform: scale(1) translateY(0); }
    to   { opacity: 0; transform: scale(0.95) translateY(4px); }
}

.droply-overlay-enter {
    animation: droply-overlay-in 300ms ease-out both;
}

.droply-panel-enter {
    /* Base transform matches the animation's from-state. Without this,
       browsers can paint a single frame where the animation class has
       been applied but the animation hasn't ticked yet, snapping the
       panel to translateX(0) for one frame and jittering into place —
       which reads as a shake on the first open. With the base pinned
       to translateX(100%), that sub-frame stays offscreen so the
       animation runs smoothly from off- to on-screen. Animation's
       `both` fill-mode holds the panel at translateX(0) after the
       400ms play-out ends, overriding the base. */
    transform: translateX(100%);
    animation: droply-slide-in-right 400ms cubic-bezier(0.16, 1, 0.3, 1) both;
}

/* "Preparing" state — panel/overlay are in the DOM but visually hidden
   and NOT animating. Used for the brief window where we need to run JS
   interop on a child element (e.g. populate intl-tel-input with the
   saved phone number on the Edit panel) BEFORE the slide-in animation
   makes the panel visible. Without this, the user would see the panel
   slide in with an empty phone input, then watch the number "pop" in a
   moment later — visually jumpy. With it, the slide-in starts only
   after the widget is fully populated, so the user sees a clean entry.

   The preparing classes use !important to override any stale animation
   class that might still be on the element (e.g. left over from a
   previous render). transform: translateX matches the from-state of
   the slide-in keyframes so when we swap classes, the element doesn't
   visibly jump position. */
.droply-panel-preparing {
    transform: translateX(100%) !important;
    opacity: 0 !important;
    animation: none !important;
}

/* Compound + descendant so this matches whether dir="rtl" is on the
   panel itself (InventoryPage puts it on the panel node) or on an
   ancestor (most other pages inherit from <html dir="rtl">). */
[dir="rtl"] .droply-panel-preparing,
[dir="rtl"].droply-panel-preparing {
    transform: translateX(-100%) !important;
}

[dir="rtl"] .droply-panel-enter,
[dir="rtl"].droply-panel-enter {
    transform: translateX(-100%);
}

.droply-overlay-preparing {
    opacity: 0 !important;
    animation: none !important;
}

.droply-dialog-enter {
    animation: droply-dialog-in 200ms ease-out both;
}

/* Exit classes — applied by droplyClosePanel() before Blazor removes the DOM */
.droply-overlay-exit {
    animation: droply-overlay-out 250ms ease-in both !important;
    pointer-events: none;
}

.droply-panel-exit {
    animation: droply-slide-out-right 300ms ease-in both !important;
}

.droply-dialog-exit {
    animation: droply-dialog-out 150ms ease-in both !important;
}

/* Panel shake — used when clicking outside a dirty edit panel */
@keyframes droply-shake {
    0%, 100% { transform: translateX(0); }
    15%      { transform: translateX(6px); }
    30%      { transform: translateX(-5px); }
    45%      { transform: translateX(4px); }
    60%      { transform: translateX(-3px); }
    75%      { transform: translateX(2px); }
}

.droply-panel-shake {
    animation: droply-shake 400ms ease-out;
}

/* ── Account-menu dropdown enter animation ──────────────────────
   Used by the topbar account menu (DashboardLayout.razor). Per
   ui-ux-pro-max: duration 140ms (in the 150–300ms micro-interaction
   band), ease-out, transform/opacity only (no layout shift),
   modal-motion style (anchored to trigger via origin-top-right).
   `prefers-reduced-motion` falls back to a snap-open so reduced-
   motion users still see the menu but without the slide. */
@keyframes droply-menu-in {
    0%   { opacity: 0; transform: translateY(-4px) scale(0.97); }
    100% { opacity: 1; transform: translateY(0) scale(1); }
}
@media (prefers-reduced-motion: reduce) {
    @keyframes droply-menu-in {
        0%, 100% { opacity: 1; transform: none; }
    }
}

/* The keyframe above scales from 0.97, so it needs an origin, and that
   origin has to follow the panel's anchor: the menu should look like it
   grew OUT OF the trigger, not out of thin air beside it.

   Written here rather than as ltr:/rtl: Tailwind utilities because
   app.tailwind.css is generated by `npm run tailwind:build`, which is not
   wired into the MSBuild pipeline — `origin-top-left` has never been
   emitted, so a utility class would have silently done nothing. This file
   is hand-authored and always ships. */
[data-account-menu-root] [role="menu"] { transform-origin: top right; }
[dir="rtl"] [data-account-menu-root] [role="menu"] { transform-origin: top left; }

/* ── Deep-link focus highlight ─────────────────────────────────
   Applied by droply-focus-field.js when the owner arrives on a
   page via a `?focus=X` deep link from the launch checklist.
   Wraps the target field in a soft amber halo that pulses once
   and decays. Tuned for "you've landed where you should act" —
   not a permanent state. Honors prefers-reduced-motion by
   collapsing to a simple solid outline. */
@keyframes droply-focus-pulse {
    0%   { box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.55), 0 0 0 0 rgba(245, 158, 11, 0.25); }
    35%  { box-shadow: 0 0 0 6px rgba(245, 158, 11, 0.20), 0 0 0 14px rgba(245, 158, 11, 0.08); }
    100% { box-shadow: 0 0 0 0 rgba(245, 158, 11, 0), 0 0 0 0 rgba(245, 158, 11, 0); }
}

.droply-focus-highlight {
    border-radius: 0.75rem;
    animation: droply-focus-pulse 1500ms ease-out;
}

@media (prefers-reduced-motion: reduce) {
    .droply-focus-highlight {
        animation: none;
        outline: 2px solid rgba(245, 158, 11, 0.55);
        outline-offset: 4px;
    }
}

/* ── Inline limit message (manual order qty stepper) ──────────
   Styled to match the dashboard's existing inline validation messages
   (e.g. card-number error: <p class="text-[0.7rem] text-[#dc2626]">),
   so this looks like every other field-validation line in a panel.
   Tiny red text, no icon, no card. Smooth fade-in is preserved so the
   message doesn't pop without warning. */
@keyframes droply-limit-in {
    from { opacity: 0; transform: translateY(-2px); }
    to   { opacity: 1; transform: translateY(0); }
}
.droply-limit-msg {
    font-size: 0.7rem;
    line-height: 1.35;
    color: #dc2626;
    animation: droply-limit-in 200ms cubic-bezier(0.2, 0.8, 0.2, 1) both;
}

/* ── Manual order line — expand/collapse ─────────────────────
   Used by the Items section in the Add/Edit Order panels. The
   body content (variant dropdowns, qty stepper, limit message)
   stays in the DOM at all times; we transition max-height +
   opacity + margin for a smooth slide-down/up rather than a
   pop-on/pop-off. overflow-hidden makes the slide work cleanly
   without scrollbars at intermediate heights. pointer-events on
   the collapsed state stops mouse interactions with hidden form
   controls; aria-hidden on the wrapper keeps screen readers
   aligned with what's visible. */
.droply-line-collapsible {
    overflow: hidden;
    max-height: 0;
    opacity: 0;
    margin-top: 0;
    pointer-events: none;
    transition:
        max-height 280ms cubic-bezier(0.2, 0.8, 0.2, 1),
        opacity   200ms ease-out,
        margin-top 280ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.droply-line-collapsible--open {
    max-height: 60rem; /* generous; covers max ~3 options + qty + msg */
    opacity: 1;
    margin-top: 0.5rem;
    pointer-events: auto;
}

/* ──────────────────────────────────────────────────────────
   Admin "Extend / Renew subscription" modal.
   Shared by the DashboardHome admin store cards AND the
   SettingsPage Stores table + View panel — both fire the
   same modal so admins see one consistent UX.
   ────────────────────────────────────────────────────────── */
.droply-extend-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(15, 23, 42, 0.5);
    backdrop-filter: blur(4px);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
    z-index: 60;
    animation: droplyExtendFadeIn 180ms ease-out;
}
@keyframes droplyExtendFadeIn {
    from { opacity: 0; }
    to   { opacity: 1; }
}
.droply-extend-modal {
    background: white;
    border-radius: 1rem;
    padding: 1.5rem;
    width: 100%;
    max-width: 28rem;
    box-shadow: 0 25px 60px -12px rgba(15, 23, 42, 0.35);
    animation: droplyExtendSlideIn 220ms cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes droplyExtendSlideIn {
    from { opacity: 0; transform: translateY(8px) scale(0.98); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
}
.droply-extend-modal__head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: 0.65rem;
}
.droply-extend-modal__eyebrow {
    font-size: 0.7rem;
    font-weight: 600;
    color: rgb(180, 83, 9);
    text-transform: uppercase;
    letter-spacing: 0.05em;
}
.droply-extend-modal__title {
    font-size: 1.05rem;
    font-weight: 700;
    color: #131b2e;
    margin-top: 0.15rem;
}
.droply-extend-modal__close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    border-radius: 0.5rem;
    background: transparent;
    color: #74808f;
    border: 0;
    cursor: pointer;
}
.droply-extend-modal__close:hover { background: rgba(19, 27, 46, 0.06); color: #131b2e; }
.droply-extend-modal__sub {
    font-size: 0.78rem;
    color: #64748b;
    line-height: 1.5;
    margin-bottom: 0.85rem;
}
.droply-extend-modal__owner {
    font-size: 0.85rem;
    font-weight: 600;
    color: #131b2e;
    padding: 0.6rem 0.8rem;
    background: rgba(19, 27, 46, 0.04);
    border-radius: 0.6rem;
    margin-bottom: 1rem;
}
.droply-extend-modal__chips {
    display: flex;
    gap: 0.5rem;
    margin-bottom: 0.85rem;
    flex-wrap: wrap;
}
.droply-extend-modal__chip {
    flex: 1;
    min-width: 5rem;
    padding: 0.55rem 0.85rem;
    border-radius: 0.6rem;
    background: rgba(19, 27, 46, 0.04);
    border: 1px solid rgba(19, 27, 46, 0.08);
    font-size: 0.85rem;
    font-weight: 600;
    color: #131b2e;
    cursor: pointer;
    transition: background-color 160ms ease, border-color 160ms ease, color 160ms ease;
}
.droply-extend-modal__chip:hover { background: rgba(180, 83, 9, 0.06); border-color: rgba(180, 83, 9, 0.25); }
.droply-extend-modal__chip--active {
    background: rgba(180, 83, 9, 0.12);
    border-color: rgba(180, 83, 9, 0.5);
    color: rgb(146, 64, 14);
}
.droply-extend-modal__custom {
    margin-bottom: 1.25rem;
}
.droply-extend-modal__label {
    display: block;
    font-size: 0.72rem;
    font-weight: 600;
    color: #74808f;
    margin-bottom: 0.35rem;
}
.droply-extend-modal__input {
    width: 100%;
    padding: 0.55rem 0.75rem;
    border-radius: 0.55rem;
    border: 1px solid rgba(19, 27, 46, 0.12);
    font-size: 0.9rem;
    font-weight: 600;
    color: #131b2e;
    outline: none;
}
.droply-extend-modal__input:focus { border-color: rgba(180, 83, 9, 0.55); box-shadow: 0 0 0 3px rgba(180, 83, 9, 0.12); }
.droply-extend-modal__actions {
    display: flex;
    gap: 0.5rem;
    justify-content: flex-end;
}
.droply-extend-modal__btn {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.55rem 1rem;
    border-radius: 0.55rem;
    font-size: 0.85rem;
    font-weight: 600;
    cursor: pointer;
    border: 1px solid transparent;
    transition: background-color 160ms ease, border-color 160ms ease;
}
.droply-extend-modal__btn:disabled { opacity: 0.6; cursor: not-allowed; }
.droply-extend-modal__btn--secondary {
    background: transparent;
    color: #131b2e;
    border-color: rgba(19, 27, 46, 0.15);
}
.droply-extend-modal__btn--secondary:hover:not(:disabled) { background: rgba(19, 27, 46, 0.04); }
.droply-extend-modal__btn--primary {
    background: rgb(180, 83, 9);
    color: white;
    border-color: rgb(180, 83, 9);
}
.droply-extend-modal__btn--primary:hover:not(:disabled) { background: rgb(146, 64, 14); border-color: rgb(146, 64, 14); }

/* ──────────────────────────────────────────────────────────
   Frozen-subscription write affordances.
   When <main data-frozen="true"> is rendered (owner whose
   subscription is Expired/Cancelled), all write buttons fade
   to a non-interactive look. The hard block is server-side via
   SubscriptionGuard — this is the visual cue + click swallow
   so the owner can't even try the action.

   What's covered:
     • .droply-fab — every Add/Create floating action button.
     • Any element with the explicit .droply-frozen-disable
       opt-in class (use for in-form Save buttons, etc).
   What's NOT covered (intentional):
     • .droply-expiry-banner__cta — these are the renewal CTAs;
       they MUST stay clickable so the owner can renew.
   ────────────────────────────────────────────────────────── */
[data-frozen="true"] .droply-fab,
[data-frozen="true"] .droply-button-primary,
[data-frozen="true"] .droply-frozen-disable,
/* Row Edit / Delete icon buttons across all dashboard tables.
   Title-attribute selectors cover both English and Arabic so the
   buttons fade out for frozen owners regardless of UI culture. */
[data-frozen="true"] button[title="Edit"],
[data-frozen="true"] button[title="تعديل"],
[data-frozen="true"] button[title="Delete"],
[data-frozen="true"] button[title="حذف"],
[data-frozen="true"] button[title="Remove"],
[data-frozen="true"] button[title="إزالة"] {
    opacity: 0.45;
    cursor: not-allowed;
    filter: grayscale(0.4);
}
/* Inner spans (icons, labels) inherit pointer-events from the parent
   so the not-allowed cursor sticks even when hovering the icon. The
   JS interceptor in droply-frozen-guard.js blocks the click; CSS
   provides the visual cue only. */
[data-frozen="true"] .droply-fab *,
[data-frozen="true"] .droply-button-primary *,
[data-frozen="true"] .droply-frozen-disable *,
[data-frozen="true"] button[title="Edit"] *,
[data-frozen="true"] button[title="تعديل"] *,
[data-frozen="true"] button[title="Delete"] *,
[data-frozen="true"] button[title="حذف"] *,
[data-frozen="true"] button[title="Remove"] *,
[data-frozen="true"] button[title="إزالة"] * {
    cursor: not-allowed;
}
/* Re-enable any explicitly opt-out children (e.g. links to /plans). */
[data-frozen="true"] .droply-frozen-keep {
    opacity: 1;
    cursor: pointer;
    pointer-events: auto;
    filter: none;
}

/* ──────────────────────────────────────────────────────────
   Frozen-subscription banner.
   Shown to owners whose plan has expired or been cancelled.
   Visual: amber warning panel sitting above the dashboard
   content with two CTAs ("Renew plan" + "See plans") that
   both deep-link to /plans. Amber (not red) — feels
   recoverable rather than punitive.
   ────────────────────────────────────────────────────────── */
.droply-expiry-banner {
    display: flex;
    align-items: flex-start;
    gap: 0.875rem;
    padding: 0.875rem 1rem;
    background: linear-gradient(135deg, #fff8eb 0%, #fff3d7 100%);
    border: 1px solid rgba(217, 119, 6, 0.25);
    border-radius: 0.75rem;
    box-shadow: 0 1px 2px rgba(217, 119, 6, 0.06);
}
.droply-expiry-banner__icon {
    color: #b45309;
    font-size: 1.5rem;
    /* Material Symbols look better filled for status icons. */
    font-variation-settings: 'FILL' 1;
    flex-shrink: 0;
    margin-top: 0.05rem;
}
.droply-expiry-banner__text {
    flex: 1;
    min-width: 0;
}
.droply-expiry-banner__title {
    color: #78350f;
    font-weight: 600;
    font-size: 0.95rem;
    line-height: 1.3;
}
.droply-expiry-banner__sub {
    color: #92400e;
    font-size: 0.8rem;
    margin-top: 0.2rem;
    line-height: 1.45;
}
/* Subscription chip row sitting between the banner title and the
   bullets — surfaces plan / date / status / time-hint at a glance.
   Tinted to fit the amber banner palette rather than introducing
   a competing color. */
.droply-expiry-banner__strip {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.45rem;
    margin: 0.4rem 0 0.1rem 0;
}
.droply-expiry-banner__chip {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.25rem 0.6rem;
    border-radius: 9999px;
    background: rgba(180, 83, 9, 0.10);
    color: #78350f;
    font-size: 0.75rem;
    font-weight: 600;
    line-height: 1.1;
}
.droply-expiry-banner__chip .material-symbols-outlined {
    font-size: 0.9rem;
    color: rgb(180, 83, 9);
}
.droply-expiry-banner__chip--date {
    background: rgba(180, 83, 9, 0.08);
}
.droply-expiry-banner__chip--status {
    background: rgba(217, 119, 6, 0.18);
    color: rgb(120, 53, 15);
    font-weight: 700;
}
.droply-expiry-banner__chip-dot {
    display: inline-block;
    width: 0.4rem;
    height: 0.4rem;
    border-radius: 9999px;
    background: rgb(180, 83, 9);
}
.droply-expiry-banner__hint {
    font-size: 0.72rem;
    font-weight: 500;
    color: #92400e;
}

/* ──────────────────────────────────────────────────────────
   Dunning variant — a renewal charge was declined.
   Same block, rose palette instead of amber.

   Why a different colour from the amber expiry banner directly
   below it: they routinely appear together (the first decline
   lands on EndDateUtc, so the subscription is already frozen by
   the time attempt 1 fails). Two identical amber panels stacked
   read as one repeated message and the owner skims both. The
   colour break is what makes "your card failed" register as a
   separate fact from "your dashboard is view-only".

   Rose, not red: the owner can still fix this, and every retry
   in the ladder is a chance for it to resolve without them.
   ────────────────────────────────────────────────────────── */
.droply-expiry-banner--dunning {
    background: linear-gradient(135deg, #fff1f2 0%, #ffe4e6 100%);
    border-color: rgba(190, 18, 60, 0.25);
    box-shadow: 0 1px 2px rgba(190, 18, 60, 0.06);
}
.droply-expiry-banner--dunning .droply-expiry-banner__icon { color: #be123c; }
.droply-expiry-banner--dunning .droply-expiry-banner__title { color: #881337; }
.droply-expiry-banner--dunning .droply-expiry-banner__sub { color: #9f1239; }
.droply-expiry-banner--dunning .droply-expiry-banner__chip {
    background: rgba(190, 18, 60, 0.10);
    color: #881337;
}
.droply-expiry-banner--dunning .droply-expiry-banner__chip .material-symbols-outlined {
    color: #be123c;
}
.droply-expiry-banner--dunning .droply-expiry-banner__chip--status {
    background: rgba(225, 29, 72, 0.18);
    color: #881337;
}
.droply-expiry-banner--dunning .droply-expiry-banner__chip--date {
    background: rgba(190, 18, 60, 0.08);
}
.droply-expiry-banner--dunning .droply-expiry-banner__chip-dot { background: #be123c; }
/* The CTA is the whole point of the banner — it must stay legible and
   clickable even under [data-frozen], same carve-out the amber banner's
   renewal CTAs get. */
.droply-expiry-banner--dunning .droply-expiry-banner__cta--primary {
    background: #be123c;
    border-color: #be123c;
}
.droply-expiry-banner--dunning .droply-expiry-banner__cta--primary:hover {
    background: #9f1239;
    border-color: #9f1239;
}

/* ──────────────────────────────────────────────────────────
   Payment card face (Billing → Payment method).

   A representation of the owner's physical card rather than a styled
   information box: ISO/IEC 7810 ID-1 proportions, EMV chip, brand mark,
   grouped number, holder and expiry along the bottom.

   Geometry is FIXED LTR (see the dir="ltr" note at the markup). A payment
   card is the same object in every market; mirroring it under Arabic
   would produce a card no bank has ever issued. Labels still translate.

   SECURITY: the leading groups are mask characters. No PAN is stored —
   PaymentMethod holds Brand / Last4 / ExpMonth / ExpYear only — so there
   is nothing to render even if someone later tried. Do not add a field.
   ────────────────────────────────────────────────────────── */
/* ── Droply button system (billing surfaces) ───────────────────────
   One place that owns padding, radius, weight and icon gap, so a
   primary and a destructive button are the same SHAPE and differ only
   in colour. Written here rather than as repeated utility strings
   because "visually balanced and aligned" is a promise about
   proportions — and proportions drift the moment each button carries
   its own copy of the spacing. */
.dpl-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    border: 1px solid transparent;
    border-radius: 0.75rem;
    padding: 0.625rem 1.1rem;
    font-size: 0.875rem;
    font-weight: 600;
    line-height: 1.25rem;
    cursor: pointer;
    text-decoration: none;
    transition: background-color 150ms ease, border-color 150ms ease, color 150ms ease;
}

.dpl-btn:disabled,
.dpl-btn[disabled] { opacity: 0.6; cursor: not-allowed; }

.dpl-btn--primary {
    background: rgb(var(--droply-primary));
    border-color: rgb(var(--droply-primary));
    color: #fff;
}
.dpl-btn--primary:hover:not(:disabled) {
    background: rgb(var(--droply-primary-dark));
    border-color: rgb(var(--droply-primary-dark));
}

.dpl-btn--secondary {
    background: #fff;
    border-color: rgb(var(--droply-border));
    color: #131b2e;
}
.dpl-btn--secondary:hover:not(:disabled) {
    border-color: #cbd5e1;
    background: #f8fafc;
}

/* Destructive, but the same silhouette as the others — a tinted fill
   rather than a solid red block, so "Cancel subscription" reads as
   available without shouting louder than the primary action. */
.dpl-btn--danger {
    background: #fef2f2;
    border-color: #fecaca;
    color: #b91c1c;
}
.dpl-btn--danger:hover:not(:disabled) {
    background: #fee2e2;
    border-color: #fca5a5;
}

/* Brand-tinted quiet action — used for "Use for renewals" in the saved
   card list, where a full primary would compete with the page's own. */
.dpl-btn--ghost-primary {
    background: rgba(28, 177, 201, 0.07);
    border-color: rgba(28, 177, 201, 0.28);
    color: rgb(var(--droply-primary-dark));
}
.dpl-btn--ghost-primary:hover:not(:disabled) {
    background: rgba(28, 177, 201, 0.13);
    border-color: rgba(28, 177, 201, 0.45);
}

.dpl-btn--sm {
    padding: 0.375rem 0.7rem;
    font-size: 0.78rem;
    border-radius: 0.55rem;
    gap: 0.35rem;
}

/* Icon-only variant keeps a square footprint so it lines up with the
   text buttons beside it instead of collapsing to its glyph. */
.dpl-btn--icon { padding: 0.4rem; border-radius: 0.55rem; }

.dpl-btn--block { width: 100%; }

/* ── Saved-card stack (the viewer) ─────────────────────────────────
   The face plus the edges of the other saved cards peeking above it.
   Purely a VIEWER: clicking a peeking card previews it. It never
   changes which card is charged — that lives on the radio below.

   Only the cards BEHIND are positioned. The front card stays in normal
   flow so its layout is identical to having no stack at all, which is
   what keeps a missing stylesheet from taking the face with it. */
.pm-stack {
    position: relative;
    width: 100%;
    max-width: 23rem;
    margin: 1rem auto 0;
    padding-top: calc(var(--pm-stack-depth, 0) * var(--pm-peek));
    --pm-peek: 2rem;
    /* Contain the stack's internal z-indexes (__front is z-20 so it sits
       over the peeking backs). Without isolation those values escaped
       into the PAGE stacking context, where the dashboard topbar is also
       z-20 — so the card face painted OVER the sticky header while
       scrolling, and over the account dropdown (whose z-50 lives INSIDE
       the header's own context and can't out-rank page-level content).
       Isolated, the whole stack is one atomic auto-level layer that the
       chrome always beats. */
    isolation: isolate;
}

.pm-stack .pm-card { margin: 0; }

.pm-stack__front { position: relative; z-index: 20; }

.pm-stack__back {
    appearance: none;
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    color: inherit;
    text-align: inherit;
    cursor: pointer;

    /* Bottom-anchored to the front card's own bottom edge, then lifted by
       depth — so adding a card never moves the front card. */
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    display: block;
    transform-origin: center top;
    transform: translateY(calc(var(--pm-depth, 1) * -1 * var(--pm-peek, 2rem)))
               scale(calc(1 - var(--pm-depth, 1) * 0.04));
    z-index: calc(10 - var(--pm-depth, 1));
    transition: transform 180ms ease, filter 180ms ease;
}

.pm-stack__back:hover,
.pm-stack__back:focus-visible {
    transform: translateY(calc(var(--pm-depth, 1) * -1 * var(--pm-peek, 2rem) - 0.4rem))
               scale(calc(1 - var(--pm-depth, 1) * 0.04));
}

.pm-stack__back:focus-visible {
    outline: 2px solid rgb(var(--droply-primary));
    outline-offset: 3px;
    border-radius: 1rem;
}

/* Compact head for the cards behind: only a ~2rem strip is on screen, so
   the chip is dropped and the brand mark is pulled to the top edge. */
.pm-stack__back .pm-card { padding-top: 0.35rem; filter: brightness(0.9); }
.pm-stack__back .pm-card__chip { display: none; }
.pm-stack__back .pm-card__head { justify-content: flex-end; }
.pm-stack__back .pm-card__brand-img { height: 1.15rem; max-width: 2.6rem; }
.pm-stack__back .pm-card__brand-text { font-size: 0.58rem; padding: 0.1rem 0.3rem; }
.pm-stack__back:hover .pm-card,
.pm-stack__back:focus-visible .pm-card { filter: brightness(1); }

@media (prefers-reduced-motion: reduce) {
    .pm-stack__back { transition: none; }
}

/* ── Saved-card selector ───────────────────────────────────────────
   Card-shaped radio options: the whole tile is the control, selection
   is shown by border + tinted fill + a filled indicator, and the
   native radio semantics come from role="radio" on the tile.

   Colour alone never carries the state — the indicator dot changes
   shape too, so the selected card is identifiable without relying on
   hue (WCAG "don't convey meaning by colour alone"). */
.pm-pick {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    width: 100%;
    padding: 0.75rem;
    border-radius: 0.85rem;
    border: 1.5px solid rgb(var(--droply-border));
    background: #fff;
    cursor: pointer;
    text-align: start;
    transition: border-color 160ms ease, background-color 160ms ease, box-shadow 160ms ease;
}

.pm-pick:hover:not(.pm-pick--on) {
    border-color: rgba(28, 177, 201, 0.45);
    background: rgba(28, 177, 201, 0.03);
}

.pm-pick:focus-visible {
    outline: 2px solid rgb(var(--droply-primary));
    outline-offset: 2px;
}

/* CHARGED. Teal border + tint + a filled indicator. This is the only
   state that means money. */
.pm-pick--on {
    border-color: rgb(var(--droply-primary));
    background: rgba(28, 177, 201, 0.06);
    box-shadow: 0 0 0 1px rgb(var(--droply-primary));
}

/* PREVIEWED — deliberately a different *kind* of signal, not a weaker
   shade of the selected one: a neutral lift rather than a tint, so
   "I'm looking at this" can never be misread as "this takes my money".
   A card can be both, in which case both treatments apply. */
.pm-pick--preview {
    box-shadow: 0 0 0 2px rgba(19, 27, 46, 0.14), 0 6px 14px -8px rgba(15, 23, 42, 0.35);
}
.pm-pick--preview.pm-pick--on {
    box-shadow: 0 0 0 1px rgb(var(--droply-primary)), 0 6px 14px -8px rgba(15, 23, 42, 0.35);
}

/* The tile BODY is the preview target. Separated from the trailing
   controls so the three actions can't be mis-hit. */
.pm-pick__hit {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    flex: 1 1 auto;
    min-width: 0;
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    color: inherit;
    text-align: start;
    cursor: pointer;
}
.pm-pick--off .pm-pick__hit { cursor: default; }

.pm-pick__tools {
    display: flex;
    align-items: center;
    gap: 0.15rem;
    flex-shrink: 0;
    margin-inline-start: auto;
}

/* Secondary by default, destructive on approach. Small glyph, but the
   button carries a 2.25rem box so the touch target clears the minimum
   without the icon growing to match. */
.pm-pick__remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    border: 0;
    border-radius: 999px;
    background: none;
    color: #94a3b8;
    cursor: pointer;
    transition: background-color 150ms ease, color 150ms ease;
}
.pm-pick__remove:hover:not(:disabled) { background: #fef2f2; color: #b91c1c; }
.pm-pick__remove:focus-visible {
    outline: 2px solid rgb(var(--droply-primary));
    outline-offset: -2px;
}
.pm-pick__remove:disabled { opacity: 0.4; cursor: not-allowed; }

/* Radio hit area: 2.25rem box, ~1.15rem dot. An 18px control is far
   below the 44px touch minimum, and padding it out is the fix that
   doesn't make the indicator visually shout. */
.pm-pick__radio {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    border: 0;
    border-radius: 999px;
    background: none;
    cursor: pointer;
}
.pm-pick__radio:disabled { cursor: not-allowed; }
.pm-pick__radio:focus-visible {
    outline: 2px solid rgb(var(--droply-primary));
    outline-offset: -2px;
}

/* Expired cards stay listed — they hold history — but can't be chosen. */
.pm-pick--off { opacity: 0.55; cursor: not-allowed; }
.pm-pick--off:hover { border-color: rgb(var(--droply-border)); background: #fff; }

/* Brand plate. Fixed box so every row's text starts on the same edge
   regardless of how wide the brand mark is. */
.pm-pick__brand {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 2.75rem;
    height: 1.9rem;
    border-radius: 0.4rem;
    background: #f8fafc;
    box-shadow: inset 0 0 0 1px rgba(19, 27, 46, 0.06);
    overflow: hidden;
}
.pm-pick__brand img { max-height: 1.1rem; max-width: 2.1rem; object-fit: contain; }

.pm-pick__body { min-width: 0; flex: 1 1 auto; }

/* The indicator itself — inside the padded .pm-pick__radio button. */
.pm-pick__dot {
    position: relative;
    flex-shrink: 0;
    width: 1.15rem;
    height: 1.15rem;
    border-radius: 999px;
    border: 2px solid rgb(var(--droply-border));
    background: #fff;
    transition: border-color 160ms ease;
}
.pm-pick--on .pm-pick__dot { border-color: rgb(var(--droply-primary)); }

/* Previewing highlights the RADIO without filling it — the user is
   pointed at the control that would commit, but nothing has committed. */
.pm-pick--preview .pm-pick__dot {
    border-color: rgba(28, 177, 201, 0.55);
    box-shadow: 0 0 0 3px rgba(28, 177, 201, 0.15);
}

.pm-pick--on .pm-pick__dot::after {
    content: "";
    position: absolute;
    inset: 0.2rem;
    border-radius: 999px;
    background: rgb(var(--droply-primary));
}

/* ── Plan cycle ring ───────────────────────────────────────────────
   Sweeps from empty to its real value once on load. Driven by
   stroke-dashoffset (a paint-only property) rather than dasharray, so
   the browser never re-lays-out the SVG mid-animation.

   `both` fill-mode holds the final offset after the animation ends —
   without it the arc would snap back to the CSS value. */
@keyframes plan-ring-fill {
    from { stroke-dashoffset: var(--ring-circumference); }
    to   { stroke-dashoffset: var(--ring-offset); }
}

.plan-ring__value {
    stroke-dasharray: var(--ring-circumference);
    stroke-dashoffset: var(--ring-offset);
    animation: plan-ring-fill 900ms cubic-bezier(0.22, 0.61, 0.36, 1) both;
}

/* Reduced motion: show the true value immediately. The information is
   the arc's LENGTH, so it must still be drawn — only the sweep goes. */
@media (prefers-reduced-motion: reduce) {
    .plan-ring__value { animation: none; stroke-dashoffset: var(--ring-offset); }
}

.pm-card {
    /* Flex items default to `min-width: auto`, which is the max of their
       content's minimum size — here the nowrap card number. Without this the
       face refuses to shrink below that number and punches out of a narrow
       column instead. `overflow: hidden` below is then the backstop. */
    min-width: 0;
    position: relative;
    /* margin-inline: auto centres the face once its column is wider than
       max-width — which is exactly what happens in the two-column billing
       band. Without it the card hugs the inline-start edge and reads as
       stranded rather than presented. */
    margin: 1rem auto 0;
    width: 100%;
    max-width: 23rem;
    /* 85.60 × 53.98 mm — the real ID-1 card ratio. Getting this wrong is
       what makes mockups read as "box with rounded corners". */
    aspect-ratio: 1.586 / 1;
    padding: 1.35rem 1.5rem;
    border-radius: 1rem;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    color: #fff;
    overflow: hidden;
    isolation: isolate;
    background-image: var(--pm-card-bg, linear-gradient(135deg, #334155 0%, #1e293b 100%));
    box-shadow:
        0 1px 1px rgba(255, 255, 255, 0.12) inset,
        0 18px 34px -18px rgba(15, 23, 42, 0.75),
        0 4px 10px -6px rgba(15, 23, 42, 0.45);
}

/* Soft off-centre bloom — the highlight a laminated card catches under a
   light. Kept low-contrast so it reads as sheen, not as a graphic. */
.pm-card__glow {
    position: absolute;
    inset: -40% -20% auto -30%;
    height: 165%;
    z-index: -1;
    background:
        radial-gradient(45% 38% at 22% 28%, rgba(255,255,255,0.22) 0%, rgba(255,255,255,0) 70%),
        radial-gradient(38% 32% at 82% 78%, var(--pm-card-glow, rgba(255,255,255,0.16)) 0%, rgba(255,255,255,0) 72%);
    pointer-events: none;
}

.pm-card__head {
    display: flex; align-items: flex-start; justify-content: space-between;
    gap: 1rem;
}

/* EMV chip, drawn rather than imaged: gold gradient with contact traces.
   The single strongest signal that this is a card — more than colour or
   gradient — and it costs one element. */
.pm-card__chip {
    position: relative;
    width: 2.6rem; height: 2rem;
    border-radius: 0.35rem;
    background: linear-gradient(145deg, #e8c56a 0%, #c9a24a 42%, #f0d894 68%, #b8913f 100%);
    box-shadow: 0 1px 2px rgba(0,0,0,0.35), 0 0 0 0.5px rgba(0,0,0,0.18);
    flex-shrink: 0;
}
.pm-card__chip::before,
.pm-card__chip::after {
    content: ""; position: absolute; background: rgba(0,0,0,0.28);
}
/* Two horizontal traces + the centre box outline. */
.pm-card__chip::before {
    left: 0; right: 0; top: 33%; height: 1px;
    box-shadow: 0 0.66rem 0 -0.5px rgba(0,0,0,0.28);
}
.pm-card__chip::after {
    left: 34%; right: 34%; top: 18%; bottom: 18%;
    background: transparent;
    border: 1px solid rgba(0,0,0,0.28);
    border-radius: 0.12rem;
}

.pm-card__brand { display: inline-flex; align-items: center; flex-shrink: 0; }
.pm-card__brand-img {
    height: 2.1rem; max-width: 4.2rem; object-fit: contain;
    /* Brand marks ship in their own colours for light backgrounds. A white
       halo keeps dark artwork legible on a tinted face WITHOUT recolouring
       it, which every card-scheme brand guide forbids. */
    filter: drop-shadow(0 0 2.5px rgba(255,255,255,0.9));
}
.pm-card__brand-text {
    display: inline-flex; align-items: center;
    padding: 0.25rem 0.55rem; border-radius: 0.35rem;
    background: rgba(255,255,255,0.2);
    font-size: 0.75rem; font-weight: 800; text-transform: uppercase;
    letter-spacing: 0.08em;
}

.pm-card__number {
    font-size: clamp(1.05rem, 4.4vw, 1.45rem);
    font-weight: 600;
    letter-spacing: 0.06em;
    /* Tabular figures keep the visible last-4 aligned card to card. */
    font-variant-numeric: tabular-nums;
    text-shadow: 0 1px 2px rgba(0,0,0,0.35);
    white-space: nowrap;
}

/* The `vw` above sizes the number against the VIEWPORT, which is the wrong
   axis: in the two-column billing band the card sits in a ~half-width column,
   so a wide window keeps the number at full size and overflows a narrow
   column. Container query units measure the card itself.

   Behind @supports so browsers without container queries keep the vw rule
   rather than dropping an unparseable clamp() and falling back to inherited
   size. */
@supports (container-type: inline-size) {
    .pm-card { container-type: inline-size; }
    .pm-card__number { font-size: clamp(0.9rem, 7cqw, 1.45rem); }
}

.pm-card__foot {
    display: flex; align-items: flex-end; justify-content: space-between;
    gap: 1rem;
}
.pm-card__field { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
.pm-card__field--exp { text-align: right; flex-shrink: 0; }
.pm-card__label {
    font-size: 0.55rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.11em;
    color: rgba(255,255,255,0.62);
}
.pm-card__value {
    font-size: 0.85rem; font-weight: 600; letter-spacing: 0.03em;
    text-transform: uppercase;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    text-shadow: 0 1px 2px rgba(0,0,0,0.3);
}

/* A declining card gets a rose edge so it is identifiable at a glance from
   the dunning banner above, which is talking about this exact card. */
.pm-card--failing {
    box-shadow:
        0 0 0 2px rgba(244, 63, 94, 0.75),
        0 18px 34px -18px rgba(190, 18, 60, 0.7);
}

/* Status lives BELOW the face — never printed on it. */
.pm-card__status {
    display: flex; align-items: center; gap: 0.4rem;
    margin-top: 0.75rem;
    font-size: 0.78rem; font-weight: 600;
}
.pm-card__status--failing  { color: #be123c; }
.pm-card__status--expiring { color: #b45309; }

/* ── Brand palettes ──────────────────────────────────────────
   Issuer-adjacent tints for the card BODY. These are not the brand marks
   (those stay as supplied artwork, uncoloured). An unrecognised brand
   falls through to --generic rather than guessing: a wrong-coloured card
   reads as a different card. */
.pm-card--visa {
    --pm-card-bg: linear-gradient(135deg, #1a1f71 0%, #2b3a9e 55%, #101340 100%);
    --pm-card-glow: rgba(120, 150, 255, 0.30);
}
.pm-card--mastercard {
    --pm-card-bg: linear-gradient(135deg, #2b2b31 0%, #46464e 55%, #1c1c20 100%);
    --pm-card-glow: rgba(255, 145, 60, 0.28);
}
.pm-card--amex {
    --pm-card-bg: linear-gradient(135deg, #0a6fd0 0%, #1188e0 55%, #064a92 100%);
    --pm-card-glow: rgba(160, 215, 255, 0.30);
}
.pm-card--discover {
    --pm-card-bg: linear-gradient(135deg, #24272b 0%, #3b3f45 55%, #17191c 100%);
    --pm-card-glow: rgba(255, 150, 40, 0.32);
}
.pm-card--diners {
    --pm-card-bg: linear-gradient(135deg, #0a5aa5 0%, #1573c4 55%, #05365f 100%);
    --pm-card-glow: rgba(150, 200, 255, 0.28);
}
.pm-card--jcb {
    --pm-card-bg: linear-gradient(135deg, #17306b 0%, #2a4a94 50%, #6b1a2c 100%);
    --pm-card-glow: rgba(190, 160, 255, 0.26);
}
.pm-card--unionpay {
    --pm-card-bg: linear-gradient(135deg, #05506f 0%, #0d7196 48%, #8c1a29 100%);
    --pm-card-glow: rgba(120, 210, 235, 0.28);
}
.pm-card--mada {
    --pm-card-bg: linear-gradient(135deg, #1f6f4a 0%, #2c9163 55%, #10362a 100%);
    --pm-card-glow: rgba(140, 235, 190, 0.26);
}
.pm-card--generic {
    --pm-card-bg: linear-gradient(135deg, #334155 0%, #475569 55%, #1e293b 100%);
    --pm-card-glow: rgba(190, 205, 225, 0.24);
}

@media (max-width: 420px) {
    .pm-card { padding: 1.1rem 1.15rem; border-radius: 0.85rem; }
    .pm-card__chip { width: 2.2rem; height: 1.7rem; }
    .pm-card__brand-img { height: 1.75rem; max-width: 3.4rem; }
}

/* The chip's metallic sheen and the face bloom are decorative only. */
@media (prefers-reduced-motion: reduce) { .pm-card__glow { opacity: 0.75; } }

/* ──────────────────────────────────────────────────────────
   Calm variant of the dunning banner — grace period, under control.

   Used while retries are still running (this may resolve itself on the
   next attempt without the owner lifting a finger) and after a
   deliberate cancellation (nothing is wrong at all). Rose is reserved
   for the one state that has earned it: the ladder has given up and the
   store closes on a known date.

   Slate rather than amber, because amber already means "expired,
   read-only" on the banner that follows this one in the lifecycle.
   Three states, three distinct colours, no ambiguity about which one
   the owner is looking at.
   ────────────────────────────────────────────────────────── */
.droply-expiry-banner--calm {
    background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
    border-color: rgba(71, 85, 105, 0.22);
    box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05);
}
.droply-expiry-banner--calm .droply-expiry-banner__icon { color: #475569; }
.droply-expiry-banner--calm .droply-expiry-banner__title { color: #1e293b; }
.droply-expiry-banner--calm .droply-expiry-banner__sub { color: #475569; }
.droply-expiry-banner--calm .droply-expiry-banner__chip {
    background: rgba(71, 85, 105, 0.10);
    color: #334155;
}
.droply-expiry-banner--calm .droply-expiry-banner__chip .material-symbols-outlined {
    color: #475569;
}
.droply-expiry-banner--calm .droply-expiry-banner__chip--status {
    background: rgba(71, 85, 105, 0.16);
    color: #1e293b;
}
.droply-expiry-banner--calm .droply-expiry-banner__chip-dot { background: #475569; }
.droply-expiry-banner--calm .droply-expiry-banner__hint { color: #475569; }
/* CTA keeps Droply teal here: this is a helpful next step, not an alarm. */
.droply-expiry-banner--calm .droply-expiry-banner__cta--primary {
    background: #1cb1c9;
    border-color: #1cb1c9;
}
.droply-expiry-banner--calm .droply-expiry-banner__cta--primary:hover {
    background: #179aaf;
    border-color: #179aaf;
}

/* Bullet list inside the banner — three concise status lines so the
   owner knows exactly what's view-only, what's disabled, and what's
   hidden from customers. Icons are tiny and amber-tinted to match
   the banner palette without competing with the title. */
.droply-expiry-banner__points {
    list-style: none;
    padding: 0;
    margin: 0.5rem 0 0 0;
    display: grid;
    gap: 0.25rem;
}
.droply-expiry-banner__points li {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    color: #78350f;
    font-size: 0.78rem;
    line-height: 1.4;
}
.droply-expiry-banner__points li .material-symbols-outlined {
    font-size: 0.95rem;
    color: #b45309;
    flex-shrink: 0;
}
.droply-expiry-banner__actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-shrink: 0;
}
.droply-expiry-banner__cta {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0.45rem 0.95rem;
    border-radius: 0.5rem;
    font-size: 0.825rem;
    font-weight: 600;
    text-decoration: none;
    transition: background-color 160ms ease, border-color 160ms ease, color 160ms ease;
    white-space: nowrap;
}
.droply-expiry-banner__cta--primary {
    background-color: #b45309;
    color: #fff;
    border: 1px solid #b45309;
}
.droply-expiry-banner__cta--primary:hover {
    background-color: #92400e;
    border-color: #92400e;
}
.droply-expiry-banner__cta--secondary {
    background-color: transparent;
    color: #78350f;
    border: 1px solid rgba(180, 83, 9, 0.4);
}
.droply-expiry-banner__cta--secondary:hover {
    background-color: rgba(180, 83, 9, 0.08);
    border-color: rgba(180, 83, 9, 0.65);
}
@media (max-width: 640px) {
    .droply-expiry-banner {
        flex-wrap: wrap;
    }
    .droply-expiry-banner__actions {
        flex-basis: 100%;
        margin-top: 0.25rem;
    }
}

/* ──────────────────────────────────────────────────────────
   Manual-order line "already on the order" highlight.
   Triggered by HighlightLineAsync in OrdersPage / AdminOrdersPage
   when the owner picks a variant that's already on another line
   (or re-adds the same simple product). Premium polish: a soft
   primary-tinted ring + barely-visible background wash that fade
   in, hold, and fade out smoothly — no pulse, no harsh jump.
   ────────────────────────────────────────────────────────── */
.droply-line-highlight {
    /* 2.8s total: ~14% (390ms) ease-in, hold to ~78% (1.79s),
       ~22% (615ms) ease-out. cubic-bezier(0.4, 0, 0.2, 1) is the
       Material "standard" curve — smooth without being mushy. */
    animation: dpLineHighlight 2.8s cubic-bezier(0.4, 0, 0.2, 1);
    /* Scope position so the ring's box-shadow renders cleanly above
       sibling cards even when stacked tight. */
    position: relative;
    z-index: 1;
}
@keyframes dpLineHighlight {
    0%, 100% {
        box-shadow: 0 0 0 0 rgba(99, 102, 241, 0),
                    0 0 0 0 rgba(99, 102, 241, 0);
        background-color: transparent;
        border-color: rgba(19, 27, 46, 0.08);
    }
    14%, 78% {
        /* Soft 2px ring + a wide diffused glow underneath. Low
           alpha values (0.22 / 0.18 / 0.04) keep it premium —
           visible but not shouty. */
        box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.22),
                    0 10px 32px -12px rgba(99, 102, 241, 0.18);
        background-color: rgba(99, 102, 241, 0.04);
        border-color: rgba(99, 102, 241, 0.28);
    }
}

/* ══════════════════════════════════════════════════════════
   PANEL HEADERS — Stunning titles for View/Edit/Create panels
   Usage:
     <div class="droply-panel-head droply-panel-head--{view|edit|create}">
       <div class="droply-panel-head-icon droply-panel-head-icon--{view|edit|create}">
         <span class="material-symbols-outlined">icon</span>
       </div>
       <div class="min-w-0 flex-1">
         <p class="droply-panel-eyebrow">View/Edit/Add Thing</p>
         <h2 class="droply-panel-title">Actual item name</h2>
       </div>
       <button class="droply-panel-close">...</button>
     </div>
   ══════════════════════════════════════════════════════════ */
.droply-panel-head {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    padding: 1rem 1.25rem;
    border-bottom: 1px solid rgba(19, 27, 46, 0.06);
    position: relative;
    flex-shrink: 0;
}
.droply-panel-head::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 3px;
}
.droply-panel-head--view::before {
    background: linear-gradient(90deg, rgb(28, 177, 201) 0%, rgba(28, 177, 201, 0) 100%);
}
.droply-panel-head--edit::before {
    background: linear-gradient(90deg, rgb(249, 115, 22) 0%, rgba(249, 115, 22, 0) 100%);
}
.droply-panel-head--create::before {
    background: linear-gradient(90deg, rgb(22, 163, 74) 0%, rgba(22, 163, 74, 0) 100%);
}

.droply-panel-head-icon {
    display: flex;
    height: 2.5rem;
    width: 2.5rem;
    flex-shrink: 0;
    align-items: center;
    justify-content: center;
    border-radius: 0.75rem;
}
.droply-panel-head-icon--view {
    background: linear-gradient(135deg, rgba(28, 177, 201, 0.2), rgba(28, 177, 201, 0.06));
    color: rgb(28, 177, 201);
    box-shadow: 0 6px 16px -4px rgba(28, 177, 201, 0.35);
}
.droply-panel-head-icon--edit {
    background: linear-gradient(135deg, rgba(249, 115, 22, 0.2), rgba(249, 115, 22, 0.06));
    color: rgb(249, 115, 22);
    box-shadow: 0 6px 16px -4px rgba(249, 115, 22, 0.35);
}
.droply-panel-head-icon--create {
    background: linear-gradient(135deg, rgba(22, 163, 74, 0.2), rgba(22, 163, 74, 0.06));
    color: rgb(22, 163, 74);
    box-shadow: 0 6px 16px -4px rgba(22, 163, 74, 0.35);
}

.droply-panel-eyebrow {
    font-size: 0.68rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.09em;
    line-height: 1;
    margin: 0;
}
.droply-panel-head--view .droply-panel-eyebrow { color: rgb(28, 177, 201); }
.droply-panel-head--edit .droply-panel-eyebrow { color: rgb(249, 115, 22); }
.droply-panel-head--create .droply-panel-eyebrow { color: rgb(22, 163, 74); }

.droply-panel-title {
    font-size: 1.0625rem;
    font-weight: 800;
    color: #0f172a;
    letter-spacing: -0.015em;
    margin: 0.3rem 0 0;
    line-height: 1.2;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.droply-panel-close {
    display: flex;
    height: 2.25rem;
    width: 2.25rem;
    flex-shrink: 0;
    align-items: center;
    justify-content: center;
    border-radius: 0.7rem;
    background: transparent;
    border: 1px solid transparent;
    color: #64748b;
    cursor: pointer;
    transition: all 0.18s;
}
.droply-panel-close:hover {
    background: rgba(19, 27, 46, 0.06);
    border-color: rgba(19, 27, 46, 0.08);
    color: #0f172a;
}

/* ══════════════════════════════════════════════════════════
   VARIANT IMAGE SELECTOR TILES
   Used in the product-edit popover to link product images to
   a variant. Selected tiles show an order number (1, 2, 3…)
   in the order the user picked them — the marketplace
   displays variant images in exactly this order on save.
   ══════════════════════════════════════════════════════════ */
.dp-var-tile {
    position: relative;
    height: 3.5rem;
    width: 3.5rem;
    border-radius: 0.55rem;
    border: 2px solid rgba(19, 27, 46, 0.10);
    transition: all 0.18s;
    overflow: visible;
}
.dp-var-tile:hover {
    border-color: rgba(139, 92, 246, 0.4);
    transform: translateY(-1px);
}
.dp-var-tile--pending {
    border-style: dashed;
    border-color: rgba(139, 92, 246, 0.4);
}
.dp-var-tile--pending:hover {
    border-color: rgba(139, 92, 246, 0.65);
}
.dp-var-tile--linked {
    border-color: rgb(139, 92, 246);
    box-shadow: 0 4px 14px -3px rgba(139, 92, 246, 0.45);
}
.dp-var-tile-img {
    height: 100%;
    width: 100%;
    border-radius: 0.4rem;
    object-fit: cover;
    cursor: pointer;
}
/* Violet tint overlay on selected tiles — makes the selection state pop */
.dp-var-tile-overlay {
    position: absolute;
    inset: 0;
    border-radius: 0.4rem;
    background: rgba(139, 92, 246, 0.18);
    pointer-events: auto;
    cursor: pointer;
}
/* The order badge — e.g. 1, 2, 3 — in the center of the tile */
.dp-var-tile-order {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1rem;
    font-weight: 900;
    color: #fff;
    text-shadow: 0 2px 6px rgba(0, 0, 0, 0.55);
    pointer-events: none;
    letter-spacing: -0.01em;
}
/* "New" strip for pending uploads */
.dp-var-tile-new {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    padding: 0.1rem 0;
    border-radius: 0 0 0.4rem 0.4rem;
    background: rgba(19, 27, 46, 0.72);
    color: #fff;
    font-size: 7px;
    font-weight: 700;
    text-align: center;
    pointer-events: none;
}
/* Primary star toggle — top-left corner */
.dp-var-tile-star {
    position: absolute;
    top: -0.375rem;
    left: -0.375rem;
    display: flex;
    height: 1.25rem;
    width: 1.25rem;
    align-items: center;
    justify-content: center;
    border-radius: 9999px;
    background: #fff;
    color: #cbd5e1;
    border: none;
    cursor: pointer;
    box-shadow: 0 3px 8px rgba(0, 0, 0, 0.15);
    box-shadow: 0 0 0 2px #fff, 0 3px 8px rgba(0, 0, 0, 0.15);
    transition: all 0.18s;
    z-index: 3;
}
[dir="rtl"] .dp-var-tile-star {
    left: auto;
    right: -0.375rem;
}
.dp-var-tile-star:hover {
    background: rgb(251, 191, 36);
    color: #fff;
}
.dp-var-tile-star--on {
    background: rgb(251, 191, 36);
    color: #fff;
}
/* Remove button for pending uploads — top-right corner */
.dp-var-tile-remove {
    position: absolute;
    top: -0.375rem;
    right: -0.375rem;
    display: flex;
    height: 1.25rem;
    width: 1.25rem;
    align-items: center;
    justify-content: center;
    border-radius: 9999px;
    background: rgb(185, 28, 28);
    color: #fff;
    border: none;
    cursor: pointer;
    box-shadow: 0 0 0 2px #fff, 0 3px 8px rgba(0, 0, 0, 0.15);
    z-index: 3;
}
[dir="rtl"] .dp-var-tile-remove {
    right: auto;
    left: -0.375rem;
}
.dp-var-tile-remove:hover {
    background: rgb(153, 27, 27);
}

/* ── Floating Action Button ── */

.droply-fab {
    position: fixed;
    bottom: 2rem;
    right: 1.75rem;
    z-index: 30;
    display: flex;
    align-items: center;
    justify-content: center;
    height: 3.25rem;
    width: 3.25rem;
    border-radius: 9999px;
    background: linear-gradient(135deg, #1cb1c9, #17a0b3);
    color: #fff;
    box-shadow: 0 4px 20px rgba(28, 177, 201, 0.35);
    cursor: pointer;
    border: none;
    overflow: hidden;
    transition: width 0.3s cubic-bezier(0.16, 1, 0.3, 1),
                box-shadow 0.3s ease,
                transform 0.15s ease;
}

/* Arabic / RTL mode: pin the FAB to the bottom-left so it sits on the
   natural reading end of the viewport. */
[dir="rtl"] .droply-fab {
    right: auto;
    left: 1.75rem;
}

.droply-fab:hover {
    width: auto;
    padding-left: 1.1rem;
    padding-right: 1.25rem;
    box-shadow: 0 6px 28px rgba(28, 177, 201, 0.45);
}

.droply-fab:active {
    transform: scale(0.95);
}

.droply-fab .droply-fab-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}

.droply-fab:hover .droply-fab-icon {
    transform: rotate(90deg);
}

.droply-fab .droply-fab-label {
    max-width: 0;
    opacity: 0;
    white-space: nowrap;
    overflow: hidden;
    font-size: 0.8125rem;
    font-weight: 600;
    margin-inline-start: 0;
    /* COLLAPSE direction (unhover): fade label out FAST and FIRST,
       then let the width/margin collapse after the text is gone.
       This prevents the trailing-edge clipping artifact where the
       container shrinks while the text is still partly visible. */
    transition: max-width 0.28s cubic-bezier(0.4, 0, 0.2, 1) 0.08s,
                opacity 0.1s ease 0s,
                margin-inline-start 0.28s ease 0.08s;
}

.droply-fab:hover .droply-fab-label {
    max-width: 12rem;
    opacity: 1;
    margin-inline-start: 0.5rem;
    /* EXPAND direction (hover): grow width first, then fade label in
       once there's enough room — matches the opening "reveal" feel. */
    transition: max-width 0.3s cubic-bezier(0.16, 1, 0.3, 1),
                opacity 0.18s ease 0.12s,
                margin-inline-start 0.3s ease;
}

@keyframes droply-fab-pulse {
    0%, 100% { box-shadow: 0 4px 20px rgba(28, 177, 201, 0.35); }
    50%      { box-shadow: 0 4px 28px rgba(28, 177, 201, 0.55); }
}

@keyframes droply-fab-in {
    from { opacity: 0; transform: scale(0.85); }
    to   { opacity: 1; transform: scale(1); }
}

.droply-fab {
    /* Entry animation, then the ambient pulse.
       Each dashboard page owns its own FAB, so navigating unmounts one and
       mounts another — the button vanished and snapped back at full size,
       which read as a flicker mid-transition. A short fade+scale makes the
       remount land softly instead. It also covers the new render gate: the
       FAB now waits for the page's data before appearing (it used to render
       during load and then disappear on pages that turned out to be empty,
       causing a second, worse flash).
       Both animations run together — the entry drives opacity/transform, the
       pulse only box-shadow, so they never fight over a property. */
    animation: droply-fab-in 200ms cubic-bezier(0.16, 1, 0.3, 1),
               droply-fab-pulse 3s ease-in-out infinite;
}

/* Reduced motion: drop BOTH the entry movement and the ambient pulse. The
   FAB still appears, just without the scale-in or the breathing shadow.
   MUST stay after the .droply-fab animation rule above — same specificity,
   so source order decides which wins. */
@media (prefers-reduced-motion: reduce) {
    .droply-fab { animation: none; }
}

/* ── Back FAB modifier ──
   Used by the Plans page when the owner arrives from
   /dashboard/billing. Inherits the FULL visual + animation
   treatment of the standard .droply-fab (cyan gradient, white
   icon, pulse, expand-on-hover, collapse-on-unhover, press
   feedback) — only the horizontal corner is swapped so it doesn't
   collide with the primary Add FAB if both ever co-exist.

   Position: BOTTOM-LEFT in LTR (bottom-right in RTL). The Add
   FAB sits at bottom-right (LTR) for "primary action / create";
   the back FAB sits on the opposite horizontal side at the same
   vertical anchor — same height off the viewport floor, mirrored
   across. Stays visible while scrolling via the inherited
   position: fixed. */
.droply-fab--back {
    /* Inherit bottom: 2rem from .droply-fab; just swap the
       horizontal anchor from right → left. */
    left: 1.75rem;
    right: auto;
}
[dir="rtl"] .droply-fab--back {
    /* RTL: swap horizontal anchor from left → right. */
    right: 1.75rem;
    left: auto;
}

/* Reverse the rotate-on-hover behavior — the Add FAB's `add` icon
   rotates 90° to suggest "opening." A back arrow shouldn't rotate
   (it would briefly point up/down mid-animation, which reads as
   confused). Override the rotation back to 0°. */
.droply-fab--back:hover .droply-fab-icon {
    transform: none;
}

/* In RTL, the back arrow needs to point right (which IS "back" in
   RTL reading order). The icon glyph itself is locale-aware in the
   markup (arrow_back rotated via classes), so no transform needed
   here — but a future-proofing safety to ensure RTL doesn't mirror
   it unintentionally. */
[dir="rtl"] .droply-fab--back .droply-fab-icon {
    transform: none;
}

/* ─────────────────────────────────────────────────────────────
   Legacy .droply-plans-back-fab styles — kept below for reference
   but no longer applied. The Back FAB now uses .droply-fab + the
   modifier above for visual parity with the Add FABs.
   ───────────────────────────────────────────────────────────── */
.droply-plans-back-fab {
    position: fixed;
    z-index: 30;
    bottom: 2rem;
    left: 1.5rem;
    display: flex;
    align-items: center;
    justify-content: center;
    /* Slightly larger than the Add FAB (3.25rem vs 3rem) so the
       icon medallion inside has room to breathe + the whole
       control has more visual weight. */
    height: 3.25rem;
    width: 3.25rem;
    /* Explicit padding on the default state so the transition has
       a starting value to interpolate from on EXIT (hover → default).
       Without this, padding snaps from hover values to "browser
       default", which is the choppy collapse the user saw. */
    padding-inline-start: 0;
    padding-inline-end: 0;
    border-radius: 9999px;
    background: linear-gradient(135deg, #ffffff 0%, rgba(28, 177, 201, 0.06) 100%);
    color: #131b2e;
    border: 1px solid rgba(28, 177, 201, 0.22);
    /* Two-layer shadow: top primary-tinted "glow" (brand presence),
       second layer neutral-dark "lift" (separates from background). */
    box-shadow:
        0 4px 16px -2px rgba(28, 177, 201, 0.25),
        0 8px 24px -8px rgba(15, 23, 42, 0.12);
    cursor: pointer;
    overflow: hidden;
    text-decoration: none;
    /* EXIT transition (hover → default).
       Includes padding so the collapse is smooth on every property
       at once — no property snaps. ease-in on width/padding makes
       the collapse start gentle and finish fast, mirroring how the
       label fades out (also fast at the start of the exit). */
    transition: width 0.28s cubic-bezier(0.4, 0, 0.2, 1),
                padding 0.28s cubic-bezier(0.4, 0, 0.2, 1),
                box-shadow 0.35s ease,
                border-color 0.25s ease,
                transform 0.2s ease;
    /* Subtle pulse on the colored shadow draws first-arrival
       attention without being demanding. Slower cadence (4s) than
       the Add FAB's 3s so the two don't compete. */
    animation: droply-plans-back-fab-breathe 4s ease-in-out infinite;
}

[dir="rtl"] .droply-plans-back-fab {
    left: auto;
    right: 1.5rem;
}

/* Desktop + tablet: lift to the top-left side gutter so it sits
   beside the plans grid rather than at the bottom corner. */
@media (min-width: 1024px) {
    .droply-plans-back-fab {
        top: 7rem;
        bottom: auto;
    }
}

.droply-plans-back-fab:hover {
    /* Fixed hover width — NOT "auto". Browsers can't reliably
       transition from a pixel value to "auto"; the reverse-direction
       collapse was snapping. Logical padding so the layout is correct
       in LTR + RTL without separate rules.

       padding-inline-start: 0 — icon medallion sits flush against the
       pill's left edge, nesting inside the rounded left cap.

       justify-content: flex-start overrides the base rule's
       justify-content: center. The base "center" rule exists so the
       single icon in the COLLAPSED state sits in the middle of the
       3.25rem circle. But once expanded to 14rem with icon + label,
       "center" wedges empty space on BOTH sides of the icon+label
       group (because the natural content is narrower than 14rem).
       Switching to flex-start pins the icon to the left and any
       extra width falls at the END of the pill, after the label —
       same shape the Add Plan FAB uses. */
    width: 14rem;
    padding-inline-start: 0;
    padding-inline-end: 1.25rem;
    justify-content: flex-start;
    border-color: rgba(28, 177, 201, 0.5);
    /* Stronger primary glow on hover — the brand color leans in. */
    box-shadow:
        0 12px 32px -4px rgba(28, 177, 201, 0.38),
        0 16px 40px -8px rgba(15, 23, 42, 0.16);
    transform: translateY(-2px);
    /* ENTER transition (default → hover).
       Same property set as the EXIT transition above but with a
       smoother out-back easing (cubic-bezier(0.16, 1, 0.3, 1)) so
       the expansion feels confident and "settled" at the end. */
    transition: width 0.32s cubic-bezier(0.16, 1, 0.3, 1),
                padding 0.32s cubic-bezier(0.16, 1, 0.3, 1),
                box-shadow 0.35s ease,
                border-color 0.25s ease,
                transform 0.2s ease;
    /* Pause the breathing animation while hovered so the lift +
       expand transitions read cleanly without competing pulses. */
    animation-play-state: paused;
}

.droply-plans-back-fab:active {
    transform: translateY(0) scale(0.96);
}

.droply-plans-back-fab .droply-plans-back-fab-icon {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    /* Bigger medallion with primary gradient fill — the focal
       point of the collapsed FAB. Inner highlight + subtle outer
       shadow give it a glossy, premium feel. */
    height: 1.9rem;
    width: 1.9rem;
    border-radius: 9999px;
    background: linear-gradient(135deg, #1cb1c9, #17a0b3);
    color: #fff;
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.25),
        0 2px 6px rgba(28, 177, 201, 0.35);
    transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}

/* Subtle icon nudge on hover — the arrow "leans" toward the
   direction it'll take you (locale-aware via the markup's rotate
   class). Reinforces "click me to go back" without being kinetic. */
.droply-plans-back-fab:hover .droply-plans-back-fab-icon {
    transform: translateX(-2px);
}

[dir="rtl"] .droply-plans-back-fab:hover .droply-plans-back-fab-icon {
    transform: translateX(2px);
}

.droply-plans-back-fab .droply-plans-back-fab-label {
    max-width: 0;
    opacity: 0;
    white-space: nowrap;
    overflow: hidden;
    /* Typography matched to .droply-fab-label (the Add Plan FAB) so
       the two controls read as siblings — same size, weight, gap.
       Color is #131b2e instead of inherited white because the Back
       FAB body is white, unlike the Add FAB's primary-gradient body. */
    font-size: 0.8125rem;
    font-weight: 600;
    color: #131b2e;
    margin-inline-start: 0;
    /* EXIT transition (hover → default).
       Opacity drops FAST (0.12s) so the text fades out BEFORE the
       max-width starts clipping it from the right edge — no visible
       text-cutoff during collapse. max-width and margin transition
       slightly faster than the enter so the collapse feels crisp. */
    transition: max-width 0.25s cubic-bezier(0.4, 0, 0.2, 1),
                opacity 0.12s ease-out,
                margin-inline-start 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}

.droply-plans-back-fab:hover .droply-plans-back-fab-label {
    max-width: 14rem;
    opacity: 1;
    /* Gap matched to Add Plan FAB (0.5rem between icon and label). */
    margin-inline-start: 0.5rem;
    /* ENTER transition (default → hover).
       Opacity gets a 0.08s delay so max-width has already opened
       enough room before the text starts appearing — text never
       fades in while its container is still too narrow. Slightly
       longer max-width (0.32s with out-back ease) gives the
       expansion a confident, settled finish. */
    transition: max-width 0.32s cubic-bezier(0.16, 1, 0.3, 1),
                opacity 0.22s ease 0.08s,
                margin-inline-start 0.32s cubic-bezier(0.16, 1, 0.3, 1);
}

@keyframes droply-plans-back-fab-breathe {
    0%, 100% {
        box-shadow:
            0 4px 16px -2px rgba(28, 177, 201, 0.25),
            0 8px 24px -8px rgba(15, 23, 42, 0.12);
    }
    50% {
        box-shadow:
            0 6px 22px -2px rgba(28, 177, 201, 0.38),
            0 10px 28px -8px rgba(15, 23, 42, 0.14);
    }
}

/* Respect user's reduced-motion preference — disables the breathe
   pulse + the hover lift, keeps the FAB fully functional and
   color-distinct without animations. */
@media (prefers-reduced-motion: reduce) {
    .droply-plans-back-fab {
        animation: none;
    }
    .droply-plans-back-fab:hover {
        transform: none;
    }
}

.droply-fab:hover {
    animation: none;
}

/* ── Store Selector Dropdown ── */

.droply-store-selector {
    position: relative;
}

.droply-store-selector .droply-store-dropdown {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    z-index: 10;
    margin-top: 0.25rem;
    max-height: 12rem;
    overflow-y: auto;
    border-radius: 0.5rem;
    border: 1px solid rgba(19, 27, 46, 0.10);
    background: #fff;
    box-shadow: 0 8px 24px rgba(19, 27, 46, 0.12);
}

.droply-store-dropdown .droply-store-option {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.75rem;
    font-size: 0.8125rem;
    color: #131b2e;
    cursor: pointer;
    transition: background 0.1s;
}

.droply-store-dropdown .droply-store-option:hover {
    background: rgba(19, 27, 46, 0.04);
}

.droply-store-dropdown .droply-store-option.active {
    background: rgba(28, 177, 201, 0.08);
    color: #1cb1c9;
    font-weight: 600;
}

/* ============================================
   Expandable detail-row reveal
   ============================================
   Shared by the Customers per-store breakdown and the Orders items
   expansion so every table's expand/collapse moves with one rhythm.
   grid-template-rows 0fr→1fr animates to the content's natural height
   without JS measurement; opacity rides along. The page holds the row
   in the DOM with the --closing class until the exit finishes (same
   choreography the slide-in panels use), so closing is as smooth as
   opening. Transforms/grid only — no width/height animation, no
   layout thrash on the surrounding table. */
.droply-detail-reveal {
    display: grid;
    grid-template-rows: 1fr;
    animation: droply-detail-open 0.26s cubic-bezier(0.22, 1, 0.36, 1);
}
.droply-detail-reveal--closing {
    animation: droply-detail-close 0.18s ease-in forwards;
}
.droply-detail-clip {
    overflow: hidden;
    min-height: 0;
}
@keyframes droply-detail-open {
    from { grid-template-rows: 0fr; opacity: 0; }
    to   { grid-template-rows: 1fr; opacity: 1; }
}
@keyframes droply-detail-close {
    from { grid-template-rows: 1fr; opacity: 1; }
    to   { grid-template-rows: 0fr; opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
    .droply-detail-reveal { animation: none; }
    /* No animation, but the end state must still apply instantly so the
       row reads as closed while the page's exit timer runs. */
    .droply-detail-reveal--closing {
        animation: none;
        grid-template-rows: 0fr;
        opacity: 0;
    }
}

/* ============================================
   Dashboard Toast Notifications
   ============================================ */
.dp-dash-toast-container {
    position: fixed;
    top: 1.25rem;
    right: 1.25rem;
    z-index: 9999;
    display: flex;
    flex-direction: column;
    gap: 0.625rem;
    pointer-events: none;
    max-width: calc(100vw - 2.5rem);
}

[dir="rtl"] .dp-dash-toast-container {
    right: auto;
    left: 1.25rem;
}

.dp-dash-toast {
    position: relative;
    display: flex;
    align-items: center;
    gap: 0.625rem;
    min-width: 280px;
    max-width: 420px;
    padding: 0.75rem 2.25rem 0.75rem 0.875rem;
    border-radius: 0.875rem;
    background: #ffffff;
    color: #131b2e;
    font-size: 0.875rem;
    font-weight: 500;
    line-height: 1.35;
    box-shadow: 0 12px 32px -12px rgba(15, 23, 42, 0.22), 0 2px 6px -2px rgba(15, 23, 42, 0.08);
    border: 1px solid rgba(19, 27, 46, 0.06);
    overflow: hidden;
    transform: translateX(120%);
    opacity: 0;
    transition: transform 0.32s cubic-bezier(0.2, 0.8, 0.2, 1), opacity 0.32s ease;
    pointer-events: auto;
}

[dir="rtl"] .dp-dash-toast {
    padding: 0.75rem 0.875rem 0.75rem 2.25rem;
    transform: translateX(-120%);
}

.dp-dash-toast--visible {
    transform: translateX(0);
    opacity: 1;
}

/* RTL sibling must match specificity of `[dir="rtl"] .dp-dash-toast` — otherwise
   the off-screen `translateX(-120%)` from the base RTL rule wins and the toast
   never animates into view when the UI is Arabic. */
[dir="rtl"] .dp-dash-toast--visible {
    transform: translateX(0);
    opacity: 1;
}

.dp-dash-toast--leaving {
    transform: translateX(120%);
    opacity: 0;
}

[dir="rtl"] .dp-dash-toast--leaving {
    transform: translateX(-120%);
    opacity: 0;
}

.dp-dash-toast__icon {
    flex-shrink: 0;
    font-size: 1.25rem !important;
    font-variation-settings: 'FILL' 1, 'wght' 500, 'GRAD' 0, 'opsz' 24;
    line-height: 1;
}

.dp-dash-toast__msg {
    flex: 1 1 auto;
    word-break: break-word;
}

/* Optional action button rendered inside the toast — set via the
   `actionLabel` + `actionUrl` args to droplyToast.show. Used by the
   launch-checklist save flow ("Logo saved · ← Back to setup"). Sits
   between the message and the close button. Subtle styling so the
   close affordance still reads as the primary dismiss. */
.dp-dash-toast__action {
    flex: 0 0 auto;
    background: rgba(255, 255, 255, 0.92);
    color: #131b2e;
    border: 1px solid rgba(19, 27, 46, 0.10);
    padding: 0.25rem 0.6rem;
    font-size: 0.75rem;
    font-weight: 700;
    line-height: 1.2;
    border-radius: 0.5rem;
    cursor: pointer;
    margin-inline-end: 1.5rem; /* keep clear of the absolute-positioned close button */
    white-space: nowrap;
    transition: background-color 0.15s ease, border-color 0.15s ease;
}

.dp-dash-toast__action:hover {
    background: #ffffff;
    border-color: rgba(19, 27, 46, 0.20);
}

.dp-dash-toast__close {
    position: absolute;
    top: 0.375rem;
    right: 0.5rem;
    width: 1.25rem;
    height: 1.25rem;
    display: flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: 0;
    padding: 0;
    font-size: 1.15rem;
    line-height: 1;
    color: #74808f;
    border-radius: 999px;
    cursor: pointer;
    transition: background-color 0.15s ease, color 0.15s ease;
}

[dir="rtl"] .dp-dash-toast__close {
    right: auto;
    left: 0.5rem;
}

.dp-dash-toast__close:hover {
    background: rgba(19, 27, 46, 0.06);
    color: #131b2e;
}

.dp-dash-toast__progress {
    position: absolute;
    left: 0;
    bottom: 0;
    height: 3px;
    width: 100%;
    transform-origin: left center;
    animation: dpDashToastProgress linear forwards;
    opacity: 0.85;
}

[dir="rtl"] .dp-dash-toast__progress {
    transform-origin: right center;
}

@keyframes dpDashToastProgress {
    from { transform: scaleX(1); }
    to   { transform: scaleX(0); }
}

.dp-dash-toast--success .dp-dash-toast__icon { color: #059669; }
.dp-dash-toast--success .dp-dash-toast__progress { background: linear-gradient(90deg, #10b981, #2bb6a8); }
.dp-dash-toast--success::before {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: linear-gradient(90deg, rgba(16, 185, 129, 0.10), rgba(16, 185, 129, 0) 55%);
}

.dp-dash-toast--error .dp-dash-toast__icon { color: #dc2626; }
.dp-dash-toast--error .dp-dash-toast__progress { background: linear-gradient(90deg, #ef4444, #f97316); }
.dp-dash-toast--error::before {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: linear-gradient(90deg, rgba(239, 68, 68, 0.10), rgba(239, 68, 68, 0) 55%);
}

.dp-dash-toast--warning .dp-dash-toast__icon { color: #d97706; }
.dp-dash-toast--warning .dp-dash-toast__progress { background: linear-gradient(90deg, #f59e0b, #fb923c); }
.dp-dash-toast--warning::before {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: linear-gradient(90deg, rgba(245, 158, 11, 0.10), rgba(245, 158, 11, 0) 55%);
}

.dp-dash-toast--info .dp-dash-toast__icon { color: #1cb1c9; }
.dp-dash-toast--info .dp-dash-toast__progress { background: linear-gradient(90deg, #1cb1c9, #8b5cf6); }
.dp-dash-toast--info::before {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: linear-gradient(90deg, rgba(28, 177, 201, 0.10), rgba(28, 177, 201, 0) 55%);
}

/* ============================================
   Cart Toast Container (marketplace)
   Same look as dashboard toasts (.dp-dash-toast), but pinned to the
   BOTTOM-right so the "added to cart!" message pops up near the cart
   icon rather than over the page header. droply-cart.js's showToast()
   renders .dp-dash-toast--<type> children into this container.
   flex-direction: column-reverse keeps newer toasts visually above
   older ones when several stack — same UX as the dashboard top-right
   container, mirrored vertically.
   ============================================ */
.dp-cart-toast-container {
    position: fixed;
    bottom: 1.25rem;
    right: 1.25rem;
    /* Sit ABOVE the QuickAdd modal (which is at z-index 10000) so a
       quantity-limit toast fired from inside the modal isn't hidden
       under the backdrop. Conflict modal is 9998, regular cart drawer
       sits below — 10001 wins over everything we render. */
    z-index: 10001;
    display: flex;
    flex-direction: column-reverse;
    gap: 0.625rem;
    pointer-events: none;
    max-width: calc(100vw - 2.5rem);
}

[dir="rtl"] .dp-cart-toast-container {
    right: auto;
    left: 1.25rem;
}

/* ============================================
   Global RTL adjustments
   One set of rules so every dashboard/admin table auto-flips in Arabic
   without per-page markup changes. Specificity is bumped (table + class)
   so we win over bare Tailwind `text-left` / `text-right` utilities.
   ============================================ */
[dir="rtl"] table th.text-left,
[dir="rtl"] table td.text-left { text-align: right; }

[dir="rtl"] table th.text-right,
[dir="rtl"] table td.text-right { text-align: left; }

/* Header rows that carry the alignment class on the ROW instead of on each
   cell — `<tr class="text-left">` — were missed entirely by the two rules
   above, which match `th.text-left` / `td.text-left`. The <th> elements
   inherited `text-align: left` from the row and stayed left-aligned, while
   the <td> cells below them, having no explicit alignment, followed the
   table's RTL default and went right. That mismatch is the offset between
   Arabic headers and their data (Users, Billing, Admin Billing).

   Flip at the row level so the inherited value is correct. Specificity is
   deliberately LOWER than the per-cell rules above (0,2,1 vs 0,2,2), so a
   cell that sets its own alignment — the Actions column's `text-right` —
   still wins and mirrors independently. */
[dir="rtl"] table tr.text-left { text-align: right; }
[dir="rtl"] table tr.text-right { text-align: left; }

/* Floating Action Buttons (create-product / create-collection / etc.).
   Any element with the .dp-fab class pinned to the bottom-right in LTR
   automatically mirrors to the bottom-left in RTL. Pages can still opt
   out by omitting the class. */
.dp-fab {
    position: fixed;
    bottom: 1.5rem;
    right: 1.5rem;
    z-index: 30;
}
[dir="rtl"] .dp-fab {
    right: auto;
    left: 1.5rem;
}

/* ============================================
   Dashboard Edit Panel — Tabs
   Used by the Product edit/create panel to group
   Variants · Collections · Related Products.
   ============================================ */
.dp-tabs { display: block; }

.dp-tabs-strip {
    display: flex;
    gap: 0.25rem;
    border-bottom: 1px solid rgba(19, 27, 46, 0.08);
    margin-bottom: 0.875rem;
    overflow-x: auto;
    scrollbar-width: none;
}
.dp-tabs-strip::-webkit-scrollbar { display: none; }

.dp-tab {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.625rem 0.9rem;
    border: none;
    background: transparent;
    color: #74808f;
    font-size: 0.8125rem;
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
    transition: color 0.2s ease;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px; /* overlap the strip border so active line is flush */
}
.dp-tab:hover { color: #131b2e; }

.dp-tab--active {
    color: rgb(28, 177, 201);
    border-bottom-color: rgb(28, 177, 201);
}

.dp-tab-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.25rem;
    height: 1.25rem;
    padding: 0 0.35rem;
    border-radius: 999px;
    background: rgba(19, 27, 46, 0.08);
    color: #64748b;
    font-size: 0.65rem;
    font-weight: 700;
    letter-spacing: 0.02em;
    line-height: 1;
    transition: background-color 0.2s ease, color 0.2s ease;
}
.dp-tab--active .dp-tab-count {
    background: rgba(28, 177, 201, 0.15);
    color: rgb(23, 154, 175);
}

/* Each pane is rendered; only the active one is visible (hidden attr).
   Native `hidden` avoids layout thrash — no mount/unmount on tab switch. */
.dp-tab-pane[hidden] { display: none !important; }
.dp-tab-pane { min-height: 3rem; }

/* ============================================
   Related Products — selected card rows
   ============================================ */
.dp-related-card {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    padding: 0.625rem 0.75rem;
    border-radius: 0.75rem;
    border: 1px solid rgba(28, 177, 201, 0.22);
    background: linear-gradient(180deg, rgba(28, 177, 201, 0.04), #ffffff 80%);
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.dp-related-card:hover {
    border-color: rgba(28, 177, 201, 0.45);
    box-shadow: 0 4px 14px -6px rgba(28, 177, 201, 0.35);
}
.dp-related-index {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.35rem;
    height: 1.35rem;
    border-radius: 999px;
    background: rgb(28, 177, 201);
    color: #ffffff;
    font-size: 0.65rem;
    font-weight: 800;
    line-height: 1;
}
.dp-related-img {
    flex-shrink: 0;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: 0.5rem;
    object-fit: cover;
    background: #f1f5f9;
}
.dp-related-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.65rem;
    height: 1.65rem;
    border-radius: 0.4rem;
    border: none;
    background: transparent;
    color: #74808f;
    cursor: pointer;
    transition: background-color 0.15s ease, color 0.15s ease;
}
.dp-related-btn:hover { background: rgba(19, 27, 46, 0.06); color: #131b2e; }
.dp-related-btn:disabled { opacity: 0.3; pointer-events: none; }
.dp-related-btn--danger { color: #b91c1c; }
.dp-related-btn--danger:hover { background: rgba(220, 38, 38, 0.10); color: #b91c1c; }

/* ============================================
   Dashboard Modal (used by Related Products picker)
   ============================================ */
.dp-modal-overlay {
    position: fixed;
    inset: 0;
    z-index: 60;
    background: rgba(15, 23, 42, 0.45);
    backdrop-filter: blur(2px);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
    animation: dpModalOverlayIn 0.18s ease-out;
}
.dp-modal-card {
    width: 100%;
    max-width: 28rem;
    max-height: 85vh;
    display: flex;
    flex-direction: column;
    background: #ffffff;
    border-radius: 1rem;
    box-shadow: 0 24px 60px -18px rgba(15, 23, 42, 0.35), 0 4px 16px -4px rgba(15, 23, 42, 0.12);
    overflow: hidden;
    animation: dpModalCardIn 0.22s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.dp-modal-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 1rem 1rem 0.75rem;
    border-bottom: 1px solid rgba(19, 27, 46, 0.06);
}
.dp-modal-search {
    position: relative;
    padding: 0.75rem 1rem 0.5rem;
}
.dp-modal-list {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 0 0.5rem 0.75rem;
}
.dp-modal-footer {
    display: flex;
    justify-content: flex-end;
    padding: 0.75rem 1rem;
    border-top: 1px solid rgba(19, 27, 46, 0.06);
    background: rgba(19, 27, 46, 0.02);
}
@keyframes dpModalOverlayIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes dpModalCardIn {
    from { opacity: 0; transform: translateY(8px) scale(0.98); }
    to   { opacity: 1; transform: translateY(0)   scale(1); }
}

/* ============================================
   Stock / Variant Availability
   Used by:
   - Marketplace + StorePage + StoreProductsPage product cards
     (sold-out card overlay + disabled cart CTA)
   - QuickAdd modal variant chips (faded + diagonal slash)
   - Manual order picker variant chips (same as QuickAdd)
   See the Stock UX Phase plan in CLAUDE.md / session notes for the
   intended state machine.
   ============================================ */

/* Card-level sold-out treatment.
   Image is dimmed + desaturated so the customer sees there's
   still a real product (vs an empty placeholder), but it visually
   recedes. The :hover scale animation is suppressed because hover
   would imply interactivity that no longer applies to the cart
   CTA — the card itself stays clickable through to the PDP. */
.mp-product-card--sold-out .mp-product-img {
    filter: grayscale(0.7) brightness(0.92);
    opacity: 0.7;
}
.mp-product-card--sold-out:hover .mp-product-img {
    transform: none;
}

/* Sold-out variant of the inline cart button. Uses the same base
   .dp-card-cart-inline shape but locks the appearance into a
   neutral, non-interactive state. The container (anchor) above
   stays clickable so customers can navigate to the PDP — only
   the cart click is disabled. */
.dp-card-cart-inline--soldout {
    opacity: 0.5;
    cursor: not-allowed;
    pointer-events: none;
}

/* Variant unavailable — shared between QuickAdd modal chips and
   the manual order picker chips. The chip stays clickable (per
   product spec: clicking an unavailable option keeps it visible
   and surfaces the "Unavailable" CTA below) but is visually
   marked with a faded background + a diagonal slash drawn from
   bottom-left to top-right. The slash is a pseudo-element so it
   sits above content without affecting layout, and pointer-events
   on the slash itself are disabled so the chip's click handler
   continues to receive events.

   Chips that use this class MUST establish a positioning context
   (position: relative) for the ::after slash to anchor against. */
.dp-variant-unavailable {
    position: relative;             /* anchor for ::after slash */
    opacity: 0.55;
    color: #74808f !important;
    background: rgba(19, 27, 46, 0.04);
}
.dp-variant-unavailable::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    border-radius: inherit;
    background: linear-gradient(
        to top right,
        transparent calc(50% - 1px),
        currentColor calc(50% - 1px),
        currentColor calc(50% + 1px),
        transparent calc(50% + 1px)
    );
}

/* NumberFlow-style price tumble.
   `[data-price-tumble]` is the numeric wrapper the server emits
   around a plan's price. On Monthly/Yearly toggle, droply-price-tumble.js
   swaps its plain text for a row of digit slots. Each slot clips a
   vertical column of digits 0-9; translating the column engages a
   CSS transform transition, so digits appear to roll into the new
   value. See the JS module for the full lifecycle. */
[data-price-tumble] {
    display: inline-flex;
    align-items: baseline;
    line-height: 1;
    font-variant-numeric: tabular-nums;
}
.dpt-slot {
    display: inline-block;
    overflow: hidden;
    height: 1em;
    line-height: 1;
    vertical-align: baseline;
}
.dpt-col {
    display: inline-flex;
    flex-direction: column;
    transition: transform 550ms cubic-bezier(0.22, 1, 0.36, 1);
    will-change: transform;
}
.dpt-col > span {
    display: block;
    height: 1em;
    line-height: 1;
}
@media (prefers-reduced-motion: reduce) {
    .dpt-col { transition: none; }
}

/* ── Quill editor host — belt-and-braces against overflow clipping ──
   The header/format picker in Quill's Snow theme opens as an absolutely
   positioned popup relative to its picker button. If any ancestor has
   overflow: hidden (drawer wrappers, rounded-corner containers) the popup
   gets clipped and only the top few items are visible. The primary fix is
   removing overflow-hidden from the immediate wrapper on the product edit
   panels; this rule lifts the picker options above sibling content and
   raises their z-index so they aren't hidden by anything else in the panel
   (Save button pill, header, etc.).

   Scoped to .droply-quill-host so it only affects the product editors and
   doesn't touch the Plans editor (which continues to work with its own
   wrapper markup). */
.droply-quill-host .ql-picker-options {
    z-index: 60;
}

/* Match the Plans description editor's visual treatment on product surfaces.
   Plans uses `overflow-hidden` on its wrapper, which cleanly hides Quill
   Snow's default `border: 1px solid #ccc` on both .ql-toolbar and
   .ql-container so only the outer wrapper border is visible. We can't use
   overflow-hidden here because it clips the header picker's dropdown (the
   original "only H1 and H2 show" bug). Instead we strip Quill's own
   borders and apply the same rounded-corner + border-separator look via
   CSS — the visual outcome matches the Plans image the user shared. */
.droply-quill-host {
    overflow: visible;
}

.droply-quill-host .ql-toolbar.ql-snow {
    border: none;
    border-bottom: 1px solid rgba(19, 27, 46, 0.10);
    background: rgba(19, 27, 46, 0.02);
    border-top-left-radius: 0.5rem;
    border-top-right-radius: 0.5rem;
    padding: 6px 8px;
}

.droply-quill-host .ql-container.ql-snow {
    border: none;
    border-bottom-left-radius: 0.5rem;
    border-bottom-right-radius: 0.5rem;
    font-size: 0.875rem;  /* Match the surrounding form's text-sm */
    font-family: inherit;
}

.droply-quill-host .ql-editor {
    padding: 12px 15px;
    min-height: 100px;
    color: #131b2e;
}

.droply-quill-host .ql-editor.ql-blank::before {
    color: #74808f;   /* Matches other placeholder / hint text on the dashboard */
    font-style: normal;
    left: 15px;
    right: 15px;
    /* The hint is written in the DASHBOARD's language while the editor's
       direction follows the STORE's, so the two can legitimately disagree.
       plaintext makes the placeholder render per its own script — an Arabic
       hint reads right-to-left even in an English store's LTR editor. */
    unicode-bidi: plaintext;
}

/* Placeholder direction is driven by the HOST's dir rather than left to
   inherit. quillInterop sets text-align on quill.root at construction, which
   covers the normal case — but only for the flag it was built with, so a host
   that is RTL for any other reason would render its Arabic placeholder
   left-aligned. Keying off the host makes it correct by construction. */
/* Placeholder direction follows the language the PLACEHOLDER IS WRITTEN IN,
   which is the dashboard language — never the editor's own direction.

   Those two genuinely diverge on the Translations screen, where an English
   and an Arabic column sit side by side: with an English dashboard, the
   Arabic column is an RTL editor showing an ENGLISH hint, which has to read
   left-to-right. Keying on the host (`[dir="rtl"].droply-quill-host`) got that
   backwards and right-aligned the English text; only the ancestor — the
   dashboard — says what language the hint is in.

   Both directions are stated explicitly rather than letting one fall through
   to inheritance, because the value inherited from .ql-editor is the EDITOR's
   direction, i.e. exactly the wrong one. (0,4,1) beats Quill's own (0,2,1)
   blank::before, which loads after app.css. */
[dir="rtl"] .droply-quill-host .ql-editor.ql-blank::before {
    direction: rtl;
    text-align: right;
}

[dir="ltr"] .droply-quill-host .ql-editor.ql-blank::before {
    direction: ltr;
    text-align: left;
}

/* ───────────────────────────────────────────────────────────────
   Quill — toolbar localization.

   Quill Snow 1.3.7 is loaded from the CDN and hardcodes its toolbar
   strings — "Normal", "Heading 1", "Enter link:", "Save" — as CSS
   `content`, so a stylesheet override is the only way to translate them.

   These key on an ANCESTOR being RTL (`[dir="rtl"] .droply-…`), i.e. the
   dashboard language, because they are chrome in the ADMIN's language and
   have nothing to do with the direction of the document being written.
   The toolbar's own layout stays LTR in every case — see the picker rules
   below — so nothing here reorders or mirrors controls.
   =============================================================== */

/* ── Header picker ("Normal" / "عادي") ────────────────────────────────
   Quill Snow pins the dropdown arrow out of flow:

     .ql-snow .ql-picker:not(.ql-color-picker):not(.ql-icon-picker) svg {
       position: absolute; margin-top: -9px; right: 0; top: 50%; width: 18px;
     }

   Two consequences, and both defeated every padding-based attempt at this:

   1. Being absolutely positioned, the arrow occupies NO width. Padding on the
      label moves the text and leaves the arrow where it is, so whether a gap
      appeared depended on the label's final box width — not on the padding.

   2. That selector is (0,4,1). Quill's stylesheet is loaded from the CDN in
      App.razor AFTER app.css, so any override of ours at (0,4,1) or below
      LOSES on source order. The child-combinator selector below is (0,6,1),
      which is what actually makes the arrow move.

   The fix puts the arrow back in FLOW, so its position is derived from the
   label text rather than set against a box edge. Once both are flex items the
   arrow simply follows the text, and `gap` is the entire distance between them
   — measured from where the text actually ends, whatever it says and in
   whichever script. Nothing is offset from an edge, so there is no value that
   has to be re-tuned per language.

   Two details make the sizing genuinely dynamic:

   • The picker drops Quill's fixed 98px `width` (below). That number was
     measured for the English labels — it is roughly right for "Heading 1" and
     far too wide for "عادي", which is what left the arrow marooned across a
     wide dead gap. Auto width lets the float shrink-wrap to its own label.

   • `gap` is in `em`, so it scales with the label's own font-size instead of
     being a pixel constant that reads tight at one size and loose at another.

   Both flex properties are writing-mode aware, so LTR and RTL are correct from
   the same declarations — no direction-specific selectors anywhere. */
.droply-quill-host .ql-snow .ql-picker.ql-header {
    width: auto;
}

.droply-quill-host .ql-snow .ql-picker-label {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 0.4em;
    padding-left: 8px;
    padding-right: 8px;
}

.droply-quill-host .ql-snow .ql-picker:not(.ql-color-picker):not(.ql-icon-picker) > .ql-picker-label > svg {
    /* (0,6,1) — deliberately out-specifies Quill's (0,4,1) rule above, which
       would otherwise win on source order and keep the arrow pinned to the
       edge, overlapping the label text. */
    position: static;
    margin-top: 0;
    flex: 0 0 auto;
}

/* The toolbar is chrome and stays LTR whatever the editor's content direction
   is, so the buttons never reorder and never end up half-mirrored (groups
   flowing one way while the buttons inside them flow the other). The editor
   body's own direction is unaffected — it is set on quill.root at construction
   and by the host's dir. */
.droply-quill-host .ql-toolbar.ql-snow {
    direction: ltr;
}

/* Heading picker labels. Quill hardcodes these in its stylesheet, so an
   Arabic admin sees "Normal" / "Heading 1" no matter the UI language.
   The toolbar is configured with [1, 2, 3, false], so those four are the
   only values that can render. */
[dir="rtl"] .droply-quill-host .ql-snow .ql-picker.ql-header .ql-picker-label::before,
[dir="rtl"] .droply-quill-host .ql-snow .ql-picker.ql-header .ql-picker-item::before {
    content: 'عادي';
}

[dir="rtl"] .droply-quill-host .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
[dir="rtl"] .droply-quill-host .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
    content: 'عنوان 1';
}

[dir="rtl"] .droply-quill-host .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
[dir="rtl"] .droply-quill-host .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
    content: 'عنوان 2';
}

[dir="rtl"] .droply-quill-host .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
[dir="rtl"] .droply-quill-host .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
    content: 'عنوان 3';
}

/* Link tooltip. Same story — the toolbar exposes a link button, so this
   panel is reachable and every string in it comes from Quill's CSS. */
[dir="rtl"] .droply-quill-host .ql-snow .ql-tooltip::before {
    content: "الرابط:";
}

[dir="rtl"] .droply-quill-host .ql-snow .ql-tooltip[data-mode="link"]::before {
    content: "أدخل الرابط:";
}

[dir="rtl"] .droply-quill-host .ql-snow .ql-tooltip a.ql-action::after {
    content: 'تعديل';
}

[dir="rtl"] .droply-quill-host .ql-snow .ql-tooltip.ql-editing a.ql-action::after {
    content: 'حفظ';
}

[dir="rtl"] .droply-quill-host .ql-snow .ql-tooltip a.ql-remove::before {
    content: 'إزالة';
}

/* The tooltip's own input stays LTR — a URL is never RTL text. */
[dir="rtl"].droply-quill-host .ql-snow .ql-tooltip input[type="text"] {
    direction: ltr;
    text-align: left;
}

/* ───────────────────────────────────────────────────────────────
   Auth surface — shared full-viewport background for the customer
   sign-in and sign-up pages. Base near-white canvas with layered
   teal accents: two large soft corner-glow radials, two blurred
   orbs (top-right + bottom-left), and a faint grid that fades
   from centre outward via a radial mask. All layers use the
   Droply brand teal (#1cb1c9) at varying opacities so the whole
   surface reads as one cohesive brand moment.

   Structure the page as:
     <div class="auth-surface">
       <div class="auth-bg-glow-tr"></div>
       <div class="auth-bg-glow-bl"></div>
       <div class="auth-bg-orb-tr"></div>
       <div class="auth-bg-orb-bl"></div>
       <div class="auth-bg-grid"></div>
       <div class="auth-surface-content">…card…</div>
     </div>
   auth-surface-content sits at z-index 1 so it always paints
   above the -1 layers regardless of source order.
   ─────────────────────────────────────────────────────────────── */
.auth-surface {
    position: relative;
    min-height: calc(100vh - 64px);
    background: #F4F7FB;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding: 3rem 1.25rem;
    overflow: hidden;
    isolation: isolate;
}

.auth-surface-content {
    position: relative;
    z-index: 1;
    width: 100%;
    display: flex;
    justify-content: center;
}

/* Corner radial glows — larger, softer, farther-back layer.
   Positioned off-screen so only the tail bleeds into the canvas. */
.auth-bg-glow-tr,
.auth-bg-glow-bl {
    position: absolute;
    z-index: -1;
    pointer-events: none;
    border-radius: 50%;
}
.auth-bg-glow-tr {
    top: -40vh; right: -20vw;
    width: 90vh; height: 90vh;
    background: radial-gradient(circle at 30% 30%,
        rgba(28, 177, 201, 0.28) 0%,
        rgba(28, 177, 201, 0) 60%);
}
.auth-bg-glow-bl {
    bottom: -40vh; left: -20vw;
    width: 90vh; height: 90vh;
    background: radial-gradient(circle at 70% 70%,
        rgba(28, 177, 201, 0.22) 0%,
        rgba(28, 177, 201, 0) 60%);
}

/* Focused orbs — smaller, higher opacity, heavy blur for the
   "internal light source" feel. Layered ABOVE the corner glows
   but still below content. */
.auth-bg-orb-tr,
.auth-bg-orb-bl {
    position: absolute;
    z-index: -1;
    pointer-events: none;
    border-radius: 50%;
    filter: blur(60px);
}
.auth-bg-orb-tr {
    top: 10%; right: 8%;
    width: 260px; height: 260px;
    background: rgba(28, 177, 201, 0.45);  /* primary teal 45% */
}
.auth-bg-orb-bl {
    bottom: 10%; left: 8%;
    width: 220px; height: 220px;
    background: rgba(38, 190, 200, 0.35);  /* slightly cyan-shifted teal 35% */
}

/* Faint grid — teal at 5% opacity, radially masked so it fades
   to invisible outside the middle 75% of the viewport. Keeps
   the corners quiet while giving the centre subtle depth. */
.auth-bg-grid {
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background-image:
        linear-gradient(rgba(28, 177, 201, 0.05) 1px, transparent 1px),
        linear-gradient(90deg, rgba(28, 177, 201, 0.05) 1px, transparent 1px);
    background-size: 44px 44px;
    -webkit-mask-image: radial-gradient(circle at 50% 40%,
        black 0%, black 30%, transparent 75%);
    mask-image: radial-gradient(circle at 50% 40%,
        black 0%, black 30%, transparent 75%);
}

/* Card-border refinement — subtle cool-tinted 1px so the card
   sits crisply on the tinted background instead of "floating".
   Slight but visible outer shadow gives lift without shouting. */
.auth-surface .cust-signin-card,
.auth-surface .cust-signup-card {
    border: 1px solid rgba(200, 214, 228, 0.7);
    box-shadow:
        0 40px 80px -30px rgba(15, 23, 42, 0.14),
        0 12px 24px -12px rgba(15, 23, 42, 0.06);
}

/* Small-screen refinements — shrink the orbs so they don't
   dominate a phone viewport, and tighten the surface's top
   padding since the mobile header is smaller than desktop. */
@media (max-width: 640px) {
    .auth-surface { padding: 1.5rem 1rem 3rem; }
    .auth-bg-orb-tr { width: 180px; height: 180px; }
    .auth-bg-orb-bl { width: 160px; height: 160px; }
}

@media (prefers-reduced-motion: reduce) {
    /* Static layers — nothing to disable, but noted so any future
       animation added here is opt-in only. */
}


/* ── Inventory drawer: rich-text description ──────────────────────────
   The EDITOR itself needs no rules here: the drawer reuses the product
   editor's markup verbatim, so .droply-quill-host already styles it. Only
   the read-only render below is drawer-specific. */
/* Read-only render of the stored HTML. Quill emits <p>/<ul>/<ol>/<a>/<strong>,
   and Tailwind's preflight strips list markers and default margins — restore
   just enough for the description to read as it was authored. */
.inv-desc-view p { margin: 0 0 0.4rem; }
.inv-desc-view p:last-child { margin-bottom: 0; }
.inv-desc-view ul { list-style: disc; padding-inline-start: 1.25rem; margin: 0 0 0.4rem; }
.inv-desc-view ol { list-style: decimal; padding-inline-start: 1.25rem; margin: 0 0 0.4rem; }
.inv-desc-view a { color: #1cb1c9; text-decoration: underline; }
.inv-desc-view strong { font-weight: 700; }
.inv-desc-view em { font-style: italic; }
