/* ==========================================================================
   Bindbee white-label theme for SFTPGo — Design System 2.0
   --------------------------------------------------------------------------
   Loaded via httpd.bindings[0].branding.{web_admin,web_client}.extra_css,
   which the templates emit AFTER the stock stylesheets — so plain overrides
   win without !important.

   SFTPGo's UI is Bootstrap 5.3.8 + Metronic. It is almost entirely token
   driven (218 uses of var(--bs-primary) vs 37 hardcoded hex), so redefining
   the token set below recolors nearly the whole product. The handful of
   components that bake the hex into their own scoped tokens are patched
   further down.

   SOURCE OF TRUTH: the product dashboard's DS 2.0 tokens (Figma "Product
   2.0"). That file is Tailwind v4 + shadcn — `@import "tailwindcss"`,
   `@theme inline`, `@custom-variant dark`. None of that survives here: this
   stylesheet is injected as plain CSS into a Bootstrap page with no build
   step and no Tailwind runtime. So the scales are transplanted verbatim as
   custom properties in section 0, and sections 1-4 map them onto the
   Bootstrap/Metronic token names. Keep section 0 in sync with the dashboard;
   everything below it is SFTPGo-specific wiring that the dashboard has no
   equivalent for.

   Two conventions read off the DS scales, used throughout:
     -900   the FILL stop. Solid backgrounds, icons, indicators.
     -1100  the TEXT stop. Every -1100 lands just above 4.5:1 on white
            (primary 4.66, green 4.72, blue 4.77, yellow 4.61), which is
            plainly deliberate. Use it wherever the color carries text.

   The brand orange is unchanged: DS --primary-900 is #f57e21, the same hex
   the previous revision of this file used. What DS 2.0 actually changes here
   is the ground — warm off-white surfaces, the 7-stop text ramp, the warm
   neutral grey scale, and the status colors.
   ========================================================================== */

/* --------------------------------------------------------------------------
   0. DS 2.0 palette — transplanted from the dashboard, unmodified
   --------------------------------------------------------------------------
   Full 12-stop scales. Several are unused by SFTPGo (pink, redpink, indigo,
   cyan have no surface in this product) but are kept so this block stays a
   straight copy of the dashboard's scales — a diff against it should show
   nothing. Consume these through the semantic tokens below, not directly.
   -------------------------------------------------------------------------- */
:root {
  /* Primary (Orange) — brand is --primary-900 (#f57e21) */
  --primary-100: #fefcfb;
  --primary-200: #fff7ed;
  --primary-300: #feefd4;
  --primary-400: #fddeb0;
  --primary-500: #fdcc8c;
  --primary-600: #f9b86b;
  --primary-700: #f4a050;
  --primary-800: #ed8836;
  --primary-900: #f57e21;
  --primary-1000: #e06d10;
  --primary-1100: #b85a09;
  --primary-1200: #4d2208;

  /* Neutral (warm) */
  --neutral-100: #fdfdfc;
  --neutral-200: #f9f9f8;
  --neutral-300: #f1f0ef;
  --neutral-400: #e9e8e6;
  --neutral-500: #e2e1de;
  --neutral-600: #dad9d6;
  --neutral-700: #cfceca;
  --neutral-800: #bcbbb5;
  --neutral-900: #8d8d86;
  --neutral-1000: #82827c;
  --neutral-1100: #63635e;
  --neutral-1200: #21201c;

  /* Grey (cool) */
  --grey-100: #fcfcfc;
  --grey-200: #f9f9f9;
  --grey-300: #f0f0f0;
  --grey-400: #e8e8e8;
  --grey-500: #e0e0e0;
  --grey-600: #d9d9d9;
  --grey-700: #cecece;
  --grey-800: #bbbbbb;
  --grey-900: #8d8d8d;
  --grey-1000: #838383;
  --grey-1100: #4a4a4a;
  --grey-1200: #202020;

  /* Green */
  --green-100: #fbfefc;
  --green-200: #f4fbf6;
  --green-300: #e6f6eb;
  --green-400: #d6f1df;
  --green-500: #c4e8d1;
  --green-600: #adddc0;
  --green-700: #8eceaa;
  --green-800: #5bb98b;
  --green-900: #30a46c;
  --green-1000: #2b9a66;
  --green-1100: #218358;
  --green-1200: #193b2d;

  /* Blue */
  --blue-100: #fbfdff;
  --blue-200: #f4faff;
  --blue-300: #e6f4fe;
  --blue-400: #d5efff;
  --blue-500: #c2e5ff;
  --blue-600: #acd8fc;
  --blue-700: #8ec8f6;
  --blue-800: #5eb1ef;
  --blue-900: #0090ff;
  --blue-1000: #0588f0;
  --blue-1100: #0d74ce;
  --blue-1200: #113264;

  /* Pink */
  --pink-100: #fffcfe;
  --pink-200: #fef7fb;
  --pink-300: #fee9f5;
  --pink-400: #fbdcef;
  --pink-500: #f6cee7;
  --pink-600: #efbfdd;
  --pink-700: #e7acd0;
  --pink-800: #dd93c2;
  --pink-900: #d6409f;
  --pink-1000: #cf3897;
  --pink-1100: #c2298a;
  --pink-1200: #651249;

  /* Yellow */
  --yellow-100: #fefdfb;
  --yellow-200: #fefbe9;
  --yellow-300: #fff7c2;
  --yellow-400: #ffee9c;
  --yellow-500: #fbe577;
  --yellow-600: #f3d673;
  --yellow-700: #e9c162;
  --yellow-800: #e2a336;
  --yellow-900: #ffc53d;
  --yellow-1000: #ffba18;
  --yellow-1100: #ab6400;
  --yellow-1200: #4f3422;

  /* Red */
  --red-100: #fffcfc;
  --red-200: #fff7f7;
  --red-300: #feebec;
  --red-400: #ffdbdc;
  --red-500: #ffcdce;
  --red-600: #fdbdbe;
  --red-700: #f4a9aa;
  --red-800: #eb8e90;
  --red-900: #e5484d;
  --red-1000: #dc3e42;
  --red-1100: #ce2c31;
  --red-1200: #641723;

  /* Indigo */
  --indigo-100: #fdfdfe;
  --indigo-200: #f7f9ff;
  --indigo-300: #edf2fe;
  --indigo-400: #e1e9ff;
  --indigo-500: #d2deff;
  --indigo-600: #c1d0ff;
  --indigo-700: #abbdf9;
  --indigo-800: #8da4ef;
  --indigo-900: #3e63dd;
  --indigo-1000: #3358d4;
  --indigo-1100: #3a5bc7;
  --indigo-1200: #1f2d5c;

  /* Redpink */
  --redpink-100: #fffcfd;
  --redpink-200: #fef7f9;
  --redpink-300: #ffe9f0;
  --redpink-400: #fedce7;
  --redpink-500: #facedd;
  --redpink-600: #f3bed1;
  --redpink-700: #eaacc3;
  --redpink-800: #e093b2;
  --redpink-900: #e93d82;
  --redpink-1000: #df3478;
  --redpink-1100: #cb1d63;
  --redpink-1200: #621639;

  /* Cyan */
  --cyan-100: #fafdfe;
  --cyan-200: #f2fafb;
  --cyan-300: #def7f9;
  --cyan-400: #caf1f6;
  --cyan-500: #b5e9f0;
  --cyan-600: #9ddde7;
  --cyan-700: #7dcedc;
  --cyan-800: #3db9cf;
  --cyan-900: #00a2c7;
  --cyan-1000: #0797b9;
  --cyan-1100: #107d98;
  --cyan-1200: #0d3c48;

  /* Black & White alpha scales */
  --black-a-100: #0000000d;
  --black-a-200: #0000001a;
  --black-a-300: #00000026;
  --black-a-400: #00000033;
  --black-a-500: #0000004d;
  --black-a-600: #00000066;
  --black-a-700: #00000080;
  --black-a-800: #00000099;
  --black-a-900: #000000b2;
  --black-a-1000: #000000cc;
  --black-a-1100: #000000e5;
  --black-a-1200: #000000f2;

  --white-a-100: #ffffff0d;
  --white-a-200: #ffffff1a;
  --white-a-300: #ffffff26;
  --white-a-400: #ffffff33;
  --white-a-500: #ffffff4d;
  --white-a-600: #ffffff66;
  --white-a-700: #ffffff80;
  --white-a-800: #ffffff99;
  --white-a-900: #ffffffb2;
  --white-a-1000: #ffffffcc;
  --white-a-1100: #ffffffe5;
  --white-a-1200: #fffffff2;

  /* Semantic surface, text, border (Figma) */
  --bg-1: #fffefc;
  --bg-2: #faf9f5;
  --bg-3: #f5f3ed;
  --bg-4: #f2f0eb;
  --bg-5: #ebe8e1;
  --bg-6: #c4c0b6;

  --text-01: #c7c4bb;
  --text-02: #b8b5ad;
  --text-03: #787671;
  --text-04: #595752;
  --text-05: #3b3833;
  --text-06: #1f1d19;
  --text-07: #141414;
  --text-highlight: #0f1d40;

  --border-outline: #ebe8e1;

  /* Shadows */
  --ds-shadow-e1: 0 1px 1px 0 rgba(0, 0, 0, 0.08);
  --ds-shadow-e3: 0 4px 5px -1px rgba(0, 0, 0, 0.08);
  --ds-shadow-e4: 0 6px 7px -2px rgba(0, 0, 0, 0.08);
}

/* --------------------------------------------------------------------------
   1. Brand tokens — light theme
   --------------------------------------------------------------------------
   The four brand knobs, now sourced from DS stops rather than hand-picked
   hex. Edit the DS scales in section 0 and the rest of the UI follows.
   -------------------------------------------------------------------------- */
:root,
[data-bs-theme="light"] {
  --bb-orange: var(--primary-900);        /* #f57e21 — fill */
  --bb-orange-dark: var(--primary-1000);  /* #e06d10 — hover / pressed */
  --bb-orange-text: var(--primary-1100);  /* #b85a09 — 4.66:1 on white */
  --bb-orange-tint: var(--primary-200);   /* #fff7ed — subtle fills, active rows */
  --bb-ink: var(--text-07);               /* #141414 */

  /* Text drawn ON TOP of the brand orange. White, matching the dashboard's
     DS (--primary-foreground: #ffffff).

     Measured, so the trade-off is on the record rather than a surprise later:
     white on --primary-900 (#f57e21) is 2.66:1, under the 4.5:1 WCAG AA wants
     for body-size text. Ink (#141414) would be 7.07:1. Consistency with the
     product dashboard was chosen over the ratio.

     If that is ever revisited, there are two one-line routes and they differ:
       - swap this token back to var(--bb-ink)              -> 7.07:1, ink label
       - keep white and darken only the button fill to
         --primary-1100 (#b85a09) in section 5              -> 4.66:1, white label
     The second keeps white text and passes AA, at the cost of a darker, less
     vivid orange on filled buttons only — links and icons are unaffected.

     Everything downstream reads this token, including the hover state of the
     outline buttons in section 5. */
  --bs-primary-inverse: #ffffff;

  --bs-primary: var(--bb-orange);
  --bs-primary-active: var(--bb-orange-dark);
  --bs-primary-light: var(--bb-orange-tint);
  --bs-primary-rgb: 245, 126, 33;
  --bs-primary-clarity: rgba(245, 126, 33, 0.2);
  --bs-primary-bg-subtle: var(--primary-300);
  --bs-primary-border-subtle: var(--primary-500);
  --bs-primary-text-emphasis: var(--primary-1200);

  /* The FILLED primary button's ground, deliberately held apart from
     --bs-primary. In this theme the two are the same stop and this pair is
     pure documentation; section 4 is where they diverge, and the note there
     explains why. Consumed by the .btn-primary block in section 5. */
  --bb-btn1-fill: var(--primary-900);        /* #f57e21 */
  --bb-btn1-fill-hover: var(--primary-1000); /* #e06d10 */

  --bs-component-active-bg: var(--bb-orange);
  --bs-component-active-color: var(--bb-ink);

  /* Links sit on light ground as small text, so they take the -1100 text
     stop (4.66:1) rather than the raw -900 fill stop (2.66:1). */
  --bs-link-color: var(--bb-orange-text);
  --bs-link-color-rgb: 184, 90, 9;
  --bs-link-hover-color: var(--primary-1200);
  --bs-link-hover-color-rgb: 77, 34, 8;

  /* Metronic redefines .text-primary AFTER Bootstrap does, and its version
     reads --bs-text-primary — a token distinct from --bs-primary that the
     overrides above never touched. So every .text-primary mark stayed stock
     #1F87FF blue while the buttons went orange: the breadcrumb home glyph, the
     path chevrons, folder rows in listings and the upload drop-zone icon. The
     component hover/checked states and the menu link colours default to the
     same blue and are set here too, so nothing flashes blue on interaction. */
  --bs-text-primary: var(--bb-orange-text);
  --bs-component-hover-color: var(--bb-orange-text);
  --bs-component-checked-bg: var(--bb-orange);
  --bs-menu-link-color-hover: var(--bb-orange-text);
  --bs-menu-link-color-active: var(--bb-orange-text);
  --bs-menu-link-color-here: var(--bb-orange-text);
  --bs-menu-link-color-show: var(--bb-orange-text);
}

/* --------------------------------------------------------------------------
   2. Surfaces, text ramp, borders and the grey scale — light theme
   --------------------------------------------------------------------------
   This is what DS 2.0 actually changes about SFTPGo. Stock Metronic sits on
   cool greys (#f9f9f9 / #ffffff / #181C32 text); DS 2.0 is warm throughout
   (#fffefc / #faf9f5, text ramp running to #141414 through warm greys).

   Bootstrap 5.3 drives surfaces off --bs-body-*, --bs-secondary-bg and
   --bs-tertiary-bg; Metronic layers --bs-app-* on top for the shell. Both
   sets are mapped so the warm ground reaches the shell, cards, tables,
   dropdowns and modals rather than just the page body.
   -------------------------------------------------------------------------- */
:root,
[data-bs-theme="light"] {
  /* --- Body and shell ---
     CAREFUL: Metronic does NOT use --bs-body-bg as the page ground. Its own
     rule is `--bs-card-bg: var(--bs-body-bg)`, declared on a more specific
     selector than :root — so --bs-body-bg is the CARD surface, and setting
     --bs-card-bg here is simply overridden and does nothing. The page ground
     is --bs-app-bg-color, a separate Metronic token.

     So: body-bg = bg-1 is the raised card, app-bg is the ground behind it.
     Getting these the wrong way round makes cards and ground identical and
     every card edge disappears — which is exactly what the first pass of this
     file did.

     The in-app content ground is bg-1 as well, i.e. white and level with the
     card. That is stock Metronic's light model (it leaves both #ffffff) and it
     is safe here only because the card is delineated without a tonal step:
       - border  --bs-root-card-border-color, set below;
       - shadow  the `.shadow-sm` utility, which SFTPGo puts on the card in the
                 template markup (`<div class="card shadow-sm">`). That is a
                 plain declaration and outranks --bs-card-box-shadow, so it
                 renders whatever this file sets the token to.
     Keep the border. Step app-bg down to bg-2 if you ever want the separation
     carried by tone instead.

     --bs-app-blank-bg-color is deliberately NOT white: the login, 2FA and
     setup pages are a single centred card on that ground with no surrounding
     chrome, so they still need the tonal step to read as a card at all. */
  --bs-body-bg: var(--bg-1);
  --bs-body-bg-rgb: 255, 254, 252;
  --bs-body-color: var(--text-07);
  --bs-body-color-rgb: 20, 20, 20;

  --bs-app-bg-color: var(--bg-1);
  --bs-app-blank-bg-color: var(--bg-2);
  --bs-page-bg: var(--bg-1);

  /* Header tracks the app ground, which is what stock does in BOTH themes
     (#ffffff/#ffffff light, #0F1014/#0F1014 dark). There is no header border
     or shadow token, so the bar is meant to read as part of the ground rather
     than as its own surface — give it a different value and it becomes a
     visible band with nothing to justify the seam. Keep these two in step. */
  --bs-app-header-base-bg-color: var(--bg-1);
  --bs-app-header-minimize-bg-color: rgba(255, 254, 252, 0.5);

  /* Sidebar ground. Metronic hardcodes #0B0C10 in BOTH its light and dark
     blocks — the sidebar is dark ground always, stock. That is overridden
     per theme here and in section 4 so it follows the theme like everything
     else, which is what lets the wordmark swap in section 6 go back to the
     ordinary [data-bs-theme] rule. */
  --bs-app-sidebar-base-bg-color: var(--bg-2);

  /* --- Raised surfaces ---
     --bs-card-bg is deliberately absent: Metronic derives it from body-bg
     above, and re-declaring it here loses to that rule anyway. */
  --bs-modal-bg: var(--bg-1);
  --bs-modal-content-bg: var(--bg-1);
  --bs-dropdown-bg: var(--bg-1);
  --bs-popover-bg: var(--bg-1);
  --bs-offcanvas-bg: var(--bg-1);
  --bs-list-group-bg: var(--bg-1);
  --bs-accordion-bg: var(--bg-1);
  --bs-drawer-bg-color: var(--bg-1);

  /* Card chrome runs through these two rather than a .card rule, so it
     follows Metronic's own cascade instead of fighting it. */
  --bs-root-card-border-color: var(--border-outline);
  --bs-root-card-box-shadow: var(--ds-shadow-e1);

  /* Menu row grounds. Stock light is #F9F9F9 (cool); this is the warm
     equivalent. The sidebar overrides these again in section 6 — it is dark
     ground in BOTH themes, so a light chip there would be inverted. */
  --bs-menu-link-bg-color-hover: var(--bg-3);
  --bs-menu-link-bg-color-active: var(--bg-3);
  --bs-menu-link-bg-color-here: var(--bg-3);
  --bs-menu-link-bg-color-show: var(--bg-3);

  /* --- Recessed / secondary surfaces --- */
  --bs-secondary-bg: var(--bg-3);
  --bs-secondary-bg-rgb: 245, 243, 237;
  --bs-tertiary-bg: var(--bg-4);
  --bs-tertiary-bg-rgb: 242, 240, 235;

  /* --- Text ramp ---
     text-03 is 4.51:1 on bg-1 — the lightest stop that still clears AA for
     body-size text, so muted copy stops there. text-01/02 are for disabled
     and placeholder states only, where AA does not apply. */
  --bs-secondary-color: var(--text-03);
  --bs-secondary-color-rgb: 120, 118, 113;
  --bs-tertiary-color: var(--text-02);
  --bs-emphasis-color: var(--text-07);
  --bs-heading-color: var(--text-06);
  --bs-text-muted: var(--text-03);

  /* --- Borders --- */
  --bs-border-color: var(--border-outline);
  --bs-border-color-translucent: var(--black-a-200);
  --bs-card-border-color: var(--border-outline);
  --bs-table-border-color: var(--border-outline);
  --bs-dropdown-border-color: var(--border-outline);
  --bs-modal-border-color: var(--border-outline);
  --bs-input-border-color: var(--bg-5);

  /* --- Grey ramp ---
     Metronic reads --bs-gray-* everywhere for dividers, muted labels, table
     stripes and disabled states. Remapped onto the DS warm neutral scale so
     no cool grey leaks through next to the warm surfaces. The DS neutral
     scale has 12 stops against Bootstrap's 9, so this is a compression:
     the light end tracks 1:1 and the dark end skips stops. */
  --bs-gray-100: var(--neutral-200);
  /* Metronic builds the striped-row fill as rgba(var(--bs-gray-100-rgb),.75)
     and --bs-input-solid-bg from gray-100, so the rgb triplet has to track
     the hex or striped tables fall back to cool grey. */
  --bs-gray-100-rgb: 249, 249, 248;
  --bs-gray-200: var(--neutral-300);
  --bs-gray-300: var(--neutral-400);
  --bs-gray-400: var(--neutral-700);

  /* 500 and up are TEXT stops, not surfaces — that is what stock means by
     them (#6D6D97, #78829D, #4B5675, #252F4A, #071437, all dark). An earlier
     pass mapped this half onto the DS neutral SURFACE scale, which left
     gray-700 at #82827c: mid-grey where stock is near-black. `.form-control`
     reads gray-700 directly, so every input rendered typed text in mid-grey.
     Same class of mistake as --bs-text-gray-*, one ramp over.

     Mapped onto the DS text ramp instead. Unlike stock this is monotonic —
     stock's 600 (#78829D) is lighter than its 500 (#6D6D97). */
  --bs-gray-500: var(--text-03);
  --bs-gray-600: var(--text-04);
  --bs-gray-700: var(--text-05);
  --bs-gray-800: var(--text-06);
  --bs-gray-900: var(--text-07);

  --bs-light: var(--neutral-200);
  --bs-light-rgb: 249, 249, 248;
  --bs-dark: var(--neutral-1200);
  --bs-dark-rgb: 33, 32, 28;

  /* --- The text-gray ramp ---
     SEPARATE FAMILY FROM --bs-gray-* ABOVE, and the one that actually
     colours type: 253 rules read var(--bs-text-gray-N) — card titles, form
     labels, table cells, menu captions, empty states. Mapping only
     --bs-gray-* leaves every one of them on Metronic's cool navy ramp
     (#071437 … #F9F9F9) next to warm surfaces. Exactly the same trap as
     --bs-text-primary vs --bs-primary in section 1.

     Runs light (100) to dark (900) under this theme and inverts under dark.
     Note stock is not monotonic here — its 500 (#6D6D97) is darker than its
     600 (#78829D); the DS ramp below is ordered, which is a fix rather than
     a faithful copy. 500 lands on text-03, the lightest stop still at 4.5:1,
     because that is where Metronic puts muted body text. */
  --bs-text-gray-100: var(--neutral-200);
  --bs-text-gray-200: var(--neutral-300);
  --bs-text-gray-300: var(--neutral-600);
  --bs-text-gray-400: var(--text-01);
  --bs-text-gray-500: var(--text-03);
  --bs-text-gray-600: var(--text-04);
  --bs-text-gray-700: var(--text-05);
  --bs-text-gray-800: var(--text-06);
  --bs-text-gray-900: var(--text-07);

  --bs-text-dark: var(--text-07);
  --bs-text-light: var(--neutral-200);

  /* --- Inputs --- */
  --bs-input-bg: var(--bg-1);
  /* Typed text in inputs. Set as its own token because .form-control reads
     --bs-gray-700 directly and ignores --bs-input-color, so the rule in
     section 5 is what actually applies it. */
  --bb-input-text: var(--neutral-1200);
  --bs-input-color: var(--neutral-1200);

  /* Secondary button, deliberately NOT read off --bs-gray-*: that ramp is now
     the text ramp, and pointing the button edge at it would render a near-
     black rim instead of the light neutral the design calls for. */
  --bb-btn2-label: var(--neutral-1200);
  --bb-btn2-edge: var(--neutral-800);
  --bb-btn2-edge-hover: var(--neutral-900);
  --bb-btn2-fill-hover: var(--bg-3);
  --bs-input-placeholder-color: var(--text-02);
  --bs-input-solid-bg: var(--bg-3);
  --bs-input-solid-color: var(--neutral-1200);
  --bs-input-solid-placeholder-color: var(--text-02);

  /* --- Auth-page input states — consumed in section 8 ---
     The login family renders .form-control-solid, a FILLED input: Metronic
     paints ground and edge the same grey and the border disappears. The
     Bindbee input is OUTLINED instead — white ground, one light edge, orange
     on focus — so section 8 restates the states against the stops below.

     Held as tokens rather than literals so lifting the treatment off
     .app-blank onto every input in the product is a selector edit rather than
     a colour hunt. Nothing outside section 8 reads them today.

     WORTH KNOWING BEFORE EDITING THE --bs-input-* NAMES ABOVE: none of them
     reach .form-control in this Metronic build. It writes
     `background-color:var(--bs-body-bg)`, `border:1px solid var(--bs-gray-300)`
     and `color:var(--bs-gray-700)` as plain declarations, and
     .form-control-solid reads --bs-gray-100/--bs-gray-200 — so the solid
     ground on the login page came from the grey ramp in this section, not
     from --bs-input-solid-bg. The --bs-input-* tokens are still set because
     they are the documented hooks and a later Metronic may honour them, but
     the rules in sections 5 and 8 are what actually renders. */
  --bb-input-border: var(--neutral-600);          /* #dad9d6 */
  --bb-input-border-hover: var(--neutral-700);    /* #cfceca */
  --bb-input-ring-hover: var(--black-a-100);      /* 5% black halo */
  --bb-input-placeholder: var(--neutral-900);     /* #8d8d86 — 3.0:1 on bg-1 */
  --bb-input-disabled-border: var(--neutral-400); /* #e9e8e6 */
  --bb-input-disabled-text: var(--text-01);       /* #c7c4bb */

  /* --- Tables --- */
  --bs-table-color: var(--text-06);
  --bs-table-striped-bg: var(--bg-2);
  --bs-table-hover-bg: var(--bg-3);

  /* --- Component ground --- */
  --bs-component-hover-bg: var(--bg-3);
  --bs-component-active-color-muted: var(--text-03);
}

/* --------------------------------------------------------------------------
   3. Status colors — light theme
   --------------------------------------------------------------------------
   SFTPGo leans on these hard: quota bars, connection state, transfer
   outcomes, the delete confirmations and every toast.

   Fill (-900) and text (-1100) split as described at the top. Solid buttons
   are the awkward case, because their label sits on the fill: white on
   green-900 is 3.16:1 and on red-900 3.91:1, both under AA. So solid status
   BUTTONS use the -1100 stop (white on green-1100 4.72:1, on red-1100
   5.21:1) while badges, icons and indicators keep the -900 fill. Section 5
   carries those button overrides.

   Yellow is the exception in the other direction: yellow-900 (#ffc53d) is
   light enough that white is hopeless on it (1.63:1), so warning takes ink.
   -------------------------------------------------------------------------- */
:root,
[data-bs-theme="light"] {
  --bs-success: var(--green-900);
  --bs-success-rgb: 48, 164, 108;
  --bs-success-active: var(--green-1000);
  --bs-success-light: var(--green-300);
  --bs-success-inverse: #ffffff;
  --bs-success-clarity: rgba(48, 164, 108, 0.2);
  --bs-text-success: var(--green-1100);
  --bs-success-bg-subtle: var(--green-300);
  --bs-success-border-subtle: var(--green-500);
  --bs-success-text-emphasis: var(--green-1200);

  --bs-info: var(--blue-900);
  --bs-info-rgb: 0, 144, 255;
  --bs-info-active: var(--blue-1000);
  --bs-info-light: var(--blue-300);
  --bs-info-inverse: #ffffff;
  --bs-info-clarity: rgba(0, 144, 255, 0.2);
  --bs-text-info: var(--blue-1100);
  --bs-info-bg-subtle: var(--blue-300);
  --bs-info-border-subtle: var(--blue-500);
  --bs-info-text-emphasis: var(--blue-1200);

  --bs-warning: var(--yellow-900);
  --bs-warning-rgb: 255, 197, 61;
  --bs-warning-active: var(--yellow-1000);
  --bs-warning-light: var(--yellow-300);
  --bs-warning-inverse: var(--text-07);   /* ink: yellow-900 is too light for white */
  --bs-warning-clarity: rgba(255, 197, 61, 0.2);
  --bs-text-warning: var(--yellow-1100);
  --bs-warning-bg-subtle: var(--yellow-300);
  --bs-warning-border-subtle: var(--yellow-500);
  --bs-warning-text-emphasis: var(--yellow-1200);

  --bs-danger: var(--red-900);
  --bs-danger-rgb: 229, 72, 77;
  --bs-danger-active: var(--red-1000);
  --bs-danger-light: var(--red-300);
  --bs-danger-inverse: #ffffff;
  --bs-danger-clarity: rgba(229, 72, 77, 0.2);
  --bs-text-danger: var(--red-1100);
  --bs-danger-bg-subtle: var(--red-300);
  --bs-danger-border-subtle: var(--red-500);
  --bs-danger-text-emphasis: var(--red-1200);

  --bs-secondary: var(--neutral-500);
  --bs-secondary-rgb: 226, 225, 222;
  --bs-secondary-active: var(--neutral-600);
  --bs-secondary-light: var(--neutral-300);
  --bs-secondary-inverse: var(--text-06);
  --bs-text-secondary: var(--text-03);

  /* --- Stragglers ---
     Tokens outside the families above that stock still paints cool. Found by
     diffing every --bs-* hex Metronic declares against the ones this file
     defines, then reading the computed value in the browser rather than the
     bundle — several are declared three times over and a grep cannot tell you
     which wins. Rerun that diff after any SFTPGo upgrade.

     Most of these already resolve warm under THIS theme, because stock light
     defines them off tokens already remapped above; it is the dark block that
     hardcodes them. They are restated here anyway so both themes read from
     the same place. */
  --bs-menu-dropdown-bg-color: var(--bg-1);
  --bs-menu-dropdown-box-shadow: 0 0 50px 0 rgba(31, 29, 25, 0.12);
  --bs-symbol-label-bg: var(--neutral-200);
  --bs-symbol-label-color: var(--text-04);
  --bs-scrollbar-color: var(--neutral-400);
  --bs-scrollbar-hover-color: var(--neutral-600);
  --bs-border-dashed-color: var(--neutral-600);
  --bs-menu-heading-color: var(--text-03);
  --bs-bullet-bg-color: var(--neutral-600);
  --bs-code-bg: var(--bg-3);

  --bs-light-active: var(--neutral-300);
  --bs-light-light: var(--bg-1);
  --bs-light-inverse: var(--text-06);
  --bs-light-bg-subtle: var(--neutral-100);
  --bs-light-border-subtle: var(--neutral-300);
  --bs-dark-light: var(--neutral-200);
  --bs-dark-active: var(--neutral-1200);
  --bs-dark-inverse: var(--bg-1);
  --bs-dark-bg-subtle: var(--neutral-800);
  --bs-dark-border-subtle: var(--text-03);

  /* Validation feedback is body-size text, so it takes the -1100 text stop
     rather than the -900 fill. */
  --bs-form-valid-color: var(--green-1100);
  --bs-form-valid-border-color: var(--green-1100);
  --bs-form-invalid-color: var(--red-1100);
  --bs-form-invalid-border-color: var(--red-1100);

  --bs-rating-color-default: var(--text-01);
  --bs-engage-btn-bg: var(--bg-1);
  --bs-engage-btn-border-color: var(--border-outline);
  --bs-engage-btn-icon-color: var(--text-03);
}

/* --------------------------------------------------------------------------
   4. Dark theme
   --------------------------------------------------------------------------
   DS 2.0 ships NO dark tokens. Its `.dark` block is labelled a placeholder
   "awaiting Figma dark tokens" and holds generic achromatic oklch greys —
   adopting it here would drop the warm character the rest of the system is
   built on and make dark mode look like a different product.

   So the surfaces below are derived from the DS warm neutral ramp instead,
   and the accents come from real DS stops lifted a few steps for legibility
   on dark ground. Replace this block wholesale once Figma publishes dark.

   This matters more than it looks: SFTPGo renders the login pages with
   data-bs-theme="dark" by default, so this block — not section 1 — is what
   an unauthenticated visitor sees first.
   -------------------------------------------------------------------------- */
[data-bs-theme="dark"] {
  --bb-orange: var(--primary-700);        /* #f4a050 — lifted for dark ground */
  --bb-orange-dark: var(--primary-900);
  --bb-orange-text: var(--primary-700);
  --bb-orange-tint: var(--primary-1200);
  --bb-ink: var(--text-07);

  /* White here too, per section 1. The ratio this used to warn about is now
     the same in both themes: white on --primary-700 (#f4a050) would be
     2.10:1, but the filled button no longer takes that stop — see
     --bb-btn1-fill below — so it sits on #f57e21 at 2.66:1, exactly as in
     light. Still under AA for body text; that trade-off is recorded in
     section 1 and is unchanged. What is gone is dark being *worse* than
     light. Everything else orange on this ground — links, icons,
     .text-primary — keeps the --primary-700 lift, which is what needed it. */
  --bs-primary-inverse: #ffffff;

  --bs-primary: var(--bb-orange);
  --bs-primary-active: var(--bb-orange-dark);
  --bs-primary-light: var(--bb-orange-tint);
  --bs-primary-rgb: 244, 160, 80;
  --bs-primary-clarity: rgba(244, 160, 80, 0.2);
  --bs-primary-bg-subtle: var(--primary-1200);
  --bs-primary-border-subtle: var(--primary-1100);
  --bs-primary-text-emphasis: var(--primary-500);

  /* THE FILLED BUTTON DOES NOT TAKE THE DARK LIFT — it keeps the light
     theme's stops exactly.

     --bs-primary is --primary-700 (#f4a050) here because orange TEXT needs
     lifting off dark ground to stay legible. A solid block does not: at
     #f4a050 the fill reads washed out beside the neutral outline next to it,
     and it is the one place in the product where the brand colour is shown at
     full size, so a visitor sees a duller orange than the brand actually is.

     It also happens to be the better contrast. White on #f4a050 is 2.10:1;
     on #f57e21 it is 2.66:1 — still under AA for body text (the trade-off
     recorded in section 1 for --bs-primary-inverse), but the same ratio the
     light theme ships rather than a worse one. This is the change that note
     described as available and left undone.

     Scoped to the button ground alone: links, icons, .text-primary, the
     active sidebar row and the focus halo all keep --primary-700, which is
     what actually needed the lift. */
  --bb-btn1-fill: var(--primary-900);        /* #f57e21 — as light */
  --bb-btn1-fill-hover: var(--primary-1000); /* #e06d10 — as light */

  --bs-component-active-bg: var(--bb-orange);
  --bs-component-active-color: var(--bb-ink);

  /* On dark ground primary-700 clears AA comfortably (8.9:1 on the ink
     sidebar), so links take the fill stop directly. */
  --bs-link-color: var(--primary-700);
  --bs-link-color-rgb: 244, 160, 80;
  --bs-link-hover-color: var(--primary-500);
  --bs-link-hover-color-rgb: 253, 204, 140;

  /* Same Metronic --bs-text-primary caveat as section 1. */
  --bs-text-primary: var(--primary-700);
  --bs-component-hover-color: var(--primary-700);
  --bs-component-checked-bg: var(--bb-orange);
  --bs-menu-link-color-hover: var(--primary-700);
  --bs-menu-link-color-active: var(--primary-700);
  --bs-menu-link-color-here: var(--primary-700);
  --bs-menu-link-color-show: var(--primary-700);

  /* --- Warm dark surfaces, derived from the DS neutral ramp ---
     neutral-1200 (#21201c) is the darkest published warm stop; the grounds
     below it are extrapolated down the same hue so the shell recedes behind
     the cards.

     Same body-bg-is-the-card caveat as section 2: body-bg is the raised card
     and app-bg the ground behind it. Stock dark keeps them a step apart
     (#15171C card over #0F1014 ground) and so does this — ink for the
     ground, a lifted warm stop for the card. */
  --bs-body-bg: #1f1e1b;
  --bs-body-bg-rgb: 31, 30, 27;
  --bs-body-color: var(--neutral-300);
  --bs-body-color-rgb: 241, 240, 239;

  --bs-app-bg-color: #141312;
  --bs-app-blank-bg-color: #141312;
  --bs-page-bg: #141312;

  /* Matches --bs-app-bg-color above, per the note in section 2. This used to
     be #1f1e1b (the card surface), which was invisible while the light header
     also sat a step off its ground — once the light ground went white the two
     themes disagreed and dark grew a header band light did not have. */
  --bs-app-header-base-bg-color: #141312;
  --bs-app-header-minimize-bg-color: rgba(20, 19, 18, 0.5);

  /* Sidebar in Bindbee ink, a step below the app ground. See section 2. */
  --bs-app-sidebar-base-bg-color: #141312;

  --bs-modal-bg: #1f1e1b;
  --bs-modal-content-bg: #1f1e1b;

  /* --- Overlay panels: separated by LIFT AND RIM, not by shadow ---
     Dropdowns float over the card, and on this ground the mechanism that
     separates them in light cannot work. Three things had gone wrong at once
     for an open "Column visibility" or user menu:

       - the panel took #1f1e1b, the SAME colour as the card underneath it;
       - Metronic gives .menu-sub-dropdown a radius and a shadow but
         `border:0`, so there was no edge;
       - --bs-menu-dropdown-box-shadow was still the light value,
         rgba(31,29,25,.12) — a near-black at 12%, invisible on near-black.

     A dark ground gives a drop shadow almost nothing to work with, so dark
     separates the way dark UIs normally do: a step UP in surface says "in
     front", a hairline says where the panel ends, and the shadow is kept only
     to anchor it. #2a2925 is this file's existing one-step-up dark surface
     (component hover, solid inputs, the secondary button's hover fill) and
     #3d3c36 the lighter of its two dark border stops, so neither is a new
     colour.

     THE RIM RIDES IN THE SHADOW on purpose. Both panels that needed fixing
     are `border:0` — .menu-sub-dropdown from a (0,1,0) Metronic rule, and
     .select2-dropdown from the bootstrap5 select2 theme — and each reads its
     ground and its shadow from a token and nothing else. Putting the rim in
     the shadow means these tokens alone reach both, with no new selector.
     Section 5 hands .modal-content and .dropdown-menu the drop WITHOUT the
     ring; the note there says why.

     Light is deliberately untouched: its shadow reads fine on a light ground
     and the panels separate correctly there today. */
  --bb-overlay-ground: #2a2925;
  --bb-overlay-rim: #3d3c36;
  --bb-overlay-drop: 0 16px 32px -8px rgba(0, 0, 0, 0.6);
  --bb-overlay-shadow: 0 0 0 1px var(--bb-overlay-rim), var(--bb-overlay-drop);

  --bs-dropdown-bg: var(--bb-overlay-ground);
  --bs-dropdown-box-shadow: var(--bb-overlay-shadow);
  --bs-popover-bg: #1f1e1b;
  --bs-offcanvas-bg: #1f1e1b;
  --bs-list-group-bg: #1f1e1b;
  --bs-accordion-bg: #1f1e1b;
  --bs-drawer-bg-color: #1f1e1b;

  --bs-root-card-border-color: #33322d;
  --bs-root-card-box-shadow: none;

  --bs-menu-link-bg-color-hover: #2a2925;
  --bs-menu-link-bg-color-active: #2a2925;
  --bs-menu-link-bg-color-here: #2a2925;
  --bs-menu-link-bg-color-show: #2a2925;

  --bs-secondary-bg: #2a2925;
  --bs-secondary-bg-rgb: 42, 41, 37;
  --bs-tertiary-bg: #33322d;
  --bs-tertiary-bg-rgb: 51, 50, 45;

  --bs-secondary-color: var(--neutral-900);
  --bs-secondary-color-rgb: 141, 141, 134;
  --bs-tertiary-color: var(--neutral-1000);
  --bs-emphasis-color: var(--neutral-100);
  --bs-heading-color: var(--neutral-200);
  --bs-text-muted: var(--neutral-900);

  --bs-border-color: #33322d;
  --bs-border-color-translucent: var(--white-a-200);
  --bs-card-border-color: #33322d;
  --bs-table-border-color: #33322d;
  --bs-dropdown-border-color: var(--bb-overlay-rim);
  --bs-modal-border-color: #33322d;
  --bs-input-border-color: #3d3c36;

  /* Grey ramp inverts under dark: gray-100 is the darkest. */
  --bs-gray-100: #2a2925;
  --bs-gray-200: #33322d;
  --bs-gray-300: #3d3c36;
  --bs-gray-400: var(--neutral-1100);
  --bs-gray-500: var(--neutral-1000);
  --bs-gray-600: var(--neutral-900);
  --bs-gray-700: var(--neutral-800);
  --bs-gray-800: var(--neutral-500);
  --bs-gray-900: var(--neutral-200);

  --bs-light: #2a2925;
  --bs-light-rgb: 42, 41, 37;
  --bs-dark: var(--neutral-200);
  --bs-dark-rgb: 249, 249, 248;

  /* text-gray ramp, inverted for dark — see the note in section 2. */
  --bs-text-gray-100: #2a2925;
  --bs-text-gray-200: #33322d;
  --bs-text-gray-300: var(--neutral-1100);
  --bs-text-gray-400: var(--neutral-1000);
  --bs-text-gray-500: var(--neutral-900);
  --bs-text-gray-600: var(--neutral-800);
  --bs-text-gray-700: var(--neutral-700);
  --bs-text-gray-800: var(--neutral-500);
  --bs-text-gray-900: var(--neutral-200);

  --bs-text-dark: var(--neutral-200);
  --bs-text-light: #2a2925;

  --bs-input-bg: var(--neutral-1200);
  --bb-input-text: var(--neutral-200);
  --bs-input-color: var(--neutral-200);

  --bb-btn2-label: var(--neutral-300);
  --bb-btn2-edge: var(--neutral-1000);
  --bb-btn2-edge-hover: var(--neutral-900);
  --bb-btn2-fill-hover: #2a2925;
  --bs-input-placeholder-color: var(--neutral-1000);
  --bs-input-solid-bg: #2a2925;
  --bs-input-solid-color: var(--neutral-200);
  --bs-input-solid-placeholder-color: var(--neutral-1000);

  /* Auth-page input states — see the note in section 2. The edges are lifted
     off the ink ground rather than dropped onto it: the resting stop is the
     same #3d3c36 --bs-input-border-color takes above, and hover is one
     lighter. The hover halo inverts to white at the same 5%. */
  --bb-input-border: #3d3c36;
  --bb-input-border-hover: var(--neutral-1100);   /* #63635e */
  --bb-input-ring-hover: var(--white-a-100);      /* 5% white halo */
  --bb-input-placeholder: var(--neutral-1000);    /* #82827c */
  --bb-input-disabled-border: #33322d;
  --bb-input-disabled-text: var(--neutral-1100);  /* #63635e */

  --bs-table-color: var(--neutral-300);
  --bs-table-striped-bg: #201f1b;
  --bs-table-hover-bg: #2a2925;

  --bs-component-hover-bg: #2a2925;

  /* --- Status, lifted to the -800 fills for dark ground --- */
  --bs-success: var(--green-800);
  --bs-success-rgb: 91, 185, 139;
  --bs-success-active: var(--green-900);
  --bs-success-light: var(--green-1200);
  --bs-success-inverse: var(--text-07);
  --bs-text-success: var(--green-800);
  --bs-success-bg-subtle: var(--green-1200);
  --bs-success-border-subtle: var(--green-1100);
  --bs-success-text-emphasis: var(--green-600);

  --bs-info: var(--blue-800);
  --bs-info-rgb: 94, 177, 239;
  --bs-info-active: var(--blue-900);
  --bs-info-light: var(--blue-1200);
  --bs-info-inverse: var(--text-07);
  --bs-text-info: var(--blue-800);
  --bs-info-bg-subtle: var(--blue-1200);
  --bs-info-border-subtle: var(--blue-1100);
  --bs-info-text-emphasis: var(--blue-600);

  --bs-warning: var(--yellow-900);
  --bs-warning-rgb: 255, 197, 61;
  --bs-warning-active: var(--yellow-1000);
  --bs-warning-light: var(--yellow-1200);
  --bs-warning-inverse: var(--text-07);
  --bs-text-warning: var(--yellow-900);
  --bs-warning-bg-subtle: var(--yellow-1200);
  --bs-warning-border-subtle: var(--yellow-1100);
  --bs-warning-text-emphasis: var(--yellow-600);

  --bs-danger: var(--red-800);
  --bs-danger-rgb: 235, 142, 144;
  --bs-danger-active: var(--red-900);
  --bs-danger-light: var(--red-1200);
  --bs-danger-inverse: var(--text-07);
  --bs-text-danger: var(--red-800);
  --bs-danger-bg-subtle: var(--red-1200);
  --bs-danger-border-subtle: var(--red-1100);
  --bs-danger-text-emphasis: var(--red-600);

  --bs-secondary: #33322d;
  --bs-secondary-rgb: 51, 50, 45;
  --bs-secondary-active: #3d3c36;
  --bs-secondary-light: #2a2925;
  --bs-secondary-inverse: var(--neutral-200);
  --bs-text-secondary: var(--neutral-900);

  /* --- Stragglers, dark ---
     See the note in section 3. This is the half that actually mattered: stock
     hardcodes every one of these cool under dark, so they survived the family
     mappings above. --bs-menu-dropdown-bg-color (#1C1D22) is the profile /
     user dropdown panel, which stayed stock cool grey against warm chrome. */
  --bs-menu-dropdown-bg-color: var(--bb-overlay-ground);
  --bs-menu-dropdown-box-shadow: var(--bb-overlay-shadow);
  --bs-symbol-label-bg: #2a2925;
  --bs-symbol-label-color: var(--neutral-500);
  --bs-scrollbar-color: #33322d;
  --bs-scrollbar-hover-color: #4a4840;
  --bs-border-dashed-color: #33322d;
  --bs-menu-heading-color: var(--neutral-900);
  --bs-bullet-bg-color: var(--neutral-1100);
  --bs-code-bg: #2a2925;

  --bs-light-active: #33322d;
  --bs-light-light: #2a2925;
  --bs-light-inverse: var(--neutral-500);
  --bs-light-bg-subtle: #2a2925;
  --bs-light-border-subtle: #3d3c36;
  --bs-dark-light: #2a2925;
  --bs-dark-active: var(--neutral-100);
  --bs-dark-inverse: var(--neutral-1200);
  --bs-dark-bg-subtle: #1f1e1b;
  --bs-dark-border-subtle: #33322d;

  --bs-form-valid-color: var(--green-800);
  --bs-form-valid-border-color: var(--green-800);
  --bs-form-invalid-color: var(--red-800);
  --bs-form-invalid-border-color: var(--red-800);

  --bs-rating-color-default: var(--neutral-1100);
  --bs-engage-btn-bg: #2a2925;
  --bs-engage-btn-border-color: #33322d;
  --bs-engage-btn-icon-color: var(--neutral-900);

}

/* --------------------------------------------------------------------------
   5. Components that hardcode the stock hex in their own scoped tokens
   -------------------------------------------------------------------------- */
.btn-primary {
  --bs-btn-color: var(--bs-primary-inverse);
  --bs-btn-bg: var(--bb-btn1-fill);
  --bs-btn-border-color: var(--bb-btn1-fill);
  --bs-btn-hover-color: var(--bs-primary-inverse);
  --bs-btn-hover-bg: var(--bb-btn1-fill-hover);
  --bs-btn-hover-border-color: var(--bb-btn1-fill-hover);
  --bs-btn-focus-shadow-rgb: var(--bs-primary-rgb);
  --bs-btn-active-color: var(--bs-primary-inverse);
  --bs-btn-active-bg: var(--bb-btn1-fill-hover);
  --bs-btn-active-border-color: var(--bb-btn1-fill-hover);
  --bs-btn-disabled-color: var(--bs-primary-inverse);
  --bs-btn-disabled-bg: var(--bb-btn1-fill);
  --bs-btn-disabled-border-color: var(--bb-btn1-fill);
}

/* …and the two rules that make the block above mean something.
   ---------------------------------------------------------------------------
   The --bs-btn-* names are Bootstrap's documented hooks and Metronic defines
   them, but its own rules never read them for this button — it writes

     .btn.btn-primary{…;background-color:var(--bs-primary)}                (0,2,0)
     .btn.btn-primary:hover:not(.btn-active){…!important}                  (0,4,0)

   as plain declarations straight off --bs-primary, the way .form-control
   ignores --bs-input-bg (section 2) and .btn-secondary reads --bs-gray-800
   rather than --bs-secondary (further down). So the token block is kept in
   sync for a Metronic that honours it, and these two rules are what actually
   paints. Only the ground is restated: the label and glyphs are
   --bs-primary-inverse in both themes and were already right.

   Weights are mirrored, not beaten. Resting is (0,2,0) against stock's
   (0,2,0), later in the cascade. The interactive branches are spelled out to
   land on (0,4,0) with !important, matching stock on both counts — the same
   discipline as the two secondary blocks below, and for the same reason: drop
   a branch and that state falls back to --bs-primary-active, which under dark
   is #f57e21 and would leave the button *lightening* on hover instead of
   deepening. Unlike .btn-secondary there is no [data-bs-theme=dark] variant
   in stock for this button, so one set of branches covers both themes. */
.btn.btn-primary {
  background-color: var(--bb-btn1-fill);
  border-color: var(--bb-btn1-fill);
}

.btn-check:active + .btn.btn-primary,
.btn-check:checked + .btn.btn-primary,
.btn.btn-primary.active,
.btn.btn-primary.show,
.btn.btn-primary:active:not(.btn-active),
.btn.btn-primary:focus:not(.btn-active),
.btn.btn-primary:hover:not(.btn-active),
.show > .btn.btn-primary {
  background-color: var(--bb-btn1-fill-hover) !important;
  border-color: var(--bb-btn1-fill-hover);
}

.btn-outline-primary {
  --bs-btn-color: var(--bs-primary);
  --bs-btn-border-color: var(--bs-primary);
  --bs-btn-hover-color: var(--bs-primary-inverse);
  --bs-btn-hover-bg: var(--bs-primary);
  --bs-btn-hover-border-color: var(--bs-primary);
  --bs-btn-focus-shadow-rgb: var(--bs-primary-rgb);
  --bs-btn-active-color: var(--bs-primary-inverse);
  --bs-btn-active-bg: var(--bs-primary);
  --bs-btn-active-border-color: var(--bs-primary);
  --bs-btn-disabled-color: var(--bs-primary);
  --bs-btn-disabled-border-color: var(--bs-primary);
}

/* Solid status buttons take the -1100 text stop rather than the -900 fill,
   so the white label clears AA. See the note in section 3. Scoped to buttons
   only — badges, icons and alert grounds keep the -900 fill. */
.btn-success {
  --bs-btn-color: #ffffff;
  --bs-btn-bg: var(--green-1100);
  --bs-btn-border-color: var(--green-1100);
  --bs-btn-hover-color: #ffffff;
  --bs-btn-hover-bg: var(--green-1200);
  --bs-btn-hover-border-color: var(--green-1200);
  --bs-btn-active-color: #ffffff;
  --bs-btn-active-bg: var(--green-1200);
  --bs-btn-active-border-color: var(--green-1200);
  --bs-btn-focus-shadow-rgb: var(--bs-success-rgb);
}

.btn-danger {
  --bs-btn-color: #ffffff;
  --bs-btn-bg: var(--red-1100);
  --bs-btn-border-color: var(--red-1100);
  --bs-btn-hover-color: #ffffff;
  --bs-btn-hover-bg: var(--red-1200);
  --bs-btn-hover-border-color: var(--red-1200);
  --bs-btn-active-color: #ffffff;
  --bs-btn-active-bg: var(--red-1200);
  --bs-btn-active-border-color: var(--red-1200);
  --bs-btn-focus-shadow-rgb: var(--bs-danger-rgb);
}

.btn-info {
  --bs-btn-color: #ffffff;
  --bs-btn-bg: var(--blue-1100);
  --bs-btn-border-color: var(--blue-1100);
  --bs-btn-hover-color: #ffffff;
  --bs-btn-hover-bg: var(--blue-1200);
  --bs-btn-hover-border-color: var(--blue-1200);
  --bs-btn-active-color: #ffffff;
  --bs-btn-active-bg: var(--blue-1200);
  --bs-btn-active-border-color: var(--blue-1200);
  --bs-btn-focus-shadow-rgb: var(--bs-info-rgb);
}

/* Warning keeps the -900 fill: it is light, so the label is ink, and the
   -1100 stop (#ab6400) would read as brown rather than a warning. */
.btn-warning {
  --bs-btn-color: var(--text-07);
  --bs-btn-bg: var(--yellow-900);
  --bs-btn-border-color: var(--yellow-900);
  --bs-btn-hover-color: var(--text-07);
  --bs-btn-hover-bg: var(--yellow-1000);
  --bs-btn-hover-border-color: var(--yellow-1000);
  --bs-btn-active-color: var(--text-07);
  --bs-btn-active-bg: var(--yellow-1000);
  --bs-btn-active-border-color: var(--yellow-1000);
  --bs-btn-focus-shadow-rgb: var(--bs-warning-rgb);
}

/* Secondary buttons — neutral outline.
   ---------------------------------------------------------------------------
   `btn-light-primary` is SFTPGo's secondary variant (56 uses: Export, Column
   visibility, share copy-link, the list-page toolbars). Stock paints it as a
   soft ORANGE fill — `color:var(--bs-primary)` on `--bs-primary-light`, about
   2.5:1. Neutral here so the orange means one thing, the primary action, and
   the label climbs to 16.7:1.

   Colours come from --bb-btn2-* in sections 2/4, not from --bs-gray-*. That
   ramp is the text ramp (near-black at the dark end), so reading the edge off
   it would draw a near-black rim instead of a light neutral one.

   THE RING IS A FORCED INSET box-shadow. This file's header says overrides win
   without !important; this block is the documented exception, because Metronic
   blocks all three ways of drawing an edge:

     - border      .btn:not(.btn-outline):not(.btn-dashed):…{border:0}  ≈(0,7,0)
     - outline     .btn{outline:0!important}
     - box-shadow  .btn:not(.btn-shadow):not(.shadow):…{box-shadow:none} ≈(0,6,0)

   against this rule's (0,2,0), and every escape hatch in those :not() chains
   is a class that would have to be added in the template, which is out of
   bounds. An inset shadow is the right shape anyway: stock buttons carry
   border:0, so a real 1px border would make this 2px larger than the
   .btn-primary beside it and break toolbar alignment. An inset shadow paints
   inside the existing box and changes no metrics — both compute to 40px.

   THE :focus-visible RULE IS NOT OPTIONAL. Bootstrap draws button focus with
   box-shadow, so an !important box-shadow on the resting state swallows the
   keyboard focus ring. It restates both layers and must stay last — it and
   :hover are both (0,3,0), so source order decides. The halo keeps the brand
   orange: a focus indicator should not be the same neutral as the resting
   edge. */
.btn.btn-light-primary {
  color: var(--bb-btn2-label);
  background-color: transparent;
  box-shadow: inset 0 0 0 1px var(--bb-btn2-edge) !important;
}

/* Metronic colours glyphs and the dropdown caret separately from the label. */
.btn.btn-light-primary .svg-icon,
.btn.btn-light-primary i,
.btn.btn-light-primary.dropdown-toggle:after {
  color: var(--bs-text-gray-600);
}

/* Hover / focus / open.
   ---------------------------------------------------------------------------
   MIRROR STOCK'S SELECTOR LIST EXACTLY, `:not(.btn-active)` included. Stock
   writes the interactive states as

     .btn.btn-light-primary:hover:not(.btn-active){…;
       background-color:var(--bs-primary)!important}

   which is (0,4,0). A plain `.btn.btn-light-primary:hover` is (0,3,0) and
   loses, !important or not — that is why an earlier pass left the button
   turning orange on hover even though every other state was neutral. The
   :not() is doing nothing for us (no .btn-active anywhere in SFTPGo's
   templates); it is carried purely to match the weight.

   Same reason the eight branches below are spelled out rather than collapsed:
   `.show>.btn…` and `.btn-check:checked+.btn…` are separate shapes stock uses,
   and dropping any of them leaves that state orange. */
.btn-check:active + .btn.btn-light-primary,
.btn-check:checked + .btn.btn-light-primary,
.btn.btn-light-primary.active,
.btn.btn-light-primary.show,
.btn.btn-light-primary:active:not(.btn-active),
.btn.btn-light-primary:focus:not(.btn-active),
.btn.btn-light-primary:hover:not(.btn-active),
.show > .btn.btn-light-primary {
  color: var(--bb-btn2-label);
  background-color: var(--bb-btn2-fill-hover) !important;
  box-shadow: inset 0 0 0 1px var(--bb-btn2-edge-hover) !important;
}

/* Glyphs and the dropdown caret are a separate stock rule at the same weight,
   flipping them to --bs-primary-inverse (white) — invisible on a light neutral
   fill. Mirrored branch for branch. */
.btn-check:active + .btn.btn-light-primary .svg-icon,
.btn-check:active + .btn.btn-light-primary i,
.btn-check:checked + .btn.btn-light-primary .svg-icon,
.btn-check:checked + .btn.btn-light-primary i,
.btn.btn-light-primary.active .svg-icon,
.btn.btn-light-primary.active i,
.btn.btn-light-primary.show .svg-icon,
.btn.btn-light-primary.show i,
.btn.btn-light-primary:active:not(.btn-active) .svg-icon,
.btn.btn-light-primary:active:not(.btn-active) i,
.btn.btn-light-primary:focus:not(.btn-active) .svg-icon,
.btn.btn-light-primary:focus:not(.btn-active) i,
.btn.btn-light-primary:hover:not(.btn-active) .svg-icon,
.btn.btn-light-primary:hover:not(.btn-active) i,
.show > .btn.btn-light-primary .svg-icon,
.show > .btn.btn-light-primary i,
.btn-check:active + .btn.btn-light-primary.dropdown-toggle:after,
.btn-check:checked + .btn.btn-light-primary.dropdown-toggle:after,
.btn.btn-light-primary.active.dropdown-toggle:after,
.btn.btn-light-primary.show.dropdown-toggle:after,
.btn.btn-light-primary:active:not(.btn-active).dropdown-toggle:after,
.btn.btn-light-primary:focus:not(.btn-active).dropdown-toggle:after,
.btn.btn-light-primary:hover:not(.btn-active).dropdown-toggle:after,
.show > .btn.btn-light-primary.dropdown-toggle:after {
  color: var(--bb-btn2-label);
}

/* Keyboard focus — see the note above. */
.btn.btn-light-primary:focus-visible {
  box-shadow: inset 0 0 0 1px var(--bb-btn2-edge-hover),
              0 0 0 0.25rem var(--bs-primary-clarity) !important;
}

/* .btn-secondary — the SECOND secondary, folded into the first.
   ---------------------------------------------------------------------------
   SFTPGo ships two secondary buttons and they looked nothing alike. The rail
   toolbars use .btn-light-primary, restyled above as a neutral outline; the
   28 other places — every confirm dialog's cancel button (#modal_alert_cancel,
   which is what `cancelButton: 'btn btn-secondary'` targets), the modal
   Cancel buttons in mfa.html and files.html, and the Export button on the logs
   page — use .btn-secondary, which Metronic renders as a SOLID DARK slab:

     .btn.btn-secondary{color:var(--bs-body-bg);background-color:var(--bs-gray-800)}

   Note it reads --bs-gray-800, not --bs-secondary (which is #e2e1de here and
   goes to the losing earlier rule). Section 2 maps gray-800 onto the DS text
   ramp — see the long note there about 500-900 being TEXT stops — so that
   declaration resolves to #1f1d19 and the cancel button beside a "Sign out"
   rendered as near-black. Stock's own is a navy slab; the ramp remap only
   changed which dark it was. This block makes it the same outline as
   .btn-light-primary, off the same --bb-btn2-* tokens, so the product has one
   secondary button rather than two.

   WHY IT IS A SEPARATE BLOCK rather than more branches on the lists above:
   the weights are different. .btn-secondary carries a [data-bs-theme=dark]
   variant that .btn-light-primary does not, which adds a class-worth of
   specificity to every dark branch, so the two cannot share a selector list
   without over- or under-shooting one of them.

   Everything else is the same three traps as above, and the same fixes:
     - `border` is blocked by .btn:not(.btn-outline)…{border:0} ≈ (0,7,0), so
       the edge is an inset box-shadow;
     - box-shadow is blocked by .btn:not(.btn-shadow)…{box-shadow:none}
       ≈ (0,6,0), so that one shadow carries !important;
     - the interactive states are `!important` in stock, so ours are too, and
       every branch is mirrored — drop one and that state stays dark.

   TWO OF STOCK'S DARK BRANCHES ARE DELIBERATELY NOT MIRRORED. Its dark list
   includes `.btn-check:active+[data-bs-theme=dark] .btn.btn-secondary` and the
   :checked and `.show>` variants of it, which read as "an element carrying
   data-bs-theme preceded by a sibling .btn-check". The attribute is set on
   <html> by the inline script in common/base.html, and <html> has no siblings,
   so those branches cannot match anything and cannot win anything either. */
.btn.btn-secondary,
[data-bs-theme="dark"] .btn.btn-secondary {
  color: var(--bb-btn2-label);
  background-color: transparent;
  box-shadow: inset 0 0 0 1px var(--bb-btn2-edge) !important;
}

/* Glyphs and the dropdown caret, resting. Purely defensive: no .btn-secondary
   in this build carries an <i> or a caret — the Export button on the logs page
   wraps a .spinner-border, which is a <span> and takes its colour from
   currentColor, so the rule above already covers it. Kept because stock points
   these at --bs-body-bg (light theme) and #fff (dark), both of which would be
   invisible the moment a template adds an icon to one of these buttons. */
.btn.btn-secondary .svg-icon,
.btn.btn-secondary i,
.btn.btn-secondary.dropdown-toggle:after,
[data-bs-theme="dark"] .btn.btn-secondary .svg-icon,
[data-bs-theme="dark"] .btn.btn-secondary i,
[data-bs-theme="dark"] .btn.btn-secondary.dropdown-toggle:after {
  color: var(--bs-text-gray-600);
}

/* Hover / focus / open. Light branches are (0,4,0) and dark are (0,5,0),
   matching stock on both sides so source order decides. */
.btn-check:active + .btn.btn-secondary,
.btn-check:checked + .btn.btn-secondary,
.btn.btn-secondary.active,
.btn.btn-secondary.show,
.btn.btn-secondary:active:not(.btn-active),
.btn.btn-secondary:focus:not(.btn-active),
.btn.btn-secondary:hover:not(.btn-active),
.show > .btn.btn-secondary,
[data-bs-theme="dark"] .btn.btn-secondary.active,
[data-bs-theme="dark"] .btn.btn-secondary.show,
[data-bs-theme="dark"] .btn.btn-secondary:active:not(.btn-active),
[data-bs-theme="dark"] .btn.btn-secondary:focus:not(.btn-active),
[data-bs-theme="dark"] .btn.btn-secondary:hover:not(.btn-active) {
  color: var(--bb-btn2-label);
  background-color: var(--bb-btn2-fill-hover) !important;
  box-shadow: inset 0 0 0 1px var(--bb-btn2-edge-hover) !important;
}

/* Glyphs and caret in those states — stock flips them to --bs-body-bg at
   (0,5,0), which is near-white on the light neutral fill. Same defensive
   reasoning as the resting rule; there is no dark counterpart in stock, so
   the dark branches above inherit this one. */
.btn-check:active + .btn.btn-secondary .svg-icon,
.btn-check:active + .btn.btn-secondary i,
.btn-check:checked + .btn.btn-secondary .svg-icon,
.btn-check:checked + .btn.btn-secondary i,
.btn.btn-secondary.active .svg-icon,
.btn.btn-secondary.active i,
.btn.btn-secondary.show .svg-icon,
.btn.btn-secondary.show i,
.btn.btn-secondary:active:not(.btn-active) .svg-icon,
.btn.btn-secondary:active:not(.btn-active) i,
.btn.btn-secondary:focus:not(.btn-active) .svg-icon,
.btn.btn-secondary:focus:not(.btn-active) i,
.btn.btn-secondary:hover:not(.btn-active) .svg-icon,
.btn.btn-secondary:hover:not(.btn-active) i,
.show > .btn.btn-secondary .svg-icon,
.show > .btn.btn-secondary i,
.btn-check:active + .btn.btn-secondary.dropdown-toggle:after,
.btn-check:checked + .btn.btn-secondary.dropdown-toggle:after,
.btn.btn-secondary.active.dropdown-toggle:after,
.btn.btn-secondary.show.dropdown-toggle:after,
.btn.btn-secondary:active:not(.btn-active).dropdown-toggle:after,
.btn.btn-secondary:focus:not(.btn-active).dropdown-toggle:after,
.btn.btn-secondary:hover:not(.btn-active).dropdown-toggle:after,
.show > .btn.btn-secondary.dropdown-toggle:after {
  color: var(--bb-btn2-label);
}

/* Keyboard focus. Same reason it is not optional above: the !important
   box-shadow on the resting state swallows Bootstrap's focus ring, so both
   layers are restated. The dark branch is (0,4,0) against the dark resting
   rule's (0,3,0), so it wins on weight rather than order — but it is written
   last anyway, matching the block above. */
.btn.btn-secondary:focus-visible,
[data-bs-theme="dark"] .btn.btn-secondary:focus-visible {
  box-shadow: inset 0 0 0 1px var(--bb-btn2-edge-hover),
              0 0 0 0.25rem var(--bs-primary-clarity) !important;
}

/* Header icon buttons — the theme switch and the user menu.
   ---------------------------------------------------------------------------
   Both are `btn btn-icon btn-active-light-primary` on a fixed 35/40px box and
   nothing else: transparent ground, `border:0`, `box-shadow:none`. Stock only
   draws them on HOVER, as a --bs-primary-light fill, so at rest the header
   showed two bare glyphs floating in a 90px band with nothing to say they were
   controls. The sidebar toggle in the same bar is the same shape and the same
   problem below lg, and is covered by the same selector.

   An inset shadow rather than a border, for the usual reason plus a sharper
   one here: the box is pinned by `w-md-40px h-md-40px`, so a real 1px border
   would make it 42x42 inside a 40px slot and knock the two buttons out of
   alignment with each other and with the section title across the bar.

   NO !important, unlike every other button in this section — worth knowing why
   rather than copying one in out of habit. The two Metronic rules that block
   edges are class chains:

     .btn:not(.btn-outline):not(.btn-dashed):…{border:0}                (0,7,0)
     .btn:not(.btn-shadow):not(.shadow):not(.shadow-sm):…{box-shadow:none}
                                                                       (0,6,0)

   and #kt_app_header is an ID, so (1,2,0) outranks both outright. Same reason
   :focus-visible below needs none, where the .btn-light-primary and
   .btn-secondary equivalents both do.

   Hover is left as stock draws it. The tint fill and the orange glyph are a
   deliberate affordance and the ask was for a resting edge, not a restyle —
   but the rim is restated across the interactive states so the shape does not
   vanish the moment the pointer arrives, and it steps to the hover stop the
   way the secondary button does. `.show` is in that list because Metronic adds
   it to the trigger itself while the menu is open (verified: the class list
   becomes `… show menu-dropdown`), so the edge holds while the panel is down.

   These read --bb-btn2-*, so the header buttons carry the same edge as every
   other secondary in the product and follow it into dark without a second
   rule. */
#kt_app_header .btn.btn-icon {
  box-shadow: inset 0 0 0 1px var(--bb-btn2-edge);
}

#kt_app_header .btn.btn-icon:hover,
#kt_app_header .btn.btn-icon:focus,
#kt_app_header .btn.btn-icon:active,
#kt_app_header .btn.btn-icon.active,
#kt_app_header .btn.btn-icon.show {
  box-shadow: inset 0 0 0 1px var(--bb-btn2-edge-hover);
}

#kt_app_header .btn.btn-icon:focus-visible {
  box-shadow: inset 0 0 0 1px var(--bb-btn2-edge-hover),
              0 0 0 0.25rem var(--bs-primary-clarity);
}

/* Typed text in inputs.
   `.form-control{color:var(--bs-gray-700)}` is a plain declaration that never
   reads --bs-input-color, so the token alone does nothing — this rule is what
   applies it. Restated for :focus too, because stock repeats the colour there
   and would otherwise pull it back to the ramp value on focus. */
.form-control,
.form-control:focus,
.form-control.form-control-solid,
.form-control.form-control-solid:focus,
.form-select,
.form-select:focus {
  color: var(--bb-input-text);
}

.nav-pills {
  --bs-nav-pills-link-active-color: var(--bs-primary-inverse);
  --bs-nav-pills-link-active-bg: var(--bs-primary);
}

.progress,
.progress-stacked {
  --bs-progress-bg: var(--bs-secondary-bg);
  --bs-progress-bar-color: var(--bs-primary-inverse);
  --bs-progress-bar-bg: var(--bs-primary);
}

/* Checkboxes / radios / switches — prominent in the file manager. */
.form-check-input:checked,
.form-check-input[type="checkbox"]:indeterminate {
  background-color: var(--bs-primary);
  border-color: var(--bs-primary);
}

.form-check-input:focus {
  border-color: var(--bs-primary);
  box-shadow: 0 0 0 0.25rem var(--bs-primary-clarity);
}

.form-range::-webkit-slider-thumb { background-color: var(--bs-primary); }
.form-range::-moz-range-thumb     { background-color: var(--bs-primary); }

/* Focus rings on inputs and selects. */
.form-control:focus,
.form-select:focus {
  border-color: var(--bs-primary);
  box-shadow: 0 0 0 0.25rem var(--bs-primary-clarity);
}

/* --bs-root-card-box-shadow is set in sections 2 and 4 because it is
   Metronic's own hook and feeds the variants it derives from the token. Note
   it does NOT decide what you see on these pages: SFTPGo writes
   `<div class="card shadow-sm">` in its templates, and that utility is a plain
   declaration that outranks --bs-card-box-shadow. The visible card shadow is
   therefore .shadow-sm in both themes. Overlays carry no such utility, so they
   take the DS shadow directly here. */
.dropdown-menu,
.modal-content {
  box-shadow: var(--ds-shadow-e4);
}

/* Dark needs its own branch. Stock writes
   `[data-bs-theme=dark] .dropdown-menu{box-shadow:0 0 30px rgba(0,0,0,.3)}`
   at (0,2,0), which outranks the (0,1,0) rule above, so neither the DS shadow
   nor --bs-dropdown-box-shadow ever reached a Bootstrap dropdown under dark.
   Mirrored at the same weight, later in the cascade.

   These two take --bb-overlay-drop rather than --bb-overlay-shadow, for
   different reasons each.

   .modal-content computes to border-width 0 in this build, so it would take a
   ring — but it does not need one: a modal sits on a dimmed backdrop, which
   already separates it from everything behind. Ringing it would read as a
   drawn box around a dialog that is not otherwise outlined.

   .dropdown-menu is DEFENSIVE ONLY. Bootstrap's dropdown does not appear in
   SFTPGo at all — the templates carry none and no page renders one; every menu
   in the product is Metronic's .menu-sub-dropdown, handled by the tokens in
   section 4. If a future template or plugin does render one it arrives with
   Bootstrap's own 1px border, which --bs-dropdown-border-color points at
   --bb-overlay-rim so it lands on the same hairline; the ring on top of that
   would be a 2px double edge. */
[data-bs-theme="dark"] .dropdown-menu,
[data-bs-theme="dark"] .modal-content {
  box-shadow: var(--bb-overlay-drop);
}

/* --------------------------------------------------------------------------
   6. Theme-aware wordmark
   --------------------------------------------------------------------------
   SFTPGo exposes a single logo_path per UI, and every template renders it as
   <img alt="Logo" src="{{.StaticURL}}{{.Branding.LogoPath}}" class="h-Npx">.
   So logo_path serves the light-background wordmark and the dark variant is
   substituted here. URLs are relative to this stylesheet, which is served
   from /static/branding/, so they resolve alongside it.
   -------------------------------------------------------------------------- */
[data-bs-theme="dark"] img[alt="Logo"] {
  content: url("bindbee_drop_dark.svg");
}

/* NOTE: the sidebar used to need its own pin here, because Metronic hardcodes
   --bs-app-sidebar-base-bg-color as #0B0C10 in BOTH its light and dark blocks —
   dark ground always — so the ink wordmark vanished into it under the light
   theme and had to be forced to the dark-bg variant in every theme.

   Sections 2 and 4 now set that token per theme, so the sidebar follows the
   theme like everything else and the rule above covers it: ink wordmark on the
   light sidebar, white wordmark on the ink one. The pin is deliberately gone —
   re-adding it would put the white wordmark back on a light ground. */

/* Drop the ShortName caption beside the sidebar wordmark.
   `short_name` is set empty in the compose file, but empty does not win: SFTPGo
   substitutes its own default, so this span renders the literal "WebAdmin" (and
   "WebClient" on the client UI — both UIs share common/base.html). The wordmark
   already carries the name, so the caption reads as stray chrome next to it.

   The login-type pages hide the same value by dropping the sibling col-7 in the
   block below; the sidebar has no col wrapper, so it is hidden here instead.
   Scoped through .app-sidebar-logo so only the logo lockup caption goes — the
   sidebar menu labels are untouched. */
#kt_app_sidebar .app-sidebar-logo > span {
  display: none;
}

/* Sidebar row states.
   The menu link COLOURS now come straight from section 1/4 (--bb-orange-text:
   #b85a09 on the light sidebar at 4.66:1, #f4a050 on the ink one at 6.93:1),
   so the old re-lift here is gone. Only the row GROUNDS need sidebar-specific
   values, because the global tokens in section 2 are tuned for menus sitting
   on cards (bg-1) and the sidebar sits on bg-2.

   Light: hover darkens a step to bg-3, active lifts to bg-1 — a white chip on
   the tinted rail. Active deliberately goes lighter rather than darker so the
   orange label keeps a white ground under it and holds 4.66:1; on bg-3 the
   same orange falls to 4.20:1 and misses AA. */
#kt_app_sidebar {
  --bb-sidebar-row-hover: var(--bg-3);
  --bb-sidebar-row-active: var(--bg-4);
  --bs-app-sidebar-base-border-end: 1px solid var(--bs-border-color);
}

/* Dark keeps the original lift off the #141312 base. */
[data-bs-theme="dark"] #kt_app_sidebar {
  --bb-sidebar-row-hover: #23221e;
  --bb-sidebar-row-active: #242320;
}

/* --------------------------------------------------------------------------
   Sidebar rules that hardcode a colour and reach no token
   --------------------------------------------------------------------------
   Everything under .app-sidebar-navs is written on the assumption that the
   sidebar is permanently dark, so the colours are baked in rather than
   tokenised and NO variable override touches them:

     .menu-link, .menu-link .menu-title          color: #dbdfe9   (pale)
     .menu-icon, .menu-icon .svg-icon, … i       color: #78829d   (mid)
     .menu-bullet .bullet, .menu-arrow:after     background: #78829d
     .menu-collapse-toggle .menu-title           color: #78829d !important
     .menu-badge .badge.badge-custom             #262626 / #78829d
     .menu-link.active                           background: #15171c; color: #fff

   Those pale values are invisible on a light rail — #dbdfe9 on bg-2 is about
   1.15:1 — so every one has to be restated. Each selector below is the stock
   one prefixed with the sidebar ID to outrank it (1,4,0 vs 0,4,0), and each
   points at a theme-aware token so the rail works in both themes rather than
   trading one hardcoded ground for another.
   -------------------------------------------------------------------------- */

/* Inactive row label. text-gray-700 is text-05 (#3b3833) on light — 11.1:1 on
   bg-2 — and neutral-700 on dark. */
#kt_app_sidebar .app-sidebar-navs .menu .menu-item .menu-link,
#kt_app_sidebar .app-sidebar-navs .menu .menu-item .menu-link .menu-title {
  color: var(--bs-text-gray-700);
}

/* Icons, bullets and the submenu chevron. These are the muted tier, so they
   take text-gray-500 (text-03, 4.4:1 on bg-2 — above the 3:1 that non-text
   contrast asks for). The collapse toggle carries !important upstream. */
#kt_app_sidebar .app-sidebar-navs .menu .menu-item .menu-link .menu-icon,
#kt_app_sidebar .app-sidebar-navs .menu .menu-item .menu-link .menu-icon .svg-icon,
#kt_app_sidebar .app-sidebar-navs .menu .menu-item .menu-link .menu-icon i {
  color: var(--bs-text-gray-500);
}

#kt_app_sidebar .app-sidebar-navs .menu .menu-item .menu-link .menu-bullet .bullet,
#kt_app_sidebar .app-sidebar-navs .menu .menu-item .menu-link .menu-arrow:after {
  background-color: var(--bs-text-gray-500);
}

#kt_app_sidebar .app-sidebar-navs .menu .menu-collapse-toggle .menu-title {
  color: var(--bs-text-gray-500) !important;
}

#kt_app_sidebar .app-sidebar-navs .menu .menu-item .menu-link .menu-badge .badge.badge-custom {
  background-color: var(--bs-secondary-bg);
  color: var(--bs-text-gray-600);
}

/* Hover / open states, which stock leaves to the token but which now need the
   sidebar-specific ground from the block above. */
#kt_app_sidebar .app-sidebar-navs .menu .menu-item .menu-link:hover,
#kt_app_sidebar .app-sidebar-navs .menu .menu-item.show > .menu-link {
  background-color: var(--bb-sidebar-row-hover);
}

/* Active row. The stock rule bakes in both the ground and `color:#fff`, which
   is what had been silently beating --bs-menu-link-color-active. */
#kt_app_sidebar .app-sidebar-navs .menu .menu-item .menu-link.active {
  background-color: var(--bb-sidebar-row-active);
  color: var(--bb-orange-text);
}

/* The label needs naming separately: .menu-item .menu-link .menu-title carries
   its own colour, so it does not inherit from the link. */
#kt_app_sidebar .app-sidebar-navs .menu .menu-item .menu-link.active .menu-title,
#kt_app_sidebar .app-sidebar-navs .menu .menu-item .menu-link.active .menu-icon,
#kt_app_sidebar .app-sidebar-navs .menu .menu-item .menu-link.active .menu-icon i {
  color: var(--bb-orange-text);
}

/* Section headings in the sidebar menu are hardcoded cool grey (#363843). */
#kt_app_sidebar .app-sidebar-navs .menu .menu-item .menu-heading {
  color: var(--bs-text-gray-500);
}

/* .text-sidebar is defined in the page's own inline <style> as
   `color: var(--bs-white)`, with a second [data-bs-theme="dark"] copy — white
   in both themes, again assuming a dark rail. The ID prefix outranks both. */
#kt_app_sidebar .text-sidebar {
  color: var(--bs-text-gray-700);
}

/* The lockup is 142x51 (~2.78:1), and the height utilities are all
   `height:Npx!important` with width left auto. Guard every placement against
   overflowing its container.

   The guard matters less than it did — the earlier single-line wordmark was
   4.23:1, so at a given height it was half again as wide as this one — but it
   stays, because the ratio is a property of whatever SVG is dropped in here,
   not of this rule. */
img[alt="Logo"] {
  max-width: 100%;
  object-fit: contain;
  object-position: left center;
}

/* Sidebar lockup — sized against the header band beside it.
   ---------------------------------------------------------------------------
   The template writes `class="h-40px app-sidebar-logo-default"`, and Metronic's
   height utilities are all `height:Npx!important`, so nothing without
   !important can reach it. Same unavoidable reason as the login sizing further
   down and the button rim in section 5, not a shortcut.

   WHY 48px, AND WHERE THE CEILING IS. The rail is 300px with `mx-10` (2.5rem)
   either side, leaving 235px, and the lockup is 2.78:1 — so width alone allows
   about 84px of height, and the 90px header band across the divider is the
   real limit. 48px is a deliberate step short of both: 133.6px wide, filling
   the band without crowding it. Raising this token is the whole edit if it
   ever wants to be bigger; the padding below follows on its own.

   THE PADDING RULE IS THE OTHER HALF OF THE CHANGE, not polish. At the stock
   40px the lockup sat under `pt-8` (2rem = 26px at the lg root size) with 24px
   left below it inside the 90px band — centred, near enough, by luck. Grow the
   lockup and that luck runs out: at 48px the stock padding leaves 26px above
   and 16px below, which reads as slipped rather than larger and sits visibly
   off the section title on the other side of the divider. So the padding is
   derived from the band instead of restated, and the lockup keeps equal air
   above and below whatever either value becomes later. It computes to 21px
   here, so the menu underneath rises by 5px — the scroll wrapper reads this
   element's height through `data-kt-scroll-dependencies`, so the nav area
   below simply gains those 5px.

   --bs-app-header-height is Metronic's own token — 90px at lg, 60px below it.
   The fallback is there for a version that renames it; without one the calc
   would be invalid at computed-value time and the padding would fall back to
   the stock 2rem, which is the safe way to fail here anyway.

   Below lg the block inside is `d-none d-lg-flex`, so only the padding lands
   there and the lockup is not drawn at all. The size is a custom property
   because two rules need to agree on it — kept here rather than in section 2
   because it is one layout constant that does not vary by theme, unlike every
   --bb-* token in that section. */
:root {
  --bb-sidebar-logo-height: 48px;
}

#kt_app_sidebar_header {
  padding-top: calc(
    (var(--bs-app-header-height, 90px) - var(--bb-sidebar-logo-height)) / 2
  ) !important;
}

#kt_app_sidebar_header .app-sidebar-logo img[alt="Logo"] {
  height: var(--bb-sidebar-logo-height) !important;
}

/* Login, 2FA, forgot/reset password, admin setup and the share pages seat the
   logo in a col-5 beside a col-7 holding ShortName. ShortName is now blank
   (the lockup carries the name), so reclaim the full card width.

   The width reclaim is no longer strictly forced. It was added for the 4.23:1
   wordmark, which at 100px tall wanted 423px in the 380px the lg login card
   leaves after padding. This lockup is 2.78:1 and wants 278px at that height,
   so it would fit inside the col-5 — but the col-7 beside it is empty chrome
   either way, and centring the lockup over the full card reads better than
   pinning it to the left two-fifths. Kept deliberately, not by inertia. */
.col-5:has(> img[alt="Logo"]) {
  width: 100%;
  text-align: center;
}

.col-5:has(> img[alt="Logo"]) ~ .col-7 {
  display: none;
}

/* 200px wide is ~72px tall at 2.78:1. The previous 260px was tuned to the
   4.23:1 wordmark, where it produced a 61px-tall mark; carried over unchanged
   it would render this two-line lockup at 94px and overpower the form.

   Centring is done with text-align on the parent above, not margin:auto: the
   <img> is a replaced INLINE element, so auto margins do nothing to it. The
   object-position override is belt-and-braces — the global guard further up
   sets `left center` for the in-app placements, and while the box here is an
   exact fit for the SVG (so it cannot actually shift), leaving a stale `left`
   on a centred lockup is a trap for whoever changes the max-width next. */
.col-5:has(> img[alt="Logo"]) > img[alt="Logo"] {
  height: auto !important;
  width: 100%;
  max-width: 200px;
  object-position: center;
}

/* Caption under the lockup.
   ---------------------------------------------------------------------------
   SCOPED TO #sign_in_form ON PURPOSE. Seven templates seat a logo in this
   col-5 — login, forgot-password, reset-password, twofactor,
   twofactor-recovery, adminsetup and sharelogin — and they all match the
   selector above. Only the two sign-in pages (admin and client, which share
   the id) should carry a caption; a product line under "Reset your password"
   or the 2FA prompt would read as stray chrome.

   It has to be a pseudo-element: the templates are out of bounds, and the one
   text node that already exists nearby is the <h1> in the sibling col-7, whose
   content is SFTPGo's ShortName fallback ("WebAdmin" / "WebClient"). That is
   hidden a few rules up, and CSS cannot rewrite its text.

   EDIT THE STRING BELOW to change the wording. Set it to "" to remove the
   caption without touching anything else. */
#sign_in_form .col-5:has(> img[alt="Logo"])::after {
  content: "Login to secure file transfer";
  display: block;
  margin-top: 2rem;
  color: var(--bs-text-neutral-1200);
  font-size: 1.5rem;
  font-weight: 600;
  line-height: 1.4;
}

/* --------------------------------------------------------------------------
   7. Footer
   --------------------------------------------------------------------------
   common/base.html renders {{.Version}} into #kt_app_footer as the literal
   "SFTPGo <version> <commit>", on every signed-in page of BOTH UIs — so
   customers saw it under the file browser, not just admins. There is no
   branding key for it; the string is built into the template, so CSS is the
   only lever short of forking the template (which the KeenThemes terms put
   out of bounds).

   LICENCE CAVEAT — READ BEFORE RELYING ON THIS. The reasoning that made this
   clearly safe was specific to v2.6.2, which predates upstream's NOTICE file
   (added 2024-10-18) and so carried no attribution term. The compose file's
   `image: drakkan/sftpgo` has NO TAG, so it resolves to :latest — the running
   container is 2.7.5, well past that line, and NOTICE now applies. Either pin
   the image back to v2.6.2 or read NOTICE and confirm this rule is still
   acceptable. See README.md and the comment block in docker-compose.yml.

   Dropping the version string also stops publishing the exact build and commit
   of an internet-facing service. Nothing else lives in that bar.
   -------------------------------------------------------------------------- */
#kt_app_footer {
  display: none;
}

/* --------------------------------------------------------------------------
   8. Login page polish
   -------------------------------------------------------------------------- */
.app-blank .btn-primary,
.d-flex.flex-column-fluid .btn-primary {
  font-weight: 600;
}

/* Inputs on the auth pages — login, 2FA, 2FA-recovery, forgot/reset password,
   admin setup and the share login. All seven render
   `form-control form-control-lg form-control-solid`, and it is the SOLID part
   that put them out of step with the rest of the product: Metronic's solid
   variant paints ground and edge the same grey, so the login fields read as
   grey slabs while every input behind the sign-in wall is a white outlined
   box. Same component, two looks, and the one an unauthenticated visitor sees
   first was the odd one.

   These rules restate the four states as an outlined input: white ground, one
   light edge, a grey halo on hover, orange on focus, and a receded disabled
   state. Section 5 already gives .form-control its orange focus ring
   product-wide, but it loses on the login page — `.form-control:focus` is
   (0,2,0) against stock's `.form-control.form-control-solid:focus` at (0,3,0),
   so the solid rule pulled the border back to grey and only the outer glow
   survived. Every selector here therefore names .form-control-solid
   explicitly to clear that bar.

   SCOPED TO .app-blank ON PURPOSE. That class is the blank/auth layout and is
   absent from every signed-in page, so nothing in the file manager or the
   admin forms is touched. Scoping to .form-control-solid alone would land in
   the same place today — the class appears nowhere else in this build — but
   .app-blank says which pages are meant, rather than relying on that staying
   true. To adopt the treatment product-wide, drop the `.app-blank ` prefixes
   and keep the .form-control-solid variants for the auth pages.

   Metrics are otherwise stock — .form-control-lg's 1rem/1.5rem padding and
   its min-height are what the design draws — but the CORNER IS NOT. Stock is
   .625rem against a field a little under 4rem tall; the design's corner is
   about a fifth of the field height, which is visibly rounder. Hence the one
   radius override below, and it is deliberately in rem: Metronic scales the
   root font-size by breakpoint (12px, then 13px from lg up), so a px value
   would drift against the height it is proportioned to. This is the only
   radius this stylesheet touches — everything else in the product is stock.
   */
.app-blank .form-control,
.app-blank .form-control.form-control-solid,
.app-blank .form-select {
  background-color: var(--bs-body-bg);
  border: 1px solid var(--bb-input-border);
  border-radius: 0.85rem;
  color: var(--bb-input-text);
  transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}

.app-blank .form-control::placeholder,
.app-blank .form-control.form-control-solid::placeholder {
  color: var(--bb-input-placeholder);
  opacity: 1;
}

/* Hover. Stock has no hover state on inputs at all — the halo is what tells a
   pointer user the box is live before they commit a click. Excluded from the
   focused and inert states so it never stacks under the orange ring or wakes
   up a field that cannot be typed into. */
.app-blank .form-control:hover:not(:focus):not(:disabled):not([readonly]),
.app-blank
  .form-control.form-control-solid:hover:not(:focus):not(:disabled):not([readonly]),
.app-blank .form-select:hover:not(:focus):not(:disabled) {
  border-color: var(--bb-input-border-hover);
  box-shadow: 0 0 0 3px var(--bb-input-ring-hover);
}

/* Focus. The design draws a 2px orange edge, and growing `border-width` to 2px
   would reflow the field by a pixel on every side — the text would jump as it
   takes focus. An inset 1px shadow doubles the edge inside the border box
   instead, so the geometry never moves. Same trick, and the same reason, as
   the secondary-button rim in section 5.

   .active/.focus/:active are carried along because stock groups them with
   :focus on the solid variant; leaving them out would let a mousedown flash
   the grey solid ground back for the length of the click. */
.app-blank .form-control:focus,
.app-blank .form-control.form-control-solid:focus,
.app-blank .form-control.form-control-solid.focus,
.app-blank .form-control.form-control-solid:active,
.app-blank .form-control.form-control-solid.active,
.app-blank .form-select:focus {
  background-color: var(--bs-body-bg);
  border-color: var(--bs-primary);
  color: var(--bb-input-text);
  box-shadow: inset 0 0 0 1px var(--bs-primary),
              0 0 0 0.25rem var(--bs-primary-clarity);
}

/* Disabled / readonly. Stock fills these with --bs-gray-200, which under an
   outlined input reads as a second, competing style rather than a muted one.
   The ground stays white and the edge and text recede instead. */
.app-blank .form-control:disabled,
.app-blank .form-control[readonly],
.app-blank .form-control.form-control-solid:disabled,
.app-blank .form-control.form-control-solid[readonly],
.app-blank .form-select:disabled {
  background-color: var(--bs-body-bg);
  border-color: var(--bb-input-disabled-border);
  color: var(--bb-input-disabled-text);
  box-shadow: none;
}

.app-blank .form-control:disabled::placeholder,
.app-blank .form-control[readonly]::placeholder,
.app-blank .form-control.form-control-solid:disabled::placeholder,
.app-blank .form-control.form-control-solid[readonly]::placeholder {
  color: var(--bb-input-disabled-text);
}

/* Autofill.
   ---------------------------------------------------------------------------
   NOT COSMETIC HOUSEKEEPING — without this the login page does not match the
   design for most returning users. Chrome, Edge and Safari paint their own
   ground onto an autofilled field (pale blue on Chrome, yellow on older
   builds) and no ordinary background-color reaches it. The grey solid input
   masked how far off that was; a white outlined box does not. The only lever
   is an inset box-shadow with enough spread to cover the padding box, plus
   -webkit-text-fill-color, which is what colours autofilled text — `color` is
   ignored there too.

   Because the fill IS a box-shadow, it collides with the state shadows above,
   so hover and focus are restated with the fill stacked in. Shadow order is
   paint order, first on top: the 1px orange edge has to be listed BEFORE the
   100px fill or the fill buries it.

   Kept in rules of their own rather than grouped with the selectors above:
   :-webkit-autofill is unknown to Firefox, and one unrecognised selector
   invalidates an entire selector list. Grouped, these would take the states
   with them. */
.app-blank .form-control:-webkit-autofill,
.app-blank .form-control:-webkit-autofill:hover,
.app-blank .form-control:-webkit-autofill:focus,
.app-blank .form-control:-webkit-autofill:active {
  -webkit-text-fill-color: var(--bb-input-text);
  caret-color: var(--bb-input-text);
  box-shadow: inset 0 0 0 100px var(--bs-body-bg);
}

.app-blank .form-control:-webkit-autofill:hover:not(:focus) {
  box-shadow: inset 0 0 0 100px var(--bs-body-bg),
              0 0 0 3px var(--bb-input-ring-hover);
}

.app-blank .form-control:-webkit-autofill:focus {
  box-shadow: inset 0 0 0 1px var(--bs-primary),
              inset 0 0 0 100px var(--bs-body-bg),
              0 0 0 0.25rem var(--bs-primary-clarity);
}
/* --------------------------------------------------------------------------
   9. Typography — Inclusive Sans
   --------------------------------------------------------------------------
   Stock is Inter, self-hosted by SFTPGo at /static/vendor/fonts/inter/. The
   whole product reads it through one chain:

     --bs-font-sans-serif  ->  --bs-body-font-family  ->  font-family

   so redefining the first token below re-faces most of the UI — but NOT the
   body copy. Metronic also writes the stack directly:

     body,html{...;font-family:Inter,Helvetica,"sans-serif";...}

   That declaration never reads the token, so the override alone leaves body
   text on Inter while everything else changes. The rule at the end of this
   section restates body,html at equal specificity to catch it.

   The files are served from this directory (./branding -> /static/branding/)
   and the src paths are relative to this stylesheet, exactly like the logo
   swaps in section 6. Deliberately NOT loaded from fonts.googleapis.com: this
   is an internet-facing service, a CDN link would leak every visitor's IP to
   a third party before login and add an availability dependency for a purely
   cosmetic asset.

   Variable font, one file per axis-range and subset. Only latin and latin-ext
   are shipped — httpd.bindings[0].languages is ["en"], so the vietnamese
   subset Google also serves would be dead weight. Add it back here if the UI
   is ever localised beyond latin scripts.

   Inclusive Sans by Olivia King, SIL Open Font License 1.1.
   https://fonts.google.com/specimen/Inclusive+Sans
   -------------------------------------------------------------------------- */

@font-face {
  font-family: "Inclusive Sans";
  font-style: italic;
  font-weight: 300 700;
  font-display: swap;
  src: url("fonts/inclusive-sans-latin-italic.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Inclusive Sans";
  font-style: italic;
  font-weight: 300 700;
  font-display: swap;
  src: url("fonts/inclusive-sans-latin-ext-italic.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Inclusive Sans";
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url("fonts/inclusive-sans-latin-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Inclusive Sans";
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url("fonts/inclusive-sans-latin-ext-normal.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Metronic's stack is `Inter, Helvetica, "sans-serif"`. The fallbacks stay
   real fallbacks: if the woff2 files 404 the UI degrades to the system sans
   rather than to an unstyled serif. */
:root {
  --bs-font-sans-serif: "Inclusive Sans", Inter, -apple-system, "Segoe UI",
    Helvetica, Arial, sans-serif;
}

/* Metronic hardcodes the stack on `body,html` rather than reading
   --bs-body-font-family, so the token override above cannot reach body copy.
   Same specificity (0,0,2), later in the cascade, so this wins. */
body,
html {
  font-family: var(--bs-font-sans-serif);
}

/* --------------------------------------------------------------------------
   10. Icon set — Lucide over Keenicons
   --------------------------------------------------------------------------
   Icons ship as Keenicons, an icon FONT: `.ki-magnifier .path1:before{
   content:"\ed02";opacity:.3}` plus a `.path2:before` for the second tone.
   Templates are out of bounds, so the glyphs cannot be swapped at source —
   but each <i> can be turned into a masked box instead:

     - ::before content is dropped, so no Keenicon glyph renders;
     - the duotone .path* spans are hidden;
     - the element masks a Lucide SVG and paints it with currentColor.

   currentColor is what makes this safe: every colour rule elsewhere in this
   file (sidebar states, .text-primary, muted tiers) keeps working untouched,
   because the icon is still painted by `color`. Sizing stays on font-size too
   — the box is 1em, so every fs-* utility in the templates still applies.

   SCOPED TO THE 44 MAPPED NAMES ON PURPOSE. A future SFTPGo version that adds
   a new ki-* class falls through to its Keenicon rather than rendering an
   empty box. Check for new names after an upgrade with:

     docker exec sftpgo sh -c 'grep -rhoE "ki-[a-z0-9-]+" \
       /usr/share/sftpgo/templates | sort -u'

   Icons: Lucide (lucide-static v1.39.0), ISC licence.
   Copyright (c) for portions derived from Feather Icons, (c) Cole Bemis.
   Everything else (c) Lucide Contributors. https://lucide.dev
   -------------------------------------------------------------------------- */

.ki-abstract-14,
.ki-add-folder,
.ki-arrow-right-left,
.ki-arrow-up,
.ki-arrow-up-down,
.ki-arrow-up-right,
.ki-arrows-circle,
.ki-calendar,
.ki-check,
.ki-check-square,
.ki-cross,
.ki-dots-square,
.ki-down,
.ki-external-drive,
.ki-eye,
.ki-eye-slash,
.ki-fasten,
.ki-file,
.ki-file-right,
.ki-file-up,
.ki-folder,
.ki-folder-down,
.ki-folder-up,
.ki-home,
.ki-information-2,
.ki-information-5,
.ki-magnifier,
.ki-moon,
.ki-night-day,
.ki-pencil,
.ki-people,
.ki-plus,
.ki-profile-user,
.ki-question-2,
.ki-right,
.ki-screen,
.ki-security-user,
.ki-setting-3,
.ki-share,
.ki-shield,
.ki-shield-tick,
.ki-trash,
.ki-user,
.ki-user-tick {
  -webkit-mask: var(--bb-icon) center / contain no-repeat;
  mask: var(--bb-icon) center / contain no-repeat;
  background-color: currentColor;
  display: inline-block;
  width: 1em;
  height: 1em;
  vertical-align: -0.125em;
}

/* Kill the Keenicon glyph and the duotone tone-layer spans. */

.ki-abstract-14::before,
.ki-add-folder::before,
.ki-arrow-right-left::before,
.ki-arrow-up::before,
.ki-arrow-up-down::before,
.ki-arrow-up-right::before,
.ki-arrows-circle::before,
.ki-calendar::before,
.ki-check::before,
.ki-check-square::before,
.ki-cross::before,
.ki-dots-square::before,
.ki-down::before,
.ki-external-drive::before,
.ki-eye::before,
.ki-eye-slash::before,
.ki-fasten::before,
.ki-file::before,
.ki-file-right::before,
.ki-file-up::before,
.ki-folder::before,
.ki-folder-down::before,
.ki-folder-up::before,
.ki-home::before,
.ki-information-2::before,
.ki-information-5::before,
.ki-magnifier::before,
.ki-moon::before,
.ki-night-day::before,
.ki-pencil::before,
.ki-people::before,
.ki-plus::before,
.ki-profile-user::before,
.ki-question-2::before,
.ki-right::before,
.ki-screen::before,
.ki-security-user::before,
.ki-setting-3::before,
.ki-share::before,
.ki-shield::before,
.ki-shield-tick::before,
.ki-trash::before,
.ki-user::before,
.ki-user-tick::before {
  content: none;
}

.ki-abstract-14 > [class*="path"],
.ki-add-folder > [class*="path"],
.ki-arrow-right-left > [class*="path"],
.ki-arrow-up > [class*="path"],
.ki-arrow-up-down > [class*="path"],
.ki-arrow-up-right > [class*="path"],
.ki-arrows-circle > [class*="path"],
.ki-calendar > [class*="path"],
.ki-check > [class*="path"],
.ki-check-square > [class*="path"],
.ki-cross > [class*="path"],
.ki-dots-square > [class*="path"],
.ki-down > [class*="path"],
.ki-external-drive > [class*="path"],
.ki-eye > [class*="path"],
.ki-eye-slash > [class*="path"],
.ki-fasten > [class*="path"],
.ki-file > [class*="path"],
.ki-file-right > [class*="path"],
.ki-file-up > [class*="path"],
.ki-folder > [class*="path"],
.ki-folder-down > [class*="path"],
.ki-folder-up > [class*="path"],
.ki-home > [class*="path"],
.ki-information-2 > [class*="path"],
.ki-information-5 > [class*="path"],
.ki-magnifier > [class*="path"],
.ki-moon > [class*="path"],
.ki-night-day > [class*="path"],
.ki-pencil > [class*="path"],
.ki-people > [class*="path"],
.ki-plus > [class*="path"],
.ki-profile-user > [class*="path"],
.ki-question-2 > [class*="path"],
.ki-right > [class*="path"],
.ki-screen > [class*="path"],
.ki-security-user > [class*="path"],
.ki-setting-3 > [class*="path"],
.ki-share > [class*="path"],
.ki-shield > [class*="path"],
.ki-shield-tick > [class*="path"],
.ki-trash > [class*="path"],
.ki-user > [class*="path"],
.ki-user-tick > [class*="path"] {
  display: none;
}


/* Per-icon mask. Left column is the Keenicon name used in the templates,
   right is the Lucide icon it now renders.

   NOTE ON ENCODING: the SVG uses single quotes and every '#' is %23. A raw
   double quote would close the url("...") string at the first SVG attribute
   and a raw '#' would start a fragment — either one silently drops the whole
   declaration, which is exactly how the first cut of this section failed. */

.ki-abstract-14 { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M4 5h16' /%3E%3Cpath d='M4 12h16' /%3E%3Cpath d='M4 19h16' /%3E%3C/svg%3E"); }  /* menu */

.ki-add-folder { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M12 10v6' /%3E%3Cpath d='M9 13h6' /%3E%3Cpath d='M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z' /%3E%3C/svg%3E"); }  /* folder-plus */

.ki-arrow-right-left { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='m16 3 4 4-4 4' /%3E%3Cpath d='M20 7H4' /%3E%3Cpath d='m8 21-4-4 4-4' /%3E%3Cpath d='M4 17h16' /%3E%3C/svg%3E"); }  /* arrow-right-left */

.ki-arrow-up { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='m5 12 7-7 7 7' /%3E%3Cpath d='M12 19V5' /%3E%3C/svg%3E"); }  /* arrow-up */

.ki-arrow-up-down { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='m21 16-4 4-4-4' /%3E%3Cpath d='M17 20V4' /%3E%3Cpath d='m3 8 4-4 4 4' /%3E%3Cpath d='M7 4v16' /%3E%3C/svg%3E"); }  /* arrow-up-down */

.ki-arrow-up-right { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M7 7h10v10' /%3E%3Cpath d='M7 17 17 7' /%3E%3C/svg%3E"); }  /* arrow-up-right */

.ki-arrows-circle { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8' /%3E%3Cpath d='M21 3v5h-5' /%3E%3Cpath d='M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16' /%3E%3Cpath d='M8 16H3v5' /%3E%3C/svg%3E"); }  /* refresh-cw */

.ki-calendar { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M8 2v3' /%3E%3Cpath d='M16 2v3' /%3E%3Crect x='3' y='3' width='18' height='18' rx='2' /%3E%3Cpath d='M3 9h18' /%3E%3C/svg%3E"); }  /* calendar */

.ki-check { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M20 6 9 17l-5-5' /%3E%3C/svg%3E"); }  /* check */

.ki-check-square { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Crect width='18' height='18' x='3' y='3' rx='2' /%3E%3Cpath d='m16 9-5.5 5.5L8 12' /%3E%3C/svg%3E"); }  /* square-check */

.ki-cross { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M18 6 6 18' /%3E%3Cpath d='m6 6 12 12' /%3E%3C/svg%3E"); }  /* x */

.ki-dots-square { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Ccircle cx='12' cy='12' r='1' /%3E%3Ccircle cx='19' cy='12' r='1' /%3E%3Ccircle cx='5' cy='12' r='1' /%3E%3C/svg%3E"); }  /* ellipsis */

.ki-down { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='m6 9 6 6 6-6' /%3E%3C/svg%3E"); }  /* chevron-down */

.ki-external-drive { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M10 16h.01' /%3E%3Cpath d='M2.212 11.577a2 2 0 0 0-.212.896V18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5.527a2 2 0 0 0-.212-.896L18.55 5.11A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z' /%3E%3Cpath d='M21.946 12.013H2.054' /%3E%3Cpath d='M6 16h.01' /%3E%3C/svg%3E"); }  /* hard-drive */

.ki-eye { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0' /%3E%3Ccircle cx='12' cy='12' r='3' /%3E%3C/svg%3E"); }  /* eye */

.ki-eye-slash { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49' /%3E%3Cpath d='M14.084 14.158a3 3 0 0 1-4.242-4.242' /%3E%3Cpath d='M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143' /%3E%3Cpath d='m2 2 20 20' /%3E%3C/svg%3E"); }  /* eye-off */

.ki-fasten { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71' /%3E%3Cpath d='M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71' /%3E%3C/svg%3E"); }  /* link */

.ki-file { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z' /%3E%3Cpath d='M14 2v5a1 1 0 0 0 1 1h5' /%3E%3C/svg%3E"); }  /* file */

.ki-file-right { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M4 11V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.706.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h7' /%3E%3Cpath d='M14 2v5a1 1 0 0 0 1 1h5' /%3E%3Cpath d='m10 18 3-3-3-3' /%3E%3C/svg%3E"); }  /* file-symlink */

.ki-file-up { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z' /%3E%3Cpath d='M14 2v5a1 1 0 0 0 1 1h5' /%3E%3Cpath d='M12 12v6' /%3E%3Cpath d='m15 15-3-3-3 3' /%3E%3C/svg%3E"); }  /* file-up */

.ki-folder { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z' /%3E%3C/svg%3E"); }  /* folder */

.ki-folder-down { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z' /%3E%3Cpath d='M12 10v6' /%3E%3Cpath d='m15 13-3 3-3-3' /%3E%3C/svg%3E"); }  /* folder-down */

.ki-folder-up { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z' /%3E%3Cpath d='M12 10v6' /%3E%3Cpath d='m9 13 3-3 3 3' /%3E%3C/svg%3E"); }  /* folder-up */

.ki-home { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8' /%3E%3Cpath d='M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z' /%3E%3C/svg%3E"); }  /* house */

.ki-information-2 { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Ccircle cx='12' cy='12' r='10' /%3E%3Cpath d='M12 16v-4' /%3E%3Cpath d='M12 8h.01' /%3E%3C/svg%3E"); }  /* info */

.ki-information-5 { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Ccircle cx='12' cy='12' r='10' /%3E%3Cpath d='M12 16v-4' /%3E%3Cpath d='M12 8h.01' /%3E%3C/svg%3E"); }  /* info */

.ki-magnifier { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='m21 21-4.34-4.34' /%3E%3Ccircle cx='11' cy='11' r='8' /%3E%3C/svg%3E"); }  /* search */

.ki-moon { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401' /%3E%3C/svg%3E"); }  /* moon */

.ki-night-day { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Ccircle cx='12' cy='12' r='4' /%3E%3Cpath d='M12 2v2' /%3E%3Cpath d='M12 20v2' /%3E%3Cpath d='m4.93 4.93 1.41 1.41' /%3E%3Cpath d='m17.66 17.66 1.41 1.41' /%3E%3Cpath d='M2 12h2' /%3E%3Cpath d='M20 12h2' /%3E%3Cpath d='m6.34 17.66-1.41 1.41' /%3E%3Cpath d='m19.07 4.93-1.41 1.41' /%3E%3C/svg%3E"); }  /* sun */

.ki-pencil { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z' /%3E%3Cpath d='m15 5 4 4' /%3E%3C/svg%3E"); }  /* pencil */

.ki-people { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2' /%3E%3Cpath d='M16 3.128a4 4 0 0 1 0 7.744' /%3E%3Cpath d='M22 21v-2a4 4 0 0 0-3-3.87' /%3E%3Ccircle cx='9' cy='7' r='4' /%3E%3C/svg%3E"); }  /* users */

.ki-plus { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M5 12h14' /%3E%3Cpath d='M12 5v14' /%3E%3C/svg%3E"); }  /* plus */

.ki-profile-user { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Ccircle cx='12' cy='12' r='10' /%3E%3Ccircle cx='12' cy='10' r='3' /%3E%3Cpath d='M7 20.662V19a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v1.662' /%3E%3C/svg%3E"); }  /* circle-user */

.ki-question-2 { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Ccircle cx='12' cy='12' r='10' /%3E%3Cpath d='M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3' /%3E%3Cpath d='M12 17h.01' /%3E%3C/svg%3E"); }  /* circle-help */

.ki-right { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='m9 18 6-6-6-6' /%3E%3C/svg%3E"); }  /* chevron-right */

.ki-screen { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Crect width='20' height='14' x='2' y='3' rx='2' /%3E%3Cline x1='8' x2='16' y1='21' y2='21' /%3E%3Cline x1='12' x2='12' y1='17' y2='21' /%3E%3C/svg%3E"); }  /* monitor */

.ki-security-user { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z' /%3E%3Cpath d='M6.376 18.91a6 6 0 0 1 11.249.003' /%3E%3Ccircle cx='12' cy='11' r='4' /%3E%3C/svg%3E"); }  /* shield-user */

.ki-setting-3 { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915' /%3E%3Ccircle cx='12' cy='12' r='3' /%3E%3C/svg%3E"); }  /* settings */

.ki-share { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Ccircle cx='18' cy='5' r='3' /%3E%3Ccircle cx='6' cy='12' r='3' /%3E%3Ccircle cx='18' cy='19' r='3' /%3E%3Cline x1='8.59' x2='15.42' y1='13.51' y2='17.49' /%3E%3Cline x1='15.41' x2='8.59' y1='6.51' y2='10.49' /%3E%3C/svg%3E"); }  /* share-2 */

.ki-shield { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z' /%3E%3C/svg%3E"); }  /* shield */

.ki-shield-tick { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z' /%3E%3Cpath d='m9 12 2 2 4-4' /%3E%3C/svg%3E"); }  /* shield-check */

.ki-trash { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M10 11v6' /%3E%3Cpath d='M14 11v6' /%3E%3Cpath d='M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6' /%3E%3Cpath d='M3 6h18' /%3E%3Cpath d='M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2' /%3E%3C/svg%3E"); }  /* trash-2 */

.ki-user { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2' /%3E%3Ccircle cx='12' cy='7' r='4' /%3E%3C/svg%3E"); }  /* user */

.ki-user-tick { --bb-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' %3E%3Cpath d='m16 11 2 2 4-4' /%3E%3Cpath d='M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2' /%3E%3Ccircle cx='9' cy='7' r='4' /%3E%3C/svg%3E"); }  /* user-check */

/* --------------------------------------------------------------------------
   11. Section title in the header
   --------------------------------------------------------------------------
   The header band is empty. common/base.html opens the navbar with

     <div class="app-navbar-item d-flex align-items-center flex-lg-grow-1 …">
     </div>

   and never fills it — Metronic's own page-title toolbar (#kt_app_toolbar) is
   commented out a few lines below — so every signed-in page renders a 90px
   strip holding nothing but the theme switch and the user menu at the far
   right. That empty div is the target here: it is already flex-centred and
   already grows, and it starts at exactly the same x as the card below it, so
   a ::before in it needs no padding of its own to line up with the content.

   SFTPGo DOES know the page name — common/base.html line ~201 does
   `$('title').text('{{.Branding.Name}} - '+$.t('{{.Title}}'))` — but it only
   ever reaches document.title, which CSS cannot read. Templates are out of
   bounds and the branding config exposes extra_css and default_css and nothing
   else (checked against the binary: there is no extra_js). So the name has to
   be derived from something already in the DOM, and written in by hand.

   HOW THE MAPPING WORKS. One rule does the drawing and reads a custom
   property; the block under it is a lookup table that sets that property per
   page. Custom properties are inherited and legal in `content`, so this stays
   a flat list of one-liners instead of nineteen copies of the selector above.
   A page that matches nothing leaves --bb-section unset, the fallback empty
   string applies, and the header renders as it does today — so an SFTPGo
   version that adds a page degrades to blank rather than to something wrong.

   TWO HOOKS, because neither covers everything:

     - LIST pages carry `active` on their sidebar link, from
       `{{- if eq .CurrentURL .UsersURL}} active{{- end}}`. Verified that
       .CurrentURL excludes the query string, so the item stays active through
       ?path=… and ?qlimit=… — which is what keeps the title up while browsing
       a folder in the client UI.
     - DETAIL pages (add/edit) set no active item at all, so they key off the
       form id instead: #user_form, #group_form, #folder_form and so on, one
       per template. They deliberately resolve to the SECTION name rather than
       the page name — "Add user" shows "Users" — so the header answers "where
       am I" and does not repeat the "Add user" heading already inside the card.

   Sidebar items are wrapped in `{{- if .LoggedUser.HasPermission …}}`, so an
   admin without a permission simply never matches that row. Nothing to do.

   THE STRINGS ARE HARDCODED AND THAT IS THE COST OF THIS FEATURE. They are
   copies of SFTPGo's own labels, which live in its i18n bundles under the
   `title.*` keys — read the cached bundle back with
   `JSON.parse(localStorage['i18next_res_en-translation']).title` to check this
   table against a new image. So:

     - Only English. httpd.bindings[0].languages is ["en"] today; add a
       language and the header stays English while the sidebar translates.
       There is no CSS fix — remove this section if that day comes.
     - An upstream rename or a new page silently drops to blank. Re-check this
       table when bumping the image.

   "Event rules" and "Event actions" are the two entries that are NOT verbatim:
   the sidebar shows them as "Rules" and "Actions" nested under an "Event
   Manager" accordion, and stripped of that parent they are too vague to stand
   alone in a header. "Server status" is expanded from "Status" for the same
   reason (and matches that page's own document.title). Change them here if you
   would rather match the rail exactly.
   -------------------------------------------------------------------------- */

/* Drawn at lg and up only. Below that the sidebar collapses to a drawer and
   the header already carries a hamburger and a second copy of the lockup; a
   title beside them competes for a strip that is 60px tall and much narrower. */
@media (min-width: 992px) {
  #kt_app_header_navbar > .app-navbar-item:first-child::before {
    content: var(--bb-section, "");
    font-size: 1.75rem;
    font-weight: 600;
    line-height: 1.2;
    color: var(--bs-text-gray-900);
  }
}

/* --- The lookup table. Admin UI --- */
body:has(#kt_app_sidebar a.menu-link.active[href$="/web/admin/users"]),
body:has(#user_form) { --bb-section: "Users"; }

body:has(#kt_app_sidebar a.menu-link.active[href$="/web/admin/groups"]),
body:has(#group_form) { --bb-section: "Groups"; }

body:has(#kt_app_sidebar a.menu-link.active[href$="/web/admin/folders"]),
body:has(#folder_form) { --bb-section: "Virtual folders"; }

body:has(#kt_app_sidebar a.menu-link.active[href$="/web/admin/connections"]) {
  --bb-section: "Active connections";
}

body:has(#kt_app_sidebar a.menu-link.active[href$="/web/admin/eventrules"]),
body:has(#eventrule_form) { --bb-section: "Event rules"; }

body:has(#kt_app_sidebar a.menu-link.active[href$="/web/admin/eventactions"]),
body:has(#eventaction_form) { --bb-section: "Event actions"; }

body:has(#kt_app_sidebar a.menu-link.active[href$="/web/admin/ip-lists"]),
body:has(#iplist_form) { --bb-section: "IP Lists"; }

body:has(#kt_app_sidebar a.menu-link.active[href$="/web/admin/managers"]),
body:has(#admin_form) { --bb-section: "Admins"; }

body:has(#kt_app_sidebar a.menu-link.active[href$="/web/admin/roles"]),
body:has(#role_form) { --bb-section: "Roles"; }

body:has(#kt_app_sidebar a.menu-link.active[href$="/web/admin/configs"]) {
  --bb-section: "Configurations";
}

body:has(#kt_app_sidebar a.menu-link.active[href$="/web/admin/maintenance"]) {
  --bb-section: "Maintenance";
}

body:has(#kt_app_sidebar a.menu-link.active[href$="/web/admin/status"]) {
  --bb-section: "Server status";
}

/* Reached from inside other pages rather than from the rail, so there is no
   active item and no form to key off. The blocklist page is the only one that
   links to itself (its refresh control); the logs page owns #dataTableLog.
   Both checked against every other admin page. */
body:has(#kt_app_content_container a[href$="/web/admin/defender"]) {
  --bb-section: "Auto Block List";
}

body:has(#dataTableLog) { --bb-section: "Logs"; }

/* --- Client UI --- */
body:has(#kt_app_sidebar a.menu-link.active[href$="/web/client/files"]) {
  --bb-section: "Files";
}

body:has(#kt_app_sidebar a.menu-link.active[href$="/web/client/shares"]) {
  --bb-section: "Shares";
}

/* --- Both UIs. These pages hang off the user menu, not the rail, and both
   products render them from the same templates — so one row each covers admin
   and client together.

   2FA keys off #id_config, the configuration <select>. mfa.html posts over the
   API and has no <form> of its own, and the obvious alternatives —
   #generate_secret_btn, #disable_btn, #id_accordion — are all inside
   `{{- if .TOTPConfig.Enabled }}`, so they exist only once 2FA is already set
   up and the page would have gone untitled in the state a user actually
   arrives in. #id_config is outside every conditional and appears in no other
   template in either UI. --- */
body:has(#id_config) { --bb-section: "Two-factor authentication"; }
body:has(#page_form) { --bb-section: "Profile"; }
body:has(#change_pwd_form) { --bb-section: "Change password"; }
