/*
 * Admin Panel ("Liquid Glass") stylesheet.
 *
 * Deliberately self-contained rather than <link>-ing
 * themes/default/css/theme.css: the Master Prompt's Module Manager
 * lets an admin disable/remove/swap the active storefront Theme at
 * any time, and the back office that very action is performed from
 * must never go unstyled because of it. The color-preset / dark-light
 * variable *contract* (--primary/--secondary/--bg/--surface/--text,
 * [data-color-preset], [data-theme-mode]) is kept identical to
 * theme.css on purpose so admin-app.js's themeSwitcher() can drive
 * both stylesheets with the exact same effective-theme payload from
 * GET /api/theme.
 */

:root {
    --glass-radius: 16px;
    --glass-radius-lg: 22px;
    --glass-blur: 18px;
    --glass-border: 1px solid rgba(255, 255, 255, 0.14);
    --glass-transition: 200ms ease;
    --sidebar-width: 248px;
    /* Card-like elevation instead of a colored gradient/glow — every
       "this is the active/primary thing" surface below (buttons, the
       active nav link, active tabs/pagination, the toggle switch) is a
       solid --primary fill lifted with one of these two shadow steps,
       never a two-color gradient. Mirrors the same shadow tokens
       Themes/DefaultTheme/Resources/assets/css/theme.css defines for
       the storefront, so admin and storefront read as one visual
       system. */
    --card-shadow: 0 6px 20px rgba(0, 0, 0, 0.16);
    --card-shadow-sm: 0 3px 10px rgba(0, 0, 0, 0.14);
    /* Same idea as --card-shadow above, for the other elevation depths
       used around the page (.glass-panel/.stat-card, .module-card
       collapsed/open, .cs-listbox, .back-to-top) — kept as variables
       so light mode below can halve every one of them in one place
       instead of overriding each selector individually. */
    --shadow-panel: 0 8px 32px rgba(0, 0, 0, 0.18);
    --shadow-card: 0 4px 18px rgba(0, 0, 0, 0.10);
    --shadow-card-open: 0 10px 30px rgba(0, 0, 0, 0.16);
    --shadow-listbox: 0 12px 32px rgba(0, 0, 0, 0.35);
    --shadow-float: 0 8px 24px rgba(0, 0, 0, 0.25);
}

[data-color-preset="default"]  { --primary: #6366f1; --secondary: #8b5cf6; --bg: #0f172a; --surface: #1e293b; --text: #f8fafc; }
[data-color-preset="ocean"]    { --primary: #0ea5e9; --secondary: #06b6d4; --bg: #0c1e2e; --surface: #132f45; --text: #e0f2fe; }
[data-color-preset="sunset"]   { --primary: #f97316; --secondary: #f43f5e; --bg: #1c1210; --surface: #2a1a16; --text: #fff7ed; }
[data-color-preset="forest"]   { --primary: #22c55e; --secondary: #84cc16; --bg: #0f1a13; --surface: #16261c; --text: #ecfdf5; }
[data-color-preset="rose"]     { --primary: #f43f5e; --secondary: #ec4899; --bg: #1f1115; --surface: #2b1720; --text: #fff1f2; }
[data-color-preset="amber"]    { --primary: #f59e0b; --secondary: #eab308; --bg: #1c1608; --surface: #2a220f; --text: #fffbeb; }
[data-color-preset="slate"]    { --primary: #64748b; --secondary: #94a3b8; --bg: #0f172a; --surface: #1e293b; --text: #f1f5f9; }
[data-color-preset="emerald"]  { --primary: #10b981; --secondary: #14b8a6; --bg: #0a1a16; --surface: #0f2921; --text: #ecfdf5; }
[data-color-preset="crimson"]  { --primary: #dc2626; --secondary: #b91c1c; --bg: #1a0e0e; --surface: #291414; --text: #fef2f2; }
[data-color-preset="cobalt"]   { --primary: #2563eb; --secondary: #1d4ed8; --bg: #0b1220; --surface: #111c33; --text: #eff6ff; }
[data-color-preset="lavender"] { --primary: #a78bfa; --secondary: #c4b5fd; --bg: #160f26; --surface: #221838; --text: #f5f3ff; }
[data-color-preset="graphite"] { --primary: #71717a; --secondary: #a1a1aa; --bg: #111113; --surface: #1c1c1f; --text: #fafafa; }
[data-color-preset="gold"]     { --primary: #d4af37; --secondary: #f5d576; --bg: #1c1708; --surface: #2a230f; --text: #fdf6e3; }
[data-color-preset="silver"]   { --primary: #c0c0c0; --secondary: #e5e5e5; --bg: #16171a; --surface: #232428; --text: #f5f5f5; }
[data-color-preset="turquoise"] { --primary: #00aab5; --secondary: #00c2ce; --bg: #06181a; --surface: #0b262a; --text: #e0fbfc; }

/* Material Design's "tonal surface" approach in both modes: never a
   fixed, preset-independent gray/black — always the active preset's
   own --primary mixed at a low, fixed percentage into a near-white
   (light) or near-black (dark) base, the same "surface = a whisper of
   the brand color over the mode's neutral extreme" rule Material 3
   uses. Switching presets therefore visibly changes the background in
   EITHER mode, not just accent colors — replaces an earlier version
   of the light rule that flattened every preset to one fixed gray,
   which was wrong. `html[data-theme-mode="…"]` has higher specificity
   than a bare `[data-color-preset="…"]` selector (0,1,1 vs 0,1,0), so
   these always win regardless of which preset rule appears first in
   the cascade. Kept in sync with theme.css's identical rule. */
html[data-theme-mode="light"] {
    /* Fixed, preset-independent light gray — like dark mode's #202020
       below, no longer a color-mix() with --primary, so every preset
       lands on the exact same light background instead of a
       preset-tinted near-white. --surface stays a touch lighter than
       --bg so cards/panels still read as raised above the page. */
    --bg: #eaecf2;
    --surface: #f2f3f7;
    --text: #0f172a;
    /* A bit darker than the previous 0.18 so the outline reads clearly
       against the #eaecf2 background instead of nearly disappearing. */
    --glass-border: 1px solid rgba(100, 116, 139, 0.28);
    /* Half the dark-mode alpha of every shadow token above — every
       card/button/dropdown built on them gets a lighter, less
       noticeable shadow in light mode without needing its own
       override. */
    --card-shadow: 0 6px 20px rgba(0, 0, 0, 0.08);
    --card-shadow-sm: 0 3px 10px rgba(0, 0, 0, 0.07);
    --shadow-panel: 0 8px 32px rgba(0, 0, 0, 0.09);
    --shadow-card: 0 4px 18px rgba(0, 0, 0, 0.05);
    --shadow-card-open: 0 10px 30px rgba(0, 0, 0, 0.08);
    --shadow-listbox: 0 12px 32px rgba(0, 0, 0, 0.175);
    --shadow-float: 0 8px 24px rgba(0, 0, 0, 0.125);
}

/* Fixed dark gray — deliberately NOT color-mix()'d with --primary like
   light mode's --bg above, and no longer a --primary-tinted near-black
   either: every preset (ocean/sunset/forest/…) and the storefront
   theme (theme.css) now land on the exact same neutral #202020 dark
   background, never a preset- or module-tinted one. Kept in sync with
   theme.css's identical rule. */
html[data-theme-mode="dark"] {
    --bg: #202020;
    --surface: #2a2a2a;
}

* { box-sizing: border-box; }

/* Themed, auto-hiding scrollbar (WebKit + Firefox), matching the
   active [data-color-preset]/[data-theme-mode] palette instead of a
   stock gray bar — same rule Themes/DefaultTheme/Resources/assets/css/theme.css
   already applies to the storefront, kept identical here so the admin
   panel reads as the same visual system. The thumb stays transparent
   at rest and fades in/out smoothly on :hover — there's no CSS
   selector for "is actively scrolling", so hovering the scrolling
   element is the standard stand-in for it.
   The `transition` lives on *::-webkit-scrollbar-thumb (WebKit/Blink:
   Chrome, Edge, Safari) so both the fade-in and fade-out animate —
   WebKit is the only engine that actually animates a scrollbar-thumb
   color change today. Firefox's own `scrollbar-color` has no
   transition support at all (a browser limitation, not something CSS
   here can work around) and always snaps instantly between states. */
* { scrollbar-color: transparent transparent; scrollbar-width: thin; }
*:hover { scrollbar-color: color-mix(in srgb, var(--primary) 55%, var(--surface)) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
    background: transparent;
    border-radius: 999px;
    border: 2px solid transparent;
    background-clip: padding-box;
    transition: background-color 320ms ease-in-out;
}
*:hover::-webkit-scrollbar-thumb {
    background: color-mix(in srgb, var(--primary) 45%, var(--surface));
    background-clip: padding-box;
    transition: background-color 150ms ease-in-out;
}
*:hover::-webkit-scrollbar-thumb:hover {
    background: color-mix(in srgb, var(--primary) 65%, var(--surface));
    background-clip: padding-box;
}

html, body {
    margin: 0;
    min-height: 100%;
    /* Safety net: this admin is a fixed dashboard layout, never meant
       to pan sideways — an accidental off-screen element (like the
       skip-link bug just fixed above) should never again be able to
       widen the whole page. Individual wide content (tables, code
       blocks) scrolls inside its own .table-wrap/etc container instead.
       `clip`, not `hidden`: per the CSS Overflow spec, setting only
       overflow-x forces the browser to resolve the unset overflow-y to
       `auto` too (so the axis pair is never "one hidden, one visible") —
       which silently turns <body> into its own scroll container and
       breaks every `position: sticky` element sized against the
       viewport (.admin-sidebar/.admin-topbar below). `clip` opts out of
       that pairing rule entirely (it was added to the spec for exactly
       this), so the page keeps exactly one scrolling box (<html>) and
       sticky positioning resolves against it correctly again. */
    overflow-x: clip;
    background: var(--bg);
    color: var(--text);
    font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
    transition: background var(--glass-transition), color var(--glass-transition);
}

a { color: inherit; }

/* ---- Glass primitives (shared vocabulary with the storefront) ---- */
.glass-panel {
    background: color-mix(in srgb, var(--surface) 65%, transparent);
    backdrop-filter: blur(var(--glass-blur));
    -webkit-backdrop-filter: blur(var(--glass-blur));
    border: var(--glass-border);
    border-radius: var(--glass-radius);
    box-shadow: var(--shadow-panel);
    transition: background var(--glass-transition), border-color var(--glass-transition);
    /* Chromium/Edge sometimes drops the blur for a single frame (the
       glass "flattens" to a flat, unblurred fill) the moment the mouse
       moves over a .glass-panel that's a dropdown/dialog — hovering an
       item inside it (e.g. .color-preset-option, .notification-bell__item)
       triggers a `background` transition on that child, and the engine's
       repaint of the child incorrectly invalidates the *parent's*
       backdrop-filter compositing layer instead of just repainting the
       child within it. Forcing this element onto its own persistent
       compositor layer up front (rather than promoting/demoting it on
       demand) keeps the backdrop-filter layer stable across a child's
       repaint, so hovering never visibly kills the glass background.
       Applies to every .glass-panel — the topbar itself, its
       color-preset/notification/account dropdowns, and any modal/dialog
       built from this same class — so the fix is consistent everywhere
       glass panels appear, not just in the header. */
    will-change: backdrop-filter;
    transform: translateZ(0);
}
/* Chromium/Edge renders an opaque/garbled square over a native
   <select>'s open dropdown list whenever ANY element on the page —
   not only the select's own ancestors — still has an active
   `backdrop-filter` while that popup is open (a long-standing engine
   compositing bug, not something fixable from the select's own
   styles). The admin-sidebar/admin-topbar (see admin.layout.blade.php)
   are both `.glass-panel` too, but they're siblings of the form the
   select lives in, not ancestors — so an earlier version of this rule
   that only dropped blur on the select's *containing* .glass-panel
   still left the sidebar/topbar blurred and the popup still came out
   corrupted. Scoping to `body:has(select:focus)` instead drops the
   blur from every glass surface on the page — sidebar, topbar, every
   .module-card, the color-preset/avatar menus, .back-to-top, all of
   it — for as long as any <select> anywhere is focused/open, which is
   what actually fixes every occurrence of this instead of only the
   one the select happens to be nested in. Needs no JS; the glass look
   returns the instant the select closes/blurs. */
body:has(select:focus) .glass-panel,
body:has(select:focus) .module-card,
body:has(select:focus) .back-to-top {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}

/* `left: -9999px` (the old approach) still counts toward the
   document's scrollable area in some layout contexts — a page with a
   wide/flex/grid ancestor can end up with a horizontal scrollbar that
   pans 9999px to nothing, which is exactly the "why is the page so
   wide" bug this replaced. Off-screen via clipping instead, the same
   technique .sr-only already uses on this stylesheet, so the link is
   invisible but never actually widens the page. */
.glass-skip-link {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip: rect(0, 0, 0, 0); white-space: nowrap;
}
.glass-skip-link:focus {
    width: auto; height: auto; overflow: visible; clip: auto; white-space: normal;
    left: 1rem; top: 1rem; z-index: 999;
    background: var(--surface); color: var(--text);
    padding: 0.5rem 1rem; border-radius: 8px;
}
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0, 0, 0, 0); }

@keyframes glass-fade-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: translateY(0); } }
@keyframes glass-rise-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
@keyframes glass-scale-in { from { opacity: 0; transform: scale(0.96); } to { opacity: 1; transform: scale(1); } }
@keyframes glass-shimmer { to { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) {
    * { animation: none !important; transition: none !important; }
}

/* Shared control height so buttons, single/multi-selects and text
   inputs all line up on one row instead of the browser's native sizing
   drifting them apart — see .input/.select below for the other half. */
:root { --control-height: 2.75rem; }

.btn {
    display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem;
    border: var(--glass-border); border-radius: 12px; padding: 0.6rem 1.15rem;
    min-height: var(--control-height);
    /* Never taller than it is wide, never wraps to a second line —
       a button's own text/icon sets its width, height stays pinned
       to --control-height, so at its narrowest (icon-only, one short
       word) it reads as a square, never a tall stack. */
    min-width: var(--control-height);
    white-space: nowrap; flex-shrink: 0;
    font-size: 0.9rem; font-weight: 600; text-decoration: none; cursor: pointer;
    background: color-mix(in srgb, var(--surface) 70%, transparent); color: var(--text);
    transition: transform 150ms ease, background var(--glass-transition), box-shadow var(--glass-transition), opacity 150ms ease;
    -webkit-tap-highlight-color: transparent;
}
.btn:hover:not(:disabled) { transform: translateY(-2px); }
.btn:active:not(:disabled) { transform: translateY(0) scale(0.98); }
.btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
.btn--primary { background: var(--primary); border-color: transparent; color: #fff; box-shadow: var(--card-shadow-sm); }
.btn--primary:hover:not(:disabled) { background: color-mix(in srgb, var(--primary) 88%, black 6%); box-shadow: var(--card-shadow); }
.btn--danger { background: #ef4444; border-color: transparent; color: #fff; }
.btn--ghost { background: transparent; }
.btn--sm { padding: 0.35rem 0.75rem; font-size: 0.8rem; border-radius: 9px; min-width: 2rem; }
.btn--icon { width: var(--control-height); height: var(--control-height); padding: 0; border-radius: 999px; }
.btn--block { width: 100%; }
/* Square icon-only button (e.g. the module-settings "recent events"
   toggle) — same footprint as .btn--icon but square corners so it
   reads as a distinct affordance from the round icon buttons; the
   hover label comes from the native `title` attribute rather than a
   custom tooltip widget. */
.btn--icon-square { width: var(--control-height); height: var(--control-height); padding: 0; border-radius: 10px; font-size: 1.1rem; display: inline-flex; align-items: center; justify-content: center; }

/* ---- Module cards: one collapsible "liquid glass" card per
   Module/Plugin section of the product form, so an admin can see at a
   glance which capability (ProductSales, Warehouse, ProductVariants,
   ProductGallery…) owns which fields, and fold away the ones they
   don't need right now. Built from the exact same glass vocabulary as
   .glass-panel above (blur/surface/border tokens) rather than
   inventing a parallel style, so it never looks like a bolted-on
   widget. */
.module-card {
    position: relative;
    border-radius: var(--glass-radius);
    margin-top: 1.15rem;
    background: color-mix(in srgb, var(--surface) 46%, transparent);
    backdrop-filter: blur(calc(var(--glass-blur) * 0.6));
    -webkit-backdrop-filter: blur(calc(var(--glass-blur) * 0.6));
    border: var(--glass-border);
    box-shadow: var(--shadow-card);
    overflow: hidden;
    transition: box-shadow var(--glass-transition), border-color var(--glass-transition), background var(--glass-transition);
    animation: glass-rise-in 280ms ease both;
}
.module-card:hover { border-color: color-mix(in srgb, var(--primary) 35%, transparent); }
.module-card.is-open {
    background: color-mix(in srgb, var(--surface) 60%, transparent);
    box-shadow: var(--shadow-card-open);
}

.module-card__header {
    position: relative; z-index: 1;
    display: flex; align-items: center; gap: 0.75rem;
    padding: 0.85rem 1.1rem; cursor: pointer; user-select: none;
    background: none; border: none; width: 100%; text-align: inherit;
    font: inherit; color: inherit;
    transition: background 150ms ease;
}
.module-card__header:hover { background: color-mix(in srgb, var(--primary) 6%, transparent); }
.module-card__header:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }

.module-card__badge {
    flex-shrink: 0;
    color: var(--primary); font-size: 0.68rem; font-weight: 700; letter-spacing: 0.01em;
    padding: 0.2rem 0.6rem; line-height: 1.5;
    border-radius: 999px;
    background: color-mix(in srgb, var(--primary) 14%, transparent);
    border: 1px solid color-mix(in srgb, var(--primary) 30%, transparent);
    white-space: nowrap;
}
.module-card__title { font-size: 0.94rem; font-weight: 600; flex: 1; }
.module-card__chevron {
    flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center;
    width: 1.6rem; height: 1.6rem; border-radius: 999px;
    color: var(--text); opacity: 0.55;
    background: color-mix(in srgb, var(--surface) 80%, transparent);
    border: var(--glass-border);
    transition: transform 260ms cubic-bezier(0.4, 0, 0.2, 1), opacity 150ms ease, background 150ms ease;
}
.module-card__header:hover .module-card__chevron { opacity: 0.9; }
.module-card.is-open .module-card__chevron { transform: rotate(180deg); opacity: 0.9; }

/* Grid-rows trick: animates height from 0fr → 1fr, which — unlike
   animating `height` itself — needs no JS-measured pixel value and
   still animates smoothly (height:auto famously can't). The inner
   wrapper needs min-height:0 for the grid track to actually collapse
   past its content's intrinsic size. */
.module-card__collapse {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows 260ms cubic-bezier(0.4, 0, 0.2, 1);
}
.module-card.is-open .module-card__collapse { grid-template-rows: 1fr; }
.module-card__body {
    min-height: 0;
    overflow: hidden;
    padding: 0 1.1rem;
    transition: padding 260ms cubic-bezier(0.4, 0, 0.2, 1);
}
.module-card.is-open .module-card__body { padding: 0.1rem 1.1rem 1.2rem; }

.field { display: flex; flex-direction: column; gap: 0.35rem; margin-bottom: 1rem; }
.field label { font-size: 0.82rem; opacity: 0.8; }
.field-hint { font-size: 0.75rem; opacity: 0.6; }
.field-error { font-size: 0.78rem; color: #f87171; }
.field-success { font-size: 0.78rem; color: #4ade80; }
.field-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem 1.25rem; }

/* A plain part-separator rule inside a form (e.g. between a form's
   general fields and its trailing set of status checkboxes) — reuses
   the same themed border every glass panel/table cell already draws
   with, never a hardcoded color. */
.field-divider { border: none; border-top: var(--glass-border); margin: 0.25rem 0 1.1rem; }

/* ---- Themed callout ---- */
/* A dialog/panel-internal highlight box (e.g. "we recognised these
   columns, apply the saved mapping?" in the products Excel-import
   wizard) — every color here is derived from the CURRENT
   [data-color-preset]/[data-theme-mode] tokens (--surface/--primary),
   never a hardcoded/invented value, so it repaints correctly across
   every preset and light/dark mode with zero extra CSS. Use this
   instead of an ad-hoc inline `style="background:..."` anywhere in
   the admin panel — see CLAUDE.md's "Admin panel design must always
   follow the active theme" rule.
*/
.callout {
    background: color-mix(in srgb, var(--primary) 12%, var(--surface));
    border: 1px solid color-mix(in srgb, var(--primary) 35%, transparent);
    border-radius: var(--glass-radius, 12px);
    padding: 0.75rem 1rem;
    margin-bottom: 1rem;
}

/* Plugins\Menus admin — drag-reorderable item list inside the "Manage
   items" dialog (Plugins/Menus/Resources/views/admin/index.blade.php).
   Reuses .callout for each row's surface so it already tracks the
   active preset/light-dark mode; only the row's own flex layout, the
   drag handle, nesting indent and the dragged-row highlight are new. */
.menu-items-list { display: flex; flex-direction: column; }
.menu-items-list--nested { margin-inline-start: 1.75rem; }
.menu-items-row { display: flex; align-items: center; gap: 0.6rem; cursor: pointer; }
.menu-items-row.is-dragging { opacity: 0.5; }
.menu-items-row.is-active { border-color: color-mix(in srgb, var(--primary) 60%, transparent); }
.menu-items-row__handle { cursor: grab; opacity: 0.6; user-select: none; }

/* The "Manage items" dialog itself: two resizable panes (item list +
   the open item's form) side by side — see startItemsPaneResize() in
   Plugins/Menus/Resources/views/admin/index.blade.php. Wider/taller
   than a plain .modal so both panes have room; the divider reuses
   --glass-border so it tracks the theme like everything else here. */
.menu-items-dialog { width: 100%; max-width: 960px; max-height: 82vh; display: flex; flex-direction: column; }
.menu-items-dialog__body { display: flex; align-items: stretch; gap: 0; overflow: hidden; flex: 1; min-height: 0; }
.menu-items-dialog__pane { overflow-y: auto; padding-inline-end: 0.25rem; flex-shrink: 0; }
.menu-items-dialog__pane.menu-items-dialog__form { flex: 1 1 auto; min-width: 260px; padding-inline-start: 1rem; }
/* Light by default (a bare border-color tint, NOT --glass-border
   itself — that token is a full `1px solid rgba(...)` shorthand and is
   only ever valid on a `border`/`border-top` property, never on
   `background`), darkening to the primary color under the pointer/while
   actually being dragged so the draggable divider reads as interactive
   without shouting for attention at rest. */
.menu-items-dialog__divider { flex: 0 0 6px; cursor: col-resize; background: color-mix(in srgb, var(--text) 12%, transparent); border-radius: 3px; margin: 0 0.25rem; transition: background-color 0.15s ease; }
.menu-items-dialog__divider:hover, .menu-items-dialog__divider.is-active { background: color-mix(in srgb, var(--primary) 65%, transparent); }

@media (max-width: 720px) {
    .menu-items-dialog__body { flex-direction: column; }
    .menu-items-dialog__pane { width: 100% !important; }
    .menu-items-dialog__divider { display: none; }
}

/* "Add item" split control (see the Manage-items dialog's own
   docblock): a primary quick-add button plus a ▾ caret that opens a
   dropdown of the item's other possible types. Built on .btn/.btn--primary
   for the two buttons and .glass-panel's own surface/border tokens for
   the dropdown, so it never invents a new color. */
.menu-items-add { position: relative; display: flex; margin-bottom: 0.6rem; }
.menu-items-add__main { flex: 1; border-start-end-radius: 0; border-end-end-radius: 0; }
.menu-items-add__caret { flex: 0 0 auto; border-start-start-radius: 0; border-end-start-radius: 0; border-inline-start: 1px solid color-mix(in srgb, var(--bg) 35%, transparent); padding-inline: 0.6rem; }
.menu-items-add__menu {
    position: absolute; inset-inline-start: 0; top: 100%; margin-top: 0.3rem; z-index: 20;
    min-width: 100%; display: flex; flex-direction: column;
    background: color-mix(in srgb, var(--surface) 96%, transparent); border: var(--glass-border); border-radius: var(--glass-radius, 12px);
    box-shadow: var(--shadow-panel, var(--shadow-float)); overflow: hidden;
}
.menu-items-add__menu button { text-align: start; padding: 0.55rem 0.85rem; border: none; background: transparent; color: var(--text); cursor: pointer; font-size: 0.85rem; }
.menu-items-add__menu button:hover { background: color-mix(in srgb, var(--primary) 12%, transparent); }

/* ---- Page Builder editor: floating block palette + centered "sheet" canvas ----
   The palette is a Windows-style floating window (draggable by its header,
   collapsible, position remembered per-admin in localStorage — see
   Plugins\PageBuilder\Resources\views\admin\edit.blade.php) so it stays
   out of the way of the canvas while blocks are dragged from it. The
   canvas renders as a page-like sheet centered in the available space,
   consistent with the RTL/LTR-agnostic .glass-panel vocabulary already
   used across the admin panel. */
.pb-builder-area {
    position: relative;
    min-height: 32rem;
}

.pb-canvas-wrap {
    display: flex;
    justify-content: center;
    padding: 0 1rem;
}

.pb-sheet {
    width: 100%;
    max-width: 46rem;
    min-height: 32rem;
}

.pb-palette {
    position: fixed;
    top: 6rem;
    inset-inline-start: 1.5rem;
    width: 14rem;
    max-height: calc(100vh - 8rem);
    overflow-y: auto;
    z-index: 40;
    box-shadow: var(--shadow-float, var(--shadow-panel));
}

.pb-palette--collapsed {
    max-height: none;
    overflow: visible;
    width: auto;
}

.pb-palette__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding-bottom: 0.5rem;
    margin-bottom: 0.5rem;
    border-bottom: var(--glass-border);
    cursor: move;
    -webkit-user-select: none;
    user-select: none;
}

.pb-palette__body {
    display: flex;
    flex-direction: column;
}

.input, .select, textarea.input {
    width: 100%; background: color-mix(in srgb, var(--surface) 80%, transparent); color: var(--text);
    border: var(--glass-border); border-radius: 10px; padding: 0.6rem 0.85rem;
    font-size: 0.92rem; font-family: inherit;
}
/* Single-line controls (everything but the free-growing textarea) share
   the same --control-height as .btn, so a toolbar mixing inputs,
   selects and buttons always lines up on one row. A native
   `<select multiple>` normally grows tall enough to show several rows
   at once — pinned to the same height + scroll instead, so it sits on
   that row too rather than blowing up the layout. */
.input:not(textarea), .select { min-height: var(--control-height); }
select[multiple].input, select[multiple].select { height: var(--control-height); overflow-y: auto; }
/* A <select>'s OWN background paints its native OS dropdown popup too
   (unlike every other .input element, whose translucent background only
   ever shows on the closed control) — Chromium/Edge don't composite a
   color-mix(...transparent) background correctly on that popup, and
   render the open options list corrupted/garbled instead of just
   see-through. Overridden to a fully opaque color so the popup renders
   cleanly; the closed control still visually matches every other .input
   thanks to the identical --surface base color, just without the glass
   translucency (which a native, OS-drawn popup can never truly achieve
   anyway). */
select.input, select.select, .field select, select.input:focus, select.select:focus {
    background: var(--surface);
    transition: border-color var(--glass-transition), box-shadow var(--glass-transition);
}
/* The garbled/half-erased look on an open <select>'s options (this
   admin runs RTL for fa/ar) is a separate Chromium/Edge bug from the
   translucent-background one above: the native popup's per-option
   hover/selection highlight rectangle is computed assuming the
   *inherited* `dir="rtl"` from <html> never reaches its internal
   layout, so the highlight bar is drawn LTR-anchored while the Persian/
   Arabic text paints RTL-anchored — the two disagree on which end of
   the row is "the start", and the highlight visually blots out part of
   the text instead of sitting cleanly behind all of it. Setting
   `direction`/`text-align` explicitly (not just inherited) on the
   <select> AND every <option> — via the `:dir()` pseudo-class, which
   reads the actually-resolved direction rather than assuming rtl — is
   what actually reaches that internal layout and fixes it. */
select:dir(rtl), select:dir(rtl) option {
    direction: rtl;
    text-align: right;
}
select:dir(ltr), select:dir(ltr) option {
    direction: ltr;
    text-align: left;
}
.input:focus, .select:focus, textarea.input:focus {
    outline: none; border-color: var(--primary);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--text) 18%, transparent);
}

/* ---- Custom select listbox (shared-assets/js/custom-select.js) ----
   The three rules above this block were three separate attempts at
   CSS-only fixes for Chromium/Edge's native <select> popup rendering
   bug (opaque background, RTL highlight misalignment, then dropping
   backdrop-filter page-wide) — none of them fully eliminated the
   corrupted/garbled popup, because the bug lives in the browser's own
   OS-drawn popup renderer, which no amount of CSS on the page can
   reach. custom-select.js intercepts every non-multiple <select>'s
   open gesture and renders this listbox instead — a normal
   absolutely-positioned DOM element painted by the page like anything
   else, so the engine bug simply has nothing to trip over. The
   <select> itself is left fully in place (still holds the real value,
   still what Alpine's x-model reads/writes, still what a form submits)
   — only the *popup* is replaced. */
.cs-listbox {
    position: fixed;
    /* Above anything a `<select>` could ever be opened from — including
       a window-manager.js dialog, whose own backdrop gets bumped to
       900+ (and rising) by bringToFront() once that Module is enabled,
       which used to leave this listbox rendering BEHIND it at the old
       z-index: 250. A transient "currently open" overlay like this one
       should always win over whatever it was opened from, dialog or not. */
    z-index: 9700;
    display: flex;
    flex-direction: column;
    background: var(--surface);
    border: var(--glass-border);
    border-radius: 10px;
    box-shadow: var(--shadow-listbox);
    padding: 0.3rem;
    max-height: 16rem;
    animation: glass-scale-in 120ms ease both;
}
/* The scrollable rows live in their own child (not `.cs-listbox`
   itself) so the search box above stays put — a fixed header — while
   only the option list underneath it scrolls. */
.cs-listbox__options { overflow-y: auto; min-height: 0; }
/* Only rendered/shown once a select has more than custom-select.js's
   own SEARCH_THRESHOLD options (see that file's docblock) — same
   themed-input look as `.input`, just borderless/inline so it reads as
   part of the listbox chrome rather than a separate form field. */
.cs-listbox__search {
    display: none;
    width: 100%;
    margin-bottom: 0.3rem;
    padding: 0.45rem 0.6rem;
    border: none;
    border-bottom: 1px solid color-mix(in srgb, var(--text) 15%, transparent);
    border-radius: 6px 6px 0 0;
    background: color-mix(in srgb, var(--primary) 8%, transparent);
    color: var(--text);
    font-size: 0.88rem;
}
.cs-listbox__search:focus { outline: none; border-bottom-color: var(--primary); }
.cs-listbox__option {
    padding: 0.5rem 0.75rem;
    border-radius: 7px;
    cursor: pointer;
    font-size: 0.92rem;
    color: var(--text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.cs-listbox__option:hover,
.cs-listbox__option.is-active {
    background: color-mix(in srgb, var(--primary) 22%, transparent);
}
.cs-listbox__option.is-selected {
    font-weight: 700;
    color: var(--primary);
}
.cs-listbox__empty { padding: 0.5rem 0.75rem; font-size: 0.85rem; opacity: 0.6; }

/* The real `<select multiple>` custom-multiselect.js enhances — kept
   focusable/scriptable/submittable (never `display:none`), just
   removed from the visual flow and painted out; its `.multi-select`
   replacement (inserted immediately after it) is what's actually
   seen/clicked. */
.ms-native-select {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; padding: 0;
}

/* ---- Custom multi-select (shared-assets/js/custom-multiselect.js) ----
   Unlike custom-select.js above (which only ever replaces a single-
   select's POPUP, leaving its collapsed look untouched), a native
   `<select multiple>`'s AT-REST rendering is a multi-row native
   listbox — not an acceptable collapsed control on its own — so this
   one replaces the whole visible control with a chip box, reusing the
   same `.cs-listbox`/`.cs-listbox__option`/`.cs-listbox__search`
   dropdown from above for the actual options panel. The real
   `<select multiple>` is visually hidden (never `display:none` — it
   must stay focusable/scriptable) right next to its `.multi-select`
   replacement; see that script's own docblock for the full mechanism. */
.multi-select {
    display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem;
    width: 100%; min-height: var(--control-height);
    background: color-mix(in srgb, var(--surface) 80%, transparent); color: var(--text);
    border: var(--glass-border); border-radius: 10px; padding: 0.4rem 0.6rem;
    cursor: text;
    transition: border-color var(--glass-transition), box-shadow var(--glass-transition);
}
.multi-select.is-open {
    outline: none; border-color: var(--primary);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--text) 18%, transparent);
}
.multi-select__chip {
    display: inline-flex; align-items: center; gap: 0.3rem;
    background: color-mix(in srgb, var(--primary) 18%, var(--surface));
    color: var(--text);
    border-radius: 999px;
    padding: 0.15rem 0.4rem 0.15rem 0.6rem;
    font-size: 0.82rem;
    white-space: nowrap;
    max-width: 100%;
}
[dir="rtl"] .multi-select__chip { padding: 0.15rem 0.6rem 0.15rem 0.4rem; }
.multi-select__chip-label { overflow: hidden; text-overflow: ellipsis; }
.multi-select__chip-remove {
    display: inline-flex; align-items: center; justify-content: center;
    width: 1.1rem; height: 1.1rem; border-radius: 50%;
    background: transparent; color: inherit; cursor: pointer; font-size: 0.85rem; line-height: 1;
    flex-shrink: 0;
}
.multi-select__chip-remove:hover { background: color-mix(in srgb, var(--text) 20%, transparent); }
.multi-select__search {
    flex: 1 1 4rem; min-width: 4rem;
    border: none; outline: none; background: transparent; color: var(--text);
    font-size: 0.92rem; font-family: inherit; padding: 0.15rem 0;
}
.multi-select__placeholder { opacity: 0.55; font-size: 0.92rem; pointer-events: none; }
/* The listbox's per-row "selected" marker here is a checkmark prefix
   (multiple rows can be selected at once, so `.cs-listbox__option`'s
   plain bold+primary-color styling above — built for single-select's
   "exactly one row is ever selected" — isn't a strong enough signal on
   its own). */
.cs-listbox__option.is-multi { display: flex; align-items: center; gap: 0.5rem; }
.cs-listbox__option.is-multi::before {
    content: ''; flex-shrink: 0; width: 1rem; height: 1rem; border-radius: 4px;
    border: var(--glass-border); background: transparent;
}
.cs-listbox__option.is-multi.is-selected::before {
    background: var(--primary); border-color: var(--primary);
    content: '✓'; display: flex; align-items: center; justify-content: center;
    font-size: 0.7rem; color: var(--bg);
}

/* ---- Themed file dropzone (<x-file-drop>, admin-assets/js/file-drop.js) ----
   Replaces the browser's own "Choose file" button look with a themed
   dropzone matching every other `.panel`/`.field` surface — the real
   `<input type="file">` stays in the DOM (`.file-drop__input`, visually
   hidden via `.sr-only`, still the actual form value) so nothing about
   how a page reads the chosen file changes; only its at-rest look and
   its drag&drop affordance are new. See that component's own docblock
   for why clicking it opens the OS file dialog (not the Media library
   modal) for every current caller. */
.file-drop {
    position: relative;
    border: 2px dashed color-mix(in srgb, var(--text) 25%, transparent);
    border-radius: var(--glass-radius, 14px);
    background: color-mix(in srgb, var(--surface) 60%, transparent);
    transition: border-color var(--glass-transition), background var(--glass-transition);
}
.file-drop.is-dragover {
    border-color: var(--primary);
    background: color-mix(in srgb, var(--primary) 10%, var(--surface));
}
.file-drop__zone {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 0.35rem; text-align: center; cursor: pointer;
    padding: 1.5rem 1rem; min-height: 6rem;
}
.file-drop__icon { font-size: 1.6rem; opacity: 0.75; }
.file-drop__hint { font-size: 0.85rem; opacity: 0.65; }
.file-drop__filename {
    font-size: 0.9rem; font-weight: 600; color: var(--primary);
    max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.file-drop__clear {
    position: absolute; top: 0.5rem; inset-inline-end: 0.5rem;
    display: inline-flex; align-items: center; justify-content: center;
    width: 1.6rem; height: 1.6rem; border-radius: 50%;
    background: color-mix(in srgb, var(--surface) 90%, transparent);
    border: var(--glass-border); color: var(--text); cursor: pointer; font-size: 0.85rem;
}
.file-drop__clear:hover { background: color-mix(in srgb, var(--primary) 25%, var(--surface)); }
/* Bound to a caller's own `busy-expr` (e.g. `busy-expr="importExcelRunning"`
   while a file is being parsed/uploaded) — dims the zone, disables
   picking (the real `<input>` itself gets `:disabled`, so a keyboard
   Tab+Space can't trigger it either), and swaps the folder icon for a
   spinning ring. */
.file-drop.is-busy .file-drop__zone { opacity: 0.65; cursor: default; }
.file-drop__spinner {
    width: 1.6rem; height: 1.6rem; border-radius: 50%;
    border: 3px solid color-mix(in srgb, var(--text) 15%, transparent);
    border-top-color: var(--primary);
    animation: file-drop-spin 700ms linear infinite;
}
@keyframes file-drop-spin { to { transform: rotate(360deg); } }
/* Modules\Tables — <x-table-context-menu>'s right-click menu, driven
   by public/admin-assets/js/smart-table.js's contextMenu* state. Same
   fixed-position floating-panel shape as .cs-listbox above (glass
   surface, --shadow-listbox, glass-scale-in), since both are "a small
   menu that appears at an arbitrary screen point and must never be
   clipped by a scrolling ancestor" — teleported to <body>, so `top`/
   `insetInlineStart` are viewport-relative (event.clientX/clientY),
   not `position: absolute` relative to any row. */
.table-context-menu {
    position: fixed;
    z-index: 9000;
    min-width: 12rem;
    background: var(--surface);
    border: var(--glass-border);
    border-radius: 10px;
    box-shadow: var(--shadow-listbox);
    padding: 0.3rem;
    animation: glass-scale-in 120ms ease both;
}
.table-context-menu__item {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    width: 100%;
    padding: 0.55rem 0.75rem;
    border-radius: 7px;
    border: none;
    background: transparent;
    color: var(--text);
    font: inherit;
    text-align: start;
    cursor: pointer;
    white-space: nowrap;
}
.table-context-menu__item:hover { background: color-mix(in srgb, var(--primary) 22%, transparent); }
/* Bulk menu item some (not all) selected rows don't offer — kept
   visible (not hidden) so its own title="..." can explain WHICH rows
   (by their # number) block it; see table-context-menu.blade.php. */
.table-context-menu__item.is-disabled {
    opacity: 0.4;
    cursor: not-allowed;
}
.table-context-menu__item.is-disabled:hover { background: transparent; }
.table-context-menu__icon { flex-shrink: 0; }
.table-context-menu__empty { padding: 0.55rem 0.75rem; font-size: 0.85rem; opacity: 0.6; white-space: nowrap; }
/* Bulk-mode header ("N انتخاب‌شده") — see table-context-menu.blade.php. */
.table-context-menu__header {
    padding: 0.35rem 0.75rem 0.5rem;
    margin-bottom: 0.2rem;
    border-bottom: var(--glass-border);
    font-size: 0.78rem;
    font-weight: 600;
    opacity: 0.75;
    white-space: nowrap;
}

/* Multi-row selection — a row selected via Ctrl/Shift-click or its own
   checkbox (see smart-table.js's onRowClick()/onRowSelectCheckboxClick())
   gets a persistent tint (NOT just a hover state) so an admin can tell
   at a glance which rows a bulk right-click will act on. */
.data-table tbody tr.is-row-selected {
    background: color-mix(in srgb, var(--primary) 14%, transparent);
}
/* Themed, rounded, animated row-select checkbox — replaces the raw
   OS-native checkbox (square, flat, ignores the panel's own accent
   color) every browser draws by default. `appearance:none` hands
   drawing fully to this rule; the check itself is a small inline SVG
   background-image that fades/scales in via `:checked`, rather than a
   ::before/::after pseudo-element, so it still works with a plain
   `background-image` transition (Safari doesn't animate `content`). */
.row-select-checkbox {
    appearance: none; -webkit-appearance: none;
    width: 1.15rem;
    height: 1.15rem;
    margin-inline-end: 0.5rem;
    vertical-align: middle;
    cursor: pointer;
    border-radius: 6px;
    border: 1.5px solid color-mix(in srgb, var(--text) 30%, transparent);
    background-color: color-mix(in srgb, var(--surface) 80%, transparent);
    background-image: none;
    background-repeat: no-repeat;
    background-position: center;
    background-size: 0.75rem;
    transition: background-color 140ms ease, border-color 140ms ease, transform 100ms ease;
    flex: 0 0 auto;
}
.row-select-checkbox:hover {
    border-color: color-mix(in srgb, var(--primary) 60%, transparent);
}
.row-select-checkbox:active {
    transform: scale(0.9);
}
.row-select-checkbox:checked {
    background-color: var(--primary);
    border-color: var(--primary);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='white' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 8.5l3 3 6-7'/%3E%3C/svg%3E");
    animation: row-select-checkbox-pop 160ms ease;
}
.row-select-checkbox:indeterminate {
    background-color: var(--primary);
    border-color: var(--primary);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='white' stroke-width='2.2' stroke-linecap='round'%3E%3Cpath d='M3.5 8h9'/%3E%3C/svg%3E");
}
.row-select-checkbox:focus-visible {
    outline: 2px solid color-mix(in srgb, var(--primary) 55%, transparent);
    outline-offset: 1px;
}
@keyframes row-select-checkbox-pop {
    0% { transform: scale(0.7); }
    60% { transform: scale(1.08); }
    100% { transform: scale(1); }
}

/* Row drag-reorder "☰" handle — same discoverability contract as
   .row-menu-trigger (see that rule's own docblock): hidden until the
   row is hovered/focused on desktop (kept out of the way of dense
   table text), but ALWAYS shown on touch/tablet (`pointer: coarse`,
   which has no hover to reveal it) — same "در گوشی و تبلت ثابت ... در
   ویندوز وقتی هاور می‌شود" contract. */
.data-table td.is-row-drag { width: 1%; text-align: center; }
.row-drag-handle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.8rem;
    height: 1.8rem;
    border-radius: 999px;
    color: inherit;
    font-size: 1.1rem;
    line-height: 1;
    cursor: grab;
    opacity: 0;
    transition: background 120ms ease, opacity 120ms ease;
}
.row-drag-handle:active { cursor: grabbing; }
.data-table tbody tr:hover .row-drag-handle,
.data-table tbody tr:focus-within .row-drag-handle {
    opacity: 0.6;
}
.row-drag-handle:hover { opacity: 1 !important; background: color-mix(in srgb, var(--primary) 16%, transparent); }
@media (pointer: coarse) {
    .row-drag-handle { opacity: 0.6; }
}

/* Small ☰ indicator next to a column's own name in the header — only
   ever shown while rowReorderEnabled is on (see toggleRowReorderMode()
   in smart-table.js); purely a visual cue that the header cell itself
   is draggable, sits right next to the column label as this feature's
   own spec asks ("کنار نام ستون‌ها"). Not its own drag source — the
   whole `<th>` already is (see :draggable on it), same as before this
   feature existed. */
.column-drag-handle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.4rem;
    height: 1.4rem;
    border-radius: 999px;
    margin-inline-start: 0.3rem;
    font-size: 0.85rem;
    opacity: 0.6;
    vertical-align: middle;
}

/* Column-resize handle — the thin hit-area on a <th>'s trailing edge
   (see startResize() in smart-table.js) that lets an admin drag to
   resize the column. Purely invisible before, so there was no visual
   cue it was there; now shows a thin "|" grip line on hover/drag so
   it reads as resizable, without adding permanent visual noise to
   every header cell. */
.column-resize-handle {
    position: absolute;
    inset-inline-end: 0;
    top: 0;
    bottom: 0;
    width: 6px;
    cursor: col-resize;
}
.column-resize-handle::after {
    content: '';
    position: absolute;
    inset-inline-end: 2px;
    top: 15%;
    bottom: 15%;
    width: 1px;
    background: color-mix(in srgb, var(--text) 35%, transparent);
    opacity: 0;
    transition: opacity 120ms ease;
}
/* Only a laptop-width+ pointer actually drags to resize — on phone/
   tablet widths this hit-area would just be in the way of scrolling
   the table, so the whole handle (not just its "|" mark) is removed
   there, same breakpoint the rest of the panel already treats as
   "no longer a touch-first layout". Above that width, the "|" stays
   invisible until the admin's pointer is over the column header
   (or the handle itself, while dragging), so headers don't carry
   permanent visual clutter. */
@media (max-width: 1024px) {
    .column-resize-handle { display: none; }
}
@media (min-width: 1025px) {
    th:hover .column-resize-handle::after,
    .column-resize-handle:hover::after,
    .column-resize-handle:active::after {
        opacity: 1;
    }
}
.column-resize-handle:hover::after,
.column-resize-handle:active::after {
    background: var(--primary, #6366f1);
    width: 2px;
}

/* <x-table-settings-menu>'s "More options" rows — a clickable list item
   inside the settings dialog's .modal/.glass-panel, same hover/shape
   language as .table-context-menu__item above but laid out in normal
   document flow (not a fixed floating panel) since it lives inside an
   already-positioned modal. */
.table-settings-menu__option {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    width: 100%;
    padding: 0.5rem 0.65rem;
    border-radius: 7px;
    border: var(--glass-border);
    background: transparent;
    color: var(--text);
    font: inherit;
    text-align: start;
    cursor: pointer;
}
.table-settings-menu__option:hover { background: color-mix(in srgb, var(--primary) 14%, transparent); }

/* <x-table-export-dialog>'s section labels ("فرمت‌ها"/"کدام سطرها") —
   same small-caps muted style .field's own <label> already carries
   elsewhere, just explicit here since these sit above a row list rather
   than a single input. */
.smart-table-dialog-section__title {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    margin: 0 0 0.35rem;
    font-size: 0.82rem;
    font-weight: 600;
    opacity: 0.7;
}

/* One selectable format/scope row inside <x-table-export-dialog> —
   IS `.table-settings-menu__option` (bordered, hoverable row already
   used by the settings menu), just rendered as a <label> wrapping its
   own checkbox/radio instead of a <button>. */
label.table-settings-menu__option { cursor: pointer; border-radius: 10px; transition: background-color .15s ease, box-shadow .15s ease; }
/* Same theme-colored, slightly-larger native checkbox/radio look
   .checkbox-field's own input already uses everywhere else in the
   panel (accent-color + a bit bigger than the browser default) —
   these export-dialog rows used to just be plain unstyled native
   controls (no accent-color at all), the one spot in the whole
   export/chart flow that still looked un-themed next to everything
   around it. Still a REAL native <input>, never hidden behind a
   fake box — accent-color only recolors the browser's own checkbox/
   radio rendering, it doesn't replace it. */
label.table-settings-menu__option input[type="checkbox"],
label.table-settings-menu__option input[type="radio"] {
    margin: 0;
    width: 1.05rem;
    height: 1.05rem;
    accent-color: var(--primary);
    transition: transform .15s ease;
}
label.table-settings-menu__option input[type="checkbox"]:checked,
label.table-settings-menu__option input[type="radio"]:checked { transform: scale(1.1); }

/* <x-table-export-dialog>'s own format list — minimal rectangular
   "buttons" stacked one below another (not a plain bordered row list
   like .table-settings-menu__option above), each one a full-width
   label wrapping a REAL, visible native radio/checkbox (no
   .sr-only hiding — the admin explicitly asked for the native
   radio-button/checkbox look back) so both the control itself and
   the whole rectangle are click targets. Selected state also gets a
   filled/tinted border on top of that, purely as an extra visual cue. */
.smart-table-export-format-btn {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    width: 100%;
    padding: 0.6rem 0.8rem;
    border-radius: 12px;
    border: var(--glass-border);
    background: transparent;
    color: var(--text);
    font: inherit;
    cursor: pointer;
    transition: background-color .15s ease, box-shadow .15s ease, transform .15s ease;
}
.smart-table-export-format-btn:hover { background: color-mix(in srgb, var(--primary) 10%, transparent); }
.smart-table-export-format-btn:active { transform: scale(0.98); }
.smart-table-export-format-btn.is-selected {
    background: color-mix(in srgb, var(--primary) 14%, transparent);
    box-shadow: inset 0 0 0 1.5px var(--primary);
    animation: smart-table-export-format-pop .18s ease;
}
.smart-table-export-format-btn.is-disabled { opacity: 0.45; cursor: not-allowed; pointer-events: none; }
/* Same theme-colored native checkbox/radio look every other choice
   row in the panel uses (.checkbox-field, .table-settings-menu__option
   above) — still a real, visible native control (per this block's
   own docblock, "no .sr-only hiding"), just recolored/sized to match
   the rest of the panel instead of the browser's plain default. */
.smart-table-export-format-btn input[type="radio"],
.smart-table-export-format-btn input[type="checkbox"] {
    margin: 0;
    width: 1.05rem;
    height: 1.05rem;
    accent-color: var(--primary);
    transition: transform .15s ease;
}
.smart-table-export-format-btn input[type="radio"]:checked,
.smart-table-export-format-btn input[type="checkbox"]:checked { transform: scale(1.1); }
.smart-table-export-format-btn__label { flex: 1; text-align: start; }

/* A quick, subtle "pop" the moment a format row becomes selected —
   purely decorative (never re-fires on hover/hold, only on the
   is-selected class actually being added), matching the same
   glass-scale-in family of small entrance animations already used
   for menus/dropdowns elsewhere in admin.css. */
@keyframes smart-table-export-format-pop {
    from { transform: scale(0.97); }
    to { transform: scale(1); }
}

/* <x-table-export-dialog>'s zip-password field — the eye/eye-slash
   toggle sits on the field's own logical END (`inset-inline-end`),
   resolved against THIS WRAPPER's own `dir` (the blade sets
   `dir="ltr"` on `.pw-field` itself, not just the `<input>` inside
   it) — never the surrounding panel's language/direction. A ltr
   field's "end" is its right edge, which is exactly where this ends
   up; a hypothetical rtl-dir password field elsewhere would put it on
   the left automatically, same rule, no separate CSS needed. */
.pw-field { position: relative; }
.pw-input { padding-inline-end: 2.4rem; }
.pw-toggle {
    position: absolute;
    top: 50%;
    inset-inline-end: 0.4rem;
    transform: translateY(-50%);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: none;
    padding: 0.25rem;
    cursor: pointer;
    color: var(--text);
    opacity: 0.65;
}
.panel.glass-panel.history a {
    display: grid;
}
.pw-toggle:hover { opacity: 1; }

/* <x-table-export-dialog>'s "scoped to one row" note — a quiet inline
   line above the form/loading/result states, not a boxed banner. */
.smart-table-export-note {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    margin: 0 0 0.9rem;
    font-size: 0.85rem;
    opacity: 0.75;
}

/* <x-table-export-dialog>'s loading/error state — replaces the form
   in-place inside the SAME dialog (no separate modal) while
   runExport() is running, or once it ends with nothing to show. */
.smart-table-export-state {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.6rem;
    padding: 1.5rem 0.25rem;
    font-size: 0.9rem;
}
/* Real row-progress percentage (rows_written/rows_count) inside the
   "در حال ساخت خروجی…" loading state — see table-export-dialog.blade.php's
   own docblock on `exportProgress`. Takes the full row on its own
   (the spinner sits before it on the same flex line) since its own
   track needs the width to read as a real progress bar, not a sliver
   squeezed next to the spinner/text. */
/* "زمان سپری‌شده" — its own full-width row so it reads as a secondary
   status line under "در حال ساخت خروجی…"/the percent bar, not crammed
   onto the spinner's own line next to them. */
.smart-table-export-elapsed { flex: 1 1 100%; color: var(--text-muted); font-size: 0.82rem; }
.smart-table-export-progress { flex: 1 1 100%; display: flex; flex-direction: column; gap: 0.35rem; }
.smart-table-export-progress__track {
    height: 6px;
    border-radius: 999px;
    background: color-mix(in srgb, var(--text) 12%, transparent);
    overflow: hidden;
}
.smart-table-export-progress__fill {
    height: 100%;
    border-radius: 999px;
    background: var(--primary);
    transition: width .3s ease;
}
.smart-table-export-state--error {
    flex-direction: column;
    align-items: stretch;
    gap: 0.5rem;
    padding: 1rem 0.25rem;
}
.smart-table-export-state__row { display: flex; align-items: center; gap: 0.5rem; color: #ef4444; }
.smart-table-export-state__details { font-size: 0.8rem; }
.smart-table-export-state__details-actions { display: flex; align-items: center; gap: 0.9rem; }
.smart-table-export-state__details-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    border: none;
    background: transparent;
    color: var(--text);
    opacity: 0.65;
    cursor: pointer;
    padding: 0;
    font: inherit;
    font-size: 0.8rem;
}
.smart-table-export-state__details-toggle:hover { opacity: 1; }
.smart-table-export-state__detail-text {
    margin: 0.4rem 0 0;
    padding: 0.5rem 0.65rem;
    border-radius: 8px;
    border: var(--glass-border);
    background: color-mix(in srgb, var(--surface) 55%, transparent);
    font-family: monospace;
    direction: ltr;
    text-align: left;
    white-space: pre-wrap;
    word-break: break-word;
    opacity: 0.8;
}

/* <x-table-export-dialog>'s result list — one row per generated file,
   each a link the admin can still view/copy even though the file was
   already auto-saved to their device (_triggerBrowserDownload()). */
.smart-table-export-results { display: flex; flex-direction: column; gap: 0.5rem; }
.smart-table-export-results__hint { margin: 0 0 0.15rem; font-size: 0.8rem; opacity: 0.7; }
.smart-table-export-results__row {
    display: block;
    align-items: center;
    gap: 0.4rem;
}
.smart-table-export-results__label {
    flex: 0 0 auto;
    font-size: 0.85rem;
    white-space: nowrap;
    opacity: 0.85;
    word-break: break-all;
}
.smart-table-export-results__row .input { flex: 1; min-width: 0; margin-top: 15px; margin-bottom: 15px; }

/* A toolbar/header icon button reflecting an active toggle state — the
   export dialog's own "🕘" history button (table-export-dialog.blade.php)
   is the first user, but generic enough for any future glass-icon-button
   toggle. */
.glass-icon-button.is-active { background: color-mix(in srgb, var(--primary) 38%, transparent); }

/* <x-table-export-dialog>'s "🕘" history mode — a per-user, per-table
   log of past exports (App\Core\Contracts\Tables\
   TableExportHistoryServiceInterface), each row showing whether the
   underlying file is still available, with its own "🔗 دریافت لینک"/
   "🗑️ حذف" actions. */
.smart-table-export-history { display: flex; flex-direction: column; gap: 0.6rem; max-height: 22rem; overflow-y: auto; }
.smart-table-export-history__list { display: flex; flex-direction: column; gap: 0.5rem; }
.smart-table-export-history__row {
    display: flex; flex-direction: column; gap: 0.3rem;
    padding: 0.6rem 0.7rem; border-radius: 10px;
    background: color-mix(in srgb, var(--surface) 55%, transparent); border: var(--glass-border);
}
.smart-table-export-history__main { display: flex; align-items: center; gap: 0.5rem; justify-content: space-between; font-size: 0.88rem; }
.smart-table-export-history__meta { font-size: 0.75rem; opacity: 0.7; display: flex; gap: 0.35rem; flex-wrap: wrap; }
.smart-table-export-history__link-hint { margin: 0; font-size: 0.72rem; opacity: 0.65; }
.smart-table-export-history__actions { display: flex; gap: 0.4rem; margin-top: 0.1rem; }
.smart-table-export-history__actions .table-columns-dialog__icon-btn:disabled { opacity: 0.4; cursor: not-allowed; }
/* "🔗 دریافت لینک" is a two-step reveal, not an immediate navigation —
   the button itself only fetches the link; this row (the URL + ↗️/📋)
   only appears once it's back, same {input, ↗️, 📋} shape
   .smart-table-export-results__row already uses for a just-finished
   export's own link. */
.smart-table-export-history__link-row { display: flex; align-items: center; gap: 0.4rem; margin-top: 0.1rem; }
.smart-table-export-history__link-row .input { flex: 1; min-width: 0; }
.smart-table-export-history__pager { display: flex; align-items: center; justify-content: center; gap: 0.75rem; font-size: 0.8rem; margin-top: 0.3rem; }

/* <x-table-settings-menu> itself — a cursor/trigger-anchored floating
   panel, same fixed-position language as .table-context-menu above
   (teleported to <body>, top/insetInlineStart are viewport-relative),
   just wider/scrollable since it hosts whole field sections instead of
   a flat list of menu items. This is what makes opening it read as
   "the same kind of thing" as right-clicking a data row instead of a
   centered modal dialog. */
.table-settings-menu {
    position: fixed;
    z-index: 260;
    width: min(22rem, calc(100vw - 2rem));
    max-height: min(30rem, calc(100vh - 2rem));
    overflow-y: auto;
    padding: 0.75rem;
    animation: glass-scale-in 120ms ease both;
}
.table-settings-menu__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    margin-bottom: 0.5rem;
}

.checkbox-field { display: flex; align-items: center; gap: 0.55rem; }
.checkbox-field input { width: 1.05rem; height: 1.05rem; accent-color: var(--primary); }

/* Multi-select checklist (e.g. categoriesPage()'s tag picker) — a
   simple wrapping grid of .checkbox-field rows rather than a plain
   flex-column list, so a longer tag list doesn't stretch the dialog
   tall for no reason. */
.checkbox-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr)); gap: 0.4rem 0.75rem; margin-top: 0.35rem; }

.switch { position: relative; display: inline-flex; align-items: center; gap: 0.6rem; cursor: pointer; }
/* The input is stretched over the whole switch (not just its native
   ~13px checkbox box) and raised above .switch__track with a z-index,
   since .switch__track — painted after it in DOM order — would
   otherwise sit visually on top and silently absorb clicks anywhere
   but that tiny native box. .switch__track's own pointer-events:none
   below is belt-and-suspenders for the same reason. */
.switch input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; z-index: 1; }
.switch__track {
    width: 2.5rem; height: 1.4rem; border-radius: 999px;
    background: color-mix(in srgb, var(--text) 20%, transparent); position: relative; transition: background 150ms ease;
    pointer-events: none;
}
.switch__track::after {
    content: ''; position: absolute; inset-inline-start: 2px; top: 2px; width: 1.1rem; height: 1.1rem;
    border-radius: 999px; background: #fff; transition: inset-inline-start 150ms ease;
}
.switch input:checked + .switch__track { background: var(--primary); box-shadow: var(--card-shadow-sm); }
.switch input:checked + .switch__track::after { inset-inline-start: calc(100% - 1.1rem - 2px); }

/* ---- App shell ---- */
.admin-shell { display: flex; min-height: 100vh; }

.admin-sidebar {
    /* --sidebar-live-width is set inline by adminShell() (sidebarWidth,
       drag-resized between the icon-only floor and --sidebar-width
       itself — see .admin-sidebar__resizer below) with a fallback to
       the static default for the brief moment before Alpine mounts. */
    width: var(--sidebar-live-width, var(--sidebar-init-width, var(--sidebar-width))); flex-shrink: 0; margin: 1rem 0 1rem 1rem;
    display: flex; flex-direction: column; padding: 1.1rem 0.85rem;
    animation: glass-fade-in 320ms ease both; position: sticky; top: 1rem; align-self: flex-start;
    /* `height` (not just `max-height`) — per request the glass
       background must always fill the full viewport height, even on a
       page with only a couple of nav items, not shrink-to-fit its own
       content. Nothing scrolls on THIS element any more (overflow
       moved to .admin-sidebar__nav below) so the settings footer
       always sits flush at the bottom instead of scrolling away with
       a long item list. */
    height: calc(100vh - 2rem); overflow: hidden;
}
/* Resizing itself (mousemove) sets --sidebar-live-width directly every
   frame — a `width` transition would visibly lag one frame behind the
   pointer, so it's deliberately left off `width` (unlike margin/padding/
   opacity below, which only ever change on the discrete collapse toggle). */
.admin-sidebar { transition: margin 220ms ease, padding 220ms ease, opacity 180ms ease, width 260ms cubic-bezier(.4,0,.2,1); }
html[dir="rtl"] .admin-sidebar { margin: 1rem 1rem 1rem 0; }

/* Icon-only floor (isSidebarIconOnly() in admin-app.js) — every label/
   group-label/search-box/settings-label is `x-show`n off by the same
   JS check, this just centers what's left (icon + settings gear) and
   drops the now-pointless horizontal padding those labels used to
   need room for. */
.admin-sidebar.is-icon-only .admin-nav-link,
.admin-sidebar.is-icon-only .admin-nav-settings__trigger { justify-content: center; padding-inline: 0.4rem; }
.admin-sidebar.is-icon-only .admin-sidebar__brand { justify-content: center; padding-inline: 0; }
/* No room for the site name text (hidden via x-show above) once the
   sidebar is at its icon-only floor — the logo itself shrinks down to
   roughly icon size too, so the brand row reads the same as every
   other icon-only nav row instead of standing out as a leftover
   full-size image. */
.admin-sidebar.is-icon-only .admin-sidebar__brand-logo { height: 1.3rem; max-width: 1.3rem; }

/* The nav list is the ONLY part of the sidebar that scrolls now (see
   .admin-sidebar's own comment above) — flex:1 lets it fill whatever
   space is left between the fixed brand/search header and the locked
   settings footer, min-height:0 is required for a flex child to
   actually shrink and scroll instead of overflowing its parent. */
.admin-sidebar__nav { flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden; }

.admin-sidebar__search { padding: 0 0.15rem 0.75rem; flex-shrink: 0; }
.admin-sidebar__search-input { width: 100%; font-size: 0.85rem; }

.admin-nav-subgroup { margin-inline-start: 0.6rem; padding-inline-start: 0.5rem; border-inline-start: 1px solid color-mix(in srgb, var(--text) 12%, transparent); margin-bottom: 0.3rem; }
.admin-nav-subgroup__label { display: flex; align-items: center; gap: 0.35rem; font-size: 0.68rem; opacity: 0.5; padding: 0.2rem 0.5rem 0.2rem; text-transform: capitalize; }
/* "گروه‌بندی" → "بستن زیرمنوها به‌صورت پیش‌فرض": the header itself
   becomes the click target once collapsing is on, so it needs a
   pointer cursor + a hover cue the plain static label never needed. */
.admin-nav-subgroup__label.is-toggle { cursor: pointer; }
.admin-nav-subgroup__label.is-toggle:hover { opacity: 0.8; }
.admin-nav-subgroup__indicator { margin-inline-start: auto; opacity: 0.7; }
.admin-nav-subgroup__icon { font-size: 0.85rem; line-height: 1; }

/* "گروه‌بندی" → "مشخص کردن گروه‌ها با رنگ پس‌زمینه" / "بردر با
   گوشه‌های گرد" — both opt-in, off by default (see navGroupHighlight/
   navGroupBorder in admin-app.js), applied to the same two nesting
   levels the settings menu describes ("در دو بخش مربوطه"): the
   top-level group and its own module subgroup. */
.admin-nav-group.is-highlighted { background: color-mix(in srgb, var(--surface) 55%, transparent); border-radius: 10px; padding: 0.5rem 0.3rem; }
.admin-nav-group.is-bordered { border: 1px solid color-mix(in srgb, var(--text) 12%, transparent); border-radius: 10px; padding: 0.5rem 0.3rem; }
.admin-nav-subgroup.is-bordered { border: 1px solid color-mix(in srgb, var(--text) 12%, transparent); border-inline-start: 1px solid color-mix(in srgb, var(--text) 12%, transparent); border-radius: 8px; padding: 0.3rem; }

/* "گروه‌بندی" → "بستن زیرمنوها به‌صورت پیش‌فرض": children themselves
   stay in the DOM (x-show on each <a>, see layout.blade.php) so this
   just tightens the now-empty subgroup's own spacing rather than
   needing a max-height transition. */
.admin-nav-subgroup.is-collapsed { margin-bottom: 0; }

.admin-nav-empty { font-size: 0.8rem; opacity: 0.6; padding: 0.5rem 0.6rem; }

/* flex-shrink:0 (not margin-top:auto any more — the sidebar itself is
   now a fixed, non-scrolling height, see .admin-sidebar's own comment
   above) keeps this "locked" flush at the very bottom of the sidebar
   regardless of how many nav items scroll above it. */
.admin-sidebar__footer { flex-shrink: 0; padding-top: 0.6rem; border-top: 1px solid color-mix(in srgb, var(--text) 10%, transparent); }
.admin-nav-settings { position: relative; }
.admin-nav-settings__trigger { width: 100%; background: none; border: none; cursor: pointer; font: inherit; }
.admin-nav-settings__menu {
    position: absolute; bottom: calc(100% + 0.4rem); inset-inline-start: 0; min-width: 13rem;
    padding: 0.4rem; z-index: 30; display: flex; flex-direction: column; gap: 0.15rem;
}
.admin-nav-settings__menu-label { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.06em; opacity: 0.55; padding: 0.3rem 0.6rem; }
.admin-nav-settings__menu button {
    display: flex; align-items: center; gap: 0.4rem; text-align: start; background: none; border: none;
    color: var(--text); font: inherit; font-size: 0.85rem; padding: 0.45rem 0.6rem; border-radius: 8px;
    cursor: pointer; opacity: 0.85;
}
.admin-nav-settings__menu button:hover { opacity: 1; background: color-mix(in srgb, var(--surface) 60%, transparent); }
.admin-nav-settings__menu button.is-selected { opacity: 1; font-weight: 600; color: var(--primary); }
.admin-nav-settings__menu button span:first-child { width: 1em; flex-shrink: 0; }

/* "گروه‌بندی" switch rows in this same menu — a full clickable row
   (label text included), unlike the bare .switch used elsewhere, since
   here the switch always needs its own caption right next to it rather
   than relying on a separate label element placed by the page around it. */
.admin-nav-settings__switch-row { width: 100%; padding: 0.3rem 0.6rem; font-size: 0.85rem; }
.admin-nav-settings__switch-row .switch__track { flex-shrink: 0; }

/* Drag-to-resize handle — a thin invisible-until-hover strip on the
   sidebar's own inline-end edge, matching the ☰-adjacent affordance
   pattern smart-table.js already uses for its own column resizers. */
.admin-sidebar__resizer {
    position: absolute; top: 0; bottom: 0; inset-inline-end: -0.35rem; width: 0.7rem;
    cursor: ew-resize; touch-action: none; z-index: 5;
}
.admin-sidebar__resizer::after {
    content: ''; position: absolute; top: 0.5rem; bottom: 0.5rem; inset-inline-start: 50%; width: 3px;
    transform: translateX(-50%); border-radius: 999px; background: transparent; transition: background 150ms ease;
}
.admin-sidebar__resizer:hover::after,
.admin-sidebar__resizer:active::after { background: color-mix(in srgb, var(--primary) 45%, transparent); }

.admin-sidebar__brand { display: flex; align-items: center; gap: 0.6rem; padding: 0.4rem 0.6rem 1.1rem; font-weight: 700; font-size: 1.05rem; flex-shrink: 0; }
/* min-width:0 lets this flex item actually shrink below its content's
   natural width — without it `scrollWidth` in fitBrandName() (admin-
   app.js) never exceeds `clientWidth` and the auto-shrink never
   triggers. white-space:nowrap+overflow:hidden is the fallback floor
   once fitBrandName() bottoms out at its minimum legible font-size. */
.admin-sidebar__brand-name { min-width: 0; flex: 1 1 auto; white-space: nowrap; overflow: hidden; }
.admin-sidebar__brand .dot { width: 0.6rem; height: 0.6rem; border-radius: 999px; background: var(--primary); flex-shrink: 0; }
/* height-fixed / width-auto so a rectangular logo (not just a square
   icon) keeps its own aspect ratio instead of being cropped — same
   "contain, don't crop" intent as .pt-spinner__logo in
   page-transition.css. max-width caps it so a very wide logo doesn't
   overflow the sidebar's brand row. */
.admin-sidebar__brand-logo { height: 1.6rem; width: auto; max-width: 8rem; border-radius: 8px; object-fit: contain; flex-shrink: 0; transition: height 150ms ease, max-width 150ms ease; }

.admin-nav-group { margin-bottom: 0.9rem; }
.admin-nav-group__label { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.08em; opacity: 0.55; padding: 0 0.6rem 0.4rem; }
/* A plain separator line before every top-level sidebar group except
   the first — independent of the "گروه‌بندی" settings menu's own
   optional highlight/border box styling above (is-highlighted /
   is-bordered wrap a WHOLE group in a box; this is just a thin rule
   between consecutive groups, always on, in every "نحوه نمایش" mode). */
.admin-nav-group + .admin-nav-group { border-top: var(--glass-border); margin-top: 0.5rem; padding-top: 0.75rem; }

.admin-nav-link {
    display: flex; align-items: center; gap: 0.65rem; padding: 0.6rem 0.7rem; border-radius: 11px;
    text-decoration: none; color: var(--text); opacity: 0.78; font-size: 0.9rem; margin-bottom: 0.15rem;
    transition: background 150ms ease, opacity 150ms ease;
}
.admin-nav-link:hover { opacity: 1; background: color-mix(in srgb, var(--surface) 60%, transparent); }
/* The active link reads as its own small card — solid tinted fill,
   lifted with a shadow — rather than a two-color gradient wash, so it
   stands out the same deliberate way .stat-card/.panel already do. */
.admin-nav-link.is-active {
    opacity: 1;
    background: color-mix(in srgb, var(--primary) 16%, var(--surface));
    box-shadow: var(--card-shadow-sm);
}
/* Base size given an explicit value (was purely inherited from
   `.admin-nav-link`'s own 0.9rem font-size before) so there's
   something concrete to visibly shrink FROM once the sidebar reaches
   its icon-only floor — see the `.is-icon-only` rule right below. */
.admin-nav-link__icon { width: 1.15rem; text-align: center; flex-shrink: 0; font-size: 1.05rem; transition: font-size 200ms ease; }
/* Per request: once the sidebar is collapsed down to its icon-only
   floor (isSidebarIconOnly() in admin-app.js), the icons themselves
   read as visibly smaller/more compact, not just sitting in a
   narrower rail at their old full size — a real size reduction, not
   only a layout one. Narrowed a touch too (1.15rem -> 1rem) since a
   smaller glyph no longer needs the wider fixed box. */
.admin-sidebar.is-icon-only .admin-nav-link__icon { font-size: 0.85rem; width: 1rem; }

/* ---- Custom nav tooltip ----------------------------------------------
   Pure CSS, driven by the `data-tooltip` attribute set in
   layout.blade.php (only while the sidebar is icon-only — see that
   markup's own comment for why) — no extra JS/Alpine state, no
   teleporting, no positioning math: `content: attr(data-tooltip)`
   reads the label straight off the anchor element itself. Deliberately
   replaces the plain native `title` tooltip this used to be: a native
   tooltip can't be themed, is slow to appear, and looks completely out
   of place next to this panel's own glass aesthetic. `[data-tooltip]`
   (not `[data-tooltip]:not([data-tooltip=""])`) is enough on its own
   — when the Alpine binding evaluates to `null`, Alpine removes the
   attribute entirely rather than setting it to an empty string, so
   there is nothing to render and no empty bubble ever appears while
   the sidebar is expanded. */
.admin-nav-link[data-tooltip] { position: relative; }
.admin-nav-link[data-tooltip]::after,
.admin-nav-link[data-tooltip]::before {
    position: absolute;
    top: 50%;
    inset-inline-start: calc(100% + 0.65rem);
    opacity: 0;
    pointer-events: none;
    transition: opacity 140ms ease, transform 140ms ease;
    /* Popping OUT of the sidebar, not just off the icon, needs to
       clear every other floating layer already in this panel (the
       taskbar dock, dialogs, ...) — see window-manager.css's own
       z-index scale, this sits comfortably below its 9000+ overlays
       but above ordinary page content. */
    z-index: 500;
}
.admin-nav-link[data-tooltip]::after {
    content: attr(data-tooltip);
    transform: translateY(-50%) translateX(var(--tooltip-shift, -6px));
    white-space: nowrap;
    padding: 0.4rem 0.7rem;
    border-radius: 0.55rem;
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--text);
    background: color-mix(in srgb, var(--surface) 94%, transparent);
    border: var(--glass-border);
    box-shadow: var(--shadow-listbox);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
}
html[dir="rtl"] .admin-nav-link[data-tooltip]::after { --tooltip-shift: 6px; }
/* Small diamond nub connecting the bubble back to the icon — a plain
   rotated filled square (no border side-picking needed, which would
   depend on exact LTR/RTL rotation direction) tinted the same as the
   bubble itself, so it simply reads as the bubble's own corner peeking
   out toward the icon rather than a separate shape. */
.admin-nav-link[data-tooltip]::before {
    content: '';
    inset-inline-start: calc(100% + 0.35rem);
    width: 0.5rem;
    height: 0.5rem;
    transform: translateY(-50%) rotate(45deg) translateX(var(--tooltip-shift, -6px));
    background: color-mix(in srgb, var(--surface) 94%, transparent);
    box-shadow: var(--shadow-listbox);
}
.admin-nav-link[data-tooltip]:hover::after,
.admin-nav-link[data-tooltip]:hover::before,
.admin-nav-link[data-tooltip]:focus-visible::after,
.admin-nav-link[data-tooltip]:focus-visible::before {
    opacity: 1;
    --tooltip-shift: 0px;
}

.admin-main { flex: 1; min-width: 0; padding: 1rem 1.25rem 2rem; }

/* Design-system rule: every admin page's content sits inside a single
   fixed-width container instead of stretching the full main area — see
   layout.blade.php's `@yield('content')` wrapper. */
.admin-page-container { max-width: 1800px; margin-inline: auto; }

.admin-topbar {
    display: flex; align-items: center; justify-content: space-between; gap: 1rem;
    padding: 0.7rem 1.1rem; margin-bottom: 1.25rem; animation: glass-fade-in 320ms ease both;
    position: sticky; top: 1rem; z-index: 20;
}
.admin-topbar__title { font-size: 1.1rem; font-weight: 700; margin: 0; }
.admin-topbar__actions { display: flex; align-items: center; gap: 0.5rem; }
.admin-sidebar-toggle { display: inline-flex; }

/* Animated hamburger ⇄ close icon on the sidebar toggle button — three
   bars that morph into an X when the sidebar is expanded/open
   (`.is-active`, bound to `sidebarOpen || ! sidebarCollapsed` in
   admin.layout.blade.php) and back to a plain hamburger when it's
   collapsed/closed. Pure CSS transforms on each bar, no separate icon
   asset needed. */
.admin-sidebar-toggle__icon { position: relative; display: inline-block; width: 0.925rem; height: 0.675rem; }
.admin-sidebar-toggle__icon span {
    position: absolute; inset-inline-start: 0; width: 100%; height: 2px; border-radius: 2px;
    background: currentColor; transition: transform 260ms cubic-bezier(.4,0,.2,1), opacity 200ms ease, top 260ms cubic-bezier(.4,0,.2,1);
}
.admin-sidebar-toggle__icon span:nth-child(1) { top: 0; }
.admin-sidebar-toggle__icon span:nth-child(2) { top: 50%; transform: translateY(-50%); }
.admin-sidebar-toggle__icon span:nth-child(3) { top: 100%; transform: translateY(-100%); }
.admin-sidebar-toggle.is-active .admin-sidebar-toggle__icon span:nth-child(1) { top: 50%; transform: translateY(-50%) rotate(45deg); }
.admin-sidebar-toggle.is-active .admin-sidebar-toggle__icon span:nth-child(2) { opacity: 0; transform: translateY(-50%) scale(0); }
.admin-sidebar-toggle.is-active .admin-sidebar-toggle__icon span:nth-child(3) { top: 50%; transform: translateY(-50%) rotate(-45deg); }

/* Desktop: the toggle button shrinks the docked sidebar down to its
   icon-only floor (SIDEBAR_MIN_WIDTH in admin-app.js) instead of
   hiding it entirely or overlaying it (that's the <=920px behavior
   below, a real off-canvas drawer). `html[data-sidebar-collapsed="true"]`
   is set by the anti-flash script in admin.layout's <head> from the
   same 'admin:sidebar-collapsed' localStorage key adminShell() reads
   on init, so a returning admin's collapsed sidebar never flashes
   open before Alpine mounts and applies the reactive `.is-collapsed`
   class it takes over from there. `isSidebarIconOnly()` in
   admin-app.js also returns true whenever this class/attribute is
   set, so the same label/group-label/search-box hiding the drag-to-
   icon-width floor already gets kicks in here too. */
@media (min-width: 921px) {
    .admin-sidebar.is-collapsed,
    html[data-sidebar-collapsed="true"] .admin-sidebar {
        width: var(--sidebar-min-width, 76px) !important;
    }

    /* A more noticeable entrance than the shared, generic `glass-fade-in`
       every panel already uses — a short slide-in from the sidebar's own
       inline-start edge, desktop-only: on the <=920px layout the sidebar
       is an off-canvas drawer that starts translated fully off-screen
       (see the max-width query below), and a transform-based intro
       animation there would fight that resting `translateX` and flash
       the drawer open on every page load. RTL-aware (the sidebar sits on
       the visual right in RTL, so it slides in from the opposite side). */
    .admin-sidebar { animation: admin-sidebar-intro 480ms cubic-bezier(.16, .84, .44, 1) both; }
    html[dir="rtl"] .admin-sidebar { animation-name: admin-sidebar-intro-rtl; }
}
@keyframes admin-sidebar-intro {
    from { opacity: 0; transform: translateX(-18px) scale(0.98); }
    to { opacity: 1; transform: translateX(0) scale(1); }
}
@keyframes admin-sidebar-intro-rtl {
    from { opacity: 0; transform: translateX(18px) scale(0.98); }
    to { opacity: 1; transform: translateX(0) scale(1); }
}

/* Pinned to the same --control-height every .input/.select/.btn in
   the rest of the panel already shares (see that variable's own
   docblock above .btn) — before this, .glass-icon-button's own fixed
   2.25rem and .glass-select's/.color-preset-trigger's padding-derived
   height both drifted from it and from each other, so the topbar's
   sidebar-toggle/dark-mode/fullscreen icon buttons, the color-preset
   picker and the language <select> never actually sat on one clean
   row the way a toolbar's inputs+buttons do everywhere else in the
   panel. */
.glass-icon-button {
    background: color-mix(in srgb, var(--primary) 18%, transparent); border: var(--glass-border); border-radius: 999px;
    width: var(--control-height); height: var(--control-height); display: inline-flex; align-items: center; justify-content: center;
    cursor: pointer; transition: transform 150ms ease, background var(--glass-transition); color: var(--text);
}
.glass-icon-button:hover:not(:disabled) { transform: scale(1.08); background: color-mix(in srgb, var(--primary) 32%, transparent); }

/* Square variant — same sizing/colors as .glass-icon-button above, just
   a minimal rounded-rectangle instead of a full pill, for the "🔗 لینک
   دریافت شد" reveal row's own 📋/↗️ buttons (both in a finished
   export's own result row and in an export-history row's link reveal
   — see table-export-dialog.blade.php) per the admin's own "دکمه‌ی
   مربعی" request. */
.glass-icon-button--square { border-radius: 8px; }

/* .glass-select is the wrapping <label>/container (color-preset-trigger
   already draws its own caret via .color-preset-caret; this is for a
   plain native <select> like the topbar language picker) — chevron
   drawn once on the wrapper via ::after since a native <select> has
   unreliable ::after support across engines. appearance:none on the
   <select> itself removes the OS's own arrow so this is the only one
   shown. */
.glass-select { position: relative; display: inline-flex; align-items: center; min-height: var(--control-height); background: color-mix(in srgb, var(--surface) 80%, transparent); color: var(--text); border: var(--glass-border); border-radius: 10px; }
/* :has(select) scopes this to a real <select> wrapper only — the
   admin's color-preset trigger button also carries the base
   .glass-select class for its background/border but draws its own
   caret via .color-preset-caret, and has no <select> inside, so it's
   naturally excluded here instead of getting a duplicate arrow. */
.glass-select:has(select)::after { content: '▾'; position: absolute; inset-inline-end: 0.65rem; font-size: 0.68rem; opacity: 0.6; pointer-events: none; }
.glass-select select {
    appearance: none; -webkit-appearance: none;
    background: transparent; color: inherit; border: none; border-radius: inherit;
    width: 100%; height: 100%;
    padding: 0.4rem 1.75rem 0.4rem 0.6rem; padding-inline-end: 1.75rem;
    cursor: pointer;
}
/* No browser-default focus ring on the topbar language <select> — the
   admin explicitly doesn't want the blue outline a picked option
   leaves around this control. */
.glass-select select:focus, .glass-select select:focus-visible { outline: none; box-shadow: none; }

.color-preset-select { position: relative; display: inline-block; }
.color-preset-trigger {
    display: inline-flex; align-items: center; gap: 0.45rem; cursor: pointer;
    min-height: var(--control-height); padding: 0.6rem 0.85rem;
    font: inherit; color: var(--text);
}
.color-preset-caret { font-size: 0.7rem; opacity: 0.65; }
.color-preset-swatch {
    display: inline-block; flex: none;
    width: 0.85rem; height: 0.85rem; border-radius: 50%;
    background: var(--primary);
    border: 0px;
    box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.08);
}
.color-preset-menu {
    position: absolute; inset-inline-start: 0; top: calc(100% + 0.5rem);
    /* Viewport-relative safety net: same class of "part of the menu
       rendered past the screen edge and got clipped/unclickable by
       html,body's overflow-x:hidden" bug found and fixed on the
       storefront's identical widget (see theme.css's own comment on
       its .color-preset-menu) — added here defensively too, since the
       admin topbar can get just as narrow on small viewports. */
    min-width: 11rem; max-width: min(16rem, calc(100vw - 2rem));
    max-height: 16rem; overflow-y: auto;
    list-style: none; margin: 0; padding: 0.4rem; z-index: 50;
    animation: glass-scale-in 150ms ease both;
}
.color-preset-option {
    display: flex; align-items: center; gap: 0.55rem;
    padding: 0.5rem 0.6rem; border-radius: 8px; cursor: pointer;
    font-size: 0.88rem; text-transform: capitalize;
}
.color-preset-option:hover,
.color-preset-option:focus-visible { background: color-mix(in srgb, var(--primary) 16%, transparent); outline: none; }
.color-preset-option.is-selected { background: color-mix(in srgb, var(--primary) 24%, transparent); font-weight: 600; }
/* Grid layout for the preset picker (swatch-only, no palette name —
   per request): options wrap into a grid of plain color circles
   instead of a vertical name list. */
.color-preset-menu--grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(2.6rem, 1fr));
    gap: 0.5rem;
}
.color-preset-option--grid {
    justify-content: center;
    padding: 0.5rem;
}
.color-preset-option--grid .color-preset-swatch {
    width: 1.6rem; height: 1.6rem;
}
.color-preset-option--grid.is-selected {
    box-shadow: 0 0 0 2px var(--primary);
}

.avatar-menu { position: relative; }
/* Header account button shows the signed-in admin's own picture (see
   admin.layout's avatar-menu) once one is set — same circular
   .glass-icon-button footprint, just clipped/filled with the photo
   instead of the initial-letter fallback. */
.header-avatar-button {
    padding: 0 !important;
    overflow: hidden;
    /* Pin the button itself to the same fixed circle every other
       .glass-icon-button uses — without this, the flex-laid-out <img>
       inside (see __img below) had nothing definite to size itself
       against and rendered at its own natural upload resolution,
       poking out past the intended small header badge. */
    width: var(--control-height) !important;
    height: var(--control-height) !important;
    flex-shrink: 0;
}
.header-avatar-button__img {
    display: block;
    width: 100%;
    height: 100%;
    max-width: 100%;
    max-height: 100%;
    object-fit: cover;
    border-radius: 50%;
}
.avatar-menu__panel.avatar-menu__panel__top {
    bottom: calc(100% + 0.5rem);
    top: unset;
}
.avatar-menu__panel {
    position: absolute; inset-inline-end: 0; top: calc(100% + 0.5rem); min-width: 200px;
    padding: 0.5rem; z-index: 50; animation: glass-scale-in 150ms ease both;
}
.avatar-menu__panel a, .avatar-menu__panel button {
    display: flex; width: 100%; text-align: start; gap: 0.5rem; align-items: center;
    background: none; border: none; color: var(--text); padding: 0.55rem 0.6rem; border-radius: 8px;
    cursor: pointer; font-size: 0.88rem; text-decoration: none;width: fit-content;
}
.avatar-menu__panel a:hover, .avatar-menu__panel button:hover { background: color-mix(in srgb, var(--primary) 16%, transparent); }

/* Notification bell (Modules\Notifications) — a wider .avatar-menu__panel
   variant hosting a scrollable list instead of a few link/button rows. */
.notification-bell__badge {
    position: absolute; top: -0.15rem; inset-inline-end: -0.15rem;
    min-width: 1.15rem; height: 1.15rem; padding: 0 0.25rem; border-radius: 999px;
    background: #ef4444; color: #fff; font-size: 0.62rem; font-weight: 700;
    display: flex; align-items: center; justify-content: center; line-height: 1;
}
.notification-bell .glass-icon-button { position: relative; }
.notification-bell__panel { width: min(24rem, calc(100vw - 2rem)); padding: 0; overflow: hidden; }
/* Periodic hard shake on the header bell (`.is-ringing`, added via
   Alpine's `:class` whenever notificationsUnreadCount > 0 — see
   admin.layout) so an unread notification is actually noticeable, not
   just a static badge. Mostly idle (rotate(0) through 92% of the 4s
   cycle) with the shake itself packed into the last ~8% so it reads
   as a sharp, repeating "ring" rather than a constant wobble. */
@keyframes notification-bell-shake {
    0%, 90%, 100% { transform: rotate(0deg); }
    91% { transform: rotate(-16deg); }
    92% { transform: rotate(14deg); }
    93% { transform: rotate(-12deg); }
    94% { transform: rotate(10deg); }
    95% { transform: rotate(-8deg); }
    96% { transform: rotate(6deg); }
    97% { transform: rotate(-3deg); }
    98% { transform: rotate(2deg); }
    99% { transform: rotate(0deg); }
}
.notification-bell__icon { display: inline-block; transform-origin: 50% 0; }
.notification-bell__icon.is-ringing { animation: notification-bell-shake 4s ease-in-out infinite; }

/* "🗑️ مشاهده شدند" clear-all button — width hugs its own content
   (inline-flex, no forced min-width unlike the generic `.btn--xs`
   default above) with a smaller font, grey/white idle colors, and a
   hover state that swaps to the panel's own theme color instead of
   `.btn--ghost`'s transparent hover. */
.notification-bell__clear-all {
    display: inline-flex; align-items: center; gap: 0.3rem; width: fit-content; min-width: 0;
    font-size: 0.68rem; background: #64748b; color: #fff; border: none;
}
.notification-bell__clear-all:hover { background: var(--primary); }

.notification-bell__header {
    display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
    padding: 0.7rem 0.85rem; border-bottom: var(--glass-border);
}
.notification-bell__list { max-height: 22rem; overflow-y: auto; padding: 0.35rem; margin-bottom: 5px; }
/* Bottom-corner "refresh" action — see refreshNotifications() in
   admin-app.js. Own footer row (border-top, same shape as the header
   above) so the button sits pinned to the panel's bottom edge rather
   than scrolling away with the list. */
.notification-bell__footer {
    display: flex; justify-content: flex-end; padding: 0.4rem 0.6rem;
    border-top: var(--glass-border);
}
.notification-bell__refresh {
    display: inline-flex; align-items: center; gap: 0.35rem;
    padding: 0.3rem 0.6rem; border-radius: 999px; border: none;
    background: transparent; cursor: pointer; font-size: 0.78rem;
    transition: background var(--glass-transition);
}
.notification-bell__refresh:hover { background: color-mix(in srgb, var(--primary) 12%, transparent); }
.notification-bell__refresh.is-spinning .notification-bell__refresh-icon { display: inline-block; animation: notification-bell-spin 0.8s linear infinite; }
.notification-bell__refresh.is-spinning .notification-bell__refresh-label { color: #94a3b8; }
@keyframes notification-bell-spin { to { transform: rotate(360deg); } }
.notification-bell__empty { padding: 1.25rem 0.5rem; text-align: center; font-size: 0.85rem; opacity: 0.6; margin: 0; }
.notification-bell__item {
    display: flex; align-items: flex-start; padding: 0.6rem 0.55rem;
    border-radius: 9px; cursor: pointer; transition: background var(--glass-transition);
}
.notification-bell__item:hover { background: color-mix(in srgb, var(--primary) 12%, transparent); }
.notification-bell__item.is-unread { background: color-mix(in srgb, var(--primary) 8%, transparent); }
.notification-bell__item-body { min-width: 0; flex: 1 1 auto; }
/* Icon sits INLINE with the title text itself (same row, small gap)
   instead of as its own column aligned with the whole title+message
   block — see resources/views/admin/layout.blade.php's own markup. */
.notification-bell__item-title { display: flex; align-items: center; gap: 0.4rem; font-size: 0.85rem; font-weight: 600; }
.notification-bell__item-icon { flex: 0 0 auto; font-size: 1.05em; line-height: 1.4; }
/* Same circular badge look as `.notification-bell__badge` on the bell
   icon itself (below), just inline instead of absolutely positioned —
   the per-item "N pending" count (see notificationCount() in
   admin-app.js), with a native `title` tooltip on hover. */
.notification-bell__item-badge {
    flex: 0 0 auto; min-width: 1.15rem; height: 1.15rem; padding: 0 0.3rem; border-radius: 999px;
    background: #ef4444; color: #fff; font-size: 0.62rem; font-weight: 700;
    display: inline-flex; align-items: center; justify-content: center; line-height: 1;
}
.notification-bell__item-message { font-size: 0.78rem; opacity: 0.75; margin-top: 0.15rem; }
.notification-bell__item--danger .notification-bell__item-title { color: #ef4444; }
.notification-bell__item--warning .notification-bell__item-title { color: #f59e0b; }
/* The item's own "✕" dismiss button (dismissNotification() in
   admin-app.js) — sits at the END of the row (row is a flex container,
   this is its last child), so in RTL (fa/ar) that's visually the LEFT
   edge and in LTR (en/ru) the RIGHT edge, matching how the rest of this
   panel already lets flow direction place trailing actions. */
.notification-bell__item-dismiss {
    flex: 0 0 auto; align-self: flex-start; margin-inline-start: 0.35rem;
    width: 1.35rem; height: 1.35rem; border: 0; border-radius: 999px; background: transparent;
    color: inherit; opacity: 0.45; cursor: pointer; font-size: 0.75rem; line-height: 1;
    display: inline-flex; align-items: center; justify-content: center; transition: opacity var(--glass-transition), background var(--glass-transition);
}
.notification-bell__item-dismiss:hover { opacity: 1; background: transparent !important;}

/* Generic per-field "❓ help" button (a field's own `help_modal` — see
   plugin-settings/show.blade.php's own openFieldHelpModal()) + the
   step-by-step modal it opens. */
.field-help-button {
    display: inline-flex; align-items: center; justify-content: center;
    width: 1.15rem; height: 1.15rem; margin-inline-start: 0.35rem;
    border-radius: 50%; border: var(--glass-border); background: transparent;
    color: var(--text); font-size: 0.65rem; line-height: 1; cursor: pointer;
    opacity: 0.75; vertical-align: middle;
}
.field-help-button:hover { opacity: 1; background: color-mix(in srgb, var(--primary) 16%, transparent); }
.field-help-steps { display: flex; flex-direction: column; gap: 0.9rem; margin: 0; padding-inline-start: 1.3rem; }
.field-help-steps__item { font-size: 0.88rem; }
.field-help-steps__title { margin: 0 0 0.2rem; font-weight: 700; }
.field-help-steps__body { margin: 0; opacity: 0.85; line-height: 1.6; }
.field-help-steps__code {
    margin: 0.5rem 0 0; padding: 0.55rem 0.7rem; border-radius: 8px;
    border: var(--glass-border); background: color-mix(in srgb, var(--surface) 55%, transparent);
    font-family: monospace; font-size: 0.78rem; overflow-x: auto; white-space: pre;
}

/* System-wide admin notice banner (Modules\Notifications) — shown on
   the dashboard and, per-module, in plugin-settings screens. */
.alert-banner {
    display: flex; align-items: flex-start; gap: 0.65rem;
    padding: 0.85rem 1rem; border-radius: 12px; margin-bottom: 1rem;
    border: 1px solid transparent;
}
.alert-banner--danger { background: color-mix(in srgb, #ef4444 14%, transparent); border-color: color-mix(in srgb, #ef4444 35%, transparent); }
.alert-banner--warning { background: color-mix(in srgb, #f59e0b 14%, transparent); border-color: color-mix(in srgb, #f59e0b 35%, transparent); }
.alert-banner--info { background: color-mix(in srgb, var(--primary) 12%, transparent); border-color: color-mix(in srgb, var(--primary) 30%, transparent); }
.alert-banner__icon { font-size: 1.2em; line-height: 1.3; }
.alert-banner__title { font-weight: 700; font-size: 0.92rem; }
.alert-banner__message { font-size: 0.85rem; opacity: 0.85; margin-top: 0.2rem; }

/* ---- Stat cards / dashboard ---- */
/* `auto-fit`, not `auto-fill`: with a sparse row (e.g. the dashboard's
   "دسترسی سریع"/quick-links or CRM-pipeline panels, 4 cards each in a
   grid that can be up to 1800px wide per .admin-page-container),
   `auto-fill` still reserves as many empty 210px tracks as fit the
   container and gives them their 1fr share too, squeezing the real
   cards into a narrow column and leaving a large blank gap. `auto-fit`
   collapses those empty tracks to 0 so the real cards' 1fr tracks
   expand to fill the row instead — identical result to `auto-fill`
   once there are enough cards to fill every track anyway. */
.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 1rem; margin-bottom: 1.5rem; }

/* Themes admin screen: capped at 4 cards per row regardless of viewport
   width (unlike .stat-grid's uncapped auto-fill), collapsing down on
   narrower screens. */
.theme-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; margin-bottom: 1.5rem; }
@media (max-width: 1100px) { .theme-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 640px) { .theme-grid { grid-template-columns: 1fr; } }

/* Dashboard's "تاریخچه بازدید صفحات" + "دسترسی سریع" pair, side by
   side: 1/3 of the page width each on desktop (a fixed 3-column grid,
   not auto-fit — these two panels only ever fill 2 of the 3 columns,
   which is the point: each stays a third of the row instead of
   stretching to half), half-half on tablet, full width/stacked on
   phone. Child `.panel`s drop their own margin-bottom since the
   grid's `gap` already spaces them. */
.dashboard-side-panels { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.25rem; margin-bottom: 1.5rem; }
.dashboard-side-panels > .panel { margin-bottom: 0; }
@media (max-width: 1024px) { .dashboard-side-panels { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 640px) { .dashboard-side-panels { grid-template-columns: 1fr; } }

/* CRM sales-funnel widget (قیف فروش) on the admin dashboard — one
   labelled rung per Modules\CRM\Support\LeadStatus stage. Pure CSS
   bars on purpose: the widget must render identically whether or not
   Modules\Charts is enabled. Both the track and the fill are derived
   from the live theme tokens (--text / --primary / --surface), so they
   follow every color preset and both light and dark mode — no literal
   color anywhere. */
.funnel-stage { display: flex; flex-direction: column; gap: 0.3rem; }
.funnel-stage__head { display: flex; align-items: baseline; justify-content: space-between; gap: 0.5rem; }
.funnel-stage__value { font-size: 0.75rem; opacity: 0.8; }
.funnel-bar {
    width: 100%; height: 0.5rem; border-radius: 999px; overflow: hidden;
    background: color-mix(in srgb, var(--text) 10%, var(--surface));
}
.funnel-bar__fill {
    height: 100%; border-radius: 999px;
    background: linear-gradient(90deg, var(--primary), color-mix(in srgb, var(--secondary) 70%, var(--primary)));
    transition: width 0.25s ease;
}

/* Square by design (aspect-ratio, not just a fixed height) — a "glass"
   placeholder thumbnail since scaffolded themes have no uploaded photo;
   generated from the theme's own primary/secondary colors (see the
   inline gradient style wherever this class is used). */
.theme-card__banner {
    width: 3.5rem; height: 3.5rem; aspect-ratio: 1 / 1; flex-shrink: 0;
    border-radius: 12px;
    border: var(--glass-border);
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
}
.theme-card__swatch {
    width: 0.85rem; height: 0.85rem; border-radius: 999px;
    border: 1px solid color-mix(in srgb, var(--text) 15%, transparent);
    box-shadow: 0 0 0 1px color-mix(in srgb, var(--bg) 60%, transparent);
    display: inline-block; cursor: pointer;
}

/* Floating "back to top" button — see #back-to-top in admin.layout.blade.php.
   Vertical stack (icon above nothing else) by design; if this ever renders
   as a wide horizontal bar, check that no other rule is overriding
   `width`/`display` on this id — it must stay a fixed-size circle. */
.back-to-top {
    position: fixed; inset-block-end: 1.5rem; inset-inline-end: 1.5rem; z-index: 40;
    width: 2.75rem; height: 2.75rem; border-radius: 999px;
    display: flex; align-items: center; justify-content: center;
    background: color-mix(in srgb, var(--surface) 85%, transparent);
    backdrop-filter: blur(var(--glass-blur)); -webkit-backdrop-filter: blur(var(--glass-blur));
    border: var(--glass-border); box-shadow: var(--shadow-float);
    color: var(--text); font-size: 1.2rem; line-height: 1; cursor: pointer;
    opacity: 0; pointer-events: none; transform: translateY(10px);
    transition: opacity 200ms ease, transform 200ms ease;
}
.back-to-top.is-visible { opacity: 1; pointer-events: auto; transform: translateY(0); }
.back-to-top:hover { transform: translateY(-3px); }
.stat-card { padding: 1.1rem 1.25rem; animation: glass-rise-in 320ms ease both; }
.stat-card__label { font-size: 0.8rem; opacity: 0.7; margin-bottom: 0.4rem; }
.stat-card__value { font-size: 1.7rem; font-weight: 700; }
.stat-card__sub { font-size: 0.78rem; opacity: 0.6; margin-top: 0.2rem; }
.stat-card { transition: background 150ms ease; }
.stat-card:hover { background: color-mix(in srgb, var(--primary) 18%, var(--surface)); }
.stat-card--link { text-decoration: none; color: var(--text); display: block; transition: transform 150ms ease, background 150ms ease; }
.stat-card--link:hover { transform: translateY(-3px); }

/* ---- Module Settings card grid ---- */
.module-settings-card { text-align: center; cursor: pointer; transition: transform 150ms ease; display: flex; flex-direction: column; align-items: center; gap: 0.5rem; }
.module-settings-card:hover { transform: translateY(-3px); }
.module-settings-card__icon { width: 3.25rem; height: 3.25rem; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 1.6rem; background: color-mix(in srgb, var(--primary) 16%, transparent); margin-bottom: 0.25rem; }
.module-settings-card__caption { font-size: 0.75rem; opacity: 0.6; margin: 0; }
.module-settings-card.is-disabled { opacity: 0.5; cursor: not-allowed; }
.module-settings-card.is-disabled:hover { transform: none; }
.module-settings-card.is-disabled .module-settings-card__icon { filter: grayscale(1) opacity(0.5); }
.module-settings-card.is-disabled .stat-card__value { opacity: 0.6; }
.module-settings-card__notice {
    display: flex; align-items: center; gap: 0.3rem; align-self: stretch;
    padding: 0.25rem 0.5rem; border-radius: 7px; font-size: 0.72rem; font-weight: 600;
    cursor: default; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.module-settings-card__notice--danger { background: color-mix(in srgb, #ef4444 18%, transparent); color: #ef4444; }
.module-settings-card__notice--warning { background: color-mix(in srgb, #f59e0b 18%, transparent); color: #f59e0b; }
.module-settings-card__plugins { display: flex; flex-wrap: wrap; justify-content: center; gap: 0.3rem; margin-top: 0.15rem; }
/* Smaller title than the shared .stat-card__value (1.7rem, used
   elsewhere e.g. dashboard stat cards) — module-settings card/detail
   titles read as tile labels, not headline numbers, so they're scoped
   down here instead of touching the shared rule above. */
.module-settings-card .stat-card__value { font-size: 1.15rem; }
.badge--outline { background: transparent; border: 1px solid color-mix(in srgb, var(--text) 25%, transparent); }

/* Windows-tile-style grid: fixed-size compact cards that pack from the
   start of the row, instead of .stat-grid's default `1fr` columns
   (which stretch every card to fill the full row width — fine for a
   handful of wide dashboard stat cards, wrong for a module picker that
   should read as a grid of app tiles). */
.module-settings-grid { grid-template-columns: repeat(auto-fill, 220px); justify-content: start; }
@media (max-width: 520px) {
    .module-settings-grid { grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); justify-content: initial; }
}

.section-heading { font-size: 0.95rem; font-weight: 600; opacity: 0.75; margin: 1.5rem 0 0.75rem; }
.section-heading:first-child { margin-top: 0; }

.panel { padding: 1.25rem; margin-bottom: 1.25rem; animation: glass-rise-in 320ms ease both; }
.panel__header { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-bottom: 1rem; flex-wrap: wrap; }
.panel__title { margin: 0; font-size: 1.05rem; }

/* ---- Data table ---- */
/* Single row, always — a toolbar that wraps to a second line is what
   made some buttons look tall/stacked instead of square; this scrolls
   horizontally on a narrow viewport instead of wrapping. */
.toolbar { display: flex; gap: 0.75rem; flex-wrap: nowrap; align-items: center; margin-bottom: 1rem; overflow-x: auto; }
.toolbar .input { max-width: 280px; flex-shrink: 0; }

.table-wrap { overflow-x: auto; border-radius: var(--glass-radius); }
table.data-table { width: 100%; border-collapse: collapse; font-size: 0.88rem; min-width: 560px; }
.data-table th, .data-table td { padding: 0.7rem 0.85rem; text-align: start; vertical-align: middle; border-bottom: var(--glass-border); white-space: nowrap; }
.data-table th { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.04em; opacity: 0.6; font-weight: 600; }
.data-table tbody tr { transition: background 150ms ease; }
.data-table tbody tr:hover { background: color-mix(in srgb, var(--primary) 6%, transparent); }
/* min-width keeps a locked row's empty actions <template x-if> from
   collapsing that column narrower than rows that DO render buttons —
   without it the table visibly reflows/shifts between locked and
   unlocked rows. */
/* display:flex was missing — gap alone does nothing on a plain
   table-cell, so action buttons/links (<a>/<button> siblings dropped
   directly into td.is-actions across every admin listing page) sat
   flush against each other with zero spacing. */
.data-table td.is-actions { display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; min-width: 13rem; }
.data-table td.wrap { white-space: normal; }

/* End-of-row "⋮" trigger — opens the SAME row context menu as
   right-clicking the row (openContextMenu(), see
   table-context-menu.blade.php), just discoverable without knowing to
   right-click. Desktop/mouse: hidden until the row is hovered/focused
   (kept out of the way of dense table text) — touch/tablet
   (`pointer: coarse`, no hover to reveal it) always shows it, per the
   task brief ("در گوشی و تبلت ثابت ... در ویندوز وقتی هاور می‌شود"). */
.data-table td.is-row-menu { width: 1%; text-align: center; }
.row-menu-trigger {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.4rem;
    height: 2.4rem;
    border: var(--glass-border);
    border-radius: 999px;
    background: transparent;
    color: inherit;
    font-size: 1.35rem;
    line-height: 1;
    cursor: pointer;
    opacity: 0;
    transition: border-color 120ms ease, color 120ms ease, opacity 120ms ease;
}
.data-table tbody tr:hover .row-menu-trigger,
.data-table tbody tr:focus-within .row-menu-trigger,
.row-menu-trigger:focus-visible {
    opacity: 0.7;
}
.row-menu-trigger:hover,
.row-menu-trigger:focus-visible { opacity: 1; border-color: var(--primary); color: var(--primary); }
@media (pointer: coarse) {
    .row-menu-trigger { opacity: 0.7; }
}

/* The small "1 USD = ..." conversion line under a rate value must
   occupy the same line-height whether it renders text or the '—'
   placeholder, so every row's height stays identical regardless of
   content length. */
.conversion-text { min-height: 1.3em; }

/* Range/zoom button rows (exchange-rate history chart and any future
   reuse) — explicit gap using the app's existing 0.4rem/0.5rem
   spacing scale (matches .is-actions/.toolbar) rather than relying on
   default inline-button adjacency, plus baseline alignment so mixed
   icon/text buttons in the same group don't drift vertically. */
.btn-group { display: flex; align-items: center; flex-wrap: nowrap; gap: 0.4rem; }

/* Mode-column popover (click/hover "who changed this" info) — reuses
   the same glass/menu vocabulary as .color-preset-menu. */
.mode-popover { position: relative; display: inline-block; cursor: pointer; }
.mode-popover__panel {
    position: absolute; inset-inline-start: 0; top: calc(100% + 0.4rem);
    min-width: 14rem; z-index: 60; padding: 0.65rem 0.8rem;
    font-size: 0.78rem; line-height: 1.5; white-space: normal;
    animation: glass-scale-in 150ms ease both;
}

/* History drill-down breadcrumb ("۲۰۲۶ ← اسفند ← ۱۴") */
.drill-breadcrumb { display: flex; align-items: center; flex-wrap: wrap; gap: 0.35rem; font-size: 0.82rem; opacity: 0.85; margin-bottom: 0.5rem; }
.drill-breadcrumb button { background: none; border: none; color: var(--primary); cursor: pointer; padding: 0; font: inherit; text-decoration: underline; }
.drill-breadcrumb span.sep { opacity: 0.5; }

/* Chart.js legend (multi-currency comparison mode) rendered below the
   canvas via a plain HTML legend so its colors follow this app's
   theme variables instead of Chart.js's own hardcoded defaults. */
.chart-legend { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 0.75rem; font-size: 0.82rem; }
.chart-legend__item { display: inline-flex; align-items: center; gap: 0.4rem; }
.chart-legend__swatch { width: 0.7rem; height: 0.7rem; border-radius: 3px; flex-shrink: 0; }

.chart-range-caption__date { background: none; border: none; padding: 0; color: inherit; font: inherit; cursor: pointer; text-decoration: underline dotted; text-underline-offset: 2px; }
.chart-range-caption__date:hover { color: var(--primary); }

.badge { display: inline-flex; align-items: center; gap: 0.3rem; padding: 0.2rem 0.55rem; border-radius: 999px; font-size: 0.68rem; font-weight: 700; letter-spacing: 0.02em; text-transform: uppercase; }
.badge--ok { background: color-mix(in srgb, #22c55e 30%, transparent); color: #22c55e; }
.badge--warn { background: color-mix(in srgb, #f59e0b 30%, transparent); color: #f59e0b; }
.badge--danger { background: color-mix(in srgb, #ef4444 25%, transparent); color: #ef4444; }
.badge--muted { background: color-mix(in srgb, var(--text) 14%, transparent); color: var(--text); opacity: 0.75; }
.badge--info { background: color-mix(in srgb, var(--primary) 25%, transparent); color: var(--text); }

.empty-state { text-align: center; padding: 3rem 1.5rem; opacity: 0.8; }
.empty-state__icon { font-size: 2.25rem; margin-bottom: 0.5rem; }

.skeleton-row td { padding: 0.5rem 0.85rem; }
/* Both the base fill and the shimmer highlight are mixed against
   `--text` (not a fixed near-white) so they stay visible in EITHER
   theme: `--text` is near-white in dark mode and near-black in light
   mode (see html[data-theme-mode] above), so mixing a small percentage
   of it into `--surface` always reads as a subtly-darker-than-surface
   block, and mixing a larger percentage into transparent always reads
   as a visible sweep — instead of the old fixed-white shimmer, which
   was correctly faint against dark mode's own dark --surface but
   nearly invisible against light mode's near-white one (both the block
   and its shimmer would all but disappear into the panel behind them).
   Shared by every `.skeleton` use in the panel — Modules\Tables's own
   `.skeleton-row` table placeholders and
   resources/views/components/form-skeleton.blade.php's form skeleton
   both get the fix for free. */
.skeleton { position: relative; overflow: hidden; height: 0.9rem; border-radius: 6px; background: color-mix(in srgb, var(--text) 10%, var(--surface)); }
.skeleton::after { content: ''; position: absolute; inset: 0; transform: translateX(-100%); background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--text) 22%, transparent), transparent); animation: glass-shimmer 1.4s ease-in-out infinite; }

/* Product form's own `loading` skeleton (resources/views/admin/products/products/form.blade.php)
   — shown instead of the real form while init()'s Promise.all (categories/
   brands/currencies/warehouses/the product itself on edit) is still in
   flight, so an edit page never flashes an empty panel before the data
   arrives. Every block below is just `.skeleton` shaped/sized to loosely
   mirror the real form's own layout (tabs row, label+input pairs, a wide
   textarea, a final button row) — purely decorative, no real markup
   underneath it. */
.form-skeleton__tabs { display: flex; gap: 0.5rem; margin-bottom: 1rem; }
.form-skeleton__tabs .skeleton { height: 2rem; width: 6rem; border-radius: 8px; }
.form-skeleton__field { margin-bottom: 1.1rem; }
.form-skeleton__field .skeleton + .skeleton { margin-top: 0.5rem; }
.form-skeleton__label.skeleton { height: 0.75rem; width: 30%; }
.form-skeleton__input.skeleton { height: 2.5rem; width: 100%; border-radius: 10px; }
.form-skeleton__input--wide.skeleton { height: 6rem; }
.form-skeleton__row { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1rem 1.25rem; }
.form-skeleton__actions { display: flex; gap: 0.75rem; margin-top: 1.5rem; }
.form-skeleton__actions .skeleton { height: 2.5rem; width: 8rem; border-radius: 10px; }

/* Modules\Tables — smart-table search box + loading-censor overlay.
   `.table-wrap` is the positioning anchor for the overlay below; every
   smart-table page already wraps its <table> in it (pre-existing
   class, just made a positioned container here). */
.table-wrap { position: relative; }

/* Table-id badge — injected once per table by _mountTableIdBadge()
   (public/admin-assets/js/smart-table.js). Faint (50% opacity) until
   hovered/focused so it never competes with the table's own content,
   brightening only when an admin is actually about to click it to
   copy. */
.smart-table-id-badge {
    display: inline-block;
    width: fit-content;
    padding: 0.1rem 0.45rem;
    border: none;
    border-radius: 999px;
    background: color-mix(in srgb, var(--surface) 88%, transparent);
    color: var(--text-muted, var(--text));
    font-size: 0.65rem;
    font-family: monospace;
    direction: ltr;
    opacity: 0.5;
    cursor: pointer;
    flex-shrink: 0;
    transition: opacity 140ms ease;
}
.smart-table-id-badge:hover,
.smart-table-id-badge:focus-visible {
    opacity: 1;
}

/* Shares one line with `.smart-table-results-summary` (the "نمایش
   :count از :total مورد" line) — the badge sits at the opposite end
   of that same row via `justify-content:space-between`, so it reads
   as "results count … table id" instead of a floating pill. On a
   narrow/phone viewport there isn't room for both on one line without
   either truncating the results text or shrinking the badge past
   legibility, so it drops back to stacking them on separate lines
   (badge below the results line) instead. */
.smart-table-id-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.6rem;
}
.smart-table-id-row > .smart-table-results-summary { margin: 0.75rem 0 0; flex: 1 1 auto; min-width: 0; }
@media (max-width: 640px) {
    .smart-table-id-row { flex-direction: column; align-items: flex-start; gap: 0.3rem; }
    .smart-table-id-row > .smart-table-id-badge { margin-inline-start: 0; }
}

/* Fallback placement (see _mountTableIdBadge()'s own docblock) for a
   table with no `.smart-table-results-summary` line to share — pinned
   to `.table-wrap`'s own bottom corner instead: `inset-inline-start`
   lands it bottom-LEFT in LTR panels, bottom-RIGHT in RTL ones.
   `position: sticky` (not `absolute`) so it stays pinned to the
   visible corner even while the wrap is horizontally scrolled, rather
   than scrolling off with the table content. */
.table-wrap > .smart-table-id-badge {
    position: sticky;
    inset-inline-start: 0.4rem;
    bottom: 0.35rem;
    display: block;
    margin-top: 0.35rem;
    margin-inline-start: 0.4rem;
}

/* Every smart-table header cell is drag-to-reorder — the move cursor
   is the only visual hint of that before the admin ever tries it. */
.data-table thead th[draggable="true"] { cursor: move; }

/* Minimal, attractive search-box feel — a soft focus glow instead of
   the browser default outline, applied globally to every admin search
   box (no per-page markup needed). */
input[type="search"].input {
    transition: box-shadow 180ms ease, border-color 180ms ease, background 180ms ease;
}
input[type="search"].input:focus {
    border-color: color-mix(in srgb, var(--primary) 55%, transparent);
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--primary) 16%, transparent);
    outline: none;
}

/* Smart-table search box — starts at a modest 32-character width (it
   sits next to a row of other toolbar controls, so it shouldn't hog
   the header) and grows to a roomy 90 characters while the admin is
   actually typing in it (focused) or has typed something into it
   (`.is-expanded`, toggled from `search` being non-empty) — shrinking
   back once it's blurred AND emptied. `ch` units track the font's own
   average character width, so "N characters" stays true across
   locales/fonts rather than a hardcoded px guess. The open/close is a
   deliberately visible animation (not just a focus-ring flash), with a
   little overshoot-free ease so it reads as "growing", not "snapping". */
.smart-table-search {
    width: 32ch;
    flex: 0 0 auto;
    transition: width 260ms cubic-bezier(0.22, 1, 0.36, 1);
    padding-inline-end: 2rem;
}
.smart-table-search:focus,
.smart-table-search.is-expanded {
    width: 90ch;
    max-width: 100%;
}

/* Wrapper anchoring the ✕ clear button to the search box's own end
   (start in RTL) — the button sits ON the input rather than beside it,
   so growing/shrinking the input doesn't shove a separate element
   around; `padding-inline-end` above just reserves room so typed text
   never runs under the button. */
.smart-table-search-wrap {
    position: relative;
    flex: 0 0 auto;
}
.smart-table-search-clear {
    position: absolute;
    inset-inline-end: 0.4rem;
    top: 50%;
    transform: translateY(-50%);
    width: 1.4rem;
    height: 1.4rem;
    border: none;
    border-radius: 50%;
    background: color-mix(in srgb, var(--text) 14%, transparent);
    color: var(--text);
    line-height: 1;
    font-size: 0.95rem;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 150ms ease;
}
.smart-table-search-clear:hover { background: color-mix(in srgb, var(--primary) 30%, transparent); }

/* Flex row hosting the tag-based search box + its "clear input"/"clear
   all" buttons side by side, used by <x-smart-table-search>. The old
   `.smart-table-search-wrap`'s `position: relative` is reused as the
   base class (same wrap element) — those two buttons are plain
   `.glass-icon-button`s now (matching every other toolbar icon button
   beside them, e.g. 📈/⬆️/🗂️/⚙️), NOT `.smart-table-search-clear`
   (that class stays in use only by the plain-input pages that never
   adopted the tag-based search, see its own docblock above). */
.smart-table-search-outer {
    display: flex;
    align-items: center;
    gap: 0.35rem;
}

/* Tag-based search box (`<x-smart-table-search>`, resources/views/
   components/smart-table-search.blade.php) — a flex-wrap "pill row"
   holding committed search tags (chips) plus the still-typing text
   input, replacing the old plain debounced `<input>`. Grows
   vertically (wraps) instead of the old input's own width-grow, since
   an arbitrary number of chips can't fit on one line. */
.smart-table-search-box {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.35rem;
    width: 32ch;
    max-width: 100%;
    /* Same fixed height every other toolbar control shares (see
       .glass-icon-button's own docblock on --control-height) — was a
       hardcoded 2.25rem before, close but not guaranteed identical, so
       a chip wrapping onto a second line could grow the box past its
       siblings' height. Fixed height + `overflow-y: auto` keeps it
       pinned to that one row height even then, scrolling internally
       instead of pushing the toolbar row taller. */
    min-height: 2.25rem;
    padding: 0.25rem 0.5rem 0rem 0.5rem;
    /* ONE neutral grey border (`--glass-border`, same token every
       other toolbar control already uses — .glass-icon-button, .input,
       .btn) plus that same primary-tinted "glass" background, on the
       outer box only — no second inner border/glow on the text input
       itself (see that rule's own docblock just below) — so the whole
       chip row matches the 📈/⬆️/🗂️/⚙️ buttons right beside it instead
       of standing out as its own differently-styled control. */
    border: var(--glass-border);
    border-radius: 10px;
    background: color-mix(in srgb, var(--primary) 12%, transparent);
    transition: width 260ms cubic-bezier(0.22, 1, 0.36, 1), background 150ms ease;
}
.smart-table-search-box:focus-within,
.smart-table-search-box.is-expanded {
    width: 90ch;
    background: color-mix(in srgb, var(--primary) 20%, transparent);
}



/* The bare text input living INSIDE the chip row — no border/bg of
   its own (the row already has both), just grows to fill remaining
   space on its current line. */
.smart-table-search-box .smart-table-search {
    flex: 1 1 8ch;
    min-width: 8ch;
    width: auto;
    border: none;
    outline: none;
    background: transparent;
    padding: 0.15rem 0.1rem;
}
/* No border/ring/glow of its own even while focused — `input[type="search"].input:focus`'s
   global glow (admin.css, this file's own "Minimal, attractive
   search-box feel" rule above) beats a same-specificity `.smart-table-
   search-box .smart-table-search:focus` override on tie-break (its
   selector carries the `input`/`[type="search"]` element+attribute on
   top of an equal class count), so it was still winning here and
   drawing a second border+glow INSIDE the borderless chip row. Matched
   on `[type="search"]` too so this side wins the specificity tie
   outright, not by source-order luck. */
.smart-table-search-box input[type="search"].smart-table-search:focus {
    width: auto;
    border: none;
    outline: none;
    box-shadow: none;
}

/* One committed search tag ("chip") — pill-shaped, colored per
   include/exclude/off state. Clicking the chip body cycles its state
   (see cycleSearchTagState() in smart-table.js); the × is a separate
   click target that removes the tag outright. `scale`+`opacity` keyframe
   plays once on mount (new tag appearing after Enter); the color/icon
   swap on state-cycle is just the existing `transition` below since
   Alpine re-renders the same element in place. */
.smart-table-search-tag {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.15rem 0.4rem 0.15rem 0.55rem;
    border-radius: 999px;
    font-size: 0.8rem;
    line-height: 1.6;
    cursor: pointer;
    user-select: none;
    white-space: nowrap;
    border: 1px solid transparent;
    transition: background 180ms ease, color 180ms ease, border-color 180ms ease;
    animation: smart-table-search-tag-in 200ms cubic-bezier(0.22, 1, 0.36, 1);
}
@keyframes smart-table-search-tag-in {
    from { opacity: 0; transform: scale(0.75); }
    to { opacity: 1; transform: scale(1); }
}
.smart-table-search-tag--include {
    background: color-mix(in srgb, #16a34a 16%, transparent);
    color: #16a34a;
    border-color: color-mix(in srgb, #16a34a 35%, transparent);
}
.smart-table-search-tag--require {
    background: color-mix(in srgb, #2563eb 16%, transparent);
    color: #2563eb;
    border-color: color-mix(in srgb, #2563eb 35%, transparent);
}
.smart-table-search-tag--exclude {
    background: color-mix(in srgb, #dc2626 16%, transparent);
    color: #dc2626;
    border-color: color-mix(in srgb, #dc2626 35%, transparent);
}
.smart-table-search-tag--off {
    background: color-mix(in srgb, var(--text) 10%, transparent);
    color: color-mix(in srgb, var(--text) 65%, transparent);
    border-color: color-mix(in srgb, var(--text) 20%, transparent);
}
.smart-table-search-tag-icon { flex: 0 0 auto; font-size: 0.85em; line-height: 1; }
.smart-table-search-tag-text { max-width: 20ch; overflow: hidden; text-overflow: ellipsis; }
.smart-table-search-tag-remove {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 1.1rem;
    height: 1.1rem;
    border: none;
    border-radius: 50%;
    background: transparent;
    color: inherit;
    opacity: 0.75;
    font-size: 0.9em;
    line-height: 1;
    cursor: pointer;
    transition: background 150ms ease, opacity 150ms ease;
}
.smart-table-search-tag-remove:hover {
    opacity: 1;
    background: color-mix(in srgb, currentColor 20%, transparent);
}

/* "X results found" line under every smart table — pinned to the
   inline-END corner (bottom-right on an LTR/en-ru panel, bottom-left
   on an RTL/fa-ar one), the opposite of a plain block element's own
   default start-aligned position, using the same logical `end` the
   rest of this codebase already prefers over hardcoded left/right so
   it tracks the ACTIVE PANEL LANGUAGE's own direction (see this
   file/CLAUDE.md's RTL/LTR rules), not a fixed side. */
.smart-table-results-summary { text-align: end; }

/* Toolbar row hosting the search box + settings trigger (+ the
   columns/view/chart/export icons shown individually once there's
   room, see .table-toolbar__icons below). `container-type` turns it
   into its own container-query context so those icons can react to
   ITS available width, not the viewport's — the same row can be roomy
   on a wide admin page and cramped inside a narrower panel. */
.table-toolbar {
    container-type: inline-size;
    container-name: table-toolbar;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 0.5rem;
}
.table-toolbar__spacer { flex: 1 1 auto; }

/* Hidden by default — only the always-present gear icon (below) shows
   in a cramped header; a wide-enough .table-toolbar reveals these as
   their own icon buttons instead of burying them one click deeper in
   the gear's dialog. */
.table-toolbar__icons {
    display: none;
    align-items: center;
    gap: 0.35rem;
}
@container table-toolbar (min-width: 20rem) {
    .table-toolbar__icons { display: flex; }
}

.table-toolbar__gear { flex: 0 0 auto; margin-inline-start: auto; }

/* Small "×" quick-clear-sort icon — appears right after the columns
   icon only while a sort is actually active (x-show="sorts.length >
   0"), so an admin who just clicked a header to sort a column has a
   one-click way back to unsorted without opening the settings dialog
   at all. Sized to the exact same --control-height as its
   .glass-icon-button neighbors (view/chart/export/columns/gear) so it
   sits on the same row without looking like a stray, undersized
   afterthought. */
.smart-table-clear-sort-icon {
    width: var(--control-height);
    height: var(--control-height);
    border: none;
    border-radius: 50%;
    background: color-mix(in srgb, var(--danger, #ef4444) 16%, transparent);
    color: var(--danger, #ef4444);
    font-size: 0.95rem;
    line-height: 1;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 150ms ease, transform 150ms ease;
}
.smart-table-clear-sort-icon:hover { transform: scale(1.08); background: color-mix(in srgb, var(--danger, #ef4444) 30%, transparent); }

/* Shown over an already-populated smart table while a search/sort/
   filter/page change is in flight — "censors" (blurs) the stale rows
   underneath instead of the old behavior of stacking new skeleton rows
   on top of untouched old ones. Only used once there's data to censor;
   the very first load (no rows yet) still uses plain skeleton rows,
   see each smart-table page's own `loading && rows.length === 0`
   guard on its skeleton template. */
.smart-table-loading-overlay {
    position: absolute; inset: 0; z-index: 4;
    display: flex; align-items: center; justify-content: center;
    background: color-mix(in srgb, var(--surface) 55%, transparent);
    backdrop-filter: blur(3px);
    -webkit-backdrop-filter: blur(3px);
    border-radius: inherit;
    animation: smart-table-overlay-in 150ms ease;
}
@keyframes smart-table-overlay-in { from { opacity: 0; } to { opacity: 1; } }

.smart-table-spinner {
    width: 1.75rem; height: 1.75rem; border-radius: 50%;
    border: 2.5px solid color-mix(in srgb, var(--text) 16%, transparent);
    border-top-color: var(--primary);
    animation: smart-table-spin 700ms linear infinite;
}
@keyframes smart-table-spin { to { transform: rotate(360deg); } }

/* Small inline spinner for a single busy table CELL (e.g. the "count"
   column while Modules\SampleData's generate() request for that row
   is still in flight) — same spin animation as .smart-table-spinner
   above, just sized to sit inline instead of centered over the whole
   table. */
.inline-spinner {
    display: inline-block; width: 1rem; height: 1rem; border-radius: 50%;
    border: 2px solid color-mix(in srgb, var(--text) 16%, transparent);
    border-top-color: var(--primary);
    animation: smart-table-spin 700ms linear infinite;
    vertical-align: middle;
}

/* Minimal grid view (Modules\Tables `viewMode: 'grid'`) — the exact
   same <table>/<thead>/<tbody>/<tr>/<td> DOM as the normal table view,
   re-laid-out purely with CSS: each row becomes one minimal card, no
   separate markup/template needed per page. Column headers are hidden
   in this mode (a card grid reads by position, not a header row) —
   the columns menu is still how an admin picks which fields show. */
.data-table--grid thead { display: none; }
.data-table--grid,
.data-table--grid tbody,
.data-table--grid tr.skeleton-row,
.data-table--grid tr:has(.empty-state) {
    display: block;
}
.data-table--grid tbody {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
    gap: 0.75rem;
}
.data-table--grid tbody tr {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    padding: 0.9rem 1rem;
    border-radius: 14px;
    border: var(--glass-border);
    background: color-mix(in srgb, var(--surface) 70%, transparent);
    transition: transform 150ms ease, background 150ms ease;
}
.data-table--grid tbody tr:hover { transform: translateY(-2px); background: color-mix(in srgb, var(--surface) 85%, transparent); }
.data-table--grid tbody td { display: block; padding: 0; border: none; background: none !important; }
.data-table--grid tr:has(.empty-state),
.data-table--grid tr.skeleton-row {
    grid-column: 1 / -1;
}
/* Card "⋮" options trigger — pinned to the corner OPPOSITE the active
   language's reading direction (top-left for rtl/fa,ar; top-right for
   ltr/en,ru), never a fixed physical side. `inset-inline-end` already
   resolves to exactly that: "left" under rtl, "right" under ltr. */
.data-table--grid tbody tr { position: relative; }
.data-table--grid td.is-row-menu {
    position: absolute;
    top: 0.5rem;
    inset-inline-end: 0.5rem;
    width: auto;
    padding: 0;
    display: flex;
    align-items: center;
    gap: 0.3rem;
}
.data-table--grid .row-menu-trigger {
    width: 2rem;
    height: 2rem;
    font-size: 1.1rem;
    background: color-mix(in srgb, var(--surface) 85%, transparent);
}

/* Column color-swatch picker inside the "columns" menu — a native
   <input type="color"> reskinned to a small round swatch so it reads
   as "pick this column's color," not a stray form control. */
.smart-table-color-swatch {
    appearance: none; -webkit-appearance: none; width: 1.1rem; height: 1.1rem;
    border: 1px solid color-mix(in srgb, var(--text) 25%, transparent);
    border-radius: 50%; padding: 0; cursor: pointer; background: none; flex: none;
}
.smart-table-color-swatch::-webkit-color-swatch-wrapper { padding: 0; border-radius: 50%; }
.smart-table-color-swatch::-webkit-color-swatch { border: none; border-radius: 50%; }

/* <x-table-columns-dialog> — one row per column (visibility checkbox +
   label + per-column actions). Kept out of .btn/.btn--xs on purpose:
   those are rectangular action buttons sized by their text, while
   every control here is icon-only and needs to read as one uniform
   square regardless of theme, so it gets its own minimal flat style
   built from the same tokens (--surface/--text/--glass-border/
   --primary) the rest of the panel already uses. */
.table-columns-dialog__switch-row {
    padding: 0.5rem 0.6rem; margin-bottom: 0.6rem; border-radius: 10px;
    background: color-mix(in srgb, var(--surface) 55%, transparent); border: var(--glass-border);
    font-size: 0.82rem; font-weight: 600;
}
.table-columns-dialog__list { max-height: 20rem; overflow-y: auto; display: flex; flex-direction: column; gap: 0.15rem; }
.table-columns-dialog__row { display: flex; gap: 0.5rem; align-items: center; white-space: nowrap; padding: 0.35rem 0.25rem; border-radius: 8px; transition: background 150ms ease; }
.table-columns-dialog__row:hover { background: color-mix(in srgb, var(--surface) 60%, transparent); }
.table-columns-dialog__label { flex: 1; overflow: hidden; text-overflow: ellipsis; }
.table-columns-dialog__actions { display: flex; align-items: center; gap: 0.35rem; }

/* Square, minimum-width-equals-height (never a squashed rectangle),
   flat by default so a row of 3 of these plus the round color swatch
   still reads as one calm control cluster instead of competing
   buttons — .is-active is the only state that adds any color. */
.table-columns-dialog__icon-btn {
    width: 1.85rem; height: 1.85rem; min-width: 1.85rem; flex: none; padding: 0;
    display: inline-flex; align-items: center; justify-content: center;
    border: var(--glass-border); border-radius: 8px;
    background: color-mix(in srgb, var(--surface) 70%, transparent); color: var(--text);
    font-size: 0.82rem; line-height: 1; cursor: pointer;
    transition: background 150ms ease, color 150ms ease, transform 150ms ease, border-color 150ms ease;
}
.table-columns-dialog__icon-btn:hover { background: color-mix(in srgb, var(--primary) 20%, transparent); transform: translateY(-1px); }
.table-columns-dialog__icon-btn.is-active { background: color-mix(in srgb, var(--primary) 30%, transparent); color: var(--primary); border-color: color-mix(in srgb, var(--primary) 55%, transparent); }

/* Per-value color panel shown while a column's 🎨 "color rows by this
   column" button is active — see setRowColorColumn()'s own docblock. */
.table-columns-dialog__row-color-panel {
    margin-top: 0.6rem; padding: 0.6rem 0.7rem; border-radius: 10px;
    background: color-mix(in srgb, var(--surface) 55%, transparent); border: var(--glass-border);
    display: flex; flex-direction: column; gap: 0.45rem;
}
.table-columns-dialog__row-color-panel__header { display: flex; align-items: center; justify-content: space-between; font-size: 0.8rem; }
.table-columns-dialog__row-color-item { display: flex; align-items: center; gap: 0.5rem; font-size: 0.8rem; }

/* "فیلتر داده" dialog (table-filters-dialog.blade.php) — reuses
   .table-columns-dialog__list/.checkbox-field as-is for the overall
   scroll/list chrome and each checklist row, this block only adds the
   bits unique to it: the per-column range pair and the checklist's own
   scroll box + inline search. */
.table-filters-dialog__field {
    padding: 0.5rem 0.25rem; border-radius: 8px;
    display: flex; flex-direction: column; gap: 0.4rem;
}
.table-filters-dialog__field + .table-filters-dialog__field { border-top: var(--glass-border); }
.table-filters-dialog__field-label { font-size: 0.82rem; }
.table-filters-dialog__range { display: flex; align-items: center; gap: 0.4rem; }
.table-filters-dialog__range .input { min-width: 0; flex: 1; }
.table-filters-dialog__range-sep { color: var(--text-muted); }
.table-filters-dialog__checklist {
    display: flex; flex-direction: column; gap: 0.2rem;
    max-height: 10rem; overflow-y: auto;
    padding: 0.4rem; border-radius: 8px;
    background: color-mix(in srgb, var(--surface) 55%, transparent); border: var(--glass-border);
}
.table-filters-dialog__checklist-search { margin-bottom: 0.3rem; }
.table-filters-dialog__checklist-row { font-size: 0.8rem; padding: 0.15rem 0.2rem; }

/* "فیلتر روی داده‌های در حال نمایش" / "فیلتر روی همه‌ی داده‌ها" scope
   toggle, above the field list — two radio pills side by side, same
   "چیپ" affordance the search tags use (rounded, `.checkbox-field`'s
   own gap rhythm) so it reads as ONE choice, not two separate checkboxes. */
.table-filters-dialog__scope {
    display: flex; gap: 0.5rem; margin-bottom: 0.75rem;
}
.table-filters-dialog__scope-option {
    display: flex; align-items: center; gap: 0.4rem;
    padding: 0.35rem 0.7rem; border-radius: 999px;
    border: var(--glass-border); font-size: 0.8rem; cursor: pointer;
    transition: background 150ms ease, border-color 150ms ease;
}
.table-filters-dialog__scope-option:has(input:checked) {
    background: color-mix(in srgb, var(--primary) 18%, transparent);
    border-color: color-mix(in srgb, var(--primary) 45%, transparent);
}

/* <x-color-picker> — the panel-wide curated-palette color TRIGGER (see
   that component's own docblock) — just the round swatch button;
   clicking it opens the shared <x-color-picker-modal> below, replacing
   native <input type="color"> everywhere a decorative highlight/tag
   color is picked. */
.color-picker__trigger {
    width: 1.85rem; height: 1.85rem; min-width: 1.85rem; padding: 0; flex: none;
    border: 1px solid color-mix(in srgb, var(--text) 25%, transparent); border-radius: 50%;
    cursor: pointer; background: var(--surface);
    transition: transform 150ms ease, border-color 150ms ease;
}
.color-picker__trigger:hover { transform: scale(1.08); }
/* No color chosen yet — a dashed ring instead of a solid fill so it never gets mistaken for an actual (gray) color choice. */
.color-picker__trigger.is-empty { background: transparent; border-style: dashed; }

.color-picker__swatch {
    width: 1.7rem; height: 1.7rem; padding: 0; border-radius: 50%; cursor: pointer;
    border: 2px solid transparent; transition: transform 150ms ease, border-color 150ms ease;
}
.color-picker__swatch:hover { transform: scale(1.15); }
.color-picker__swatch.is-selected { border-color: var(--text); }

/* <x-color-picker-modal> — the ONE shared top-level dialog every
   <x-color-picker> trigger opens (see both components' own docblocks).
   Two sections (curated palette / this admin's own custom colors),
   same .modal/.modal__header/.modal__footer shell every other admin
   dialog already uses. */
.color-picker-modal { max-width: 24rem; }
.color-picker-modal__section { margin-bottom: 1rem; }
.color-picker-modal__section-title { font-size: 0.8rem; font-weight: 600; margin-bottom: 0.5rem; color: color-mix(in srgb, var(--text) 75%, transparent); }
.color-picker-modal__grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 0.5rem; }
.color-picker-modal__custom-swatch { position: relative; display: inline-flex; }
.color-picker-modal__custom-remove {
    position: absolute; top: -0.35rem; right: -0.35rem; width: 1.1rem; height: 1.1rem;
    border-radius: 50%; border: none; background: var(--danger, #ef4444); color: #fff;
    font-size: 0.6rem; line-height: 1; cursor: pointer; display: flex; align-items: center; justify-content: center;
    box-shadow: 0 1px 3px rgba(0,0,0,0.3);
}
.color-picker-modal__add-row { display: flex; align-items: center; gap: 0.5rem; margin-top: 0.75rem; }
.color-picker-modal__native-input { width: 2.5rem; height: 2.25rem; padding: 0; border: var(--glass-border); border-radius: 8px; cursor: pointer; background: none; }

/* Chart's group-by-count toggle and the columns dialog's row-numbers
   toggle both reuse the app's existing `.switch` component as-is (see
   its own definition above, near .checkbox-field) — no new switch
   styling needed here. */

/* <x-table-chart-modal> — minimal, professional layout: a two-column
   field row (label column + type), a segmented control instead of a
   plain <select> for chart type, chip toggles instead of raw
   checkboxes for value columns, and one clearly-separated "group &
   count" row with the new .switch above. */
.smart-table-chart-toolbar { display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 1rem; }
.smart-table-chart-field { flex: 1 1 12rem; min-width: 12rem; display: flex; flex-direction: column; gap: 0.35rem; }
.smart-table-chart-field > label { font-size: 0.78rem; font-weight: 600; color: color-mix(in srgb, var(--text) 75%, transparent); }

.smart-table-chart-segmented {
    display: flex; border: var(--glass-border); border-radius: 10px; overflow: hidden;
    background: color-mix(in srgb, var(--surface) 70%, transparent);
}
.smart-table-chart-segmented button {
    flex: 1; border: none; background: none; color: var(--text); cursor: pointer;
    padding: 0.5rem 0.4rem; font-size: 0.8rem; font-weight: 600;
    transition: background 150ms ease, color 150ms ease;
}
.smart-table-chart-segmented button + button { border-inline-start: var(--glass-border); }
.smart-table-chart-segmented button.is-active { background: color-mix(in srgb, var(--primary) 24%, transparent); color: var(--primary); }

.smart-table-chart-chips { display: flex; gap: 0.4rem; flex-wrap: wrap; }
.smart-table-chart-chip {
    border: var(--glass-border); border-radius: 999px; padding: 0.3rem 0.75rem; font-size: 0.78rem;
    background: color-mix(in srgb, var(--surface) 70%, transparent); color: var(--text); cursor: pointer;
    transition: background 150ms ease, color 150ms ease, border-color 150ms ease;
}
.smart-table-chart-chip.is-active { background: color-mix(in srgb, var(--primary) 26%, transparent); color: var(--primary); border-color: color-mix(in srgb, var(--primary) 50%, transparent); }

.smart-table-chart-empty-hint { font-size: 0.82rem; text-align: center; padding: 0.5rem 0; }
.smart-table-chart-canvas-wrap { position: relative; height: 20rem; margin-top: 0.5rem; }

.media-load-more { display: flex; flex-direction: column; align-items: center; gap: 0.5rem; margin-top: 1.25rem; }

.media-suggestions-hint { margin-top: 0.5rem; opacity: 0.75; font-size: 0.9em; }

.pagination { display: flex; justify-content: center; gap: 0.4rem; margin-top: 1.25rem; flex-wrap: wrap; }
.pagination button {
    min-width: 2.15rem; height: 2.15rem; border-radius: 10px; border: var(--glass-border);
    background: color-mix(in srgb, var(--surface) 75%, transparent); color: var(--text); cursor: pointer;
    transition: background var(--glass-transition), transform 150ms ease;
}
.pagination button:hover:not(:disabled) { transform: translateY(-2px); }
.pagination button:disabled { opacity: 0.35; cursor: not-allowed; }
.pagination button.is-active { background: var(--primary); color: #fff; border-color: transparent; box-shadow: var(--card-shadow-sm); }
/* <x-table-pagination>'s own inner groups — the «/‹/page-numbers/›/»
   buttons stay their own group (hidden together on a single-page
   table) so the page-size selector next to it can stay visible
   independent of that — see that component's own docblock for why. */
.pagination__pages { display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; }
/* <x-table-pagination>'s "جهش به صفحه" input and page-size selector —
   sized to sit inline with the »/‹ buttons above without stretching
   the whole footer row taller than they do. */
.pagination__jump-input { width: 3.6rem; min-width: 3.6rem; height: 2.15rem; padding: 0 0.35rem; text-align: center; }
.pagination__per-page { display: inline-flex; align-items: center; gap: 0.4rem; margin-inline-start: 0.5rem; }
.pagination__per-page-label { font-size: 0.8rem; color: var(--text); opacity: 0.7; white-space: nowrap; }
.pagination__per-page-select { height: 2.15rem; padding: 0 1.6rem 0 0.5rem; }

/* ---- Modal ---- */
.modal-backdrop { position: fixed; inset: 0; background: rgba(2, 6, 23, 0.55); z-index: 90; display: flex; align-items: flex-start; justify-content: center; padding: 3.5vh 1rem; overflow-y: auto; animation: glass-fade-in 180ms ease both; }
/* Vertically centers the dialog instead of the panel's usual top-anchor
   (see .modal-backdrop's own comment on why tall forms stay top-
   anchored) — for a short, fixed-height dialog like <x-confirm-dialog>
   this reads as the expected "popped up in the middle of the screen"
   placement rather than pinned near the top. */
.modal-backdrop--center { align-items: center; }
.modal { width: 100%; max-width: 640px; padding: 1.5rem; animation: glass-scale-in 200ms ease both; }
.modal--lg { max-width: 860px; }
/* `flex-wrap: wrap` — every modal header in the panel pairs a title
   with one or more `.glass-icon-button`s (a lone "✕" close button on
   most dialogs, a whole button GROUP on e.g. <x-table-export-dialog>/
   <x-table-chart-modal> — see `.modal__header-actions` below). Without
   wrap, a long/translated title and a multi-button group compete for
   the same single row on a narrow (mobile-width) screen with neither
   side able to shrink below its own content size, so the buttons get
   squeezed into/past each other instead of the row just growing
   taller. `flex-wrap` lets the button group drop to its own line under
   the title the moment it doesn't fit — the two-value `gap` keeps the
   horizontal gap (title-to-buttons, `1rem`) tighter than the row gap
   (title-line-to-button-line, `0.6rem`) once that happens. */
.modal__header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 1.1rem; gap: 0.6rem 1rem; flex-wrap: wrap; }
/* `min-width: 0` — a flex item's default `min-width: auto` floors it at
   its own content width, so without this a long title simply refuses
   to shrink/wrap and pushes (or, pre-`flex-wrap` above, overlaps) the
   header's button(s) instead. `overflow-wrap: anywhere` lets even a
   single long untranslated word (or an RTL run with no natural break
   point) wrap mid-word rather than hold the line open. */
.modal__title { margin: 0; font-size: 1.15rem; flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }

/* <x-module-info-dialog>'s own icon badge — see that component's own
   comment for why this is a real inline <svg> rather than a bare
   emoji span. */
.module-info-dialog__title { display: flex; align-items: center; gap: 0.55rem; }
.module-info-dialog__icon {
    width: 1.9rem; height: 1.9rem; flex-shrink: 0;
    overflow: visible; /* the <text> glyph can slightly exceed the 32x32 viewBox */
}
.module-info-dialog__icon circle {
    fill: color-mix(in srgb, var(--primary) 22%, var(--surface));
    stroke: color-mix(in srgb, var(--text) 12%, transparent);
}
.module-info-dialog__icon text { font-size: 15px; }
/* The multi-button group some modal headers show beside the title
   (e.g. <x-table-export-dialog>'s "🕘 history"+"✕ close",
   <x-table-chart-modal>'s own icon row) — `flex-shrink: 0` keeps every
   button in the group at its normal round `.glass-icon-button` size
   instead of the flex row squeezing them thinner/oval as space runs
   out; `.modal__header`'s own `flex-wrap` above is what actually gives
   the group somewhere to go (its own line under the title) once it no
   longer fits beside it. */
.modal__header-actions { display: flex; align-items: center; gap: 0.4rem; flex-shrink: 0; }
.modal__footer { display: flex; justify-content: flex-end; gap: 0.6rem; margin-top: 1.25rem; flex-wrap: wrap; }

/* ---- Confirm dialog (Alpine.store('confirm'), see <x-confirm-dialog>) ---- */
.confirm-dialog { max-width: 440px; display: flex; align-items: flex-start; gap: 0.9rem; }
.confirm-dialog__icon { flex: 0 0 auto; width: 2.75rem; height: 2.75rem; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 1.35rem; background: color-mix(in srgb, #ef4444 16%, transparent); }
.confirm-dialog--default .confirm-dialog__icon { background: color-mix(in srgb, var(--primary) 16%, transparent); }
.confirm-dialog__content { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.confirm-dialog__body { flex: 1 1 auto; min-width: 0; }
.confirm-dialog__body .modal__title { font-size: 1.05rem; }
.confirm-dialog__message { margin: 0.4rem 0 0; color: var(--text-muted); font-size: 0.9rem; line-height: 1.6; }
.confirm-dialog .modal__footer { margin-top: 1.1rem; }
/* "اعمال به همه (:count باقی‌مانده)" row — sits between the message and
   the confirm/cancel row (see confirm-dialog.blade.php's own docblock
   on why it's a full row, not a third button) — reuses .switch as-is,
   just widened to `width: 100%` so its trailing label wraps naturally
   instead of being squeezed by the switch's own `inline-flex`. */
.confirm-dialog__apply-all { width: 100%; margin-top: 0.85rem; font-size: 0.88rem; }

/* ---- Tabs (translatable field locales) ---- */
.tabs { display: flex; gap: 0.4rem; margin-bottom: 1rem; flex-wrap: wrap; }
.tabs button {
    border: var(--glass-border); background: color-mix(in srgb, var(--surface) 70%, transparent); color: var(--text);
    padding: 0.4rem 0.9rem; border-radius: 999px; font-size: 0.8rem; cursor: pointer; opacity: 0.75;
}
.tabs button.is-active { opacity: 1; background: var(--primary); color: #fff; border-color: transparent; box-shadow: var(--card-shadow-sm); }

/* ---- Toasts ---- */
.toast-stack { position: fixed; bottom: 1.25rem; inset-inline-end: 1.25rem; z-index: 200; display: flex; flex-direction: column; gap: 0.6rem; max-width: 320px; }
.toast { padding: 0.8rem 1rem; display: flex; align-items: center; gap: 0.6rem; animation: glass-rise-in 220ms ease both; font-size: 0.85rem; }
.toast--success::before { content: '✓'; color: #22c55e; font-weight: 700; }
.toast--error::before { content: '!'; color: #ef4444; font-weight: 700; }
.toast--info::before { content: 'i'; color: var(--primary); font-weight: 700; font-style: italic; }
.toast__message { cursor: pointer; }

/* ---- Gallery (product images) ---- */
.gallery-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 0.7rem; margin: 0.75rem 0; }
.gallery-grid__item { position: relative; aspect-ratio: 1; border-radius: 10px; overflow: hidden; border: var(--glass-border); }
.gallery-grid__item img { width: 100%; height: 100%; object-fit: cover; }
.gallery-grid__remove { position: absolute; top: 0.25rem; inset-inline-end: 0.25rem; background: rgba(0,0,0,0.55); color: #fff; border: none; border-radius: 999px; width: 1.5rem; height: 1.5rem; cursor: pointer; line-height: 1; }
.gallery-grid__upload { display: flex; align-items: center; justify-content: center; border: 2px dashed color-mix(in srgb, var(--text) 25%, transparent); border-radius: 10px; aspect-ratio: 1; cursor: pointer; font-size: 1.5rem; opacity: 0.6; }
.gallery-grid__upload:hover { opacity: 1; }

/* ---- Auth page ---- */
.auth-screen { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 1.5rem; position: relative; overflow: hidden; }
.auth-screen::before {
    content: ''; position: absolute; inset: -40%;
    background: radial-gradient(circle at 25% 25%, color-mix(in srgb, var(--primary) 35%, transparent), transparent 60%),
                radial-gradient(circle at 80% 80%, color-mix(in srgb, var(--secondary) 30%, transparent), transparent 55%);
    animation: glass-drift 16s ease-in-out infinite alternate; z-index: 0;
}
@keyframes glass-drift { from { transform: translate(-2%, -2%) scale(1); } to { transform: translate(2%, 2%) scale(1.08); } }
.auth-card { position: relative; z-index: 1; width: 100%; max-width: 380px; padding: 2rem 1.85rem; animation: glass-scale-in 280ms ease both; }
/* Language switcher + light/dark toggle for the (unauthenticated)
   login screen — same .glass-icon-button/.avatar-menu widgets the
   topbar uses once signed in. A normal flow row stacked below
   .auth-card (not floating over it), same z-index:1 as the card so
   both sit above .auth-screen::before's drifting gradient. */
.auth-toolbar { position: relative; z-index: 1; width: 100%; max-width: 380px; margin-top: 1rem; display: flex; align-items: center; justify-content: center; gap: 0.5rem; }
.auth-card__brand { display: flex; align-items: center; gap: 0.6rem; font-weight: 700; font-size: 1.2rem; margin-bottom: 0.35rem; justify-content: center; }
.auth-card__brand .dot { width: 0.65rem; height: 0.65rem; border-radius: 999px; background: var(--primary); }
.auth-card__brand--logo { flex-direction: column; gap: 0.5rem; }
.auth-card__logo { max-height: 3.25rem; max-width: 12rem; width: auto; height: auto; object-fit: contain; }
/* Same light/dark logo swap as .pt-spinner__logo (page-transition.css)
   and the storefront's .site-logo (theme.css) — only one of the pair
   is ever visible at a time, driven by the same [data-theme-mode]
   attribute on <html>. Also inlined verbatim into admin/auth/
   login.blade.php's own <head> (same "must not depend on this file's
   own network fetch" reasoning as page-transition.css's copy of this
   pattern) — keep all copies identical if this rule ever changes. */
.auth-card__logo.site-logo--dark { display: none; }
html[data-theme-mode="dark"] .auth-card__logo.site-logo--light { display: none; }
html[data-theme-mode="dark"] .auth-card__logo.site-logo--dark { display: block; }
.auth-card__site-name { font-weight: 700; font-size: 1.05rem; }
.auth-card__subtitle { text-align: center; opacity: 0.7; font-size: 0.88rem; margin-bottom: 1.5rem; }

/* ---- Utility ---- */
.text-muted { opacity: 0.65; }
.mono { font-family: 'Consolas', 'SFMono-Regular', monospace; font-size: 0.85em; }
.flex-between { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.gap-sm { gap: 0.5rem; }
.mt-lg { margin-top: 1.5rem; }
[x-cloak] { display: none !important; }

@media (max-width: 920px) {
    .admin-sidebar {
        position: fixed; inset-inline-start: 0.75rem; top: 0.75rem; bottom: 0.75rem; z-index: 80;
        transform: translateX(-110%); transition: transform 220ms ease; margin: 0; max-height: none;
        /* height:auto so top+bottom (both set above) define this
           element's height per the normal fixed-position rules —
           the desktop rule's own explicit `height: calc(100vh - 2rem)`
           would otherwise win over top/bottom and throw off the
           0.75rem bottom inset on this off-canvas overlay. */
        height: auto;
        /* The drag-resize/icon-only floor is a desktop-only affordance
           (startSidebarResize() itself no-ops below SIDEBAR_DESKTOP_QUERY)
           — the mobile off-canvas overlay always shows at its full,
           labeled default width regardless of what was dragged on desktop. */
        width: var(--sidebar-width) !important;
    }
    html[dir="rtl"] .admin-sidebar { transform: translateX(110%); margin: 0; }
    .admin-sidebar.is-open { transform: translateX(0); }
    .admin-sidebar__resizer { display: none; }
    .admin-main { padding: 0.75rem 0.85rem 2rem; }
    .stat-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
}

/* ---- Modules\Media library (resources/views/admin/media/partials/library.blade.php,
   resources/views/components/media-picker.blade.php) — Phase 2 ---- */
.media-toolbar { display: flex; flex-wrap: wrap; gap: 0.85rem; align-items: stretch; margin-bottom: 1rem; }
.media-dropzone {
    flex: 1 1 260px; min-height: 84px; display: flex; align-items: center; justify-content: center; gap: 0.75rem;
    border: 2px dashed color-mix(in srgb, var(--text) 25%, transparent); border-radius: var(--glass-radius);
    cursor: pointer; padding: 0.75rem 1rem; text-align: center; opacity: 0.85; transition: var(--glass-transition);
}
.media-dropzone:hover, .media-dropzone.is-dragover { opacity: 1; border-color: var(--primary); background: color-mix(in srgb, var(--primary) 8%, transparent); }
.media-filters { flex: 2 1 420px; display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; }
.media-filters .input { flex: 1 1 130px; min-width: 110px; }

/* ---- Upload History log (Upload tab, "تاریخچه‌ی آپلودها") ---- */
.media-upload-history { margin-top: 1.25rem; border-top: 1px solid color-mix(in srgb, var(--text) 12%, transparent); padding-top: 1rem; }
.media-upload-history__title { margin: 0 0 0.6rem; font-size: 0.9rem; font-weight: 600; }
.media-upload-history__loading, .media-upload-history__empty, .media-upload-history__error { opacity: 0.75; font-size: 0.85rem; }
.media-upload-history__error { color: #ef4444; opacity: 1; }
.media-upload-history__table-wrap { overflow-x: auto; }
.media-upload-history__table { width: 100%; border-collapse: collapse; font-size: 0.82rem; }
.media-upload-history__table th, .media-upload-history__table td { padding: 0.45rem 0.5rem; border-bottom: 1px solid color-mix(in srgb, var(--text) 8%, transparent); text-align: start; vertical-align: middle; }
.media-upload-history__preview img { width: 42px; height: 42px; object-fit: cover; border-radius: 6px; display: block; }
.media-upload-history__source-url { display: inline-block; font-size: 0.78rem; opacity: 0.8; max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: middle; }
.media-upload-history__delete { line-height: 1; }
/* Requirement 1 ("موفق/ناموفق بودن آپلود در تاریخچه"): the failed-
   attempt reason shown as small text under the red status badge —
   .badge--ok/.badge--danger themselves are unchanged/reused as-is. */
.media-upload-history__failure-reason { margin-top: 0.25rem; font-size: 0.72rem; opacity: 0.8; max-width: 220px; }

.upload-queue { display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 1rem; }
.upload-item { display: grid; grid-template-columns: minmax(120px, 220px) 1fr auto; align-items: center; gap: 0.75rem; font-size: 0.85rem; }
.upload-item__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.upload-item__status { opacity: 0.75; min-width: 3.5rem; text-align: end; }
.upload-item__status.is-error { color: #ef4444; opacity: 1; }
.upload-item__status.is-done { color: #22c55e; opacity: 1; }
.progress-bar { height: 8px; border-radius: 999px; background: color-mix(in srgb, var(--text) 12%, transparent); overflow: hidden; }
/* Part 2, step 1: lengthened from 120ms so even a very fast upload's
   fill still visibly animates from its previous value to 100% instead
   of reading as an instant jump — paired with runUpload()'s own
   MIN_VISIBLE_MS "done" hold in media-picker.js. */
.progress-bar__fill { height: 100%; background: var(--primary); border-radius: inherit; transition: width 220ms ease; }

.media-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 1rem; margin: 1rem; }
.media-card { display: flex; flex-direction: column; padding: 0.75rem; gap: 0.5rem; }
.media-card__preview { aspect-ratio: 1; border-radius: 10px; overflow: hidden; background: color-mix(in srgb, var(--text) 6%, transparent); display: flex; align-items: center; justify-content: center; }

/* ---- View-mode switcher (Windows-Explorer-style Card/List/Square) ---- */
.media-view-switcher { display: inline-flex; border: var(--glass-border); border-radius: 999px; overflow: hidden; }
.media-view-switcher__btn {
    border: none; background: transparent; color: var(--text); cursor: pointer;
    width: 2rem; height: 2rem; display: inline-flex; align-items: center; justify-content: center;
    font-size: 0.95rem; opacity: 0.6; transition: background var(--glass-transition), opacity 150ms ease;
}
.media-view-switcher__btn:hover { opacity: 0.9; background: color-mix(in srgb, var(--primary) 12%, transparent); }
.media-view-switcher__btn.is-active { opacity: 1; background: color-mix(in srgb, var(--primary) 22%, transparent); color: var(--primary); }

/* List view: one compact horizontal row per item — thumbnail on the
   inline-start side, title/meta/tags/usages filling the rest. */
.media-grid--list { display: flex; flex-direction: column; gap: 0.5rem; }
.media-grid--list .media-card { flex-direction: row; align-items: center; padding: 0.5rem 0.75rem; }
.media-grid--list .media-card__preview { width: 3rem; height: 3rem; aspect-ratio: unset; flex: 0 0 auto; }
.media-grid--list .media-card__body { flex: 1 1 auto; min-width: 0; }
.media-grid--list .media-card__title { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* Square view: thumbnail-only tiles, no title/meta/tags/usages at all
   (that block is skipped server/client-side, not just hidden — see
   library.blade.php) — smaller, denser grid, closer to an icon view. */
.media-grid--square { grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); }
.media-grid--square .media-card { padding: 0.4rem; gap: 0; }

/* List view's per-row actions: inline icon row pinned to the row's
   trailing (reading-direction end) edge instead of the card/square
   modes' absolute hover overlay — no expanding text label, the
   native `title` attribute every action button already carries is
   the tooltip. Still gated by the same .is-visible hover toggle as
   the overlay (opacity/pointer-events rules are inherited, untouched
   here — only layout changes). */
.media-grid--list .media-card__scrim { display: none; }
.media-grid--list .media-card__actions {
    position: static; inset: auto; flex-direction: row; align-items: center;
    overflow: visible; margin-inline-start: auto; flex: 0 0 auto;
}
.media-grid--list .media-card__action-btn-label { display: none; }
.media-grid--list .media-card__action-btn { min-width: 2rem; height: 2rem; padding: 0 0.45rem; }

/* Filter trigger reuses the app's own .btn/.btn--icon component (see
   admin.css's .btn rules) instead of a bespoke button, so it always
   matches the active color preset/theme automatically. .is-active
   (drawer currently open) tints it the same way .media-view-switcher's
   active mode does. */
.btn--icon.is-active { background: color-mix(in srgb, var(--primary) 22%, transparent); border-color: var(--primary); color: var(--primary); }

/* Floating Windows-Explorer-style context menu (square view's
   hover-hold/right-click, any view's touch long-press) — see
   openContextMenu() in media-picker.js. */
.media-context-menu-backdrop { position: fixed; inset: 0; z-index: 150; background: transparent; }
.media-context-menu {
    position: fixed; z-index: 151; min-width: 200px; max-width: 260px;
    display: flex; flex-direction: column; gap: 0.15rem; padding: 0.35rem;
    border-radius: 10px; box-shadow: var(--card-shadow);
}
.media-context-menu__item {
    display: flex; align-items: center; gap: 0.6rem; width: 100%; text-align: start;
    padding: 0.5rem 0.65rem; border-radius: 6px; border: none; background: transparent;
    color: var(--text); font-size: 0.85rem; cursor: pointer; transition: background var(--glass-transition);
}
.media-context-menu__item:hover, .media-context-menu__item:focus-visible { background: color-mix(in srgb, var(--primary) 14%, transparent); }
.media-context-menu__item:disabled { opacity: 0.45; cursor: not-allowed; }
.media-context-menu__item--danger { color: #ef4444; }
.media-context-menu__icon { flex: 0 0 auto; width: 1.1rem; text-align: center; }

/* Filter drawer's height (per requirement) must span the whole media
   picker dialog, not just `.media-library`'s own intrinsic content
   height — inside the modal, `.modal--media-picker__body` is the
   flex item that actually owns the full available height
   (.modal--fullscreen .modal--media-picker__body above), so stretch
   `.media-library` to match it; the drawer's own top:0/bottom:0 then
   spans that full height. Scoped to the modal so the standalone
   /admin/media page (no such flex ancestor) is unaffected.

   Same stretch also makes each tab panel a flex column filling that
   height, so panel content can carve out its own independently-
   scrolling region (select tab's results, upload tab's history) while
   a "header" part (search toolbar, dropzone/queue) stays put — see
   .media-select-scroll / .media-upload-history below. */
.modal--media-picker__body .media-library { min-height: 100%; display: flex; flex-direction: column; }
.modal--media-picker__body .media-tabs { flex: 0 0 auto; }
.modal--media-picker__body .media-tab-panel { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }

/* Select tab: search/view-switcher/filter toolbar stays put, only the
   results list below it scrolls — see setupInfiniteScroll()'s root
   fix in media-picker.js (the sentinel now watches THIS container,
   not the viewport). Position:relative so the filter drawer (already
   `position:absolute` against `.media-library`) isn't affected — it
   anchors to the ancestor further up, untouched here. */
.media-select-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; }

/* Upload tab: dropzone + upload queue stay fixed at the top, the
   history log scrolls in its own bounded region below them. Now that
   `.media-tab-panel` is a flex COLUMN (see above), `.media-dropzone`'s
   own `flex: 1 1 260px` (meant for an old horizontal layout — that
   basis is dead weight now) would otherwise grow to fill the whole
   panel height on the main (vertical) axis instead of staying
   compact, competing with the history section below for the same
   space; pin it back to its content height here. */
.modal--media-picker__body .media-dropzone,
.modal--media-picker__body .upload-queue { flex: 0 0 auto; }
.modal--media-picker__body .media-upload-history { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.modal--media-picker__body .media-upload-history__table-wrap { flex: 1 1 auto; min-height: 0; overflow-y: auto; }

/* Dropzone grows while a file is being dragged over it — visually
   claiming more of the tab so the drop can't miss it and land
   somewhere else on the page (requirement). */
.media-dropzone { transition: min-height 200ms ease; }
.media-dropzone.is-dragover { min-height: 220px; }
.media-card__preview img, .media-card__preview video { width: 100%; height: 100%; object-fit: cover; }
.media-card__no-preview { opacity: 0.55; font-size: 0.8rem; }
.media-card__title { font-size: 0.85rem; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.media-card__meta { display: flex; flex-wrap: wrap; gap: 0.35rem; }
.media-card__tags { display: flex; flex-wrap: wrap; gap: 0.3rem; }
.media-card__actions { display: flex; flex-wrap: wrap; gap: 0.35rem; margin-top: auto; }

.tag-input { display: flex; flex-wrap: wrap; gap: 0.35rem; align-items: center; }
.tag-input .badge { display: inline-flex; align-items: center; gap: 0.3rem; }
.tag-input .badge button { border: none; background: transparent; cursor: pointer; line-height: 1; opacity: 0.7; }
.tag-input .badge button:hover { opacity: 1; }
.tag-input .input { flex: 1 1 140px; }

/* Media details dialog ("جزئیات") — bigger click-to-zoom preview +
   a definition-list of fields. */
.modal--media-details { max-width: 720px; }
.media-details { display: flex; flex-wrap: wrap; gap: 1.25rem; margin-block: 0.5rem 1rem; }
.media-details__preview {
    flex: 1 1 260px; max-width: 320px; aspect-ratio: 1; border-radius: 12px; overflow: hidden;
    background: color-mix(in srgb, var(--text) 6%, transparent); display: flex; align-items: center; justify-content: center;
}
.media-details__preview img, .media-details__preview video { width: 100%; height: 100%; object-fit: contain; }
.media-details__preview:has(img), .media-details__preview:has(.media-details__placeholder) { cursor: zoom-in; }
/* Zoomed: the image is enlarged past its container (width/height, not
   a transform — transform:scale doesn't affect layout/scroll size, so
   it can't be panned) and the container becomes scrollable, letting
   click-and-drag (detailsPointerDown/Move/Up in media-picker.js) or a
   trackpad/touch swipe reach every part of the image, not just
   whatever was centered when it zoomed in. */
.media-details__preview.is-zoomed { cursor: grab; overflow: auto; align-items: flex-start; justify-content: flex-start; }
.media-details__preview.is-zoomed:active { cursor: grabbing; }
/* flex-shrink:0 (+ min-width) is the actual fix for horizontal
   panning: `.media-details__preview` is a row-direction flex
   container, and a flex item's default flex-shrink:1 lets the browser
   compress it back down to fit the row's main (horizontal) axis no
   matter what `width` says — so the image was silently capped at the
   container's own width, leaving nothing to horizontally scroll to.
   The cross axis (height) has no such shrink behavior, which is why
   vertical panning already worked before this. */
.media-details__preview.is-zoomed img { width: 220%; height: 220%; min-width: 220%; max-width: none; flex-shrink: 0; object-fit: cover; }
.media-details__preview img { transition: width 200ms ease, height 200ms ease; }
.media-details__fields { flex: 1 1 320px; display: flex; flex-direction: column; gap: 0.7rem; margin: 0; }
.media-details__field dt { font-size: 0.75rem; opacity: 0.6; margin-bottom: 0.15rem; }
.media-details__field dd { margin: 0; font-size: 0.9rem; word-break: break-word; }
.media-details__tags { display: flex; flex-wrap: wrap; gap: 0.3rem; }

.modal--crop { max-width: 640px; }
.crop-canvas-wrap { display: flex; flex-direction: column; align-items: center; gap: 0.5rem; margin: 0.5rem 0; }
.crop-canvas-wrap canvas { max-width: 100%; border-radius: 8px; cursor: crosshair; touch-action: none; }
/* Live "W × H px" readout under the crop canvas while a selection is
   being drawn or moved — see cropSizeLabel() in media-picker.js. */
.crop-size-label { font-size: 0.8rem; font-family: var(--font-mono, monospace); color: var(--text-muted); min-height: 1.2em; }

/* ---- x-media-picker component (resources/views/components/media-picker.blade.php) ---- */
.media-picker-field { display: flex; flex-direction: column; gap: 0.5rem; }
.media-picker-field__label { font-size: 0.85rem; font-weight: 600; opacity: 0.85; }
.media-picker-field__preview { width: 100%; max-width: 220px; aspect-ratio: 1; border-radius: 10px; overflow: hidden; background: color-mix(in srgb, var(--text) 6%, transparent); display: flex; align-items: center; justify-content: center; }
.media-picker-field__preview img, .media-picker-field__preview video { width: 100%; height: 100%; object-fit: cover; }
.media-picker-field__empty { opacity: 0.5; font-size: 0.8rem; }
.media-picker-field__actions { display: flex; gap: 0.5rem; }
.modal--media-picker { max-width: 960px; width: 96vw; }
.media-picker-field--inline { flex-direction: row; align-items: center; gap: 0.5rem; }
.media-picker-field__preview--sm { width: 48px; max-width: 48px; aspect-ratio: 1; flex-shrink: 0; }

/* ---- Picker dialog UX overhaul: always-fullscreen modal, two tabs,
   slide-out filter drawer, upload queue with cancel/retry, usage
   links, copy-link, multi-select. Applies to every opener of the
   Media Library dialog (x-media-picker / media-picker-inline). ---- */
/* Covers the whole viewport but keeps a reasonable breathing margin on
   every side instead of going true edge-to-edge — 100vw/100vh with
   zero padding (the previous rule) left the dialog flush against the
   browser chrome with no visual separation from the backdrop. */
.modal-backdrop--fullscreen { padding: clamp(0.75rem, 3vh, 2.5rem) clamp(0.75rem, 3vw, 2.5rem); align-items: stretch; }
.modal--fullscreen { max-width: none; width: 100%; height: 100%; display: flex; flex-direction: column; }
.modal--fullscreen .modal--media-picker__body { flex: 1 1 auto; overflow-y: auto; min-height: 0; }

.media-tabs { display: flex; gap: 0.4rem; margin-bottom: 1rem; border-bottom: var(--glass-border); }
.media-tabs__tab {
    border: none; background: transparent; color: var(--text); opacity: 0.6; cursor: pointer;
    padding: 0.6rem 1.1rem; font-size: 0.9rem; font-weight: 600; border-bottom: 2px solid transparent;
}
.media-tabs__tab.is-active { opacity: 1; color: var(--primary); border-bottom-color: var(--primary); }
.media-tab-panel { padding-block: 0.25rem; }

.media-select-toolbar { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; margin-bottom: 1rem; }
.media-select-toolbar .input { flex: 1 1 260px; min-width: 160px; }
.media-select-toolbar__spacer { flex: 1 1 auto; }

/* Search field: collapsed to ~1/3 of the toolbar until focused/typed
   into, then animates out to the full row — see searchExpanded in
   media-picker.js. flex-basis (not width) so it still cooperates with
   the toolbar's other flex/wrap siblings at both sizes. */
.media-search-input { flex: 0 0 33%; min-width: 140px; transition: flex-basis 220ms ease; }
.media-search-input.is-expanded { flex: 1 1 100%; }

/* Media Link tab ("لینک مدیا") — paste-a-URL preview + download/use-as-is actions. */
.media-link-preview { margin-block: 1rem; }
.media-link-preview__label { display: block; margin-bottom: 0.4rem; font-size: 0.85rem; font-weight: 600; opacity: 0.8; }
.media-link-preview__box {
    display: flex; align-items: center; justify-content: center; min-height: 180px;
    border: var(--glass-border); border-radius: var(--radius, 10px); background: color-mix(in srgb, var(--text) 4%, transparent);
    overflow: hidden; padding: 0.5rem;
}
.media-link-preview__box img, .media-link-preview__box video { max-width: 100%; max-height: 260px; object-fit: contain; }
.media-link-preview__empty { opacity: 0.6; font-size: 0.85rem; }
.media-link-actions { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-top: 0.75rem; }

/* Requirement 4: the drawer used to be `position: fixed` (relative to
   the whole viewport), which slid it in from the edge of the *screen*
   instead of from inside the Media Library panel/modal. `.media-library`
   (the shared root element in library.blade.php, used both by the
   standalone /admin/media page and injected into the picker modal's
   body) is given `position: relative; overflow: hidden` below so it
   becomes the containing block these two now clip/anchor to instead —
   `.modal-backdrop`/`.modal` (rename/meta/crop) stay `position: fixed`
   so they're unaffected by this ancestor's new overflow:hidden. */
.media-library { position: relative; overflow: hidden; }
.media-filter-drawer-backdrop { position: absolute; inset: 0; background: rgba(2, 6, 23, 0.4); z-index: 95; }
/* Opens from the reading-direction "end" edge (left in rtl/Persian,
   right in ltr) inward — `inset-inline-end` anchors it there
   regardless of dir, and the two transforms below just hide it off
   whichever physical side that resolves to. Width is only as wide as
   the filter form actually needs on desktop, capped at 90vw so it
   still fits (per requirement: up to ~90% of the screen) on mobile. */
.media-filter-drawer {
    position: absolute; top: 0; bottom: 0; inset-inline-end: 0; width: 360px; max-width: 90vw; z-index: 96;
    background: var(--surface); border-inline-start: var(--glass-border); box-shadow: var(--card-shadow);
    display: flex; flex-direction: column; transform: translateX(105%); transition: transform 200ms ease;
}
html[dir="rtl"] .media-filter-drawer { transform: translateX(-105%); }
/* `html[dir="rtl"] .media-filter-drawer` outranks a bare `.is-open`
   (an attribute selector counts as a class for specificity), so the
   open state needs its own `[dir="rtl"]`-qualified rule too, or the
   drawer would stay stuck off-screen in rtl locales. */
.media-filter-drawer.is-open,
html[dir="rtl"] .media-filter-drawer.is-open { transform: translateX(0); }
.media-filter-drawer__header { display: flex; align-items: center; justify-content: space-between; padding: 1rem 1.1rem; border-bottom: var(--glass-border); }
.media-filter-drawer__header h3 { margin: 0; font-size: 1rem; }
.media-filter-drawer__body { flex: 1 1 auto; overflow-y: auto; padding: 1rem 1.1rem; display: flex; flex-direction: column; gap: 0.85rem; }
.media-filter-drawer__footer { display: flex; justify-content: flex-end; gap: 0.5rem; padding: 1rem 1.1rem; border-top: var(--glass-border); }

.upload-item { grid-template-columns: minmax(120px, 220px) 1fr auto auto; }
.upload-item__actions { display: flex; gap: 0.3rem; }
.upload-item.is-canceled .upload-item__status { color: #f59e0b; opacity: 1; }

.media-card__preview { position: relative; }
.media-card__checkbox { position: absolute; top: 0.4rem; inset-inline-start: 0.4rem; width: 1.1rem; height: 1.1rem; z-index: 1; }
.media-card.is-selected { outline: 2px solid var(--primary); outline-offset: 2px; }
.media-card__usages { font-size: 0.75rem; opacity: 0.85; display: flex; flex-wrap: wrap; gap: 0.3rem; }
.media-card__usages-label { opacity: 0.6; }
.media-card__usage-item a { color: var(--primary); text-decoration: underline; }
.btn--xs { padding: 0.25rem 0.55rem; font-size: 0.72rem; min-width: 1.6rem; }

/* ---- Select-tab card interaction overhaul: hover/right-click/long-press
   actions overlay + glass scrim, green "selected" checkmark badge. ---- */
.media-card { position: relative; }
/* Requirement 3: green checkmark badge, reusing .badge--ok's existing
   green token instead of a new one-off color. */
.media-card__selected-badge {
    position: absolute; top: 0.5rem; inset-inline-end: 0.5rem; z-index: 3;
    width: 1.5rem; height: 1.5rem; border-radius: 999px; display: flex; align-items: center; justify-content: center;
    background: color-mix(in srgb, #22c55e 85%, transparent); color: #fff; font-size: 0.8rem; font-weight: 700;
    box-shadow: 0 2px 6px rgba(2, 6, 23, 0.35);
    /* Part 2, step 2b: a brief scale/fade "pop" every time this badge
       newly mounts (x-show flips display:none -> flex) — covers both
       an explicit select-tab click AND an upload/import's
       completeUploadSelection() auto-select, since both just set
       isSelected(item) true and let this same element appear. */
    animation: media-badge-pop 260ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

@keyframes media-badge-pop {
    0% { opacity: 0; transform: scale(0.4); }
    60% { opacity: 1; transform: scale(1.15); }
    100% { opacity: 1; transform: scale(1); }
}

/* Frosted-glass scrim over the WHOLE card (not just the preview image)
   while the actions overlay is showing — .media-card__scrim is now a
   direct child of .media-card (position:relative), so inset:0 spans
   the entire card. Still reuses .glass-panel's own blur/border recipe
   (--glass-blur/--glass-border) as an overlay layer rather than
   inventing a new effect. */
.media-card__scrim {
    position: absolute; inset: 0; z-index: 1; border-radius: inherit;
    background: color-mix(in srgb, var(--surface) 55%, transparent);
}

/* Actions now stack top-to-bottom, centered over the whole (blurred)
   card, instead of a horizontal row pinned to the preview's bottom
   edge. overflow-y:auto guards against a card with many actions
   (select/copy-link/rename/edit/crop/delete) being taller than the
   card on a small grid column. */
.media-card__actions {
    position: absolute; inset: 0.5rem; z-index: 2;
    display: flex; flex-direction: column; justify-content: center;
    gap: 0.4rem; overflow-y: auto; overflow-x: hidden;
    opacity: 0; pointer-events: none;
    transition: opacity 150ms ease;
    /* Requirement: the hover actions overlay must follow the active
       panel language's reading direction, not sit centered regardless
       of it — Persian (rtl) pins the stack to the right, English/other
       ltr locales pin it to the left. `flex-start` on the cross axis
       of a column flex container resolves against `dir`, so this
       needs no JS/locale check of its own. */
    align-items: flex-start;
}
.media-card__actions.is-visible { opacity: 1; pointer-events: auto; }

/* Circular icon-only buttons; hovering an individual button expands a
   text label next to the icon (no pre-existing tooltip/expanding-label
   component in this codebase fit an icon button, so this is a plain
   CSS :hover reveal, matching the rest of admin.css's glass styling). */
.media-card__action-btn {
    display: inline-flex; align-items: center; gap: 0.35rem; overflow: hidden;
    height: 2.15rem; min-width: 2.15rem; padding: 0 0.6rem 0 0.6rem; border-radius: 999px;
    border: var(--glass-border); background: color-mix(in srgb, var(--surface) 85%, transparent); color: var(--text);
    cursor: pointer; transition: background var(--glass-transition), min-width 150ms ease, padding 150ms ease;
}
.media-card__action-btn-icon { font-size: 0.9rem; line-height: 1; flex: 0 0 auto; }
.media-card__action-btn-label {
    max-width: 0; opacity: 0; white-space: nowrap; overflow: hidden; font-size: 0.75rem;
    transition: max-width 150ms ease, opacity 150ms ease;
}
.media-card__action-btn:hover, .media-card__action-btn:focus-visible {
    background: color-mix(in srgb, var(--primary) 16%, transparent);
}
.media-card__action-btn:hover .media-card__action-btn-label,
.media-card__action-btn:focus-visible .media-card__action-btn-label {
    max-width: 8rem; opacity: 1;
}
.media-card__action-btn--primary { border-color: var(--primary); color: var(--primary); }
.media-card__action-btn--danger { color: #ef4444; }
.media-card__action-btn:disabled { opacity: 0.45; cursor: not-allowed; }

/* ---- Help trigger + dialog (Plugins\Help) ----
   The trigger is an ORDINARY `.glass-icon-button` — same round pill
   every other topbar icon (dark mode, fullscreen, language,
   notifications, PageHistory's 📌) already has; only the glyph itself
   is a few pixels larger than its siblings' implicit size, via
   `calc(1em + 5px)` on the inner <span> — never override the button's
   own background/border here again. */
.help-widget__trigger span { font-size: calc(1em + 5px); line-height: 1; }

/* The dialog itself: the SAME `.modal-backdrop` + `.modal.glass-panel`
   + `.modal__header` shape every other admin dialog uses
   (color-picker-modal.blade.php, confirm-dialog.blade.php, …) — no
   bespoke split-pane "book" layout, just this file's ordinary themed
   primitives. `.help-dialog` only adds the sizing/scroll a longer
   read needs on top of the shared `.modal`/`.modal--lg` rules. */
.help-dialog { max-width: 42rem; width: 94vw; max-height: 82vh; display: flex; flex-direction: column; }
.help-dialog__body { flex: 1 1 auto; overflow-y: auto; display: flex; flex-direction: column; gap: 0.75rem; }
.help-dialog__index-title { font-weight: 600; margin-block: 0.25rem 0; }

.help-dialog__list { display: flex; flex-direction: column; gap: 0.75rem; }
.help-dialog__group { display: flex; flex-direction: column; gap: 0.25rem; }
.help-dialog__group-title {
    font-size: 0.78rem; font-weight: 600; opacity: 0.65;
    text-transform: uppercase; letter-spacing: 0.03em;
    padding: 0.25rem 0.25rem 0;
}
.help-dialog__page-link {
    display: flex; flex-direction: column; align-items: flex-start; gap: 0.15rem;
    width: 100%; text-align: start;
    background: color-mix(in srgb, var(--surface) 80%, transparent);
    border: var(--glass-border); border-radius: 10px;
    padding: 0.55rem 0.75rem; cursor: pointer; color: var(--text);
    transition: background var(--glass-transition);
}
.help-dialog__page-link:hover, .help-dialog__page-link:focus-visible {
    background: color-mix(in srgb, var(--primary) 12%, transparent);
}
.help-dialog__page-link.is-active {
    background: color-mix(in srgb, var(--primary) 18%, transparent);
    border-color: color-mix(in srgb, var(--primary) 45%, transparent);
}
.help-dialog__page-desc { font-size: 0.78rem; opacity: 0.65; }

.help-dialog__content { font-size: 0.94rem; line-height: 1.8; }
.help-dialog__content :is(h1, h2, h3) { margin-block: 0.85rem 0.4rem; }
.help-dialog__content p { margin-block: 0.55rem; }
.help-dialog__content ul, .help-dialog__content ol { padding-inline-start: 1.35rem; }
