/* DataHex overrides for OpenMetadata's SPA.
 *
 * WHY THIS FILE EXISTS (see docs/plan/om-ui-customisation-framework.md §3):
 * Styling and layout changes belong at TIER 2 — a build-time transform — not at tier 4, patching
 * components. This stylesheet is appended last, so it wins over antd and OM's Less without touching
 * a single upstream file. Every rule here costs nothing at OM-upgrade time; the same change made by
 * patching a component would have to be rebased on every release.
 *
 * RULES OF THE ROAD
 *  - Prefer overriding CSS custom properties over hard-coding values, so one change propagates.
 *  - Target OM/antd class names, which are stable across patch releases; avoid generated hashes.
 *  - If a change cannot be expressed here, THEN consider a patch — and say why in the patch.
 *
 * Tokens mirror frontend/src/catalog/tokens.css so the two apps stay recognisably one product.
 */

/* ---- Tokens: ONE block, and the primary is not ours to define ----
 *
 * The single source of truth for the brand colour is OM's own theme config
 * (`customUiThemePreference.customTheme.primaryColor`) — written by scripts/brand-om-ui.sh and
 * editable by an admin at /settings/preferences/appearance. OM runs antd 4 in CSS-VARIABLE mode
 * (styles/antd-master.less imports antd/dist/antd.variable.less), so ConfigProvider publishes that
 * colour to :root as --ant-primary-color at runtime. Consuming it means the appearance page
 * re-themes this stylesheet too, instead of the two drifting apart.
 *
 * The fallback is deliberately the same navy brand-om-ui.sh writes: if the variable is ever absent,
 * every rule below behaves exactly as it did when the value was hard-coded.
 *
 * TINT and HOVER are DERIVED, not read from OM's `hoverColor`/`selectedColor` fields. Those are
 * free-form colour pickers, and each background here is paired with a fixed ink (navy on tint,
 * white on dark). A dark `hoverColor` would put navy ink on a dark row — unreadable. Deriving keeps
 * the tint near-white and the dark-hover near-primary for ANY primary, so no admin setting can
 * produce an unreadable state.
 */
:root {
  --dh-primary: var(--ant-primary-color, #002147);  /* deep institutional navy by default */
  --dh-primary-fg: #fafafa;
  --dh-border: #e5e5e5;

  --dh-primary-tint: color-mix(in srgb, var(--dh-primary) 10%, #fff);   /* light row: navy ink */
  --dh-primary-hover: color-mix(in srgb, var(--dh-primary) 88%, #fff);  /* dark row: white ink */
}

/* ---- Correct antd's palette ramp for a DARK primary ----
 *
 * ROOT CAUSE of every stray blue in this app. OM derives an 11-step palette from primaryColor
 * (styles/colorPallet.ts) with fixed HSL deltas that ADD lightness at the low indices — palette[2]
 * is `l + 0.4`. Those deltas assume a mid-lightness primary like OM's default #1570ef (l≈0.51).
 * Our navy is l≈0.14, so the ramp INVERTS: slots antd uses for pale tints come out as bright blues.
 * Measured in the browser for #002147 — --ant-primary-1 = #149aff, -2 = #0094f4, -25 = #38a5ff.
 * These were never antd's #1890ff defaults; they are OM's own generator misfiring on a dark colour.
 *
 * Only `hoverColor` (slot 1) and `selectedColor` (slot 7) have a config surface — tested, and it
 * fixes 2 of the 8 wrong slots. The rest can only be corrected here, so the whole ramp is corrected
 * here for coherence, keeping primaryColor as the single configured value.
 *
 * `!important` on a custom property in a stylesheet BEATS the non-important inline value that
 * AntDConfigProvider writes with documentElement.style.setProperty. Verified in the browser.
 *
 * antd's semantics: 1..9 run light -> dark with 6 as the base. Deriving each step from the primary
 * means an admin changing primaryColor re-themes the entire ramp correctly, at any lightness. */
:root {
  --ant-primary-25: color-mix(in srgb, var(--dh-primary)  4%, #fff) !important;
  --ant-primary-50: color-mix(in srgb, var(--dh-primary)  7%, #fff) !important;
  --ant-primary-1:  var(--dh-primary-tint) !important;   /* 10% — subtle hover backgrounds */
  --ant-primary-2:  color-mix(in srgb, var(--dh-primary) 18%, #fff) !important;
  --ant-primary-3:  color-mix(in srgb, var(--dh-primary) 30%, #fff) !important;
  --ant-primary-4:  color-mix(in srgb, var(--dh-primary) 45%, #fff) !important;  /* submenu border */
  --ant-primary-5:  color-mix(in srgb, var(--dh-primary) 70%, #fff) !important;
  --ant-primary-6:  var(--dh-primary) !important;        /* the base step */
  --ant-primary-7:  var(--dh-primary) !important;        /* active/pressed */
  --ant-primary-8:  color-mix(in srgb, var(--dh-primary) 85%, #000) !important;
  --ant-primary-9:  color-mix(in srgb, var(--dh-primary) 70%, #000) !important;

  --ant-primary-color-hover:  var(--dh-primary-hover) !important;
  --ant-primary-color-active: var(--dh-primary) !important;
}

/* Top bar: OM's is white and undifferentiated from the page. DataHex leads with the navy band, which
   is the single most recognisable brand cue and the cheapest one to apply. */
.app-bar-container,
[data-testid='app-bar'] {
  background: var(--dh-primary) !important;
  border-bottom: 1px solid var(--dh-primary) !important;
}

/* Anything sitting on the navy band needs inverted ink, or it disappears. */
.app-bar-container .ant-typography,
.app-bar-container a,
.app-bar-container button,
[data-testid='app-bar'] .ant-typography,
[data-testid='app-bar'] a,
[data-testid='app-bar'] button {
  color: var(--dh-primary-fg) !important;
}

/* Brand lockup: OM's slot is ~40px tall and clamps the logo with `h-full`. Give it a little more
   room so the wordmark lands at v1's scale (.dh-brand__wm is 18px in the DataHex app). */
img[src*="datahex-logo"] {
  height: 40px !important;
  max-height: none !important;
  width: auto !important;
}

/* SIX co-brand logo — the other half of the brand toggle (OM logo switched to /six-logo.svg via
   brand-om-ui.sh's LOGO). The SIX wordmark is wide and short (368x102), so it sits a touch under the
   40px slot to read balanced against the nav height. */
img[src*="six-logo"] {
  height: 30px !important;
  max-height: none !important;
  width: auto !important;
}


/* ---- The one rule the corrected ramp cannot express: hover feedback on DARK rows ----
 *
 * Everything else that used to live here (≈110 lines of per-state menu selectors) was whack-a-mole
 * against the broken ramp above, and was deleted once the ramp was fixed at the source. This is the
 * genuine remainder: antd expresses hover as a LIGHT TINT (--ant-primary-1), which is invisible on a
 * navy row, so a selected item or an open submenu title gets no feedback at all. Verified in the
 * browser — hovering the selected item left the background at exactly rgb(0, 33, 71).
 *
 * Dark rows therefore LIGHTEN instead of tinting: same hue, raised lightness, which reads as a
 * press/hover on navy. Derived from the primary, so it holds for any configured brand colour. */
.ant-menu-item-selected:hover,
.ant-menu-submenu-open > .ant-menu-submenu-title:hover,
.ant-menu-submenu-selected > .ant-menu-submenu-title:hover {
  background: var(--dh-primary-hover) !important;
  transition: background 120ms ease;
}
/* Keep the ink white through that hover — antd's hover rule otherwise recolours the label to the
   primary, which on a navy row is navy-on-navy. */
.ant-menu-item-selected:hover,
.ant-menu-item-selected:hover *,
.ant-menu-submenu-open > .ant-menu-submenu-title:hover,
.ant-menu-submenu-open > .ant-menu-submenu-title:hover * {
  color: var(--dh-primary-fg) !important;
}

/* ============================================================================
 * RESPONSIVE GATE for everything below — read this before editing the top nav
 * ----------------------------------------------------------------------------
 * The whole top-nav transformation is scoped to a MIN-WIDTH. Below the breakpoint not one rule
 * applies and OM's native left sidebar returns, unmodified and fully functional.
 *
 * Why a hard fallback rather than a squeezed top bar: the bar's content does not compress. Measured
 * at equal-width tabs — brand 204px + 7 tabs x 166px (1162px) + Settings/Logout 184px + the identity
 * cluster ~340px + gaps = ~1950px. There is no honest way to fit that in 1100px; every alternative
 * (truncating labels, hiding items, wrapping to two rows) trades a working nav for a broken-looking
 * one. A vertical rail is the correct shape for a narrow viewport, and OM already ships one.
 *
 * So: 1280px+ -> top nav. Below -> OM's sidebar. Nothing to maintain for the small case.
 * ========================================================================== */
@media (min-width: 1280px) {

  /* ============================================================================
   * TOP NAV — move OM's left sidebar into a horizontal bar, matching app.localhost
   * ----------------------------------------------------------------------------
   * TIER 2, no patch. OM renders the nav as an antd <Menu mode="inline"> inside an
   * <aside class="ant-layout-sider"> (LeftSidebar.component.tsx:126-151). The DOM order is already
   * correct for a top bar — sider first, then content — so the whole change is layout:
   *
   *   section.ant-layout.app-container   flex ROW    -> flex COLUMN
   *   └ aside.ant-layout-sider           228px tall  -> full-width, auto-height bar
   *     └ div.menu-container             flex COLUMN -> flex ROW
   *       └ ul.left-sidebar-menu         column      -> row
   *
   * Two things make this harder than it looks, and both are handled below:
   *  1. antd writes the sider's width as INLINE style (`flex: 0 0 228px; min/max/width: 228px`),
   *     so every dimension override needs !important to beat it.
   *  2. `mode="inline"` renders submenus as a nested <ul> that expands IN PLACE, which in a
   *     horizontal bar would push the page down instead of overlaying. They are re-cast as
   *     absolutely-positioned dropdowns. Changing to `mode="horizontal"` would be a tier-4 patch
   *     on a component; this achieves the same effect for free at upgrade time.
   * ========================================================================== */

  /* 1. Stack the bar above the content instead of beside it */
  .ant-layout.ant-layout-has-sider.app-container { flex-direction: column !important; }

  /* 2. The sider becomes the bar. Every one of these fights an inline style. */
  .ant-layout-sider,
  .ant-layout-sider.sidebar-open {
    width: 100% !important;
    min-width: 0 !important;
    max-width: none !important;
    flex: 0 0 auto !important;
    height: auto !important;
    background: #fff !important;
    border-bottom: 1px solid var(--dh-border) !important;
  }
  .ant-layout-sider-children { height: auto !important; }
  .left-sidebar-layout { height: auto !important; }

  /* 3. Row-ify the menu stack; push the trailing menu (Settings/Logout) to the right */
  .left-sidebar-layout .menu-container {
    flex-direction: row !important;
    align-items: center !important;
    height: auto !important;
    gap: 8px;
    padding: 0 12px;
  }
  .left-sidebar-layout .top-menu { height: auto !important; flex: 1 1 auto; }
  .left-sidebar-layout .menu-container > :last-child { margin-left: auto; }

  /* 4. The menus themselves run horizontally */
  ul.left-sidebar-menu {
    flex-direction: row !important;
    align-items: center !important;
    height: auto !important;
    border: 0 !important;
    background: transparent !important;
  }
  ul.left-sidebar-menu > li {
    width: auto !important;
    height: 48px !important;
    line-height: 48px !important;
    margin: 0 2px !important;
    padding: 0 12px !important;
    display: flex !important;
    align-items: center !important;
    border-radius: 6px !important;
  }

  /* 5. Inline-mode submenus -> overlay dropdowns, so opening one does not shove the page down */
  ul.left-sidebar-menu > li.ant-menu-submenu { position: relative !important; padding: 0 !important; }
  ul.left-sidebar-menu > li.ant-menu-submenu > .ant-menu-submenu-title {
    height: 48px !important;
    line-height: 48px !important;
    padding: 0 12px !important;
    display: flex !important;
    align-items: center !important;
    border-radius: 6px !important;
  }
  ul.left-sidebar-menu > li.ant-menu-submenu > .ant-menu-sub {
    position: absolute !important;
    top: 100% !important;
    left: 0 !important;
    min-width: 224px;
    padding: 6px !important;
    background: #fff !important;
    border: 1px solid var(--dh-border) !important;
    border-radius: 8px !important;
    box-shadow: 0 8px 24px rgb(0 0 0 / 12%) !important;
    z-index: 1050 !important;
  }
  ul.left-sidebar-menu > li.ant-menu-submenu > .ant-menu-sub > li {
    width: 100% !important;
    height: 40px !important;
    line-height: 40px !important;
    border-radius: 6px !important;
  }

  /* 6. The content pane collapses to width:0 once the layout is a column.
   *    antd sizes `.ant-layout-has-sider > .ant-layout` for a ROW container, where the main axis is
   *    horizontal and `flex: 1 1 auto` gives it the remaining width. Turned column, that basis applies
   *    to height instead and the measured width came out as 0 — the page rendered blank while its
   *    children painted outside the box (overflow was visible, which is why it looked half-alive
   *    rather than empty). Restore the cross-axis size explicitly. */
  .ant-layout.ant-layout-has-sider.app-container > section.ant-layout {
    width: 100% !important;
    min-width: 0 !important;
  }

  /* 7. Brand and nav share one row, as in app.localhost's CatalogHeader — OM stacks the brand above
   *    the menu, which wastes a whole band of vertical space on a top nav. */
  .left-sidebar-layout {
    display: flex !important;
    flex-direction: row !important;
    align-items: center !important;
    gap: 4px;
  }
  .left-sidebar-layout > .menu-container { flex: 1 1 auto; }

  /* 8. Reserve room for the submenu caret. In inline mode antd absolutely positions the arrow near the
   *    right edge of the item; with our tighter horizontal padding the label ran underneath it
   *    ("Observability", "Data Marketplace" and "Govern" were visibly overlapped). */
  ul.left-sidebar-menu > li.ant-menu-submenu > .ant-menu-submenu-title { padding-right: 32px !important; }
  ul.left-sidebar-menu > li.ant-menu-submenu > .ant-menu-submenu-title .ant-menu-submenu-arrow {
    right: 12px !important;
  }

  /* 9. Brand and nav on ONE row. The brand is NOT inside .left-sidebar-layout — it sits in
   *    .logo-container, a SIBLING of it under .ant-layout-sider-children. Row-ifying
   *    .left-sidebar-layout (rule 7) therefore could not work; the flex container has to be the
   *    common parent. */
  .ant-layout-sider-children {
    display: flex !important;
    flex-direction: row !important;
    align-items: center !important;
    gap: 20px;
    padding: 0 16px;
  }
  .ant-layout-sider-children > .logo-container { flex: 0 0 auto; }
  .ant-layout-sider-children > .left-sidebar-layout { flex: 1 1 auto; min-width: 0; }

  /* 10. Drop the sidebar collapse toggle. It controlled the WIDTH of a left rail; against a top nav it
   *     is meaningless — collapsing a horizontal bar has nothing to hide. */
  [data-testid='sidebar-toggle'] { display: none !important; }

  /* 11. Arrow clearance, at a specificity that actually wins. OM's own Less carries
   *     `.left-sidebar-menu.ant-menu.ant-menu-vertical .ant-menu-submenu… { padding-right: 14px
   *     !important }` — measured as the winner, which is why an earlier 32px !important lost and the
   *     labels stayed overlapped ("Observability", "Data Marketplace", "Govern"). Prefixing with the
   *     sider class clears it. Verified by enumerating the matching rules, not by guessing. */
  .ant-layout-sider ul.left-sidebar-menu > li.ant-menu-submenu > .ant-menu-submenu-title {
    padding-right: 34px !important;
  }
  .ant-layout-sider ul.left-sidebar-menu > li.ant-menu-submenu > .ant-menu-submenu-title
    .ant-menu-submenu-arrow { right: 12px !important; }

  /* 12. Spacing: fit the row without clipping. Logout was measured overflowing to x=1261 in a 1200px
   *     viewport. Tighter item padding, no inter-item margin, and let the two menus size to content. */
  .left-sidebar-layout .menu-container { gap: 0 !important; padding: 0 !important; }
  ul.left-sidebar-menu > li,
  .ant-layout-sider ul.left-sidebar-menu > li.ant-menu-submenu > .ant-menu-submenu-title {
    padding-left: 10px !important;
    margin: 0 !important;
  }
  ul.left-sidebar-menu > li { height: 44px !important; line-height: 44px !important; }
  .ant-layout-sider ul.left-sidebar-menu > li.ant-menu-submenu > .ant-menu-submenu-title {
    height: 44px !important;
    line-height: 44px !important;
  }
  /* The trailing menu (Settings / Logout) hugs the right edge and never shrinks away. */
  .left-sidebar-layout .menu-container > :last-child { flex: 0 0 auto; }

  /* 13. THE DROPDOWNS MUST ESCAPE THE BAR.
   *     As a left rail the sider was a tall scrolling column, so OM gives it `overflow: auto`. Now that
   *     it is a 44px-tall bar, that same overflow CLIPS the absolutely-positioned submenu from rule 5 —
   *     the panel was being rendered and painted outside the visible box, so clicking "Data
   *     Marketplace" or "Govern" turned the item navy (correctly, it IS open) while nothing appeared.
   *     Every ancestor between the dropdown and the page must stop clipping. */
  .ant-layout-sider,
  .ant-layout-sider-children,
  .left-sidebar-layout,
  .left-sidebar-layout .menu-container,
  .left-sidebar-layout .top-menu {
    overflow: visible !important;
  }
  /* antd animates an inline submenu open by driving its HEIGHT, and leaves height/overflow as inline
   *     styles. Absolute positioning alone does not undo that, so an opened panel could still measure
   *     zero-height. Pin it open. (`.ant-menu-hidden` still hides the closed state.) */
  .ant-layout-sider ul.left-sidebar-menu > li.ant-menu-submenu > .ant-menu-sub:not(.ant-menu-hidden) {
    height: auto !important;
    overflow: visible !important;
    opacity: 1 !important;
  }

  /* 14. Item metrics, at a specificity that beats OM's own.
   *     OM's Less carries a SIX-class selector
   *     (`.left-sidebar-menu.ant-menu.ant-menu-inline .ant-menu-submenu.ant-menu-submenu-inline
   *       .ant-menu-submenu-title`) with !important, which outranked two earlier attempts here —
   *     measured, not guessed, by enumerating every matching rule. These use SEVEN classes. */
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline > li.ant-menu-item,
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline
    > li.ant-menu-submenu.ant-menu-submenu-inline > .ant-menu-submenu-title {
    height: 40px !important;
    line-height: 40px !important;
    padding-left: 12px !important;
    padding-right: 12px !important;
    margin: 0 !important;
  }
  /* Submenu titles reserve room for the caret, which antd pins near the right edge. */
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline
    > li.ant-menu-submenu.ant-menu-submenu-inline > .ant-menu-submenu-title {
    padding-right: 30px !important;
  }
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline
    > li.ant-menu-submenu.ant-menu-submenu-inline > .ant-menu-submenu-title
    > .ant-menu-submenu-arrow { right: 10px !important; }

  /* 15. Breathing room: brand, nav and the trailing actions read as three groups, not one dense run. */
  .ant-layout-sider-children { gap: 28px; padding: 0 20px; }
  .ant-layout-sider ul.left-sidebar-menu > li { margin-right: 2px !important; }

  /* 16. Lift the whole bar into its own stacking context, above the content.
   *     The dropdown from rule 5 measured correctly (224x158 at the right offset) but was invisible:
   *     the content <section> is a LATER SIBLING of the sider in DOM order, so it paints over it, and a
   *     z-index on the panel alone is compared inside the sider's context — it cannot climb out. Give
   *     the bar itself the z-index. */
  .ant-layout-sider { position: relative !important; z-index: 100 !important; }

  /* 17. TEXT-ONLY nav, which is both v1 parity and what makes the row fit.
   *     app.localhost's CatalogHeader renders its nav as plain text links (CatalogHeader.tsx
   *     navLinksFor -> label + path, no icons). Icons belong to a vertical rail, where there is room
   *     for a 24px glyph beside every label; in a single row nine of them cost ~250px and pushed
   *     "Govern", Settings and Logout off a 1200px viewport, adding a horizontal page scrollbar.
   *     Dropping them matches v1 AND reclaims the width.
   *     To put them back, delete this rule — the markup is untouched. */
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline > li > .anticon.ant-menu-item-icon,
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline
    > li > .ant-menu-submenu-title > .anticon.ant-menu-item-icon {
    display: none !important;
  }
  /* Icons stay INSIDE the dropdowns, where they aid scanning and cost no horizontal room. */

  /* 18. Final spacing pass now that the icons are gone. */
  .ant-layout-sider-children { gap: 16px; padding: 0 20px; }
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline > li.ant-menu-item,
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline
    > li.ant-menu-submenu.ant-menu-submenu-inline > .ant-menu-submenu-title {
    padding-left: 14px !important;
    padding-right: 14px !important;
  }
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline
    > li.ant-menu-submenu.ant-menu-submenu-inline > .ant-menu-submenu-title {
    padding-right: 28px !important;
  }

  /* 19. Last 39px. Text-only got the row from 1441px down to 1239px against a 1200px viewport — still
   *     overflowing, and an overflowing bar means a horizontal scrollbar on the whole page. 10px of
   *     side padding per item reclaims ~72px across the nine, which clears it with room to spare. */
  .ant-layout-sider-children { gap: 12px; padding: 0 16px; }
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline > li.ant-menu-item,
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline
    > li.ant-menu-submenu.ant-menu-submenu-inline > .ant-menu-submenu-title {
    padding-left: 10px !important;
    padding-right: 10px !important;
  }
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline
    > li.ant-menu-submenu.ant-menu-submenu-inline > .ant-menu-submenu-title {
    padding-right: 26px !important;
  }

  /* 20. THE ACTUAL REASON THE DROPDOWNS WERE INVISIBLE — and it was not z-index.
   *     Rule 13 stopped the sider chain clipping, and rule 16 raised the bar's stacking context. The
   *     panel still did not show. Measurement settled it: forcing the sider to z-index 2000 changed
   *     nothing, while walking the panel's ancestors found `overflow: hidden` on BOTH
   *     `li.ant-menu-submenu` and the root `ul.ant-menu-inline`. antd needs those to animate an inline
   *     submenu's height, and they clip an absolutely-positioned child to a zero-height box. The panel
   *     was being rendered and then clipped away — never hidden behind anything.
   *     Lesson worth keeping: "element measures correctly but is invisible" is a CLIPPING question at
   *     least as often as a stacking one; check overflow on the ancestor chain before touching z-index. */
  .ant-layout-sider ul.left-sidebar-menu,
  .ant-layout-sider ul.left-sidebar-menu > li.ant-menu-submenu,
  .ant-layout-sider ul.left-sidebar-menu > li.ant-menu-submenu.ant-menu-submenu-inline {
    overflow: visible !important;
  }

  /* 21. Equal-width tabs, sized to the widest label.
   *     Deliberately a explicit width, not an intrinsic-grid trick. `display: grid` with
   *     `grid-auto-columns: 1fr; width: max-content` DID equalise the tabs (166px each, verified) but
   *     the tracks refused to collapse to content — the row still measured 1509px instead of 7x166,
   *     leaving ~350px of dead space and clipping the right end at 1800px. A single variable is
   *     predictable, and one number is easier to re-derive than a track-sizing interaction.
   *
   *     HOW TO RE-DERIVE if labels change (they are translated, so they will):
   *       widest label text width + 20px side padding + 26px caret room
   *     Measured 2026-07-28: Home 40, Explore 50, Lineage 53, Observability 89, Insights 53,
   *     Data Marketplace 118, Govern 49  ->  118 + 20 + 26 = 164, rounded to 166. */
  .ant-layout-sider { --dh-tab-w: 166px; }

  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline > li.ant-menu-item,
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline > li.ant-menu-submenu {
    flex: 0 0 var(--dh-tab-w) !important;
    width: var(--dh-tab-w) !important;
    justify-content: center !important;
    border: 0 !important;
  }
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline
    > li.ant-menu-submenu.ant-menu-submenu-inline > .ant-menu-submenu-title {
    justify-content: center !important;
    width: 100% !important;
  }
  .left-sidebar-layout .top-menu { flex: 0 0 auto !important; }

  /* The dropdown starts at its tab's width and grows only as far as its own longest item needs —
     "Ontology Explorer" and "Column Bulk Operations" were truncating at a fixed 224px. */
  .ant-layout-sider ul.left-sidebar-menu > li.ant-menu-submenu > .ant-menu-sub {
    min-width: var(--dh-tab-w) !important;
    width: max-content !important;
    max-width: 300px !important;
  }


}

/* Equal-width tabs are the FIRST thing to go as the viewport narrows: they cost ~350px versus
 * content-sized tabs (1162px vs ~810px) and are purely cosmetic. Below 1600px the tabs size to their
 * own labels, which keeps every item visible and the row un-clipped.
 * Above it, rule 21's intrinsic grid applies and they equalise to the widest label. */
@media (min-width: 1280px) and (max-width: 1599px) {
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline > li.ant-menu-item,
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline > li.ant-menu-submenu {
    flex: 0 0 auto !important;
    width: auto !important;
  }
}

/* Tighter still: 1280-1399px drops the wordmark and keeps only the DH chip (~120px back). */
@media (min-width: 1280px) and (max-width: 1399px) {
  .ant-layout-sider-children { gap: 8px; padding: 0 10px; }
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline > li.ant-menu-item,
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline
    > li.ant-menu-submenu.ant-menu-submenu-inline > .ant-menu-submenu-title {
    padding-left: 8px !important;
    padding-right: 8px !important;
  }
}

/* ============================================================================
 * v1 PARITY PASS — no bounding boxes, one bar
 * ----------------------------------------------------------------------------
 * Ported from the live app.localhost header rather than from memory. Measured there:
 *   bar        65px tall, background rgba(255,255,255,.95)
 *   nav link   14px / weight 500 / padding 22px 0 (NO horizontal padding)
 *              no background, no border-radius, no border-bottom
 *   active     oklch(0.145 0 0)  (near-black)      inactive  oklch(0.556 0 0)  (grey)
 *   gap        24px between items
 *   identity   bell + user icon, far right of the SAME bar
 *
 * Two things this corrects about the first cut:
 *  1. Filled navy "tab" boxes. v1 has no boxes at all — the active item is distinguished by INK,
 *     not by a background. The boxes were imported from the sidebar's selected-row treatment, where
 *     a filled row is right; in a horizontal bar they read as chunky and un-DataHex.
 *  2. Equal-width tabs. Those existed to make the boxes line up. With no boxes there is nothing to
 *     line up, and natural widths are both lighter and ~540px cheaper — which is precisely what
 *     buys room for the identity cluster on the same row.
 * ========================================================================== */
@media (min-width: 1280px) {

  /* --- no boxes: strip every background/radius the sidebar treatment applied --- */
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline > li.ant-menu-item,
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline > li.ant-menu-item-selected,
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline > li.ant-menu-item:hover,
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline > li.ant-menu-submenu
    > .ant-menu-submenu-title,
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline
    > li.ant-menu-submenu.ant-menu-submenu-open > .ant-menu-submenu-title,
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline
    > li.ant-menu-submenu.ant-menu-submenu-selected > .ant-menu-submenu-title,
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline
    > li.ant-menu-submenu > .ant-menu-submenu-title:hover {
    background: transparent !important;
    border-radius: 0 !important;
    box-shadow: none !important;
  }

  /* --- natural widths, v1 metrics --- */
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline > li.ant-menu-item,
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline > li.ant-menu-submenu {
    flex: 0 0 auto !important;
    width: auto !important;
    height: 64px !important;
    line-height: 64px !important;
    padding: 0 !important;
    /* 6px, was 12px. THIS IS THE NAV-OVERLAP FIX — see the boundary-budget note further down.
     * At 1512px the row ran from x=232 to x=1010 while the identity cluster starts at x=988, so
     * `Settings` sat 10px under a transparent cluster: two sets of text on top of each other.
     * The row is content-sized, so the only lever that moves its right edge is the items' own
     * width. Measured by applying each candidate inline and reading the geometry back:
     *     12px -> right edge 998, gap -10   (the bug)
     *      8px -> 971, gap +17
     *      6px -> 941, gap +47              <- chosen
     *      4px -> 911, gap +77
     * 8px would clear it today, but the cluster's width tracks the user's display name, so a
     * longer name than "Alice Tenant-A" eats 17px easily. 6px keeps ~47px of slack. */
    margin: 0 6px !important;       /* 12px between neighbours */
    font-size: 14px !important;
    font-weight: 500 !important;
  }
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline
    > li.ant-menu-submenu > .ant-menu-submenu-title {
    height: 64px !important;
    line-height: 64px !important;
    padding: 0 18px 0 0 !important;  /* right side only, for the caret */
    width: auto !important;
    font-size: 14px !important;
    font-weight: 500 !important;
  }
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline
    > li.ant-menu-submenu > .ant-menu-submenu-title > .ant-menu-submenu-arrow {
    right: 0 !important;
  }

  /* --- ink carries the state, exactly as in v1 --- */
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline > li,
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline > li * {
    color: #737373 !important;                    /* oklch(0.556 0 0) */
  }
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline > li.ant-menu-item-selected,
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline > li.ant-menu-item-selected *,
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline
    > li.ant-menu-submenu-selected > .ant-menu-submenu-title,
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline
    > li.ant-menu-submenu-selected > .ant-menu-submenu-title *,
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline
    > li.ant-menu-submenu-open > .ant-menu-submenu-title,
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline
    > li.ant-menu-submenu-open > .ant-menu-submenu-title *,
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline > li:hover,
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline > li:hover * {
    color: #1a1a1a !important;                    /* oklch(0.145 0 0) */
  }

  /* --- the bar itself --- */
  .ant-layout-sider {
    background: rgb(255 255 255 / 95%) !important;
    height: 65px !important;
  }
  .ant-layout-sider-children { height: 65px !important; align-items: center !important; }

  /* --- ONE BAR: lift OM's identity row (EN / bell / help / avatar) into it ---
   * .navbar-container is a 40px band inside the content pane. Taking it out of flow collapses that
   * band automatically, and .content-wrapper is a positioned ancestor of BOTH it and the sider, so
   * top/right resolve against the same box. Room is reserved on the nav side rather than guessed. */
  .navbar-container {
    position: absolute !important;
    top: 0 !important;
    right: 20px !important;
    width: auto !important;
    height: 65px !important;
    display: flex !important;
    align-items: center !important;
    z-index: 101 !important;          /* the bar is 100; identity sits just above it */
    background: transparent !important;
    border: 0 !important;
  }
  /* Keep the nav clear of it. Generous, because the cluster's width varies with the display name. */
  .left-sidebar-layout .menu-container { padding-right: 380px !important; }
}

/* ============================================================================
 * CONSOLIDATION, CORRECTED — move only the identity cluster, not the whole navbar
 * ----------------------------------------------------------------------------
 * The first consolidation lifted all of `.navbar-container` into the bar. That is wrong because the
 * navbar's CONTENTS ARE ROUTE-DEPENDENT. Measured on /explore:
 *     .flex-center.gap-2            791px   <- OM's global search, present only on some routes
 *     .flex-center.nav-bar-side-item 387px  <- EN / bell / help / avatar
 * Total 1178px starting at x=587, against a nav ending at x=1046 — so on /explore and /lineage the
 * search bar sat on top of Insights / Data Marketplace / Govern / Settings / Logout and hid them.
 * On /my-data there is no search, the container is 387px, and it fitted — which is why the bug only
 * appeared on some pages.
 *
 * So relocate ONLY the identity cluster, which is present on every route and has a stable width, and
 * leave the search where OM puts it.
 * (Selector note: the class is .nav-bar-side-items — PLURAL. An earlier diagnostic truncated class
 * strings to 35 chars and silently dropped the trailing "s", so the first selector matched nothing
 * and the identity cluster stayed behind in the collapsed band, clipped out of sight.)
 * ========================================================================== */
@media (min-width: 1280px) {

  /* Put the navbar back in the flow — it owns the search bar again. */
  .navbar-container {
    position: static !important;
    height: auto !important;
    width: auto !important;
    right: auto !important;
    z-index: auto !important;
  }

  /* Relocate just the identity cluster into the bar. Its width is stable across routes — 387px when
   * this was written, 504px since AGT-01 added the Ask trigger.
   * FIXED (not absolute) so it stays pinned when the page scrolls — see "PIN THE TOP BAR" just below.
   * Fixed anchors to the viewport here (verified live: no transformed ancestor on these routes), which
   * is also more robust than absolute against the route-specific positioned-ancestor trap noted lower:
   * a `position: relative` header creates a containing block for absolute children but NOT for fixed. */
  .nav-bar-side-items {
    position: fixed !important;
    top: 0 !important;
    right: 20px !important;
    height: 65px !important;
    display: flex !important;
    align-items: center !important;
    z-index: 101 !important;
  }

  /* PIN THE TOP BAR ON SCROLL.
   * The page scrolls at the WINDOW level (there is no inner overflow:auto container), so the navy bar
   * — OM's left sider, row-ified into a top bar above — scrolled away with the content. `position:
   * sticky` is NOT enough: the sider's containing block (.app-container) is only viewport-tall, so a
   * sticky sider unsticks after ~700px (measured live). `fixed` pins it for the whole scroll; because
   * that takes the 65px bar out of flow, the content column gets a matching top margin so nothing
   * hides under it. The identity cluster is pinned by its own `position: fixed` above; z-order is
   * unchanged (sider 100, cluster 101). Verified live at scrollY 0 / 600 / 2500 — bar frozen at y=0,
   * cards scroll under it, first card unchanged at rest. */
  .app-container > .ant-layout-sider {
    position: fixed !important;
    top: 0 !important;
    left: 0 !important;
    width: 100% !important;
    z-index: 100 !important;
    /* Translucent frosted bar, matching v1: content is faintly visible through it as it scrolls under.
     * The light blur keeps the nav text legible over whatever passes beneath (pure alpha alone would
     * let dark cards wash it out). A deliberate, user-requested v1-parity deviation from the design
     * system's "no glass" rule — kept subtle (0.82 alpha, 8px blur), and scoped to this one pinned bar. */
    background: rgb(255 255 255 / 82%) !important;
    -webkit-backdrop-filter: saturate(1.3) blur(8px) !important;
    backdrop-filter: saturate(1.3) blur(8px) !important;
    border-bottom: 1px solid rgb(0 0 0 / 6%) !important;
  }
  .app-container > section.ant-layout {
    margin-top: 65px !important;
  }

  /* THE CLUSTER'S WIDTH IS HALF OF THE OVERLAP EQUATION — bound it, then collapse it.
   *
   * Because the cluster is right-anchored at 20px, its left edge is `viewport - width - 20`, while
   * the nav row is content-sized and ends at a fixed x=926. Overlap is therefore purely
   *     viewport - width - 20 < 926
   * Halving the nav's item margins (see `margin: 0 6px` above) fixed 1512px and nothing narrower:
   * measured 1440 -> gap -10, and 1280 -> gap -160. The rest has to come from `width`.
   *
   * Measured breakdown of the 504px: user chip 189, Ask trigger 98, EN 57, bell 40, help 40, plus
   * 4 x 20px gaps. Two of those are text that a cramped bar does not need.
   *
   * 1. Bound the display name. Untouched, the chip is as wide as whoever is logged in — so the
   *    overlap threshold moved with the user, which is why it looked intermittent. 120px with an
   *    ellipsis makes the cluster's worst case a constant. (The name also remains in the avatar's
   *    dropdown, so nothing becomes unreachable.)
   * 2. Below 1500px, drop the name entirely and let the avatar stand alone — the conventional
   *    responsive pattern, and the same trade ask.css already makes for the trigger's label.
   *
   * Verified by hiding these inline and re-measuring at both bad widths:
   *     1440: cluster 504 -> 319, gap -10  -> +175
   *     1280: cluster 504 -> 319, gap -160 -> +25
   */
   * Step 2 lives in its own block at the END of this file — putting it here would have meant
   * closing this @media early and silently re-scoping every rule that follows it.
   */
  .nav-bar-side-items .name-persona-container {
    max-width: 120px !important;
    overflow: hidden !important;
  }

  /* The rule above only works because this header is NOT a positioned ancestor.
   *
   * DataMarketplacePage sets an inline `position: relative; z-index: 10` on .ant-layout-header so
   * the navbar can overlay its gradient hero. That did two things, and both broke the bar — on that
   * one route only, which is what made it look marketplace-specific rather than structural:
   *   1. It became the nearest positioned ancestor, so `top: 0` above resolved against the header
   *      at y=66 instead of .content-wrapper at y=0 — the identity cluster dropped into a second
   *      band BELOW the nav.
   *   2. The z-index made it a stacking context, which CAPS every descendant. So even once the
   *      cluster was placed correctly it painted under the sider (z-index: 100) and was
   *      unclickable — elementFromPoint over the avatar returned .menu-container. A child cannot
   *      escape its parent's stacking context, so no z-index on the cluster itself can fix this.
   *
   * Both have to be undone, and `position: static` alone does NOT do it. The usual rule of thumb —
   * "z-index is ignored on a static element" — does not hold here: this header's parent is
   * `display: flex`, and z-index DOES apply to a flex item whatever its position. So a static
   * header with `z-index: 10` still forms a stacking context and still caps the cluster under the
   * sider. Verified: with only `position: static`, the cluster sat correctly at y=0 and
   * elementFromPoint over the avatar still returned .menu-container. Hence both declarations.
   *
   * This is preferred over lifting the header above the sider — that was tried first and regressed
   * /explore, where the raised header then painted over the Govern dropdown. Neutralising instead
   * of raising leaves every route behaving identically.
   * Safe for the hero: on this route .navbar-container is collapsed to height 0 by the rule below,
   * so the header has nothing left to be overlaid by the gradient. */
  .ant-layout-header {
    position: static !important;
    z-index: auto !important;
  }

  /* With no search on the route, the navbar is left holding nothing and reserved an empty band under
   * the bar — the gap visible on /my-data. `:has()` collapses it precisely when it is empty, and
   * leaves it alone on routes where the search really is there. */
  .navbar-container:not(:has(input)) {
    height: 0 !important;
    min-height: 0 !important;
    padding: 0 !important;
    margin: 0 !important;
    overflow: hidden !important;
  }

  /* ...and collapse the HEADER around it, not just the container.
   *
   * The rule above zeroes .navbar-container, but the <header> it sits in carries its own
   * `padding: 20px 24px` and `--ant-navbar-height: 80px`. So on every route with no search bar the page
   * still opened with an 80px empty band between the nav and the first heading. Measured: /my-data's H1
   * at y=178 against /data-marketplace's title at y=98 — the 80px was the whole difference, and the
   * marketplace only looked right because it used to slide up under the band with a negative margin
   * (removed in NavBar.tsx; this rule is what makes that hack unnecessary).
   *
   * SELECTOR NOTE: the obvious `:has(.navbar-container:not(:has(input)))` is INVALID — CSS forbids
   * `:has()` inside `:has()`, so the browser drops the whole rule silently and nothing changes (that
   * cost one round of "why is the header still 80px"). `:not(:has())` is the legal nesting, and it says
   * the same thing here: the only input this header ever holds is the route-dependent search bar.
   *
   * The relocated identity cluster is `position: absolute`, hence out of flow, so collapsing its parent
   * does not move it. Below 1280 this whole block is inert and the header keeps its height, which is
   * correct: there the cluster is still inside it. */
  header.ant-layout-header:not(:has(input)) {
    height: 0 !important;
    min-height: 0 !important;
    padding-top: 0 !important;
    padding-bottom: 0 !important;
  }

  /* Reserve room for the identity cluster only (387px + breathing space), not for the search. */
  .left-sidebar-layout .menu-container { padding-right: 420px !important; }

  /* THE PAGE'S SLOT IN THIS ROW.
   * Once the identity cluster moves up into the nav bar (above), the header row holds a 504px search box
   * and a domain button and then ~750px of nothing, while the page below opens with a breadcrumb in a row
   * of its own that is just as empty. src/datahex/nav/NavSlot.tsx portals page chrome into this slot
   * instead; DataProductsDetailsPage sends its breadcrumb, which returns 36px to the content.
   *
   * `flex: 1` is what makes this work without arithmetic: .navbar-container is `justify-content:
   * space-between`, so a plain third child would be flung to the far right — and the offset needed to
   * clear the search cluster is not a constant, because the search box is flex-sized. Letting the slot
   * absorb the free space parks its content immediately after the domain button at every width.
   *
   * Its content is right-aligned, which reads better than parking it against the domain button: the
   * breadcrumb then sits at the row's right edge, over the page's own right edge below it, and the empty
   * space falls between the two clusters instead of after them.
   *
   * min-width: 0 and the overflow rules matter for deep breadcrumbs (service > database > schema >
   * table): without them the row grows and pushes the search box narrower instead of clipping.
   * useBreadcrumbs gives its container `mb: 3` for the row it no longer occupies; in a centred flex row
   * that bottom margin reads as a 12px upward shift, so it goes. */
  .dh-nav-slot {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    padding-left: 20px;
    overflow: hidden;
  }
  /* The breadcrumb Box is a block that fills the slot, so right-aligning the slot's content is not
   * enough on its own — the <ol> inside it has to be pushed over too. */
  .dh-nav-slot [data-testid='breadcrumb'] { width: auto !important; }
  .dh-nav-slot .MuiBreadcrumbs-root { display: flex; justify-content: flex-end; }
  .dh-nav-slot [data-testid='breadcrumb'] { margin-bottom: 0 !important; }
  .dh-nav-slot .MuiBreadcrumbs-ol { flex-wrap: nowrap !important; }

  /* The row the breadcrumb vacated was also the only gap between the header and the page's first card,
   * so the entity icon ended up flush against the card's top border. A little of it comes back. Inside
   * this ≥1280 block deliberately: below it the breadcrumb is still in place and already spaces the card,
   * and a second gap there would be a double one. */
  .dh-entity-page-top { padding-top: 12px; }
}

@media (min-width: 1280px) {
  /* Settings sits immediately after Govern rather than being pushed to the far right. OM renders it
   * in a second <Menu> (.bottom-menu) which the earlier `margin-left: auto` shoved to the end of the
   * row; dropping that lets it read as one continuous nav. */
  .left-sidebar-layout .bottom-menu { margin-left: 0 !important; flex: 0 0 auto !important; }

  /* Drop the duplicated Logout — OM's avatar menu already has it (verified: View Profile, Switch
   * Persona, Roles, Teams, Logout). Settings is NOT in that menu, so it stays in the bar.
   * The uuid in data-menu-id is regenerated per render, so match on the stable suffix. */
  .left-sidebar-layout .bottom-menu li[data-menu-id$='logout'] { display: none !important; }

  /* The divider between the two menus was a horizontal rule separating stacked sections of a vertical
   * rail. In a single row it is a stray 22px tick. */
  .left-sidebar-layout .bottom-menu .ant-menu-item-divider { display: none !important; }
}

@media (min-width: 1280px) {
  /* 23. Stop the dropdown resizing between opens.
   *     Reproduced by opening the same submenu three times: 178.53x158, 178.53x158, then
   *     174.53x182. Two causes, both from `width: max-content` on a panel that antd is still
   *     animating — the inline style left behind reads `height: 0px; opacity: 0`, so an intrinsic
   *     measurement taken during that transition resolves against half-laid-out content and lands on
   *     a different value each time.
   *
   *     Fix both halves:
   *       - a FIXED width, so there is no intrinsic measurement to be unstable. 260px fits the
   *         longest child labels ("Ontology Explorer", "Column Bulk Operations") which were
   *         truncating at 224px, and a uniform width across menus looks deliberate rather than ragged.
   *       - NO transition. antd animates height to expand an inline submenu in place; we render an
   *         overlay instead, so the animation has nothing useful to do and is the source of every
   *         transient state. Instant open/close is both stabler and more dropdown-like. */
  .ant-layout-sider ul.left-sidebar-menu > li.ant-menu-submenu > .ant-menu-sub {
    width: 260px !important;
    min-width: 260px !important;
    max-width: 260px !important;
    transition: none !important;
    animation: none !important;
  }
  .ant-layout-sider ul.left-sidebar-menu > li.ant-menu-submenu > .ant-menu-sub > li {
    transition: none !important;
    animation: none !important;
  }
}

@media (min-width: 1280px) {
  /* 24. Dropdown items need their own palette — the top-bar rules were leaking into them.
   *     The v1-parity block greys the nav with `... > li, ... > li *`, and that `*` matches EVERY
   *     descendant, dropdown children included. So a selected child kept the navy background from the
   *     sidebar's selected-row rules while its label was forced to #737373 grey: dark-grey-on-navy,
   *     which is what made "Incident Manager" unreadable when it was the current route.
   *
   *     Give the panel an explicit, self-consistent palette instead of inheriting the bar's. Selected
   *     is a light tint with navy ink rather than a filled navy row — consistent with the box-free
   *     bar above it, and it cannot produce a low-contrast pair whatever the brand colour becomes. */
  .ant-layout-sider ul.left-sidebar-menu > li.ant-menu-submenu > .ant-menu-sub > li,
  .ant-layout-sider ul.left-sidebar-menu > li.ant-menu-submenu > .ant-menu-sub > li * {
    color: #1a1a1a !important;
    background: transparent !important;
  }
  /* Current route */
  .ant-layout-sider ul.left-sidebar-menu > li.ant-menu-submenu
    > .ant-menu-sub > li.ant-menu-item-selected,
  .ant-layout-sider ul.left-sidebar-menu > li.ant-menu-submenu
    > .ant-menu-sub > li.ant-menu-item-selected * {
    color: var(--dh-primary) !important;
    font-weight: 600 !important;
  }
  .ant-layout-sider ul.left-sidebar-menu > li.ant-menu-submenu
    > .ant-menu-sub > li.ant-menu-item-selected {
    background: var(--dh-primary-tint) !important;
  }
  /* Hover — neutral, so it never competes with the selected state for attention */
  .ant-layout-sider ul.left-sidebar-menu > li.ant-menu-submenu > .ant-menu-sub > li:hover,
  .ant-layout-sider ul.left-sidebar-menu > li.ant-menu-submenu > .ant-menu-sub > li:hover * {
    color: #1a1a1a !important;
  }
  .ant-layout-sider ul.left-sidebar-menu > li.ant-menu-submenu > .ant-menu-sub > li:hover {
    background: #f4f5f7 !important;
  }
  /* A hover on the selected row keeps the tint rather than reverting to neutral grey. */
  .ant-layout-sider ul.left-sidebar-menu > li.ant-menu-submenu
    > .ant-menu-sub > li.ant-menu-item-selected:hover {
    background: var(--dh-primary-tint) !important;
  }
}

@media (min-width: 1280px) {
  /* 25. Even the TEXT rhythm, not just the box rhythm.
   *     Box-to-box gaps were already a uniform 24px, but text-edge-to-text-edge measured
   *     34 / 34 / 44 / 60 / 44 / 70px. The eye reads the text rhythm, so the bar looked irregular.
   *
   *     Every gap is the sum of four values: prevPaddingRight + prevMarginRight + curMarginLeft +
   *     curPaddingLeft. Plain items are symmetric (5/5 padding, 12/12 margin) and sum to 34. Submenu
   *     items break it twice: they reserve ~26px on the right for the caret AND carry 10px more left
   *     padding. Fixing only the right side left a 27px spread — both sides have to be normalised.
   *
   *     Solve for 34 on all three boundary kinds with submenu padding 5/16 and margin 12/1:
   *       plain  -> caret :  5 + 12 + 12 +  5 = 34
   *       caret  -> plain : 16 +  1 + 12 +  5 = 34
   *       caret  -> caret : 16 +  1 + 12 +  5 = 34
   *
   *     Deliberately NOT by making antd's arrow inline — it is absolutely positioned and drawn from
   *     two transformed pseudo-elements, so restyling its box is far more fragile than budgeting the
   *     space around it. The 16px right padding keeps the caret clear of its own label. */
  /*     UPDATE 2026-08-02 — the uniform 34px could not survive the nav-overlap fix.
   *
   *     Generalising the three equations above with plain margin Mp and submenu margins Ms_l/Ms_r,
   *     a uniform boundary G requires Mp = Ms_l = (G-10)/2 and Ms_r = G - 21 - Mp. That yields
   *     Ms_r = 1 at G=34 (the original) and Ms_r = 0 at G=32 — below which Ms_r goes NEGATIVE, so
   *     uniformity is unreachable by margins alone. Freeing it would mean spending the submenu's
   *     16px right padding, which exists to keep the caret clear of its own label.
   *
   *     The row had to lose ~57px to stop colliding with the identity cluster, so uniformity is
   *     relaxed rather than the caret compromised. With Mp = 6, Ms_l = 6, Ms_r = 1:
   *       plain  -> caret :  5 +  6 +  6 +  5 = 22
   *       caret  -> plain : 16 +  1 +  6 +  5 = 28
   *       caret  -> caret : 16 +  1 +  6 +  5 = 28
   *     A 6px spread instead of a dead-flat 34. If the nav ever gets its width back (fewer items,
   *     a narrower cluster), restore Mp = Ms_l = 12 and Ms_r = 1 to return to uniform 34. */
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline > li.ant-menu-submenu {
    margin-left: 6px !important;
    margin-right: 1px !important;
  }
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline
    > li.ant-menu-submenu.ant-menu-submenu-inline > .ant-menu-submenu-title {
    padding-left: 5px !important;
    padding-right: 16px !important;
  }
  .ant-layout-sider .left-sidebar-menu.ant-menu.ant-menu-inline
    > li.ant-menu-submenu.ant-menu-submenu-inline > .ant-menu-submenu-title
    > .ant-menu-submenu-arrow { right: 3px !important; }
}

/* ============================================================================
 * NAV/IDENTITY COLLISION, step 2 of 2 — collapse the display name when it does not fit
 * ----------------------------------------------------------------------------
 * Step 1 (bounding the name to 120px) is in the top-nav block above, next to the geometry it
 * belongs with. This half needs a DIFFERENT breakpoint, so it has to be its own block: written
 * inline up there it would have closed that @media early and re-scoped every rule after it.
 *
 * Range, not a plain max-width: below 1280px the whole top-nav transformation is inert and OM's
 * native sidebar returns, where the cluster is not in the bar at all and there is nothing to
 * collide with. Hiding the name there would be a pointless regression.
 *
 * 1559 is the upper bound. MEASURED 2026-08-11 at 1512px with the name AND the Ask label shown: the
 * cluster is 563px wide, its left edge lands at x=914, and Settings' right edge is x=922 — an 8px
 * overlap. Solving `viewport - clusterRight(≈598) >= settingsRight(922)` puts the true clear point at
 * ~1520px, so the name may only reappear above that. 1559 keeps ~40px of margin (name shows from
 * 1560px, where the left edge is x=962 vs Settings' 922) and absorbs font/name variance. The bump from
 * the old 1499 is because the navy-filled Ask pill (ask.css) widened the cluster past the old
 * threshold. The Ask LABEL itself stays visible down to 1499 (its own rule in ask.css) — only the
 * display name collapses here, which is the bigger, more variable contributor. Below 1280 the whole
 * top-nav transformation is inert (OM's native sidebar returns) so there is nothing to collide with.
 * ========================================================================== */
@media (min-width: 1280px) and (max-width: 1559px) {
  .nav-bar-side-items .name-persona-container {
    display: none !important;
  }
}

/* ============================================================================
 * DataHex — v1-style segmented pill tab bar (data-product detail page ONLY).
 *
 * v1's product-detail tabs are a shadcn segmented control: a neutral-grey rounded
 * WELL holding the tabs evenly spread edge to edge, the active one lifted out as a
 * WHITE pill with a soft shadow (no underline). Stock OM `tabs-new` is the inverse
 * — a white bordered bar with a navy underline ink-bar — which read flat/"boxed".
 *
 * Scoped to `.data-product-details-page-tabs` (unique to DataProductsDetailsPage)
 * so every other entity page keeps OM's native tab treatment. Everything is a
 * runtime override over the compiled `tabs-new` rules in styles/components/tabs.less.
 * ==========================================================================*/
.data-product-details-page-tabs .ant-tabs.tabs-new .ant-tabs-nav {
  /* the grey well: v1's SINGLE neutral — navy primary at 5% (bg-primary/5, #f2f4f6),
     the exact same grey as the page background, so there is no colour variation. */
  background: #f2f4f6 !important;
  border: 1px solid #e6e8ec !important;
  border-radius: 12px !important;
  padding: 5px !important;     /* inset gutter so the active pill floats inside */
  height: auto !important;
}
/* let the tab list own the full width so the tabs distribute across it */
.data-product-details-page-tabs .ant-tabs.tabs-new .ant-tabs-nav-wrap {
  flex: 1;
}
.data-product-details-page-tabs .ant-tabs.tabs-new .ant-tabs-nav-list {
  width: 100%;
  gap: 4px;
}
.data-product-details-page-tabs .ant-tabs.tabs-new .ant-tabs-tab {
  flex: 1 1 0;                 /* even spread — "more spaced out" */
  justify-content: center;
  margin: 0 !important;        /* kill antd's 32px inter-tab left margin */
  padding: 8px 12px !important;
  border-radius: 8px;
  color: #5c6472;             /* muted-foreground on the resting pills */
  transition: background 120ms ease, color 120ms ease, box-shadow 120ms ease;
}
.data-product-details-page-tabs .ant-tabs.tabs-new .ant-tabs-tab:hover {
  color: #26303f;
}
.data-product-details-page-tabs
  .ant-tabs.tabs-new
  .ant-tabs-tab.ant-tabs-tab-active {
  background: #fff;
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.08), 0 1px 3px rgba(16, 24, 40, 0.04);
}
.data-product-details-page-tabs
  .ant-tabs.tabs-new
  .ant-tabs-tab.ant-tabs-tab-active
  .ant-tabs-tab-btn {
  color: var(--dh-primary, #002147) !important;
  font-weight: 600;
}
/* the underline is replaced by the pill — remove it */
.data-product-details-page-tabs .ant-tabs.tabs-new .ant-tabs-ink-bar {
  display: none !important;
}
