/*
 * @es/shared/styles/topbar.css
 *
 * Single source of truth for the Energy Strategies "chrome":
 *   - Design tokens (brand colors, surfaces, ink, status) for dark + light themes.
 *   - Topbar (DashboardMenu, brand, ThemeToggle, PendingPill).
 *   - Sidebar (sibling-tool navigation).
 *   - Chrome layout helpers (.est-chrome-body, .est-chrome-content).
 *
 * Token namespace is --est-*. Apps alias their own variable names to
 * these so they automatically follow the active theme — see each app's
 * top-of-CSS aliasing block. Toggling the html[data-theme] attribute
 * cascades through every app.
 *
 * Brand colors mirror the Energy Strategies palette (es-blue / teal /
 * green / orange) so the topbar looks identical across every dashboard.
 */

:root {
  /* Tell the browser to render native widgets (date pickers,
     scrollbars, select dropdowns, etc.) using its dark-theme
     palette. Without this, an empty <input type="date"> renders
     its "mm/dd/yyyy" placeholder text in the light-theme color
     which reads as bright white against our dark surfaces. */
  color-scheme: dark;

  /* ---------- Brand ---------- */
  --est-es-blue: #1ea7e0;
  --est-es-teal: #3eb6a1;
  --est-es-green: #6cc04a;
  --est-es-orange: #d9853f;

  /* ---------- Surfaces (dark, default) ---------- */
  --est-bg:          #0b0d10;
  --est-bg-elev:     #0f1216;
  --est-surface:     #161a1f;
  --est-surface-2:   #1c2129;
  --est-line:        #2a2f37;
  --est-line-strong: #3a4049;
  --est-line-focus:  var(--est-es-blue);

  /* ---------- Text ---------- */
  --est-ink:         #e7eaef;
  --est-ink-soft:    #c4c9d2;
  --est-muted:       #8b929e;
  --est-placeholder: #5e6571;

  /* ---------- Status ---------- */
  --est-accent:       var(--est-es-blue);
  --est-accent-ink:   #ffffff;
  --est-accent-hover: #2bb3ec;
  --est-warn-bg:      #2a2310;
  --est-warn-line:    #5a4c20;
  --est-warn-ink:     #e8c97a;
  --est-ok-bg:        #122a23;
  --est-ok-line:      #2d6655;
  --est-ok-ink:       #76d3a8;
  --est-err:          #ff7a7a;
  --est-err-bg:       #2a1414;
  --est-err-line:     #5a2424;

  /* ---------- Shape ---------- */
  --est-radius:    8px;
  --est-radius-lg: 12px;
  --est-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
  --est-shadow-md: 0 4px 16px rgba(0, 0, 0, 0.45);
}

:root[data-theme="light"] {
  /* Light-theme overrides flip native widgets back to their light
     palette. Pairs with the :root color-scheme: dark default above. */
  color-scheme: light;

  --est-bg:          #f4f6f9;
  --est-bg-elev:     #ffffff;
  --est-surface:     #ffffff;
  --est-surface-2:   #eef1f5;
  --est-line:        #d6dbe2;
  --est-line-strong: #b3bac4;

  --est-ink:         #1a1d22;
  --est-ink-soft:    #3e4550;
  --est-muted:       #6a727f;
  --est-placeholder: #9aa1ac;

  --est-accent-ink:   #ffffff;
  --est-warn-bg:      #fdf3dd;
  --est-warn-line:    #d4ba6d;
  --est-warn-ink:     #7a5817;
  --est-ok-bg:        #dff3e8;
  --est-ok-line:      #8cc7a9;
  --est-ok-ink:       #166441;
  --est-err:          #c43030;
  --est-err-bg:       #fce3e3;
  --est-err-line:     #d59c9c;

  --est-shadow-sm: 0 1px 2px rgba(20, 26, 35, 0.06);
  --est-shadow-md: 0 4px 14px rgba(20, 26, 35, 0.10);
}

/* Cross-app form-control normalisation. Applied to any <input>,
   <select>, <textarea> that's a child of a topbar-equipped page (i.e.
   anything in our suite). Each app may still override locally, but
   the default is the consistent BQE-flavour focus ring + the
   --est-accent tint on checkboxes/radios. This is what the audit
   flagged as the most visible drift between BQE (custom focus glow)
   and gridview (browser default outline). */
:where(body) input:not([type='checkbox']):not([type='radio']):not([type='file']):not([type='range']):focus,
:where(body) select:focus,
:where(body) textarea:focus {
  outline: none;
  border-color: var(--est-line-focus);
  box-shadow: 0 0 0 3px rgba(30, 167, 224, 0.18);
}
:where(body) input[type='checkbox'],
:where(body) input[type='radio'] {
  accent-color: var(--est-accent);
}
/* :focus-visible (vs :focus) so the focus ring only appears on
   keyboard nav, not on every click. Both included so older browsers
   without :focus-visible still get the ring. */
:where(body) input:focus-visible,
:where(body) select:focus-visible,
:where(body) textarea:focus-visible {
  outline: 2px solid var(--est-accent);
  outline-offset: 0;
}

/* Reserve the topbar's eventual height on the empty mount container
   so below-the-fold content doesn't visibly shift down when
   mountTopbar() injects the <header class="est-topbar"> on first
   paint. 80px matches the .est-topbar height set below (a typical
   app-header dimension that comfortably fits the 56px primary brand
   logo). Both #es-topbar (the consumer's placeholder div) and the
   mounted header get it so the height matches either way. PR #9
   stripped the Pending Approvals / Connected / Sign-out widgets, so
   the topbar no longer needs the old 128px envelope that fit a 96px
   brand logo + the stacked right-column pills. */
#es-topbar,
.est-topbar {
  min-height: 80px;
  box-sizing: border-box;
}

.est-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  /* Fixed height + horizontal padding owned by the topbar itself.
     The height pins the chrome to a typical app-header dimension so
     the brand logos (constrained below) can't grow it. The tab bar
     below sits flush — no margin-bottom — with the topbar's
     border-bottom serving as the divider. */
  height: 80px;
  padding: 0 2rem;
  border-bottom: 1px solid var(--est-line);
  font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  color: var(--est-ink);
}
.est-topbar-left {
  display: flex;
  align-items: center;
  gap: 1.25rem;
}
.est-topbar-right {
  /* 2x3 grid (was 2x2 — added an `extras` column on the left).
     The grid guarantees the theme toggle aligns with the Pending
     Approvals pill row-by-row and Sign out aligns with the Connected
     pill — so the smaller right-column items get vertically centred
     against their row partner instead of floating between rows.

     The leftmost `extras` column is reserved for app-specific
     content portaled in by the consuming app (BQE uses it for the
     developer build-stamp + admin view-as toggle, stacked
     vertically). The column is `auto`-sized, so it collapses to 0
     width when empty — sibling apps that don't fill it look
     identical to before this column was added.

     Row heights auto-size to the taller item in each row; column
     widths to the wider. `align-items: center` vertically centres
     shorter items within their row, `justify-self` per-cell controls
     left/right pinning so the pills' left edges line up vertically
     and the right-column items pin to the right. */
  display: grid;
  grid-template-columns: auto auto auto;
  grid-template-areas:
    "extras pending   theme"
    "extras connected signout";
  column-gap: 1.75rem;
  row-gap: 0.55rem;
  align-items: center;
}
.est-topbar-cell-extras    {
  grid-area: extras;
  /* Spans both rows. Empty by default — apps portal their own
     content in. The cell's children stack vertically; the cell
     collapses to 0 width when no children are present. */
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  align-items: stretch;
  justify-self: stretch;
  align-self: stretch;
  justify-content: center;
}
.est-topbar-cell-pending   { grid-area: pending;   justify-self: stretch; }
/* The theme toggle (~46 px wide) and the sign-out pill (~90 px) sit
   in column 2. With `justify-self: end` on both, each was pinned to
   the right edge of the column — but because their widths differ,
   their visual centres ended up at different x-positions. Switching
   to `justify-self: center` lines their centres up on the same
   vertical axis (the centre of column 2's natural width = the wider
   of the two children). */
.est-topbar-cell-theme     { grid-area: theme;     justify-self: center; }
.est-topbar-cell-connected { grid-area: connected; justify-self: stretch; }
.est-topbar-cell-signout   { grid-area: signout;   justify-self: center; }

.est-brand {
  display: flex;
  align-items: center;
  /* Spacing math — divider sits visually equidistant from both
     logos' VISIBLE content (accounting for transparent whitespace
     baked into each PNG).
     Measured (rendered at the heights below):
       ES logo right whitespace:        6.74 px
       Hitachi logo left whitespace:    0    px
     With `gap: 0.83rem` and `padding-left: 1.25rem` on the
     secondary, both sides have a ~20 px visible gap to the divider:
       ES right edge → divider: 0.83rem (13.3px) + 6.74 = 20.04 px
       Divider → Hitachi edge:  1.25rem (20.00px) + 0    = 20.00 px
     If a future secondary logo has DIFFERENT leading whitespace
     than the Hitachi PNG, the padding-left will need to be
     recomputed to keep the divider visually balanced.
     A previous version of this rule used padding-left: 1.75rem,
     which made the Hitachi side ~8 px further from the divider
     than the ES side — visually off. 1.25rem is the calibrated
     value. */
  gap: 0.83rem;
  color: var(--est-ink);
  text-decoration: none;
}
.est-brand .est-brand-logo {
  height: 56px;
  width: auto;
  display: block;
}
.est-brand .est-brand-secondary {
  height: 48px;
  width: auto;
  display: block;
  padding-left: 1.25rem;  /* see math in .est-brand comment above */
  border-left: 1px solid var(--est-line);
}

/* Build-SHA badge — the "deploy is this commit" tag near the brand.
   Renders as a dim monospace pill so it's findable when you go
   looking but doesn't compete with the brand visually. Read-only when
   it's a <span>, click-through to GitHub when the full SHA is known
   (rendered as an <a>). When no SHA is available at deploy time the
   element isn't emitted at all — there's no empty box. */
.es-topbar-build {
  /* `position: relative` anchors the absolutely-positioned popover
     below to the badge's bounding box. */
  position: relative;
  display: inline-flex;
  align-items: center;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11px;
  color: var(--est-muted);
  padding: 2px 6px;
  border: 1px solid var(--est-line);
  border-radius: 3px;
  letter-spacing: 0.5px;
  opacity: 0.7;
  text-decoration: none;
  /* The brand uses 0.83rem gap to the secondary logo (see math
     above). The badge sits past the secondary, so a slightly larger
     gap reads as a visual separator from the brand block rather than
     a third logo. */
  margin-left: 0.75rem;
  white-space: nowrap;
  transition: opacity 0.12s ease, border-color 0.12s ease;
}
a.es-topbar-build:hover {
  opacity: 1;
  border-color: var(--est-line-strong);
  color: var(--est-ink-soft);
}
a.es-topbar-build:focus-visible {
  outline: 2px solid var(--est-accent);
  outline-offset: 1px;
  opacity: 1;
}

/* Inner spans: SHA + thin separator + deploy timestamp. The SHA span
   keeps the badge's monospace + size (inherited from .es-topbar-build).
   The separator is a dim mid-dot; the time span sits flush against it
   so the badge reads as a single composed pill rather than two badges.
   When the time source is absent (local dev, missing CF_VERSION_METADATA
   timestamp), only the .es-topbar-build-sha span is emitted — the
   separator and time spans don't render, so the badge collapses back
   to its SHA-only shape with no broken separator. */
.es-topbar-build-sep {
  margin: 0 4px;
  opacity: 0.5;
}
.es-topbar-build-time {
  /* same monospace + size as the SHA — inherits from .es-topbar-build */
}

/* Rich hover popover — emitted alongside the inline SHA/time display
   when a raw ISO-8601 deploy timestamp is available. Line 1 is "Build
   <sha>" in slightly bolder type; line 2 is "<relative> · <full
   localized>" in the muted color. Replaces the plain `title=` tooltip
   so users see relative deploy age ("6 mins ago") at a glance — much
   more legible than parsing a YYYY-MM-DD HH:MM string.

   Positioned absolutely below + right-aligned with the badge so it
   doesn't run off the right edge of the topbar (the badge sits near
   the brand on the left, but the popover is wider than the badge). On
   narrow viewports the badge itself is close to the left edge, so the
   right-alignment still keeps the popover on-screen. CSS-only show/
   hide — `:hover` / `:focus-within` on the parent badge toggle opacity
   + pointer-events. The 100ms transition softens the appear/disappear
   without introducing a perceptible delay. */
.es-topbar-build-popover {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 100;
  background: var(--est-surface-2, #1a2130);
  border: 1px solid var(--est-line-strong, rgba(255, 255, 255, 0.14));
  border-radius: 4px;
  padding: 10px 14px;
  min-width: 240px;
  white-space: nowrap;
  /* Reset the badge's monospace + tracking — the popover is a
     prose-style tooltip, not part of the inline pill. */
  font-family: -apple-system, "Segoe UI", system-ui, sans-serif;
  font-size: 12px;
  letter-spacing: normal;
  color: var(--est-ink);
  opacity: 0;
  pointer-events: none;
  transition: opacity 100ms ease;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
  /* Reset the parent badge's `opacity: 0.7` — the popover should
     paint at full opacity on hover. */
  text-align: left;
}
.es-topbar-build:hover .es-topbar-build-popover,
.es-topbar-build:focus-within .es-topbar-build-popover {
  opacity: 1;
  pointer-events: auto;
}
.es-topbar-build-popover-line1 {
  font-size: 13px;
  margin-bottom: 4px;
}
.es-topbar-build-popover-line1 strong {
  /* Monospace SHA inside the otherwise-prose line, matching the inline
     badge's font choice so the SHA reads as the same identifier across
     the badge body and the popover heading. */
  font-family: ui-monospace, Menlo, Consolas, monospace;
}
.es-topbar-build-popover-line2 {
  color: var(--est-muted, #8a93a3);
  font-size: 12px;
}
.es-topbar-build-popover-sep {
  margin: 0 6px;
  opacity: 0.5;
}

/* Dashboards switcher: hamburger trigger + dropdown popup. */
.est-dashboards {
  position: relative;
}
.est-dashboards-trigger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid var(--est-line);
  border-radius: 10px;
  background: var(--est-bg-elev);
  color: var(--est-ink-soft);
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}
.est-dashboards-trigger:hover {
  background: var(--est-surface-2);
  color: var(--est-ink);
  border-color: var(--est-line-strong);
}
.est-dashboards-trigger[aria-expanded="true"] {
  border-color: var(--est-es-blue);
  color: var(--est-es-blue);
  background: rgba(30, 167, 224, 0.10);
}
.est-dashboards-popup {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  min-width: 320px;
  background: var(--est-surface-2);
  border: 1px solid var(--est-line-strong);
  border-radius: 10px;
  box-shadow: var(--est-shadow-md);
  padding: 0.4rem;
  z-index: 30;
}
.est-dashboards-popup-heading {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--est-muted);
  padding: 0.5rem 0.6rem 0.4rem;
}
.est-dashboards-item {
  display: block;
  padding: 0.55rem 0.7rem;
  border-radius: 6px;
  color: var(--est-ink);
  text-decoration: none;
  transition: background 0.12s ease;
}
.est-dashboards-item:hover { background: rgba(30, 167, 224, 0.12); }
.est-dashboards-item.active {
  background: rgba(30, 167, 224, 0.10);
  cursor: default;
}
.est-dashboards-item-label {
  font-weight: 600;
  font-size: 0.92rem;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.est-dashboards-item-current {
  font-size: 0.72rem;
  font-weight: 500;
  color: var(--est-es-blue);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.est-dashboards-item-desc {
  font-size: 0.82rem;
  color: var(--est-muted);
  margin-top: 2px;
}

/* Pending-approvals pill — clickable, links to the BQE Approvals page.
   Gold when there's work, green when caught up, hidden when signed out.
   Content centered (align-items + text-align) because the parent
   topbar grid stretches the pill wider than its intrinsic content
   width — left-aligned text then sits flush-left with empty space
   on the right, which reads as off-centered. */
.est-pending {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 0.1rem;
  padding: 0.4rem 0.95rem;
  background: var(--est-warn-bg);
  border: 1px solid var(--est-warn-line);
  color: var(--est-warn-ink);
  border-radius: 10px;
  font: inherit;
  font-size: 0.85rem;
  font-weight: 500;
  line-height: 1.25;
  text-align: center;
  cursor: pointer;
  white-space: nowrap;
  text-decoration: none;
  transition: filter 0.15s ease, background 0.15s ease;
}
.est-pending:hover { filter: brightness(1.1); }
/* Skeleton state — pill chrome is visible from frame 1 while
   /api/approvals/projects is in flight. Dimmed so it reads as
   placeholder rather than real data. Class removed once render()
   has applied the resolved counts. */
.est-pending-loading { opacity: 0.55; pointer-events: none; }
.est-pending-label {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  opacity: 0.8;
  margin-bottom: 0.15rem;
}
.est-pending-line { font-weight: 500; }
.est-pending-line strong {
  font-weight: 700;
  color: inherit;
  margin-right: 0.3rem;
}
.est-pending.caught-up {
  background: var(--est-ok-bg);
  border-color: var(--est-ok-line);
  color: var(--est-ok-ink);
}

/* "Connected to BQE Core" pill — small green badge with a dot.
   Unified with .est-pending above: same border-radius, padding,
   and font-size so the two pills read as visual siblings in the
   right-side topbar chrome. The colour (green vs gold) is the
   only difference, which lets the user read each pill's STATE
   at a glance without re-parsing its SHAPE. */
.est-topbar-indicator {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.4rem 0.95rem;
  background: var(--est-ok-bg);
  border: 1px solid var(--est-ok-line);
  color: var(--est-ok-ink);
  border-radius: 10px;
  font-size: 0.85rem;
  font-weight: 500;
  white-space: nowrap;
  line-height: 1.25;
}
.est-topbar-indicator-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--est-ok-ink);
  box-shadow: 0 0 0 3px rgba(118, 211, 168, 0.18);
  flex-shrink: 0;
}

/* Sign out button — styled as a pill to match the Connected
   indicator visually. The previous link-styled treatment looked
   out of place sitting next to the indicator pill above it; pill
   shape makes them read as siblings in the right-side chrome. */
.est-topbar-signout {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.3rem 0.95rem;
  background: transparent;
  border: 1px solid var(--est-line);
  border-radius: 999px;
  color: var(--est-accent);
  cursor: pointer;
  font: inherit;
  font-size: 0.82rem;
  font-weight: 500;
  white-space: nowrap;
  transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}
.est-topbar-signout:hover {
  color: var(--est-accent-hover);
  border-color: var(--est-line-strong);
  background: var(--est-bg-elev);
}
.est-topbar-signout:focus-visible {
  outline: 2px solid var(--est-accent);
  outline-offset: 1px;
}

/* The .est-topbar-stack flex-column rules used to live here.
   Replaced by the 2x2 grid on .est-topbar-right (above). Items
   are now direct children of .est-topbar-right, each assigned a
   grid-area via .est-topbar-cell-<name>. */

/* Sliding theme toggle. Replaces the old icon-only button — the
   slider's left/right state makes "this is a binary mode switch"
   self-evident in a way a single glyph couldn't. The sun + moon
   icons inside the track act as orientation hints at low opacity,
   not interactive elements. */
.est-theme-toggle {
  position: relative;
  display: inline-block;
  cursor: pointer;
  user-select: none;
  /* Visually align with the sign-out link below in the stack. */
  margin: 0;
}
.est-theme-toggle input {
  position: absolute;
  inset: 0;
  margin: 0;
  opacity: 0;
  cursor: pointer;
  z-index: 1;
}
.est-theme-toggle-track {
  position: relative;
  display: inline-block;
  width: 46px;
  height: 22px;
  border-radius: 999px;
  background: var(--est-surface-2);
  border: 1px solid var(--est-line);
  transition: background 0.18s ease, border-color 0.18s ease;
  vertical-align: middle;
}
.est-theme-toggle:hover .est-theme-toggle-track {
  border-color: var(--est-line-strong);
}
.est-theme-toggle input:focus-visible + .est-theme-toggle-track {
  outline: 2px solid var(--est-accent);
  outline-offset: 2px;
}
.est-theme-toggle-knob {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--est-ink);
  transition: transform 0.18s ease, background 0.18s ease;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
}
.est-theme-toggle--dark .est-theme-toggle-knob {
  transform: translateX(22px);
}
.est-theme-toggle-icon {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  font-family: "Segoe UI Symbol", "Apple Symbols", "Noto Sans Symbols 2",
               "Segoe UI Emoji", "Apple Color Emoji", system-ui, sans-serif;
  font-size: 11px;
  line-height: 1;
  opacity: 0.55;
  color: var(--est-ink-soft);
  pointer-events: none;
}
.est-theme-toggle-icon-sun  { left: 5px; }
.est-theme-toggle-icon-moon { right: 5px; }

/* Tools tab bar (sibling-tool navigation, horizontal). Sits directly
   under the shared topbar on every page in the GridView suite so
   users can hop between the validator and database compare tools.
   Replaced the older left-rail .est-sidebar layout — those rules are
   kept below for backward compat but no app mounts them anymore.

   Visual treatment: "folder tab" — tabs sit in a discrete left-aligned
   card-group (.es-tools-tabs-group) with rounded top corners. The
   active tab's background matches the page background and its
   bottom border merges into the content panel below, so the strip
   reads as a folder tab opening into the page content rather than an
   edge-to-edge horizontal bar. The outer .es-tools-tabbar is a
   transparent strip that just owns padding + the divider line below
   the tab group. */
.es-tools-tabbar {
  display: flex;
  align-items: flex-end;
  background: var(--est-bg);            /* match page background, NOT panel */
  border-bottom: 1px solid var(--est-line);
  padding: 14px 24px 0;                 /* breathing room above + sides */
  font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  /* No fixed height — the tabs themselves determine height. */
}
.es-tools-tabs-group {
  display: inline-flex;
  background: var(--est-surface);
  border: 1px solid var(--est-line);
  border-radius: 6px 6px 0 0;
  border-bottom: 0;                     /* active tab's "open bottom" connects to content */
}
.es-tools-tab {
  display: flex;
  align-items: center;
  padding: 10px 18px;
  font-size: 13px;
  font-weight: 500;
  color: var(--est-muted);
  text-decoration: none;
  cursor: pointer;
  border-right: 1px solid var(--est-line);
  transition: color 0.12s ease, background 0.12s ease;
}
.es-tools-tab:last-child { border-right: none; }
.es-tools-tab:hover {
  color: var(--est-ink);
  background: var(--est-surface-2);
}
.es-tools-tab:focus-visible {
  outline: 2px solid var(--est-accent);
  outline-offset: -2px;
}
.es-tools-tab-active {
  color: var(--est-es-blue);
  background: var(--est-bg);            /* same as page bg — tab "opens into" content */
  font-weight: 600;
  /* Overlap the .es-tools-tabbar's border-bottom so the active tab
     reads as continuous with the content below — no visible line
     slicing through it. */
  margin-bottom: -1px;
  cursor: default;
}
.es-tools-tab-active:hover {
  background: var(--est-bg);
  color: var(--est-es-blue);
}

/* Sidebar (sibling-tool navigation). Rendered on every page in the
   GridView suite so users can hop between the validator and database
   compare tools without going up to the hamburger menu. Visual
   language mirrors the dashboards popup.
   NOTE: replaced by .es-tools-tabbar above. These rules are retained
   for backward compat in case a downstream consumer still mounts
   .est-sidebar; no GridView app does. */
.est-sidebar {
  width: 240px;
  flex: 0 0 240px;
  /* Outer panel is now transparent — the cards inside ARE the
     visible structure. Keeps the sidebar from being a "card in a
     card" when sections become cards themselves (Option A from the
     style picker). Width + padding stay so the layout reserves the
     same horizontal slot. */
  background: transparent;
  border: 0;
  border-radius: 0;
  padding: 4px 0;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  color: var(--est-ink);
}
/* Each section is its own card with a tinted background + 1px
   border + rounded corners. The vertical gap on .est-sidebar (above)
   spaces them — no inter-section divider rule needed. Single-section
   sidebars (gridview) render as one card, multi-
   section sidebars (BQE) as a stack of cards. Same component, both
   layouts consistent.

   Background switched from --est-surface-2 to --est-surface so the
   section cards visually match the form .card surface in BQE
   (2026-05 form refresh). Headings adopt the same caption style
   used for form labels: tiny, uppercase, muted, no decorations. */
.est-sidebar-section {
  background: var(--est-surface);
  border: 1px solid var(--est-line);
  border-radius: 8px;
  padding: 0.6rem 0.55rem;
}
.est-sidebar-heading {
  margin: 4px 6px 8px;
  padding: 0 0 0 6px;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--est-muted);
  line-height: 1.2;
}
.est-sidebar-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.est-sidebar-item {
  display: block;
  width: 100%;
  text-align: left;
  text-decoration: none;
  padding: 9px 12px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  /* Soft ink so inactive nav items don't compete for attention
     against the muted form aesthetic (full --est-ink read as
     visually loud). Hover bumps back to full ink for tactile
     feedback that the row is interactive. */
  color: var(--est-ink-soft);
  cursor: pointer;
  font: inherit;
  font-size: 0.86rem;
  position: relative;
  transition: background 0.12s ease, color 0.12s ease;
}
.est-sidebar-item:hover {
  background: rgba(30, 167, 224, 0.10);
  color: var(--est-ink);
}
.est-sidebar-item:focus-visible {
  outline: 2px solid var(--est-accent);
  outline-offset: 1px;
}
/* Active item: subtle accent tint + accent text + 3px left bar.
   Replaces the previous "solid blue bg + white text" treatment
   which read as visually loud against the muted form aesthetic
   added in the 2026-05 form refresh. */
.est-sidebar-item.active {
  background: rgba(30, 167, 224, 0.10);
  color: var(--est-es-blue);
  font-weight: 600;
  cursor: default;
}
.est-sidebar-item.active::before {
  content: '';
  position: absolute;
  left: 0;
  top: 5px;
  bottom: 5px;
  width: 3px;
  border-radius: 2px;
  background: var(--est-es-blue);
}
.est-sidebar-item.active:hover { background: rgba(30, 167, 224, 0.14); }

/* Helper layout: a flex row that places the sidebar to the left of the
   main content. Consumers can opt in by wrapping their page body in
   <div class="est-chrome-body"> and putting the mounted sidebar +
   their main content inside it. Sized so the topbar + this row fill
   the viewport with no body scroll. 126px = 80px topbar + ~46px
   folder-tab nav (14px top padding + 32px tab height including 1px
   border overlap). The folder-tab strip has no fixed height — this is
   a defensive reservation that matches the typical rendered height
   so the page below doesn't visibly shift on hydration. */
.est-chrome-body {
  display: flex;
  gap: 14px;
  padding: 0 18px 18px;
  height: calc(100vh - 126px);
  min-height: 0;
}
.est-chrome-content {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  overflow: auto;
}

/* -------------------------------------------------------------------
 * Shared panel surface
 * -------------------------------------------------------------------
 * Standard "card" container used as the top-level content surface in
 * every dashboard: BQE's section panels, gridview's Inputs card, etc.
 * Apps used to roll their own panel CSS with slightly different
 * padding, headings, and borders — that drift is what made the
 * dashboards look like different products.
 *
 * Use:
 *   <section class="es-panel">
 *     <h2 class="es-panel-heading">Projects</h2>
 *     ...content...
 *   </section>
 *
 * Apps can still scope additional overrides to their own children
 * (e.g. flex-direction, max-width), but should not redefine the
 * background / border / shadow — those belong here so the look stays
 * coherent across the suite.
 */
.es-panel {
  background: var(--est-surface);
  border: 1px solid var(--est-line);
  border-radius: var(--est-radius);
  box-shadow: var(--est-shadow-sm);
  padding: 18px 20px;
  margin-bottom: 16px;
}

.es-panel-heading {
  margin: 0 0 14px 0;
  font-size: 13px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--est-muted);
}

/* In-page page-title pair. Mirrors BQE's .form-header h2 + .muted .small
   pattern (1.15rem / 600 weight title with a 0.85rem muted lede 2px
   below) so the "Create project" wizard header and the gridview page
   headers share one visual hierarchy across the whole suite.

   The title sits inline at the top of the page content — no panel-
   tinted strip, no card around it. The folder-tab nav above already
   provides the visual hand-off from chrome to content. */
.es-page-title {
  margin: 0 0 4px 0;
  font-size: 1.15rem;
  font-weight: 600;
  color: var(--est-ink);
}
.es-page-lede {
  margin: 2px 0 0 0;
  color: var(--est-muted);
  font-size: 0.85rem;
}
