:root {
    --hub-primary: var(--accent);
    --hub-bg: var(--surface-sunken);
}

body {
    background: var(--hub-bg);
    font-family: var(--font-sans);
    color: var(--text-primary);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    letter-spacing: -0.004em;                    /* slight tightening at UI sizes */
}
::selection { background: var(--p-200); color: var(--p-900); }
h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5 {
    font-family: var(--font-sans);
    letter-spacing: -0.018em;                    /* headings sit a touch tighter */
    color: var(--text-primary);
}

/* Reduced motion: keep state changes instant — no transitions, no spin/pop.
   A crossfade-free instant swap is the accessible fallback (WCAG 2.3.3). */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.001ms !important;
    }
}

/* ---- Auth ---- */
.auth-body {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    /* soft brand wash at the top edge — first screen sets the language */
    background:
        radial-gradient(900px 460px at 50% -10%, rgba(45, 212, 191, .14) 0%, rgba(45, 212, 191, 0) 60%),
        var(--surface-sunken);
}
.auth-wrap { width: 100%; max-width: 400px; padding: 1.5rem; }
.auth-card {
    background: var(--n-0);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    padding: 2.25rem 2rem;
    box-shadow: var(--shadow-md);
}
.auth-brand {
    display: flex;
    align-items: center;
    gap: .55rem;
    margin-bottom: 1.5rem;
}
.auth-logo {
    width: 38px; height: 38px;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--hub-primary);
    color: var(--n-0);
    border-radius: var(--radius-md);
    font-size: var(--text-lg);
}
.auth-brand-name { font-size: var(--text-lg); color: var(--text-primary); }
.auth-title { font-size: 1.4rem; font-weight: 600; margin-bottom: .25rem; }
.auth-sub { color: var(--text-muted); font-size: var(--text-base); margin-bottom: 1.5rem; }
.auth-foot { text-align: center; color: var(--text-muted); font-size: var(--text-sm); margin-top: 1.25rem; }

/* ==== Button system ==========================================================
   Bootstrap buttons, tuned on our tokens + Emil-grade press feedback. Every
   pressable control gets: a consistent radius, an ease-out transition, a subtle
   scale-down on :active (the UI "hears" the press) and a token focus ring.
   Only transform/box-shadow/background animate (GPU-cheap). */
.btn {
    --bs-btn-border-radius: var(--radius-md);
    --bs-btn-font-weight: var(--font-weight-medium);
    --bs-btn-focus-box-shadow: var(--focus-ring);
    transition: transform var(--dur-press) var(--ease-out),
                box-shadow var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}
.btn:active { transform: scale(0.97); }
.btn:focus-visible { box-shadow: var(--focus-ring); outline: none; }

.btn-primary {
    --bs-btn-bg: var(--hub-primary);
    --bs-btn-border-color: var(--hub-primary);
    --bs-btn-hover-bg: var(--accent-hover);
    --bs-btn-hover-border-color: var(--accent-hover);
    --bs-btn-active-bg: var(--accent-hover);
    --bs-btn-active-border-color: var(--accent-hover);
    /* Bootstrap's disabled state ships literal #0d6efd — rebind or a disabled
       primary flashes blue (the "Merge selected (0)" bug). */
    --bs-btn-disabled-bg: var(--accent);
    --bs-btn-disabled-border-color: var(--accent);
    --bs-btn-disabled-color: var(--n-0);
    /* concept: inner top highlight + brand-tinted shadow (double-bezel hint) */
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .18), 0 2px 5px -1px rgba(15, 118, 110, .22);
}
.btn-primary:hover { box-shadow: inset 0 1px 0 rgba(255, 255, 255, .18), 0 4px 10px -2px rgba(15, 118, 110, .35); }
/* Bootstrap ships outline-primary / btn-link with a literal #0d6efd — rebind. */
.btn-outline-primary {
    --bs-btn-color: var(--accent);
    --bs-btn-border-color: var(--accent);
    --bs-btn-hover-bg: var(--accent);
    --bs-btn-hover-border-color: var(--accent);
    --bs-btn-hover-color: var(--n-0);
    --bs-btn-active-bg: var(--accent-hover);
    --bs-btn-active-border-color: var(--accent-hover);
    --bs-btn-active-color: var(--n-0);
    --bs-btn-disabled-color: var(--accent);
    --bs-btn-disabled-border-color: var(--accent);
}
/* Positive review-gate actions (mark processed, quick-save, inline-create):
   keep the success semantics but on OUR green, not Bootstrap's #198754. */
.btn-success {
    --bs-btn-bg: var(--success);
    --bs-btn-border-color: var(--success);
    --bs-btn-hover-bg: #15803d;
    --bs-btn-hover-border-color: #15803d;
    --bs-btn-active-bg: #15803d;
    --bs-btn-active-border-color: #15803d;
    --bs-btn-disabled-bg: var(--success);
    --bs-btn-disabled-border-color: var(--success);
}
.btn-link { --bs-btn-color: var(--accent); --bs-btn-hover-color: var(--accent-hover); }
.btn-outline-danger {
    --bs-btn-color: var(--danger);
    --bs-btn-border-color: color-mix(in srgb, var(--danger) 45%, var(--border-control));
    --bs-btn-hover-bg: var(--danger);
    --bs-btn-hover-border-color: var(--danger);
}
/* Inline-create "+" ships as btn-outline-success in 6 templates — a success hue
   on a neutral action (audit P2). Rebind the class to the quiet secondary look
   so every "add related" control reads neutral; accent appears on hover only. */
.btn-outline-success {
    --bs-btn-color: var(--text-secondary);
    --bs-btn-border-color: var(--border-control);
    --bs-btn-hover-bg: var(--n-50);
    --bs-btn-hover-border-color: var(--n-300);
    --bs-btn-hover-color: var(--accent);
    --bs-btn-active-bg: var(--n-100);
    --bs-btn-active-border-color: var(--n-300);
    --bs-btn-active-color: var(--accent);
}

/* Secondary / outline: quiet surface controls that lift a hair on hover. */
.btn-outline-secondary {
    --bs-btn-color: var(--text-secondary);
    --bs-btn-border-color: var(--border-control);
    --bs-btn-hover-bg: var(--surface-hover);
    --bs-btn-hover-border-color: var(--n-300);
    --bs-btn-hover-color: var(--text-primary);
    --bs-btn-active-bg: var(--n-100);
    --bs-btn-active-border-color: var(--n-300);
    --bs-btn-active-color: var(--text-primary);
}
.btn-light {
    --bs-btn-bg: var(--surface);
    --bs-btn-border-color: var(--border-control);
    --bs-btn-color: var(--text-secondary);
    --bs-btn-hover-bg: var(--surface-hover);
    --bs-btn-hover-border-color: var(--n-300);
    --bs-btn-hover-color: var(--text-primary);
}

/* ---- Icon button: a square, rounded action holding a single glyph. Used for
   logout, filter funnel, per-row kebab, sidebar collapse. 36px = AA target. */
.hub-icon-btn {
    --sz: 36px;
    flex: 0 0 auto;
    width: var(--sz); height: var(--sz);
    display: inline-flex; align-items: center; justify-content: center;
    padding: 0;
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    background: transparent;
    color: var(--text-secondary);
    font-size: 1rem;
    line-height: 1;
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out),
                transform var(--dur-press) var(--ease-out);
}
.hub-icon-btn:hover { background: var(--surface-hover); color: var(--text-primary); }
.hub-icon-btn:active { transform: scale(0.94); }
.hub-icon-btn:focus-visible { box-shadow: var(--focus-ring); outline: none; }
.hub-icon-btn.is-bordered { border-color: var(--border-control); background: var(--surface); }
.hub-icon-btn.is-danger:hover { background: color-mix(in srgb, var(--danger) 10%, var(--surface)); color: var(--danger); }

/* ---- Portal shell ---- */
.portal-shell { display: flex; min-height: 100vh; }

/* ---- Ink sidebar ---------------------------------------------------------
   Dark chrome separates navigation from the data surface (Stripe / Retool /
   Metabase pattern; Attio pairs the same slate-950 with a teal accent). Active
   item = tinted lift + white text + teal icon — no rails, no stripes. On ink,
   elevation is hairlines (rgba-white borders), never shadows. */
.portal-sidebar {
    width: 248px;
    flex-shrink: 0;
    background: var(--ink);
    display: flex;
    flex-direction: column;
    position: sticky;
    top: 0;
    height: 100vh;
    transition: width .15s ease;
    overflow: hidden;
}
.sidebar-brand {
    display: flex;
    align-items: center;
    gap: .55rem;
    padding: 1.05rem 1.1rem;
    border-bottom: 1px solid var(--ink-hairline);
    font-size: var(--text-lg);
    color: var(--ink-text-hi);
}
/* the brand mark needs a brighter teal to read on ink */
.portal-sidebar .auth-logo { background: var(--p-600); color: var(--n-0); }
.portal-sidebar .auth-brand-name { color: var(--ink-text-hi); }
.sidebar-nav {
    padding: .75rem .6rem;
    overflow-y: auto;
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    scrollbar-width: thin;
    scrollbar-color: rgba(255, 255, 255, .16) transparent;
}
.sidebar-nav::-webkit-scrollbar { width: 8px; }
.sidebar-nav::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, .14); border-radius: var(--radius-xs); }
.sidebar-foot { margin-top: auto; padding-top: .5rem; border-top: 1px solid var(--ink-hairline); }
/* Approved 3-tier separation (concept v3): the tiers differ by STRUCTURE, not
   just gray value — role gets a hairline + air, group gets a disclosure
   chevron, items hang on a left thread (the VS Code guide-line pattern). */
/* Tier 1 — ROLE (Supplier / Consumer): hairline above + micro-caps, dimmest. */
.sidebar-section {
    text-transform: uppercase;
    font-size: 9.5px;
    letter-spacing: .11em;
    color: var(--ink-label);
    padding: .85rem .6rem .5rem;
    font-weight: 600;
}
.sidebar-role {
    border-top: 1px solid var(--ink-hairline);
    margin-top: 1rem;
}
/* Tier 2 — GROUP header: disclosure chevron + medium weight, lighter tone. */
.sidebar-subsection {
    display: flex;
    align-items: center;
    gap: 7px;
    text-transform: none;
    letter-spacing: 0;
    font-size: 11.5px;
    font-weight: 500;
    color: var(--ink-muted);
    padding: .1rem .6rem .4rem .7rem;
    border-top: 0;
    margin-top: 0;
}
.sidebar-subsection::before {
    content: "";
    width: 5px; height: 5px;
    border-right: 1.4px solid #5e6a7e;
    border-bottom: 1.4px solid #5e6a7e;
    transform: rotate(45deg);
    margin-top: -3px;
    flex: 0 0 auto;
}
/* Tier 3 — ITEM GROUP: left thread + tight rhythm inside, air after. */
.sidebar-grp {
    display: flex;
    flex-direction: column;
    gap: 2px;
    border-left: 1px solid var(--ink-thread);
    margin: 0 0 1rem .9rem;
    padding-left: 6px;
}
.sidebar-nav .nav-link {
    display: flex;
    align-items: center;
    gap: .55rem;
    padding: .42rem .65rem;
    border-radius: var(--radius-md);
    color: var(--ink-text);
    font-size: var(--text-sm);
    white-space: nowrap;
    transition: background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.sidebar-nav .nav-link:hover { background: var(--ink-hover); color: var(--ink-text-hi); }
.sidebar-nav .nav-link:hover i { color: var(--ink-text-hi); }
.sidebar-nav .nav-link.active { background: var(--ink-active); color: var(--ink-text-hi); font-weight: var(--font-weight-medium); }
.sidebar-nav .nav-link.active i { color: var(--p-400); }
.sidebar-nav .nav-link i { font-size: 1rem; width: 1.1rem; text-align: center; color: var(--ink-icon); transition: color var(--dur-fast) var(--ease-out); }
/* "no company" note carries a bootstrap text-muted utility — unreadable on ink */
.sidebar-empty { color: var(--ink-muted) !important; }

.portal-main { flex: 1; display: flex; flex-direction: column; min-width: 0; }
.portal-topbar {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: .65rem 1.25rem;
    background: var(--n-0);
    border-bottom: 1px solid var(--border);
    position: sticky;
    top: 0;
    z-index: 10;
}
/* Collapse arrow lives inside the menu header; the topbar ☰ only appears once
   the menu is hidden, so there is always a way back. */
.sidebar-collapse-btn {
    border: 1px solid var(--ink-hairline);
    background: transparent;
    color: var(--ink-muted);
    padding: .2rem .45rem;
    line-height: 1;
    border-radius: var(--radius-md);
    transition: background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.sidebar-collapse-btn i { font-size: 1rem; }
.sidebar-collapse-btn:hover { color: var(--ink-text-hi); background: var(--ink-hover); }

/* ---- Collapsed: slim icon rail (instead of hiding the menu outright) ---- */
.portal-shell.sidebar-collapsed .portal-sidebar { width: 60px; }
/* hide everything textual; keep only the icons + the toggle */
.portal-shell.sidebar-collapsed .auth-logo,
.portal-shell.sidebar-collapsed .auth-brand-name,
.portal-shell.sidebar-collapsed .sidebar-section { display: none; }
.portal-shell.sidebar-collapsed .sidebar-brand { justify-content: center; padding: 1.1rem 0; }
.portal-shell.sidebar-collapsed .sidebar-collapse-btn { margin: 0 !important; }
.portal-shell.sidebar-collapsed .sidebar-nav { padding: .75rem 0; align-items: center; }
.portal-shell.sidebar-collapsed .sidebar-foot { width: 100%; display: flex; justify-content: center; }
/* font-size:0 collapses the label text node; the icon keeps its own size */
.portal-shell.sidebar-collapsed .sidebar-nav .nav-link {
    font-size: 0;
    justify-content: center;
    width: 44px;
    padding: .55rem 0;
    gap: 0;
}
.portal-shell.sidebar-collapsed .sidebar-nav .nav-link i { font-size: var(--text-lg); width: auto; }
.portal-shell.sidebar-collapsed .sidebar-grp { border-left: 0; margin: 0; padding: 0; align-items: center; }
.portal-topbar { min-height: 56px; }
.topbar-title { font-size: 15px; font-weight: var(--font-weight-semibold); color: var(--text-primary); letter-spacing: -0.01em; }
.topbar-divider { width: 1px; align-self: stretch; margin: .55rem 0; background: var(--border); }

/* ---- Topbar ghost menus (company / storefront / language) -------------------
   Research: modern admin topbars keep switchers GHOST — text + chevron with a
   quiet hover fill; boxes are reserved for inputs. Icons stay gray, never
   brand-tinted: color is dosed, accent only marks actions/active state. */
.hub-top-menu {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    padding: .38rem .6rem;
    background: transparent;
    border: 0;
    border-radius: var(--radius-md);
    color: var(--n-700);
    font-size: var(--text-sm);
    font-weight: var(--font-weight-medium);
    line-height: 1.2;
    transition: background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.hub-top-menu:hover { background: var(--n-100); color: var(--text-primary); }
.hub-top-menu:focus-visible { box-shadow: var(--focus-ring); outline: none; }
.hub-top-menu > i:first-child { color: var(--n-400); font-size: .95rem; }
.hub-top-menu.dropdown-toggle::after { margin-left: .1rem; color: var(--n-400); }
.hub-top-menu.is-lang { font-variant-numeric: tabular-nums; }

/* signed-in user: ink initial disc + name */
.topbar-account {
    display: inline-flex;
    align-items: center;
    gap: .5rem;
    font-size: var(--text-sm);
    color: var(--n-700);
    font-weight: var(--font-weight-medium);
}
.topbar-avatar {
    width: 26px; height: 26px;
    border-radius: var(--radius-round);
    background: var(--n-800);
    color: var(--n-0);
    display: inline-flex; align-items: center; justify-content: center;
    font-size: var(--text-2xs);
    font-weight: var(--font-weight-semibold);
}

/* ---- ⌘K global search — fills the topbar's dead center ---- */
.hub-cmdk {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 0 1 340px;
    min-width: 180px;
    margin-left: auto;
    margin-right: auto;
    background: var(--n-50);
    border: 1px solid var(--n-150);
    border-radius: var(--radius-md);
    padding: 5px 6px 5px 10px;
    transition: border-color var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out);
}
.hub-cmdk:hover { border-color: var(--n-200); }
.hub-cmdk:focus-within { background: var(--n-0); border-color: var(--p-500); box-shadow: var(--focus-ring); }
.hub-cmdk .bi { color: var(--n-400); font-size: .85rem; }
.hub-cmdk-input {
    flex: 1 1 auto;
    min-width: 0;
    border: 0; outline: 0; background: transparent;
    font-size: var(--text-sm);
    color: var(--text-primary);
}
.hub-cmdk-input::placeholder { color: var(--n-400); }
.hub-cmdk kbd {
    background: var(--n-0);
    border: 1px solid var(--n-150);
    border-radius: 5px;
    padding: 1px 5px;
    font-family: var(--font-mono);
    font-size: 10.5px;
    color: var(--n-500);
    line-height: 1.4;
}
@media (max-width: 1150px) { .hub-cmdk { display: none; } }

/* ---- Dropdown menu: soft elevated panel, brand-tinted active (kills the harsh
   Bootstrap #0d6efd), origin-aware ease-out open. Applies to EVERY dropdown. */
.dropdown-menu {
    --bs-dropdown-border-color: var(--border);
    --bs-dropdown-border-radius: var(--radius-lg);
    --bs-dropdown-box-shadow: var(--shadow-pop);
    --bs-dropdown-link-active-bg: var(--accent-tint);
    --bs-dropdown-link-active-color: var(--accent-hover);
    --bs-dropdown-link-hover-bg: var(--surface-hover);
    --bs-dropdown-link-border-radius: var(--radius-sm);
    --bs-dropdown-item-padding-y: .45rem;
    --bs-dropdown-padding-x: .35rem;
    --bs-dropdown-padding-y: .35rem;
    box-shadow: var(--shadow-pop);
    padding: var(--bs-dropdown-padding-y) var(--bs-dropdown-padding-x);
}
.dropdown-menu .dropdown-item {
    border-radius: var(--radius-sm);
    font-size: var(--text-sm);
    padding: var(--bs-dropdown-item-padding-y) .6rem;
    transition: background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.dropdown-menu .dropdown-item.active,
.dropdown-menu .dropdown-item:active {
    background: var(--accent-tint);
    color: var(--accent-hover);
    font-weight: var(--font-weight-medium);
}
/* Open animation. Popper positions the menu a frame AFTER .show is added and
   writes an inline transform — so (a) never animate transform here (it would
   override Popper's positioning mid-flight), and (b) keep the menu invisible
   until Popper stamps data-popper-placement (static menus carry data-bs-popper
   instead). This kills the "opens on top of the trigger, then jumps down". */
.dropdown-menu.show { opacity: 0; }
.dropdown-menu.show[data-popper-placement],
.dropdown-menu.show[data-bs-popper] { opacity: 1; animation: hub-pop var(--dur-pop) var(--ease-out); }
@keyframes hub-pop {
    from { opacity: 0; }
    to   { opacity: 1; }
}

/* ---- Live jobs pill: an active import/parse run, clickable to its page. ---- */
.hub-jobs-pill {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    padding: .35rem .7rem;
    border-radius: var(--radius-pill);
    background: var(--accent-tint);
    border: 1px solid color-mix(in srgb, var(--accent) 25%, var(--border));
    color: var(--p-800);
    font-size: var(--text-xs);
    font-weight: var(--font-weight-semibold);
    text-decoration: none;
    line-height: 1.2;
    transition: background-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
.hub-jobs-pill:hover { background: var(--p-100); box-shadow: var(--shadow-xs); color: var(--p-900); }
.hub-jobs-pill .bi { animation: hub-spin 1.1s linear infinite; font-size: .9rem; }
.hub-jobs-count {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 1.2em; padding: 0 .35em;
    border-radius: var(--radius-pill);
    background: var(--accent); color: var(--n-0);
    font-size: var(--text-2xs); font-weight: var(--font-weight-bold);
    font-variant-numeric: tabular-nums;
}
@keyframes hub-spin { to { transform: rotate(360deg); } }

.portal-content { padding: 1.5rem; }

/* ---- Narrow viewports: keep the layout inside the screen ----
   Below the lg breakpoint the fixed 248px sidebar would push page content past
   the right edge, leaving controls unreachable (there is no page-level
   horizontal scroll to chase them). Force the slim icon rail regardless of the
   saved collapse state, let the horizontal bars wrap instead of widening the
   page, and lean on .table-responsive so wide listings scroll inside their own
   box rather than overflowing the whole page. */
@media (max-width: 991.98px) {
    .portal-sidebar { width: 60px; }
    .portal-shell .auth-logo,
    .portal-shell .auth-brand-name,
    .portal-shell .sidebar-section { display: none; }
    .portal-shell .sidebar-brand { justify-content: center; padding: 1.1rem 0; }
    /* Nothing to collapse further — the rail is already forced here. */
    .portal-shell .sidebar-collapse-btn { display: none; }
    .portal-shell .sidebar-nav { padding: .75rem 0; align-items: center; }
    .portal-shell .sidebar-foot { width: 100%; display: flex; justify-content: center; }
    .portal-shell .sidebar-nav .nav-link {
        font-size: 0;
        justify-content: center;
        width: 44px;
        padding: .55rem 0;
        gap: 0;
    }
    .portal-shell .sidebar-nav .nav-link i { font-size: var(--text-lg); width: auto; }
    .portal-shell .sidebar-grp { border-left: 0; margin: 0; padding: 0; align-items: center; }

    /* Wrap the action bars so their buttons drop to a new line instead of
       forcing the page wider than the viewport. */
    .portal-topbar { flex-wrap: wrap; row-gap: .5rem; }
    .page-head { flex-wrap: wrap; }
    .hub-filterbar { flex-wrap: wrap; }

    .portal-content { padding: 1rem; }
}

/* ---- Tables ---- */
.hub-table thead th {
    font-size: var(--text-2xs);
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--text-muted);
    font-weight: var(--font-weight-medium);
    background: var(--n-25);
    border-bottom: 1px solid var(--n-150);
}
.hub-table td { border-color: var(--n-100); font-size: var(--text-base); }
/* Status badges inside tables → concept dot-pills: tinted pill + leading dot
   in the badge's own emphasis color (works with every -subtle/-emphasis pair). */
.hub-table .badge {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    border-radius: var(--radius-pill);
    font-size: 11.5px;
    font-weight: var(--font-weight-medium);
    padding: .3em .7em .3em .55em;
}
.hub-table .badge::before {
    content: "";
    width: 5px; height: 5px;
    border-radius: var(--radius-round);
    background: currentColor;
    flex: 0 0 auto;
}
.hub-table tbody tr:hover { background: var(--n-25); }
.hub-thumb {
    width: 38px; height: 38px;
    border-radius: var(--radius-md);
    object-fit: cover;
    background: var(--n-50);
    display: inline-flex; align-items: center; justify-content: center;
    color: var(--n-300);
}
.hub-thumb-empty { font-size: var(--text-lg); }

/* ---- Value chips (attribute values) ---- */
.hub-chip {
    display: inline-block;
    padding: .12rem .5rem;
    margin: .1rem .2rem .1rem 0;
    font-size: var(--text-xs);
    line-height: 1.4;
    color: var(--n-700);
    background: var(--n-50);
    border-radius: var(--radius-pill);
    white-space: nowrap;
}
.hub-chip-more { color: var(--text-muted); background: transparent; padding-left: .15rem; }
.hub-type-badge {
    font-size: var(--text-xs);
    font-weight: 600;
    padding: .18rem .5rem;
    border-radius: var(--radius-sm);
    background: var(--accent-tint);
    color: var(--accent-hover);
}

/* ---- Rendered docs (Markdown) ---- */
.hub-doc { font-size: .94rem; color: var(--n-800); line-height: 1.6; }
.hub-doc h1 { font-size: 1.5rem; font-weight: 600; margin: 0 0 1rem; }
.hub-doc h2 { font-size: var(--text-lg); font-weight: 600; margin: 1.6rem 0 .6rem; }
.hub-doc h3 { font-size: 1rem; font-weight: 600; margin: 1.2rem 0 .5rem; }
.hub-doc p, .hub-doc ul, .hub-doc ol { margin: 0 0 .8rem; }
.hub-doc blockquote {
    margin: 0 0 1rem; padding: .4rem .9rem;
    border-left: 3px solid var(--p-100); background: var(--n-25);
    color: var(--text-secondary); border-radius: var(--radius-xs);
}
.hub-doc code { background: var(--n-50); padding: .08rem .35rem; border-radius: var(--radius-xs); font-size: var(--text-sm); }
.hub-doc pre { background: var(--n-900); color: var(--n-150); padding: .9rem; border-radius: var(--radius-md); overflow-x: auto; }
.hub-doc pre code { background: transparent; color: inherit; padding: 0; }
.hub-doc table { width: 100%; border-collapse: collapse; margin: 0 0 1rem; font-size: var(--text-base); }
.hub-doc th, .hub-doc td { border: 1px solid var(--border); padding: .4rem .6rem; text-align: left; }
.hub-doc th { background: var(--n-25); font-weight: 600; }

/* ---- Detail page ---- */
.hub-tabs { border-bottom: 1px solid var(--n-150); }
.hub-tabs .nav-link {
    border: none;
    color: var(--text-muted);
    font-size: var(--text-base);
    padding: .6rem .9rem;
    border-bottom: 2px solid transparent;
}
.hub-tabs .nav-link:hover { color: var(--text-primary); }
.hub-tabs .nav-link.active {
    color: var(--hub-primary);
    background: transparent;
    border-bottom-color: var(--hub-primary);
    font-weight: 500;
}
.hub-lang-tabs .nav-link {
    padding: .2rem .7rem;
    font-size: var(--text-sm);
    color: var(--text-muted);
    background: var(--n-50);
    border-radius: var(--radius-sm);
}
.hub-lang-tabs .nav-link.active { background: var(--hub-primary); color: var(--n-0); }

.hub-dl { display: grid; grid-template-columns: 40% 60%; row-gap: .55rem; }
.hub-dl dt { color: var(--text-muted); font-weight: 500; font-size: var(--text-sm); }
.hub-dl dd { margin: 0; font-size: var(--text-base); }

.hub-media-cell {
    aspect-ratio: 1;
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    overflow: hidden;
    display: flex; align-items: center; justify-content: center;
    background: var(--n-25);
}
.hub-media-cell img { width: 100%; height: 100%; object-fit: contain; }
.hub-media-empty { color: var(--n-300); font-size: var(--text-2xl); }

/* ---- Category tree: zebra whole subtrees by their top-level root, so each
   root group reads as a separate block ---- */
.hub-table tbody tr.cat-grp-a > td { background: var(--n-0); }
.hub-table tbody tr.cat-grp-b > td { background: var(--n-25); }
/* a small gap above each new root group (border colour = card background) */
.hub-table tbody tr.cat-grp-start > td { border-top: 7px solid var(--n-0); }
/* root rows: heavier, darker; child rows: lighter so depth reads at a glance */
.hub-table tbody tr.cat-grp-head > td:first-child { font-weight: 600; color: var(--text-primary); }
.hub-table tbody tr.cat-row:not(.cat-grp-head) td:first-child a { color: var(--text-muted); }
/* keep a visible hover even though the row background is set on the cells */
.hub-table tbody tr.cat-row:hover > td { background: var(--accent-tint); }
.cat-toggle { color: var(--n-400); line-height: 1; }
.cat-toggle:hover { color: var(--hub-primary); }
/* tree indent: one rule instead of 4235 inline styles (--d = depth level) */
.cat-indent { display: inline-block; width: calc(var(--d, 0) * 1.2rem); }
.cat-toggle-cell { display: inline-block; width: 1.4rem; text-align: center; }
.cat-icon {
    width: 22px; height: 22px;
    object-fit: contain;
    border-radius: var(--radius-xs);
    vertical-align: text-bottom;
}

/* "Publish to store" dot toggles — clickable pills with a fill-in dot,
   replacing plain checkboxes for choosing where a product is published. */
.store-dots { display: flex; flex-wrap: wrap; gap: .5rem; }
.store-dots .btn-check { position: absolute; opacity: 0; width: 1px; height: 1px; pointer-events: none; }
.store-dot {
    display: inline-flex; align-items: center; gap: .45rem;
    padding: .3rem .75rem; border-radius: var(--radius-pill);
    border: 1px solid var(--n-200); background: var(--n-0); color: var(--text-muted);
    cursor: pointer; user-select: none; font-size: var(--text-sm); line-height: 1.3;
    transition: background .12s ease, border-color .12s ease, color .12s ease;
}
.store-dot .dot {
    width: .7rem; height: .7rem; border-radius: var(--radius-round);
    border: 2px solid var(--n-200); background: transparent; flex: 0 0 auto;
    transition: background .12s ease, border-color .12s ease;
}
.store-dot:hover { border-color: var(--hub-primary); color: var(--text-primary); }
.btn-check:checked + .store-dot {
    border-color: var(--hub-primary); background: var(--accent-tint); color: var(--p-700); font-weight: 600;
}
.btn-check:checked + .store-dot .dot { border-color: var(--hub-primary); background: var(--hub-primary); }
.btn-check:focus-visible + .store-dot { box-shadow: var(--focus-ring); }

/* Per-row publish dots in the product list (one clickable pill per store). */
.store-dots-row { display: flex; flex-wrap: wrap; gap: .3rem; }
.store-dot-btn {
    display: inline-flex; align-items: center; gap: .35rem;
    padding: .12rem .55rem; border-radius: var(--radius-pill);
    border: 1px solid var(--n-200); background: var(--n-0); color: var(--text-muted);
    cursor: pointer; font-size: var(--text-xs); line-height: 1.3;
    transition: background .12s ease, border-color .12s ease, color .12s ease;
}
.store-dot-btn .dot {
    width: .6rem; height: .6rem; border-radius: var(--radius-round);
    border: 2px solid var(--n-200); background: transparent; flex: 0 0 auto;
    transition: background .12s ease, border-color .12s ease;
}
.store-dot-btn:hover { border-color: var(--hub-primary); color: var(--text-primary); }
.store-dot-btn.on {
    border-color: var(--hub-primary); background: var(--accent-tint); color: var(--p-700); font-weight: 600;
}
.store-dot-btn.on .dot { border-color: var(--hub-primary); background: var(--hub-primary); }
.store-dot-btn.blocked { opacity: .5; cursor: not-allowed; }
.store-dot-btn.htmx-request { opacity: .5; }
/* One live toggle + its optional "open on store" link. */
.store-dot-cell { display: inline-flex; align-items: center; }
.store-open {
    display: inline-flex; align-items: center; margin-left: -.15rem; padding: .15rem .3rem;
    color: var(--hub-primary); text-decoration: none; font-size: var(--text-sm); border-radius: var(--radius-sm);
}
.store-open:hover { background: var(--accent-tint); }

/* ---- Page header (inline icon + title + subtitle + primary action) ----
   v3: the 46px brand-tinted icon plate is gone — a quiet inline glyph and a
   Linear-scale title (color is dosed; identity comes from type, not a box). */
.page-head {
    display: flex;
    align-items: center;
    gap: .7rem;
    margin-bottom: 1.25rem;
}
.page-head-icon {
    flex: 0 0 auto;
    display: inline-flex; align-items: center; justify-content: center;
    color: var(--n-400);
    font-size: 1.25rem;
}
/* text stretches; actions sit flush right with the flex gap — never give each
   action its own margin-left:auto (two autos split the free space and the
   first button drifts to the center — audit P3). */
.page-head-text { min-width: 0; flex: 1 1 auto; }
.page-head-title {
    font-size: 21px;
    font-weight: var(--font-weight-semibold);
    letter-spacing: -0.02em;
    color: var(--text-primary);
    line-height: 1.2;
    margin: 0;
}
.page-head-sub { font-size: var(--text-sm); color: var(--text-muted); }
.page-head-action { flex: 0 0 auto; }
/* one control height inside every page header — btn-sm leftovers grow to standard */
.page-head .btn-sm {
    padding: .5rem .8rem;
    font-size: var(--text-sm);
    border-radius: var(--radius-md);
}

/* ---- Dashboard at-a-glance stat tiles ----
   Number-forward operator metrics: a small label, one big tabular figure and a
   contextual foot line (often a filtered link into the list). Tiles that flag
   work needing attention (unprocessed products, unmapped categories) carry a
   warning tint on the whole surface — never a side stripe. */
.hub-stat-group { margin-bottom: var(--space-6); }
.hub-stat-group-label {
    font-size: var(--text-2xs);
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--text-muted);
    margin-bottom: var(--space-3);
}
.hub-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
    gap: var(--space-4);
}
.hub-stat {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    padding: var(--space-5);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    text-decoration: none;
    color: inherit;
    transition: border-color .12s ease, box-shadow .12s ease, transform .12s ease;
}
a.hub-stat:hover {
    border-color: var(--p-200);
    box-shadow: var(--shadow-sm);
    transform: translateY(-1px);
}
.hub-stat-label {
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--text-muted);
}
.hub-stat-value {
    font-size: var(--text-2xl);
    font-weight: 700;
    line-height: 1.1;
    color: var(--text-primary);
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum";
}
.hub-stat-foot {
    font-size: var(--text-sm);
    color: var(--text-muted);
}
.hub-stat-foot .hub-num { font-weight: 600; color: var(--text-secondary); }
.hub-stat-ic {
    position: absolute;
    top: var(--space-4);
    right: var(--space-4);
    font-size: 1.1rem;
    color: var(--n-400);
}
/* Attention state — a full warm-tinted surface, not a colored edge. */
.hub-stat.is-attention {
    background: color-mix(in srgb, var(--warning) 7%, var(--surface));
    border-color: color-mix(in srgb, var(--warning) 28%, var(--border));
}
.hub-stat.is-attention .hub-stat-value,
.hub-stat.is-attention .hub-stat-ic { color: var(--warning); }
.hub-stat.is-ok .hub-stat-ic { color: var(--success); }

/* ---- Filter toolbar (search + funnel + drop-downs) ---- */
.hub-card { border-radius: var(--radius-lg); }
.hub-filterbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .6rem;
    margin-bottom: 1.1rem;
}
.hub-search { position: relative; flex: 1 1 260px; min-width: 220px; }
.hub-search .form-control {
    padding-left: 2.2rem;
    border-radius: var(--radius-md);
    background: var(--surface);
    border-color: var(--border-control);
}
.hub-search .form-control:focus {
    background: var(--n-0);
    border-color: var(--p-500);
    box-shadow: var(--focus-ring);
}
.hub-search-icon {
    position: absolute; left: .85rem; top: 50%; transform: translateY(-50%);
    color: var(--n-400); pointer-events: none;
}
.hub-filter-funnel {
    flex: 0 0 auto;
    width: 38px; height: 38px;
    display: inline-flex; align-items: center; justify-content: center;
    border: 1px solid var(--border-control); border-radius: var(--radius-md);
    background: var(--n-0); color: var(--text-muted);
}
.hub-filter-funnel:hover { border-color: var(--hub-primary); color: var(--hub-primary); background: var(--accent-tint); }
.hub-filter-select {
    flex: 0 1 auto;
    width: auto; min-width: 150px;
    border-radius: var(--radius-md);
    border-color: var(--border-control);
    color: var(--n-700);
}
.hub-filter-reset {
    flex: 0 0 auto;
    color: var(--n-400); padding: .25rem .4rem;
}
.hub-filter-reset:hover { color: var(--hub-primary); }

/* ---- Table row density (master catalog + store lists) ----
   Tightened from the old "airy" .8rem to an 8px rhythm — a data-tool wants rows
   the operator can scan, not a comfortable web-app spacing. The product-name
   column still wraps to its full text (shipped requirement), so long names
   still grow their row; short-name rows now pack far tighter. */
.hub-table-airy thead th { padding-top: var(--space-2); padding-bottom: var(--space-2); }
.hub-table-airy td { padding-top: var(--space-2); padding-bottom: var(--space-2); }
.hub-name-link { color: var(--text-primary); font-weight: 500; text-decoration: none; }
.hub-name-link:hover { color: var(--hub-primary); }
.hub-code { text-decoration: none; }
/* Numeric columns: tabular figures so digits line up column-wise (prices,
   margins, stock). Identifiers: monospaced + slashed zero + tabular. */
.hub-num { font-family: var(--font-mono); font-size: 12.5px; font-variant-numeric: tabular-nums; font-feature-settings: "tnum"; }
/* Column-header abbreviations (ЗЦ/РЦ/…): keep the operator-known short code but
   carry the full name for tooltip + screen reader; drop the browser's dotted rule. */
.hub-table thead th abbr[title] { text-decoration: none; cursor: help; border: 0; }
.hub-id, .hub-code code {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums slashed-zero;
    font-feature-settings: "tnum", "zero";
}
.hub-status { font-weight: 600; border-radius: var(--radius-pill); padding: .3rem .6rem; }

/* ---- Sortable column headers (sort_th tag) ---- */
.hub-sort {
    display: inline-flex;
    align-items: center;
    gap: .25rem;
    color: inherit;
    text-decoration: none;
    white-space: nowrap;
}
.hub-sort:hover { color: var(--hub-primary); }
.hub-sort-ic { font-size: .7rem; opacity: .4; transition: opacity .1s; }
.hub-sort:hover .hub-sort-ic { opacity: .8; }
.hub-sort.is-active { color: var(--hub-primary); }
.hub-sort.is-active .hub-sort-ic { opacity: 1; }
/* Right-aligned columns: keep the arrow tucked beside right-justified labels. */
.hub-table th.text-end .hub-sort { flex-direction: row-reverse; }

/* ---- Per-row quick actions: a faint icon strip that brightens on row hover
   (folder = category, image = media, sliders = characteristics, compare =
   competitors, files = duplicate). Each opens the product editor on the right
   tab/modal; duplicate POSTs to product_clone. ---- */
/* ---- Stable column widths (fixed layout) -------------------------------
   Sortable lists set their column widths via a <colgroup>; fixed layout makes
   those widths authoritative so re-sorting (which brings differently-sized rows
   onto the page) never recomputes — the table no longer "jumps" on each click.
   Cells that can overflow their column clip with an ellipsis. Pair with a
   <colgroup> in the template. width:auto + min-width:100% = size to the
   colgroup, but never narrower than the container (scrolls in .table-responsive
   when the columns need more room). */
.hub-table-fixed { table-layout: fixed; width: 100%; }
.hub-table-fixed th,
.hub-table-fixed td { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Competitor prices stack one line per competitor — clip each line, not the cell. */
.hub-table-fixed .competitor-prices { white-space: normal; }
.hub-table-fixed .competitor-prices > div { overflow: hidden; text-overflow: ellipsis; }
/* Store-publish dots wrap freely inside their own (flex) cell. */
.hub-table-fixed .store-dots-row { white-space: normal; }
/* Opt-out of the ellipsis clip for cells that must show their full content
   (product name column): wrap onto multiple lines instead of truncating.
   The <colgroup> width still caps the column — the row grows in height. */
.hub-table-fixed td.hub-cell-wrap {
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
    word-break: break-word;
}

/* ---- TomSelect dropdowns parked on <body> -------------------------------
   Selects inside modals get dropdownParent:'body' (see the init in base.html):
   the scrollable .modal-body would clip the absolutely-positioned dropdown at
   the modal edge (CRM ecom-4930 п.2). Parked on <body>, the dropdown must
   stack above the Bootstrap modal (z-index 1055). In-modal selects keep the
   default in-place dropdown, which this selector does not match. */
body > .ts-dropdown { z-index: 1060; }

/* ---- No phantom vertical scrollbar on wide listings ---------------------
   .table-responsive only ever needs to scroll SIDEWAYS (its height is auto and
   grows with the table). But overflow-x: auto computes overflow-y to auto too,
   and with classic (non-overlay) scrollbars — external mouse / macOS "show
   scroll bars: always" — sub-pixel row rounding leaves the box 1-15px "taller"
   than its client height. Chrome then draws a second, near-useless vertical
   scrollbar that latches wheel events: the page freezes until the inner
   scroller exhausts its few pixels. Vertical clipping is safe — row dropdowns
   already stay inside the box via Popper (see the kebab notes below). */
.table-responsive { overflow-y: hidden; }

/* ---- Sticky horizontal scrollbar (wide list tables) ---------------------
   Proxy scrollbar fixed to the bottom edge of the viewport, created by
   static/portal/js/sticky-hscroll.js for [data-sticky-hscroll] containers.
   Shown only while the container overflows sideways and its native scrollbar
   is below the fold. The explicit scrollbar styling opts out of overlay
   scrollbars, so the bar is always visible — not only mid-scroll. */
.hub-sticky-hscroll {
    display: none;
    position: fixed;
    bottom: 0;
    height: 14px;
    z-index: 1030;
    background: var(--border);
    border-top: 1px solid var(--n-150);
    cursor: pointer;
    touch-action: none;              /* our pointer handlers own the gestures */
    user-select: none;
}
.hub-sticky-hscroll.is-visible { display: block; }
/* Custom thumb — the native scrollbar of a proxy div stays INVISIBLE until the
   first scroll on macOS overlay mode (ecom-4930-bf1 баг 2), so the bar draws
   its own always-visible dark thumb and handles dragging itself. */
.hub-sticky-hscroll-thumb {
    position: absolute;
    top: 2px;
    height: 10px;
    background: var(--text-muted);
    border-radius: var(--radius-xs);
    cursor: grab;
}
.hub-sticky-hscroll-thumb:hover { background: var(--text-secondary); }
.hub-sticky-hscroll.is-dragging .hub-sticky-hscroll-thumb {
    background: var(--n-700);
    cursor: grabbing;
}

/* ---- Row quick-actions kebab (⋮) ---------------------------------------
   Consolidates the per-row quick actions into one ⋮ menu pinned to the left of
   the row (always visible — no horizontal scroll to reach it). The cell opts
   out of the ellipsis clip so the menu can render; Popper keeps the menu inside
   the .table-responsive scroll box (flips up near the bottom edge). */
.hub-table-fixed th.hub-actions-cell,
.hub-table-fixed td.hub-actions-cell { overflow: visible; }
.hub-row-actions-menu { line-height: 1; }
.hub-actions-toggle {
    padding: .25rem .4rem;
    line-height: 1;
    border: 0;
    background: transparent;
    color: var(--n-400);
    border-radius: var(--radius-md);
}
.hub-actions-toggle:hover,
.hub-actions-toggle:focus,
.hub-actions-toggle[aria-expanded="true"] {
    color: var(--hub-primary);
    background: var(--accent-tint);
}
.hub-actions-toggle::after { display: none; }  /* no Bootstrap caret */
.hub-actions-menu {
    --bs-dropdown-min-width: 12rem;
    z-index: 1030;
    box-shadow: 0 8px 28px rgba(15, 23, 42, .14);
}
.hub-actions-menu .dropdown-item { display: flex; align-items: center; }
.hub-actions-menu form { margin: 0; }

/* Quick favorite ⭐ toggle in the product-list row (next to the ⋮ menu). */
.hub-row-tools { display: inline-flex; align-items: center; gap: .1rem; }
.hub-fav-toggle {
    padding: .25rem .35rem;
    line-height: 1;
    border: 0;
    background: transparent;
    color: var(--n-200);
    border-radius: var(--radius-md);
}
.hub-fav-toggle:hover,
.hub-fav-toggle:focus { color: var(--star); background: #fffbeb; }
.hub-fav-toggle.is-fav { color: var(--star); }

/* ---- Aligned field grids ------------------------------------------------
   In a row of side-by-side fields, a long label (esp. RU) wraps to two lines in
   a narrow column while its neighbours' labels fit on one — pushing that field's
   input down (or a short label pulling its input up), so inputs no longer line
   up (CRM 8780 #3/#4). Reserve a consistent two-line label height at md+ and
   bottom-align the text, so every input in the row starts at the same y. On
   mobile the fields stack, so no reserved height is needed. */
@media (min-width: 768px) {
  .hub-aligned-fields > [class*="col-"] > .form-label {
    min-height: 3rem;  /* reserve two label lines so a wrapped long label and a
                          one-line neighbour keep their inputs on the same line */
    display: flex;
    align-items: flex-end;
    margin-bottom: .25rem;
  }
}

/* Collapsed rows in a long attribute-value list — hidden but kept in the DOM so
   the formset still submits them (CRM 8780 #5). */
tr.value-collapsed { display: none; }

/* ---- Image drop-zone + drag-reorder (product editors) ---- */
.hub-dropzone {
    border: 2px dashed var(--n-200);
    border-radius: var(--radius-md);
    padding: 1.25rem 1rem;
    text-align: center;
    color: var(--text-muted);
    cursor: pointer;
    transition: border-color .15s, background .15s;
}
.hub-dropzone:hover { border-color: var(--n-300); background: var(--n-25); }
.hub-dropzone.is-dragover { border-color: var(--p-400); background: var(--accent-tint); color: var(--p-800); }
.hub-drag-handle { cursor: grab; align-self: center; }
.hub-drag-handle:active { cursor: grabbing; }
.image-row.sortable-ghost { opacity: .4; }
.image-row.sortable-chosen { background: var(--n-25); border-radius: var(--radius-md); }

/* ---- Dropdown-tree select (js-treeselect) -------------------------------
   Browse mode: collapsed roots + chevrons, indent via padding (never dashes).
   Search mode (.is-searching): flat matches with a muted breadcrumb path.
   The control shows the clean selected name and never accepts inline typing. */
.ts-wrapper.hub-filter-select { min-width: 230px; max-width: 320px; }
/* The dropdown outgrows the control (Ant: dropdownMatchSelectWidth=false) —
   tree rows need room for real category names. */
.ts-wrapper.hub-filter-select .ts-dropdown { min-width: 320px; max-width: 420px; width: max-content; }
.ts-wrapper.hub-filter-select .ts-control { padding-right: 2rem; flex-wrap: nowrap; }
.ts-wrapper.hub-filter-select .ts-control > div {
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
}
/* Hierarchy = indent CELLS (each with a guide line) + chevron. The guides sit
   in dedicated flex cells before the chevron/text (VS Code / file-tree pattern),
   so a line can never cross the label. Single-line labels, no wrapping. */
.ts-tree-row { display: flex; align-items: center; gap: 0;
    min-height: 2rem; padding: 0 0.75rem 0 0.5rem; }
.ts-tree-hidden { display: none; }
/* One 20px indent cell per ancestor level; the guide line runs down its middle,
   full row height, so lines connect across stacked siblings. */
.ts-tree-indent { flex: 0 0 20px; align-self: stretch; position: relative; }
.ts-tree-indent::before { content: ""; position: absolute;
    left: 9px; top: 0; bottom: 0; border-left: 1px solid var(--n-150); }
.ts-tree-chevron { flex: 0 0 1.1rem; width: 1.1rem; font-size: var(--text-xs); line-height: 1;
    display: inline-flex; align-items: center; justify-content: center;
    color: var(--text-muted); cursor: pointer; transition: transform 0.15s ease-out; }
.ts-tree-chevron:hover { color: var(--text-primary); }
.ts-tree-chevron.is-open { transform: rotate(90deg); }
.ts-tree-chevron-none { cursor: default; }
.ts-tree-label { min-width: 0; flex: 1 1 auto; padding-left: 0.2rem; }
.ts-tree-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ts-tree-path { font-size: var(--text-xs); color: var(--text-muted); display: none;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.is-searching .ts-tree-path { display: block; }
/* Search = flat list: no indent cells, no chevrons, path shown instead. */
.is-searching .ts-tree-indent { display: none; }
.is-searching .ts-tree-chevron { display: none; }
@media (prefers-reduced-motion: reduce) {
    .ts-tree-chevron { transition: none; }
}

/* ==== Form controls ==========================================================
   One vocabulary for every input: gray-300 border, 8px radius, soft 4px teal
   halo on focus (no layout shift — Untitled UI ring pattern). */
.form-control, .form-select {
    border-color: var(--border-control);
    border-radius: var(--radius-md);
    color: var(--text-primary);
    transition: border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
.form-control:focus, .form-select:focus {
    border-color: var(--p-500);
    box-shadow: var(--focus-ring);
}
.form-control::placeholder { color: var(--n-400); }
.form-label { font-size: var(--text-sm); font-weight: var(--font-weight-medium); color: var(--n-700); }
.form-check-input:checked { background-color: var(--accent); border-color: var(--accent); }
.form-check-input:focus { border-color: var(--p-400); box-shadow: var(--focus-ring); }
.form-text { color: var(--text-muted); }

/* ==== TomSelect: match the input vocabulary ==================================
   In the bootstrap5 theme the visible box is the WRAPPER (.ts-wrapper.form-select
   / .form-control), not .ts-control — style both. */
.ts-wrapper.form-select,
.ts-wrapper.form-control,
.ts-wrapper .ts-control {
    border-color: var(--border-control);
    border-radius: var(--radius-md);
    box-shadow: none;
    transition: border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
.ts-wrapper.form-select.focus,
.ts-wrapper.form-control.focus,
.ts-wrapper.focus .ts-control { border-color: var(--p-500); box-shadow: var(--focus-ring); }
.ts-dropdown {
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-pop);
    margin-top: 4px;
    padding: .3rem;
}
.ts-dropdown .option { border-radius: var(--radius-sm); padding: .4rem .6rem; }
.ts-dropdown .option.active { background: var(--accent-tint); color: var(--p-900); }
.ts-dropdown .create.active { background: var(--accent-tint); color: var(--p-900); }
.ts-dropdown .dropdown-input {
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    box-shadow: none;
    padding: .4rem .6rem;
}
.ts-dropdown .dropdown-input:focus { border-color: var(--p-500); box-shadow: var(--focus-ring); }
.ts-wrapper.multi .ts-control > div {
    background: var(--n-100);
    color: var(--n-700);
    border-radius: var(--radius-sm);
}

/* ==== Cards / modals / alerts ================================================ */
.card {
    --bs-card-border-color: var(--border);
    --bs-card-border-radius: var(--radius-lg);
    --bs-card-cap-bg: var(--n-25);
}
.shadow-sm { box-shadow: var(--shadow-sm) !important; }  /* soften the utility to hairline scale */
.modal-content {
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
}
.modal-header { border-bottom-color: var(--border); }
.modal-footer { border-top-color: var(--border); }
.portal-content .alert { border-radius: var(--radius-md); }

/* ==== Bootstrap remap sweep ================================================
   Components whose defaults still shipped raw blue/green/yellow. Every state
   (hover / focus / active / disabled) rebound to tokens. */

/* Pagination — sits under EVERY list; was default-blue links + blue active. */
.pagination {
    --bs-pagination-color: var(--text-secondary);
    --bs-pagination-bg: var(--n-0);
    --bs-pagination-border-color: var(--n-150);
    --bs-pagination-border-radius: var(--radius-md);
    --bs-pagination-hover-color: var(--accent);
    --bs-pagination-hover-bg: var(--n-50);
    --bs-pagination-hover-border-color: var(--n-200);
    --bs-pagination-focus-color: var(--accent);
    --bs-pagination-focus-bg: var(--n-50);
    --bs-pagination-focus-box-shadow: var(--focus-ring);
    --bs-pagination-active-color: var(--n-0);
    --bs-pagination-active-bg: var(--n-900);      /* concept: ink page chip */
    --bs-pagination-active-border-color: var(--n-900);
    --bs-pagination-disabled-color: var(--n-400);
    --bs-pagination-disabled-bg: var(--n-0);
    --bs-pagination-disabled-border-color: var(--n-150);
}
.page-link { font-size: var(--text-sm); }

/* Raw nav-pills (category filters block) — active was bootstrap blue. */
.nav-pills {
    --bs-nav-pills-link-active-bg: var(--accent);
    --bs-nav-pills-link-active-color: var(--n-0);
    --bs-nav-pills-border-radius: var(--radius-md);
}
/* Raw .nav-tabs (source detail) — unify with the hub underline vocabulary. */
.nav-tabs {
    --bs-nav-tabs-border-width: 0;
    border-bottom: 1px solid var(--n-150);
}
.nav-tabs .nav-link {
    border: 0;
    color: var(--text-muted);
    border-bottom: 2px solid transparent;
    border-radius: 0;
    margin-bottom: -1px;
}
.nav-tabs .nav-link:hover { color: var(--text-primary); }
.nav-tabs .nav-link.active {
    color: var(--hub-primary);
    background: transparent;
    border-bottom-color: var(--hub-primary);
    font-weight: var(--font-weight-medium);
}

/* Blue/green/gray utilities → tokens (bootstrap defines them !important). */
.text-primary { color: var(--accent) !important; }
.bg-primary { background-color: var(--accent) !important; }
.bg-primary-subtle { background-color: var(--p-50) !important; }
.text-primary-emphasis { color: var(--p-800) !important; }
.text-success { color: var(--success) !important; }
.text-danger { color: var(--danger) !important; }
.text-muted { color: var(--text-muted) !important; }
.text-secondary { color: var(--text-secondary) !important; }
.bg-secondary { background-color: var(--n-500) !important; }

/* Review-gate negative action ("Mark unprocessed") — amber token, not #ffc107. */
.btn-outline-warning {
    --bs-btn-color: var(--warning);
    --bs-btn-border-color: color-mix(in srgb, var(--warning) 55%, var(--border-control));
    --bs-btn-hover-bg: var(--warning);
    --bs-btn-hover-border-color: var(--warning);
    --bs-btn-hover-color: var(--n-0);
    --bs-btn-active-bg: var(--warning);
    --bs-btn-active-border-color: var(--warning);
    --bs-btn-active-color: var(--n-0);
    --bs-btn-disabled-color: var(--warning);
    --bs-btn-disabled-border-color: var(--warning);
}

/* Validation — bootstrap red #dc3545 → our danger, focus halo included. */
.form-control.is-invalid, .form-select.is-invalid { border-color: var(--danger); }
.form-control.is-invalid:focus, .form-select.is-invalid:focus {
    border-color: var(--danger);
    box-shadow: 0 0 0 4px rgba(220, 38, 38, .15);
}
.invalid-feedback { color: var(--danger); }

/* ==== Empty states =========================================================
   One vocabulary for "nothing here" cells: roomy, quiet, 13px; an icon inside
   the cell (when present) becomes a soft block glyph. */
.hub-table td.hub-empty, td.hub-empty {
    text-align: center;
    color: var(--text-muted);
    font-size: var(--text-sm);
    padding-top: 2.75rem !important;
    padding-bottom: 2.75rem !important;
}
td.hub-empty > .bi, td.hub-empty > i.bi {
    display: block;
    font-size: 1.5rem;
    color: var(--n-300);
    margin-bottom: .5rem;
}

/* ==== Skeleton loader (HTMX blocks / modal bodies) ========================= */
.hub-skeleton { display: flex; flex-direction: column; gap: 10px; padding: 6px 0; }
.hub-skeleton i {
    display: block;
    height: 12px;
    border-radius: 6px;
    background: linear-gradient(90deg, var(--n-100) 25%, var(--n-50) 45%, var(--n-100) 65%);
    background-size: 200% 100%;
    animation: hub-shimmer 1.2s linear infinite;
}
.hub-skeleton i:nth-child(2) { width: 82%; }
.hub-skeleton i:nth-child(3) { width: 58%; }
@keyframes hub-shimmer { to { background-position: -200% 0; } }

/* ==== Entry stagger (motion conveys arrival, capped to the visible top) ====
   Only the first 12 rows animate — deep lists (4235-row category tree) must
   not composite thousands of layers. Tiles stagger on the dashboard. */
@media (prefers-reduced-motion: no-preference) {
    .hub-table tbody tr:nth-child(-n+12) { animation: hub-row-in .3s var(--ease-out) backwards; }
    .hub-table tbody tr:nth-child(2)  { animation-delay: 25ms; }
    .hub-table tbody tr:nth-child(3)  { animation-delay: 50ms; }
    .hub-table tbody tr:nth-child(4)  { animation-delay: 75ms; }
    .hub-table tbody tr:nth-child(5)  { animation-delay: 100ms; }
    .hub-table tbody tr:nth-child(6)  { animation-delay: 125ms; }
    .hub-table tbody tr:nth-child(7)  { animation-delay: 150ms; }
    .hub-table tbody tr:nth-child(8)  { animation-delay: 175ms; }
    .hub-table tbody tr:nth-child(9)  { animation-delay: 200ms; }
    .hub-table tbody tr:nth-child(10) { animation-delay: 225ms; }
    .hub-table tbody tr:nth-child(11) { animation-delay: 250ms; }
    .hub-table tbody tr:nth-child(12) { animation-delay: 275ms; }
    .hub-stats .hub-stat { animation: hub-row-in .35s var(--ease-out) backwards; }
    .hub-stats .hub-stat:nth-child(2) { animation-delay: 40ms; }
    .hub-stats .hub-stat:nth-child(3) { animation-delay: 80ms; }
    .hub-stats .hub-stat:nth-child(4) { animation-delay: 120ms; }
}
@keyframes hub-row-in {
    from { opacity: 0; transform: translateY(4px); }
    to   { opacity: 1; transform: translateY(0); }
}
