/* ============================================================
   360eDash — the glass layer (app pages: shell chrome only)
   ------------------------------------------------------------
   WHAT THIS FILE IS. A GROUND and a surface material. The ground
   (section 0) is the landing hero, pre-blurred offline, pinned
   behind the whole app shell. The material is what the chrome is
   made of as it floats over that ground.

   Ported in spirit (not in DOM) from the edashboard console's
   console/public/glass.css and the edash-glass-ui rulebook.

   WHY THE GROUND HAD TO COME FIRST. An earlier pass shipped the
   material tokens and then correctly observed that this shell
   never overlaps itself: .edash-layout is `display:flex;
   height:100dvh; overflow:hidden`, .edash-main is a flex column,
   and ONLY .edash-content scrolls. The header is a fixed-height
   flex item ABOVE that scroller and the desktop sidebar is a flex
   item BESIDE it, so nothing passed behind either one and their
   backdrop was a flat fill. Technically right, visually nothing:
   a translucent surface over one solid colour is just a different
   solid colour, and a blur of it is a blurred picture of a solid
   colour. The fix is not more blur. It is to put something behind
   the chrome worth seeing. That is section 0.

   WHERE THE PHOTOGRAPH IS ACTUALLY VISIBLE. Exactly the two bands
   that have the fixed ground and nothing else behind them:
     .edash-header          tinted teal   — light .84 / dark .80
     .edash-sidebar >=901px tinted column — light .88 / dark .82
   .edash-content stays OPAQUE on purpose (see section 0), so the
   ground never reaches the page text. Everything else — the
   drawer, the popovers, the dialog — floats over .edash-content,
   which means the thing behind them is page content, not the
   photo, and those keep the treatment they already had.

   BLUR IS NEVER RUN AT RUNTIME ON THE GROUND. The image ships
   pre-blurred at 480x270 / 2,466 bytes and is upscaled by
   `background-size: cover`. A filter:blur(40px) on the 4211x2369
   original would be a per-frame cost on a phone for a picture
   that is blurry either way.

   WHERE BACKDROP BLUR IS SPENT (2 surfaces in the whole app):
     .hd-profile-menu   thick material, blur 24 — floats over
                        .edash-content, does not scroll itself
     .lg-modal          thick material, blur 24 — floats over the
                        scrim, does not scroll itself
   Blurred layers per view: 0 at rest on every page. 1 with the
   profile menu open. 1 with the logout dialog raised (the menu
   closes first). Worst case anywhere is 1. Budget is 3. The
   ground is a still image with no filter and does not count.

   WHERE BACKDROP BLUR IS REFUSED, and why:
     .edash-header          its backdrop is a still, already
                            blurred image — blurring it again is
                            a compositor layer for no pixels
     .edash-sidebar desktop same
     .edash-sidebar mobile  it scrolls its own content (.sb-scroll)
     .hd-notif-panel        it scrolls its own content (.hd-notif-list)
     .edash-toast           on every page, and it is signal
     .lg-overlay            a scrim dims, it never blurs — a
                            full-viewport blur is the single most
                            expensive effect on a phone
     any card holding a chart, a table or a map canvas — never.

   LEGIBILITY BEATS THE EFFECT, EVERY TIME. Every tint above was
   chosen by measuring the shipped ground_blur.webp's darkest and
   brightest pixel, compositing the tint over each, and checking
   the text that actually sits on it. The arithmetic is written
   out at each surface. Nothing here is below 4.5:1 that was not
   already below 4.5:1 before this file existed, and one label
   (.sb-nav-title, 3.54:1 on paper) is lifted above AA because
   the tint would otherwise have pushed it further down.

   MOTION. This file adds no animation at all. Nothing here
   animates height, width, padding, margin or an offset, and
   nothing animates a filter. The two blurred surfaces are
   animated by their own files on transform and opacity only,
   which is the only pair allowed on a backdrop-filtered element.
   The rail's pre-existing `transition: width .25s` (sidebar.css)
   is left alone: the rail is a TINT, not a backdrop-filtered
   element, so an animated width re-composites but never re-runs
   a blur. If a backdrop-filter is ever added to the rail, that
   transition has to go in the same commit.

   GATES. Five of them, and every one works by REDEFINING THE
   TOKENS at :root — see the bottom of this file. No component
   selector appears inside any gate definition. That is
   deliberate: the scattered per-component version is how a gate
   gets missed the next time a surface is added.

   SPECIFICITY. This file is @imported at the top of
   css/global.css (index.html is not ours to edit), so it is the
   first stylesheet in the cascade. Every rule therefore carries
   `html body` to beat master/*/​*.css, and a
   `html[data-theme="dark"] body` twin to beat
   css/dark-mode-v8.css, which loads last.
============================================================= */

/* ---------- tokens ---------- */
:root {
  /* The two materials this shell needs. Thicker carries text and
     fine features; the design's 10–10.5px uppercase labels are
     fine features, so every text-bearing surface here is thick. */
  --mat-thick: rgba(255, 255, 255, .94);
  --mat-regular: rgba(255, 255, 255, .90);
  /* The drawer's own thick material. Same thickness, but tinted
     with the sidebar's ground rather than the card ground,
     because dark-mode-v8.css gives the sidebar #122227 while
     every other dark surface is #18272C. Separate token so the
     drawer stays the sidebar's colour, not a card's. */
  --mat-nav: rgba(255, 255, 255, .94);

  /* The blur+saturate pair as ONE token, so a gate switches it to
     `none` in one place and every surface follows. */
  --mat-filter-thick: blur(24px) saturate(1.4);
  --mat-filter-regular: blur(18px) saturate(1.4);

  /* The opaque ground every gate falls back to. Matches
     --surface in css/global.css and the sidebar's own white. */
  --glass-paper: #FFFFFF;
  --glass-paper-nav: #FFFFFF;

  /* The dimming layer behind a dialog and behind the off-canvas
     drawer. These are the exact values logout-modal.css and
     sidebar.css already ship — lifted onto tokens, not changed,
     so the gates can reach them. */
  --mat-scrim: rgba(18, 30, 34, .50);
  --mat-scrim-nav: rgba(11, 42, 45, .42);

  /* One edge, one specular highlight. --mat-hi is for a material
     over the paper; --mat-hi-deep is the dimmer rim a highlight
     takes on a dark ground (the teal header) — a highlight on
     dark is dimmer, never brighter. */
  --glass-edge: rgba(37, 52, 63, .14);
  --mat-hi: rgba(255, 255, 255, .92);
  --mat-hi-deep: rgba(255, 255, 255, .22);

  /* One shadow scale, on the teal the design already casts.
     1 a surface at rest, 2 a bar, 3 a popover or a dialog. */
  --mat-shadow-1: 0 1px 2px rgba(11, 79, 84, .06), 0 10px 28px -8px rgba(11, 79, 84, .16);
  --mat-shadow-2: 0 4px 16px rgba(11, 79, 84, .14);
  --mat-shadow-3: 0 2px 6px rgba(11, 79, 84, .10), 0 24px 48px -12px rgba(11, 79, 84, .32);

  /* ---------- THE GROUND ----------
     The landing hero, pre-blurred OFFLINE to 480x270 and shipped at
     2,466 bytes (assets/ground-blur.webp). It is never blurred at
     runtime: a filter:blur() on the 4211x2369 original would be a
     per-frame cost on a phone, and the picture is going to be blurry
     either way, so the blur is baked in and the tiny file is simply
     scaled back up by `background-size: cover`.

     --ground-wash is the per-theme veil that sets how much of the
     photo survives. It is the ONE number that decides every contrast
     figure below, which is why it is a token and not a literal.
     Light: 62% paper over the photo, composite sRGB 169..235.
     Dark:  55% page over the photo, composite sRGB  30..114. */
  --ground-image: url("../assets/ground-blur.webp");
  --ground-base: #F4F7F7;
  --ground-wash: rgba(244, 247, 247, .62);

  /* The two surfaces that genuinely sit ON the ground, and therefore
     the only two that are allowed to be translucent enough to show a
     photograph. Both are tints, NOT backdrop filters — the ground is
     already blurred, so re-blurring it would buy a compositor layer
     and change nothing you can see. */
  --mat-bar: rgba(15, 106, 113, .84);   /* the header, over the ground */
  --mat-rail: rgba(255, 255, 255, .88); /* the desktop sidebar column */
  --mat-bar-solid: #0F6A71;
  --mat-rail-solid: #FFFFFF;

  /* The reading ground. .edash-content is opaque in BOTH themes and
     in every state: pages put bare 12px muted text straight on it
     (.section-heading p in css/project-overview.css is #80939a), and
     that text cannot afford a photograph underneath. Legibility wins;
     the ground shows through the chrome instead. */
  --glass-paper-page: #F4F7F7;

  /* .sb-nav-title is a 10.5px uppercase label sitting bare on the
     rail. At the shipped #7C8B8D it is 3.54:1 on solid white — already
     under AA — and the rail's tint would take it to 3.25:1. Rule: a
     surface that cannot hold its text gets fixed, not shipped. This
     ink reads 5.46:1 on the tinted rail and 5.94:1 on the opaque
     fallback, so it is better than today in every gate. */
  --mat-nav-title-ink: #55676A;
}

/* Dark sits behind the explicit data-theme the app already uses
   (applySavedTheme() in js/main.js). prefers-color-scheme is not
   used here, exactly as css/dark-mode-v8.css does not use it. */
:root[data-theme="dark"] {
  --mat-thick: rgba(24, 39, 44, .94);      /* #18272C, the dark --surface */
  --mat-regular: rgba(24, 39, 44, .90);
  --mat-nav: rgba(18, 34, 39, .94);        /* #122227, the dark sidebar */
  --glass-paper: #18272C;
  --glass-paper-nav: #122227;
  --mat-scrim: rgba(0, 0, 0, .55);
  --mat-scrim-nav: rgba(0, 0, 0, .50);
  --glass-edge: rgba(231, 239, 240, .18);
  --mat-hi: rgba(255, 255, 255, .16);
  --mat-hi-deep: rgba(255, 255, 255, .14);
  --mat-shadow-1: 0 1px 2px rgba(0, 0, 0, .25), 0 10px 28px -8px rgba(0, 0, 0, .45);
  --mat-shadow-2: 0 4px 16px rgba(0, 0, 0, .35);
  --mat-shadow-3: 0 2px 6px rgba(0, 0, 0, .30), 0 24px 48px -12px rgba(0, 0, 0, .60);

  /* Same photo, a darker veil. Dark can afford MORE photo than light
     can: the ground composite tops out at L=0.093, so white header
     text actually gains contrast over the flat teal it has today
     (7.60:1 vs 6.32:1), and the rail's own #B8CACC body text only
     falls from 9.63:1 to 8.51:1. */
  --ground-base: #101B1F;
  --ground-wash: rgba(16, 27, 31, .55);
  --mat-bar: rgba(15, 106, 113, .80);
  --mat-rail: rgba(18, 34, 39, .82);   /* #122227, the dark sidebar */
  --mat-bar-solid: #0F6A71;            /* dark-mode-v8.css leaves --hd-bg teal */
  --mat-rail-solid: #122227;
  --glass-paper-page: #101B1F;
  /* Dark's own muted (#9AB0B4) reads 6.37:1 on the tinted rail, so
     the label needs no lift here — it keeps the theme's own ink. */
  --mat-nav-title-ink: #9AB0B4;
}

/* ============================================================
   0. THE GROUND — the blurred hero behind the whole app shell.
   ------------------------------------------------------------
   This is the piece that was missing. Without it the chrome had
   nothing to float over, so every translucent surface rendered a
   picture of one flat fill and the effect was invisible.

   WHERE IT LIVES. index.html is not ours to edit, so the ground
   is a pseudo-element on .edash-layout — the shell's own root.
   That scopes it: pages/battery-station.html,
   pages/solar-calculator.html and pages/privacy-policy.html also
   link css/global.css but have no .edash-layout, so they are
   untouched and keep their flat page colour. A body::before
   would have painted a photograph behind those pages' bare text.

   WHY position:fixed AND NOT background-attachment:fixed. The
   pseudo-element is pinned to the viewport itself, so it never
   moves and never repaints while .edash-content scrolls.
   `background-attachment: fixed` would have produced the same
   picture by re-rasterising the background on every scroll frame,
   which is the single worst thing you can do to a phone.

   .edash-layout is display:flex, but an absolutely/fixed
   positioned child of a flex container is NOT a flex item, so
   this adds no column. .edash-layout has overflow:hidden and no
   transform/filter/will-change, so it is not a containing block
   for fixed descendants and does not clip this. z-index:-1 puts
   it behind every in-flow child of the shell, above the canvas.

   COST. Zero runtime filters. One 2,466-byte image, decoded once,
   upscaled by the compositor. It is not a blurred layer and does
   not count against the three-layer budget.
============================================================= */
html body .edash-layout::before,
html[data-theme="dark"] body .edash-layout::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-color: var(--ground-base);
  background-image: linear-gradient(var(--ground-wash), var(--ground-wash)), var(--ground-image);
  background-repeat: no-repeat;
  background-position: center center;
  background-size: cover;
}

/* THE READING GROUND stays opaque, in both themes and on every
   page. .edash-content is the scroller that holds the pages, and
   the pages put bare text directly on it — .project-detail h1,
   .section-heading h2/p, .project-meta — including 12px #80939a,
   which is already only 3.0:1 on paper. A photograph under that
   is not a trade worth making, so the ground is covered here and
   shows through the chrome instead.

   Specificity: index.html's inline <style> has
   `html[data-theme="dark"] .edash-content` (0,2,1) and loads AFTER
   this file, so the dark twin carries `body` to reach (0,2,2).
   .edash-content:fullscreen (css/operator/operator-dashboard.css)
   is (0,2,1) against this rule's (0,1,2) and still wins, which is
   what we want — a fullscreen chart keeps its own ground. */
html body .edash-content,
html[data-theme="dark"] body .edash-content {
  background: var(--glass-paper-page);
}

/* ============================================================
   1. THE HEADER — the ground's teal glass. No runtime blur.
   Now that section 0 exists, the header DOES have something
   behind it: the fixed, pre-blurred hero. So the teal becomes a
   tint at 84% (80% in dark) and the photograph reads through the
   full width of the bar. It keeps the specular top edge and the
   bar shadow that make it a floating material.

   NO backdrop-filter, deliberately. The thing behind it is a
   still, already-blurred image that never moves — blurring it
   again would cost a compositor layer on every page and produce
   a picture nobody can tell apart. The blur budget is spent
   where content genuinely moves behind a surface (sections 5, 6).

   CONTRAST, white header text over the composite:
     light  #0F6A71 @ .84 over ground sRGB 169..235
            -> composite L 0.1457 .. 0.1651 -> 5.37:1 .. 4.88:1
     dark   #0F6A71 @ .80 over ground sRGB  30..114
            -> composite L 0.0881 .. 0.1100 -> 7.60:1 .. 6.56:1
   Flat-teal baseline is 6.32:1. Light loses some and stays above
   4.5:1 at its worst pixel; dark gains. Every control on the bar
   (.hd-menu-toggle, .hd-lang, .hd-profile) keeps its own opaque
   white pill, so no small text is ever on the tint itself.
============================================================= */
html body .edash-header,
html[data-theme="dark"] body .edash-header {
  background: var(--mat-bar);
  box-shadow: inset 0 1px 0 var(--mat-hi-deep), var(--mat-shadow-2);
}

/* ============================================================
   2. THE DESKTOP RAIL — the ground's glass column. No blur.
   Same reasoning as the header: the rail is a full-height band
   with the fixed ground behind it, so it becomes a tint (88%
   white in light, 82% #122227 in dark) and the photograph shows
   in the gutters between the nav pills. The specular rim stays
   on the inner edge and the rail keeps its resting shadow.

   WHAT STAYS OPAQUE, and it is most of the ink: every
   .sb-nav-item and every .sb-btn already paints its own solid
   --sb-surface pill (master/sidebar/sidebar.css), so all the
   13.5px nav labels sit on solid ground, not on the tint. Only
   .sb-nav-title and .sb-greeting are bare on the column.

   CONTRAST on the tinted rail (worst .. best ground pixel):
     light #FFFFFF @ .88 -> composite L 0.9145 .. 0.9554
       --sb-ink #25343F     11.75:1 .. 12.25:1  (opaque 12.79:1)
       .sb-nav-title         5.46:1 ..  5.69:1  at the lifted ink
                             (it was 3.54:1 on paper at #7C8B8D)
     dark  #122227 @ .82 -> composite L 0.0150 .. 0.0226
       body   #B8CACC        8.51:1 ..  9.50:1  (opaque  9.63:1)
       muted  #9AB0B4        6.37:1 ..  7.11:1  (opaque  7.20:1)
       active #E4F1F1       12.50:1 .. 13.95:1  (opaque 14.14:1)
   Every one clears 4.5:1.

   KNOWN, NOT FIXED HERE: .sb-greeting is brand orange #FA891A,
   which is 2.43:1 on solid white today and 2.23:1 on the tint.
   It fails AA either way; opacity is not the lever (it fails at
   100% too) and the only fix is recolouring a brand accent,
   which is not mine to decide. Flagged in the report.
============================================================= */
@media (min-width: 901px) {
  html body .edash-sidebar,
  html[data-theme="dark"] body .edash-sidebar {
    background: var(--mat-rail);
    box-shadow: inset -1px 0 0 var(--mat-hi), var(--mat-shadow-1);
  }
  /* .sb-brand paints its own opaque ground (white in sidebar.css,
     #122227 in css/dark-mode-v8.css). Let the column's tint run
     the full height instead of capping it with a solid block. */
  html body .edash-sidebar .sb-brand,
  html[data-theme="dark"] body .edash-sidebar .sb-brand {
    background: transparent;
  }
}

/* The one bare small label on the rail, lifted so the tint cannot
   push it further under AA. Applies at every width — the drawer
   carries the same label. See --mat-nav-title-ink for the
   arithmetic. The gates do NOT put this back to #7C8B8D: the
   lifted ink is better on the opaque fallback too (5.94:1 vs
   3.54:1), and a gate exists to improve legibility, never to
   undo it. forced-colors hands it to CanvasText, as it must.

   The footer group is EXCLUDED. .sb-nav-group-footer paints its
   own opaque #6DC3BB panel and deliberately runs white titles on
   it (sidebar.css), so the rail's tint never reaches it and a
   dark ink there would be wrong. That panel's own white-on-teal
   contrast is a pre-existing question this file does not touch. */
html body .edash-sidebar .sb-nav-group:not(.sb-nav-group-footer) .sb-nav-title,
html[data-theme="dark"] body .edash-sidebar .sb-nav-group:not(.sb-nav-group-footer) .sb-nav-title {
  color: var(--mat-nav-title-ink);
}

/* ============================================================
   3. THE OFF-CANVAS DRAWER (<=900px) — thick tint, no blur.
   Here the sidebar IS a layer over the page, so it takes the
   material. It also scrolls its own content (.sb-scroll), so by
   the rule it keeps the tint and drops the blur: a blurred
   surface that scrolls repaints the blur on every frame.
============================================================= */
@media (max-width: 900px) {
  html body .edash-sidebar,
  html[data-theme="dark"] body .edash-sidebar {
    background: var(--mat-nav);
    border-right: 1px solid var(--glass-edge);
  }
  html body .edash-sidebar.is-open,
  html[data-theme="dark"] body .edash-sidebar.is-open {
    box-shadow: inset 0 1px 0 var(--mat-hi), var(--mat-shadow-3);
  }
  /* .sb-brand paints its own opaque ground (white in sidebar.css,
     #122227 in dark-mode-v8.css). Let the drawer's material show
     through it so the top of the drawer is one surface, not an
     opaque cap on a translucent column. */
  html body .edash-sidebar .sb-brand,
  html[data-theme="dark"] body .edash-sidebar .sb-brand {
    background: transparent;
  }
}

/* The drawer's scrim: a dim, never a blur. The same colour the
   sidebar already shipped, moved onto the token so the gates
   reach it. */
html body .edash-sidebar-backdrop {
  background: var(--mat-scrim-nav);
}

/* ============================================================
   4. THE NOTIFICATION POPOVER — thick tint, no blur.
   .hd-notif-list is `overflow-y:auto`: the panel scrolls its own
   content, so it is tint only. It takes the material edge, the
   top specular line and the popover shadow. The list rows and
   the panel head keep their own opaque grounds — they are what
   carries the small muted text — so only the panel's own chrome
   is translucent.
============================================================= */
html body .edash-header .hd-notif-panel,
html[data-theme="dark"] body .edash-header .hd-notif-panel {
  background: var(--mat-thick);
  border: 1px solid var(--glass-edge);
  box-shadow: inset 0 1px 0 var(--mat-hi), var(--mat-shadow-3);
}

/* ============================================================
   5. THE PROFILE POPOVER — thick material, BLUR. (1 of 2)
   It hangs below the header over .edash-content, it does not
   scroll its own content, and it is 280px wide. This is the one
   piece of desktop chrome where content genuinely passes behind
   a surface, so this is where the blur is spent. Its gradient
   banner and its rows stay opaque: the menu is the tray, not the
   content. It already animates on transform and opacity only
   (header.css), which is the only motion allowed on a
   backdrop-filtered element.
============================================================= */
html body .edash-header .hd-profile-menu,
html[data-theme="dark"] body .edash-header .hd-profile-menu {
  background: var(--mat-thick);
  border: 1px solid var(--glass-edge);
  box-shadow: inset 0 1px 0 var(--mat-hi), var(--mat-shadow-3);
  -webkit-backdrop-filter: var(--mat-filter-thick);
          backdrop-filter: var(--mat-filter-thick);
}

/* ============================================================
   6. THE LOGOUT DIALOG — thick material, BLUR. (2 of 2)
   The scrim loses its blur(4px) and becomes a dim: a dialog's
   backdrop dims and never blurs, and a full-viewport blur is the
   most expensive single effect on a phone. The card takes that
   budget instead — it is 420px, it does not scroll, and the blur
   is what makes it read as a sheet lifted off the page.
   Blurring both would be blur nested inside blur, which is
   forbidden outright.
============================================================= */
html body .lg-overlay,
html[data-theme="dark"] body .lg-overlay {
  background: var(--mat-scrim);
  -webkit-backdrop-filter: none;
          backdrop-filter: none;
}
html body .lg-modal,
html[data-theme="dark"] body .lg-modal {
  background: var(--mat-thick);
  border: 1px solid var(--glass-edge);
  box-shadow: inset 0 1px 0 var(--mat-hi), var(--mat-shadow-3);
  -webkit-backdrop-filter: var(--mat-filter-thick);
          backdrop-filter: var(--mat-filter-thick);
}

/* ============================================================
   7. THE TOAST — material chrome, no blur.
   It is signal, it is on every page, and a blurred 44px strip is
   exactly the layer to spare. It takes the edge, the specular
   line and the resting shadow only; the coloured left rule that
   says which kind of toast it is stays fully opaque.
============================================================= */
html body .edash-toast,
html[data-theme="dark"] body .edash-toast {
  background: var(--mat-thick);
  border-color: var(--glass-edge);
  box-shadow: inset 0 1px 0 var(--mat-hi), var(--mat-shadow-2);
}

/* ============================================================
   THE FIVE ACCESSIBILITY GATES
   ------------------------------------------------------------
   Every gate does the same NINE things and NOTHING else:
     --mat-thick          -> the opaque paper
     --mat-regular        -> the opaque paper
     --mat-nav            -> the opaque nav paper
     --mat-filter-thick   -> none
     --mat-filter-regular -> none
     --ground-image       -> none          (NO IMAGE, a solid fill)
     --ground-wash        -> transparent   (nothing left to veil)
     --mat-bar            -> --mat-bar-solid   (flat teal header)
     --mat-rail           -> --mat-rail-solid  (flat sidebar column)
   Because every surface above reads those nine tokens and never
   writes a literal blur, a literal tint or a literal url(), one
   gate switch turns the whole effect off everywhere at once —
   INCLUDING the ground, which falls back to the solid
   --ground-base with no photograph at all. No component selector
   appears in any gate definition. A surface added later inherits
   all five gates for free, which a per-component gate list does
   not give you — that is the defect this shape exists to avoid.

   The one thing a gate never does is put --mat-nav-title-ink
   back to the failing #7C8B8D. A gate exists to make the page
   more legible, never less; forced-colors hands that ink to
   CanvasText, which is the system taking over, not a rollback.

   The five conditions are enumerated here and nowhere else:
     (a) no backdrop-filter support   @supports not (...)
     (b) prefers-reduced-transparency: reduce
     (c) prefers-contrast: more
     (d) forced-colors: active
     (e) html.glass-lite              the opt-out class
   (b), (c) and (d) are one comma-enumerated media condition.
   (a) and (e) cannot syntactically join a @media list, so they
   are two more blocks carrying the identical nine lines. No CSS
   construct merges an @supports, an @media and a class selector
   into a single rule without a build step, and this repo has no
   build step.
============================================================= */

/* (a) no backdrop-filter support */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  :root, :root[data-theme="dark"] {
    --mat-thick: var(--glass-paper);
    --mat-regular: var(--glass-paper);
    --mat-nav: var(--glass-paper-nav);
    --mat-filter-thick: none;
    --mat-filter-regular: none;
    /* and the ground goes with them: a SOLID fill, no image. */
    --ground-image: none;
    --ground-wash: transparent;
    --mat-bar: var(--mat-bar-solid);
    --mat-rail: var(--mat-rail-solid);
  }
}

/* (b) reduced transparency   (c) increased contrast   (d) forced colours */
@media (prefers-reduced-transparency: reduce), (prefers-contrast: more), (forced-colors: active) {
  :root, :root[data-theme="dark"] {
    --mat-thick: var(--glass-paper);
    --mat-regular: var(--glass-paper);
    --mat-nav: var(--glass-paper-nav);
    --mat-filter-thick: none;
    --mat-filter-regular: none;
    /* and the ground goes with them: a SOLID fill, no image. */
    --ground-image: none;
    --ground-wash: transparent;
    --mat-bar: var(--mat-bar-solid);
    --mat-rail: var(--mat-rail-solid);
  }
}

/* (e) the opt-out class, set on <html> by hand or by a future
   capability check before first paint */
html.glass-lite, html.glass-lite[data-theme="dark"] {
  --mat-thick: var(--glass-paper);
  --mat-regular: var(--glass-paper);
  --mat-nav: var(--glass-paper-nav);
  --mat-filter-thick: none;
  --mat-filter-regular: none;
  /* and the ground goes with them: a SOLID fill, no image. */
  --ground-image: none;
  --ground-wash: transparent;
  --mat-bar: var(--mat-bar-solid);
  --mat-rail: var(--mat-rail-solid);
}

/* Increased contrast additionally draws every material's edge in
   the ink rather than at 14% of it, and deepens the scrims. Also
   a token change, so it still names no component. */
@media (prefers-contrast: more) {
  :root { --glass-edge: #25343F; --mat-scrim: rgba(18, 30, 34, .66); --mat-scrim-nav: rgba(11, 42, 45, .60) }
  :root[data-theme="dark"] { --glass-edge: #E7EFF0; --mat-scrim: rgba(0, 0, 0, .70); --mat-scrim-nav: rgba(0, 0, 0, .66) }
}

/* Forced colours: the system owns the palette. The four token
   lines above already removed every tint and every blur; these
   hand the surfaces to the system canvas so the forced palette
   is not fighting a hard-coded white, and drop the header's
   shadow, which forced-colors renders as a grey smear. */
@media (forced-colors: active) {
  :root, :root[data-theme="dark"] {
    --glass-paper: Canvas;
    --glass-paper-nav: Canvas;
    --glass-paper-page: Canvas;
    --glass-edge: CanvasText;
    /* The gate above already set --ground-image:none and handed
       --mat-bar/--mat-rail to the solid tokens; here the solids
       themselves become the system canvas so the forced palette is
       not fighting a hard-coded teal or a hard-coded white. */
    --ground-base: Canvas;
    --mat-bar-solid: Canvas;
    --mat-rail-solid: Canvas;
    --mat-nav-title-ink: CanvasText;
  }
  html body .edash-header,
  html[data-theme="dark"] body .edash-header { box-shadow: none }
  html body .edash-sidebar,
  html[data-theme="dark"] body .edash-sidebar { box-shadow: none }
}

/* ============================================================
   THE PRE-EXISTING BLUR IN THE PAGE STYLESHEETS.
   These backdrop-filters were in the repo before this file and
   are not shell chrome, but they are real blurred layers and the
   five gates must reach them or the gates are a lie. They cannot
   read the tokens above without editing each page's own rule, so
   they are switched off here by name — the one place in this
   file where components are enumerated, and it is a fallback
   list, not a gate definition.
     .po-map-stat               css/dashboard.css      blur(6px)
     .ap-modal-overlay          css/add-project.css    blur(4px)
     .sp-csv-drop-overlay       css/catalog.css        blur(1.5px)
     .sp-modal-backdrop         css/catalog.css        blur(2px)
     .st-modal-overlay          css/setting.css        blur(4px)
     .modal-backdrop,
     .pd-report-modal-backdrop,
     .pd-env-settings-modal-backdrop
                                css/project-detail.css blur(2px)
============================================================= */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  html body :is(.po-map-stat, .ap-modal-overlay, .sp-csv-drop-overlay, .sp-modal-backdrop,
                .st-modal-overlay, .modal-backdrop, .pd-report-modal-backdrop,
                .pd-env-settings-modal-backdrop) {
    -webkit-backdrop-filter: none; backdrop-filter: none;
  }
}
@media (prefers-reduced-transparency: reduce), (prefers-contrast: more), (forced-colors: active) {
  html body :is(.po-map-stat, .ap-modal-overlay, .sp-csv-drop-overlay, .sp-modal-backdrop,
                .st-modal-overlay, .modal-backdrop, .pd-report-modal-backdrop,
                .pd-env-settings-modal-backdrop) {
    -webkit-backdrop-filter: none; backdrop-filter: none;
  }
  html body .po-map-stat { background: var(--glass-paper) }
}
html.glass-lite body :is(.po-map-stat, .ap-modal-overlay, .sp-csv-drop-overlay, .sp-modal-backdrop,
                         .st-modal-overlay, .modal-backdrop, .pd-report-modal-backdrop,
                         .pd-env-settings-modal-backdrop) {
  -webkit-backdrop-filter: none; backdrop-filter: none;
}

/* ============================================================
   Dark mode (folded in from dark-mode-v8.css, TKT-000398/359)
   Same rule shape as the rest of this file: light tokens above,
   redefined once here under html[data-theme="dark"], never a
   second copy of a selector's structural rules.
   ============================================================ */
/* ============================================================
   360eDash — Dark Mode Consistency v9
   Scope: existing pages/components only.
   Does NOT redesign layouts or touch header/notification.
   v9 adds dark-mode styling for the sidebar "Setting" button
   (previously excluded / left disabled-looking in v8).
   Palette follows the existing guide tokens and the established
   360eDash dark theme in global.css.
============================================================= */

html[data-theme="dark"] {
  /* Shared dark surfaces derived from the existing guide palette. */
  --surface-alt: #203238;
  --surface-raised: #1c2d32;
  --field-bg: #16262b;
  --border-strong: #385057;
}

/* ---------- Page-local token families ---------- */
html[data-theme="dark"] {
  --al-primary: #21a7ae;
  --al-primary-dark: #16868d;
  --al-primary-soft: rgba(33,167,174,.16);
  --al-header-teal: #69c8c2;
  --al-success: #4fbd8a;
  --al-success-soft: rgba(79,189,138,.14);
  --al-warning: #e8b34a;
  --al-warning-soft: rgba(232,179,74,.14);
  --al-danger: #ef6b6b;
  --al-danger-soft: rgba(239,107,107,.14);
  --al-info: #69a8d6;
  --al-info-soft: rgba(105,168,214,.14);
  --al-ink: #e7eff0;
  --al-muted: #9ab0b4;
  --al-border: #2a3d42;
  --al-surface: #18272c;
  --al-page-bg: #101b1f;

  --si-primary: #21a7ae;
  --si-primary-dark: #16868d;
  --si-primary-light: #69c8c2;
  --si-primary-soft: rgba(33,167,174,.16);
  --si-accent: #fa9a3a;
  --si-success: #4fbd8a;
  --si-success-soft: rgba(79,189,138,.14);
  --si-warning: #e8b34a;
  --si-warning-soft: rgba(232,179,74,.14);
  --si-danger: #ef6b6b;
  --si-danger-soft: rgba(239,107,107,.14);
  --si-info: #69a8d6;
  --si-info-soft: rgba(105,168,214,.14);
  --si-ink: #e7eff0;
  --si-muted: #9ab0b4;
  --si-border: #2a3d42;
  --si-surface: #18272c;
  --si-page-bg: #101b1f;

  --surface: #18272c;
  --page-bg: #101b1f;
  --ink: #e7eff0;
  --text: #e7eff0;
  --muted: #9ab0b4;
  --border: #2a3d42;
  --primary: #21a7ae;
  --primary-dark: #16868d;
  --primary-soft: rgba(33,167,174,.16);
  --success: #4fbd8a;
  --success-soft: rgba(79,189,138,.14);
  --warning: #e8b34a;
  --warning-soft: rgba(232,179,74,.14);
  --danger: #ef6b6b;
  --danger-soft: rgba(239,107,107,.14);
  --info: #69a8d6;
  --info-soft: rgba(105,168,214,.14);

  /* Admin Calculator / Admin View local token families. */
  --accent-orange: #fa9a3a;
}

/* ---------- Sidebar: active/inactive states remain visible but softer ---------- */
html[data-theme="dark"] .edash-sidebar {
  background: #122227;
  border-color: #294047;
  color: #dce8e9;
}
html[data-theme="dark"] .edash-sidebar .sb-nav-item,
html[data-theme="dark"] .edash-sidebar .sb-nav-link {
  color: #b8cacc;
  border-color: #294047;
  background: transparent;
}
html[data-theme="dark"] .edash-sidebar .sb-nav-item:hover,
html[data-theme="dark"] .edash-sidebar .sb-nav-link:hover {
  background: rgba(33,167,174,.10);
  color: #dce8e9;
}
html[data-theme="dark"] .edash-sidebar .sb-nav-item.active,
html[data-theme="dark"] .edash-sidebar .sb-nav-link.active,
html[data-theme="dark"] .edash-sidebar .active {
  background: rgba(33,167,174,.22);
  border-color: rgba(105,200,194,.45);
  color: #e4f1f1;
}
html[data-theme="dark"] .edash-sidebar .sb-nav-item.active i,
html[data-theme="dark"] .edash-sidebar .sb-nav-link.active i,
html[data-theme="dark"] .edash-sidebar .active i {
  color: #69c8c2;
}

/* ---------- Alerts ---------- */
html[data-theme="dark"] .alert-page,
html[data-theme="dark"] .alert-page * {
  --al-surface: #18272c;
}
html[data-theme="dark"] .alert-search input,
html[data-theme="dark"] .alert-sort,
html[data-theme="dark"] .alert-table-card,
html[data-theme="dark"] .alert-panel,
html[data-theme="dark"] .alert-stat-card,
html[data-theme="dark"] .alert-modal,
html[data-theme="dark"] .alert-form-card {
  background: var(--al-surface);
  color: var(--al-ink);
  border-color: var(--al-border);
}
html[data-theme="dark"] .alert-search input:focus,
html[data-theme="dark"] .alert-sort:focus {
  background: #1d3035;
}
html[data-theme="dark"] .alert-table thead th {
  background: #203238;
  color: var(--al-muted);
}
html[data-theme="dark"] .alert-table tbody td {
  background: var(--al-surface);
  color: var(--al-ink);
  border-color: var(--al-border);
}
html[data-theme="dark"] .alert-table tbody tr:hover td {
  background: rgba(33,167,174,.08);
}
html[data-theme="dark"] .alert-stat-icon-total { background: rgba(33,167,174,.16); }
html[data-theme="dark"] .alert-stat-icon-high { background: rgba(239,107,107,.14); }
html[data-theme="dark"] .alert-stat-icon-medium { background: rgba(232,179,74,.14); }
html[data-theme="dark"] .alert-stat-icon-low { background: rgba(79,189,138,.14); }
html[data-theme="dark"] .alert-action-btn,
html[data-theme="dark"] .alert-icon-btn {
  background: #1d3035;
  border-color: var(--al-border);
  color: var(--al-muted);
}
html[data-theme="dark"] .alert-action-btn:hover,
html[data-theme="dark"] .alert-icon-btn:hover {
  background: rgba(33,167,174,.14);
  color: var(--al-primary);
}
html[data-theme="dark"] .pg-info {
  color: #c9dde0;
}
html[data-theme="dark"] .alert-page-btn {
  background: #1d3035 !important;
  color: #ffffff !important;
  border-color: #2a3d42 !important;
}
html[data-theme="dark"] .alert-page-btn i {
  color: #ffffff !important;
}
html[data-theme="dark"] .alert-page-btn:disabled {
  opacity: 0.85 !important;
}
html[data-theme="dark"] .alert-page-btn.is-active {
  background: var(--al-primary) !important;
  border-color: var(--al-primary) !important;
  color: #ffffff !important;
}
html[data-theme="dark"] .alert-page-dots {
  color: #c9dde0;
}

/* ---------- Project Selector ---------- */
html[data-theme="dark"] .ps-page .ps-map-wrap,
html[data-theme="dark"] .ps-page .ps-detail-panel,
html[data-theme="dark"] .ps-page .ps-power-panel,
html[data-theme="dark"] .ps-page .ps-summary-card,
html[data-theme="dark"] .ps-page .ps-project-card,
html[data-theme="dark"] .ps-page .ps-panel,
html[data-theme="dark"] .ps-page .ps-locate-btn,
html[data-theme="dark"] .ps-page .ps-power-toggle,
html[data-theme="dark"] .ps-page .ps-detail-tech-btn,
html[data-theme="dark"] .ps-page .ps-modal,
html[data-theme="dark"] .ps-page .ps-search,
html[data-theme="dark"] .ps-page input,
html[data-theme="dark"] .ps-page select,
html[data-theme="dark"] .ps-page textarea {
  background: var(--surface);
  color: var(--ink);
  border-color: var(--border);
}
html[data-theme="dark"] .ps-page .ps-detail-close,
html[data-theme="dark"] .ps-page .ps-power-refresh {
  background: #203238;
  color: var(--muted);
  border-color: var(--border);
}
html[data-theme="dark"] .ps-page .ps-power-toggle-btn.is-active,
html[data-theme="dark"] .ps-page .ps-detail-tech-btn:hover,
html[data-theme="dark"] .ps-page .ps-power-refresh:hover {
  background: rgba(33,167,174,.16);
  color: var(--primary);
}
html[data-theme="dark"] .ps-page .demo-map-card {
  border-color: var(--border);
}

/* "Project Locations" collapsible header -- has its own hardcoded
   white background separate from .ps-panel's, so it stays white even
   after .ps-panel itself goes dark. */
html[data-theme="dark"] .ps-page .ps-panel-toggle {
  background: #18272c;
  color: #e7eff0;
}
html[data-theme="dark"] .ps-page .ps-panel-toggle i:first-child {
  background: rgba(33,167,174,.18);
  color: #69c8c2;
}
html[data-theme="dark"] .ps-page .ps-panel-toggle-chevron {
  color: #9ab0b4;
}
html[data-theme="dark"] .ps-page .ps-panel.is-open .ps-panel-toggle {
  border-bottom-color: #2a3d42;
}

/* Detail card: project name / power output title -- hardcoded dark
   text, invisible on the now-dark panel background. */
html[data-theme="dark"] .ps-page .ps-detail-name,
html[data-theme="dark"] .ps-page .ps-power-title {
  color: #e7eff0;
}
html[data-theme="dark"] .ps-page .ps-detail-location,
html[data-theme="dark"] .ps-page .ps-power-eyebrow {
  color: #9ab0b4;
}

/* "Upcoming Schedule" collapsible panel (own hardcoded white
   background, separate component from .ps-panel). */
html[data-theme="dark"] .ps-page .ps-history-panel {
  background: #18272c;
  border-color: #2a3d42;
  box-shadow: 0 10px 32px rgba(0,0,0,.4);
}
html[data-theme="dark"] .ps-page .ps-history-toggle {
  background: #18272c;
  color: #e7eff0;
}
html[data-theme="dark"] .ps-page .ps-history-toggle i:first-child {
  background: rgba(250,137,26,.18);
  color: #fa9a3a;
}
html[data-theme="dark"] .ps-page .ps-history-count,
html[data-theme="dark"] .ps-page .ps-history-toggle-chevron {
  color: #9ab0b4;
}
html[data-theme="dark"] .ps-page .ps-history-panel.is-open .ps-history-toggle {
  border-bottom-color: #2a3d42;
}
html[data-theme="dark"] .ps-page .ps-history-item {
  background: #1d3035;
}
html[data-theme="dark"] .ps-page .ps-history-item:hover {
  border-color: #21a7ae;
}
html[data-theme="dark"] .ps-page .ps-history-icon {
  background: rgba(250,137,26,.18);
  color: #fa9a3a;
}
html[data-theme="dark"] .ps-page .ps-history-item-title {
  color: #e7eff0;
}
html[data-theme="dark"] .ps-page .ps-history-item-note {
  color: #9ab0b4;
}
html[data-theme="dark"] .ps-page .ps-history-tag {
  background: #203238;
  color: #9ab0b4;
  border-color: #2a3d42;
}
html[data-theme="dark"] .ps-page .ps-history-tag-device {
  background: rgba(33,167,174,.16);
  color: #69c8c2;
  border-color: rgba(33,167,174,.3);
}
html[data-theme="dark"] .ps-page .ps-empty {
  color: #9ab0b4;
}

/* Summary cards (All Projects / Online / Offline / Active Alerts) */
html[data-theme="dark"] .ps-page .ps-summary-item {
  background: #1d3035;
  border-color: #2a3d42;
}
html[data-theme="dark"] .ps-page .ps-summary-value {
  color: #e7eff0;
}
html[data-theme="dark"] .ps-page .ps-summary-label {
  color: #9ab0b4;
}
html[data-theme="dark"] .ps-page .ps-summary-icon-all     { background: rgba(33,167,174,.18); color: #69c8c2; }
html[data-theme="dark"] .ps-page .ps-summary-icon-online  { background: rgba(79,189,138,.18); color: #4fbd8a; }
html[data-theme="dark"] .ps-page .ps-summary-icon-offline { background: rgba(239,107,107,.18); color: #ef6b6b; }
html[data-theme="dark"] .ps-page .ps-summary-icon-alerts  { background: rgba(232,179,74,.18); color: #e8b34a; }

/* Category cards (Solaris BSS / Rooftop Solar / Solar Fish Farm) --
   colors come from inline CSS vars (--cat-bg etc.) set by JS, so we
   must override the background/border property directly. */
html[data-theme="dark"] .ps-page .ps-category-item {
  background: #1d3035 !important;
  border-color: #2a3d42 !important;
}
html[data-theme="dark"] .ps-page .ps-category-icon {
  background: rgba(250,137,26,.18) !important;
  color: #fa9a3a !important;
}
/* Restore per-category color identity to match the Dashboard's
   PO_CATEGORY_COLORS mapping (BSS = orange, Rooftop Solar = blue,
   Fish Farm = green) instead of the old teal/orange/green order --
   PS_CATEGORY_ORDER always renders bss, rooftop, fish-farm in this
   fixed order, so nth-of-type is safe here. */
html[data-theme="dark"] .ps-page .ps-category-item:nth-of-type(2) .ps-category-icon {
  background: rgba(47,128,237,.18) !important;
  color: #6ba8f2 !important;
}
html[data-theme="dark"] .ps-page .ps-category-item:nth-of-type(3) .ps-category-icon {
  background: rgba(39,174,96,.18) !important;
  color: #4fbd8a !important;
}
html[data-theme="dark"] .ps-page .ps-category-value {
  color: #e7eff0;
}
html[data-theme="dark"] .ps-page .ps-category-label,
html[data-theme="dark"] .ps-page .ps-section-title,
html[data-theme="dark"] .ps-page .ps-empty,
html[data-theme="dark"] .ps-page .ps-table-head {
  color: #9ab0b4;
}

/* Today's Generation stat boxes (MWh / MWp / CO2) */
html[data-theme="dark"] .ps-page .ps-gen-item {
  background: #1d3035;
  border-color: #2a3d42;
}
html[data-theme="dark"] .ps-page .ps-gen-value {
  color: #e7eff0;
}
html[data-theme="dark"] .ps-page .ps-gen-unit {
  color: #69c8c2;
}
html[data-theme="dark"] .ps-page .ps-gen-label {
  color: #9ab0b4;
}

/* "Today" floating chart card */
html[data-theme="dark"] .ps-page .ps-today-float {
  background: #18272c;
  border-color: #2a3d42;
  box-shadow: 0 8px 28px rgba(0,0,0,.45);
}
html[data-theme="dark"] .ps-page .ps-today-title,
html[data-theme="dark"] .ps-page .ps-today-value span,
html[data-theme="dark"] .ps-page .ps-today-legend-item {
  color: #9ab0b4;
}
html[data-theme="dark"] .ps-page .ps-today-value {
  color: #69c8c2;
}
html[data-theme="dark"] .ps-page .ps-today-now-label {
  color: #e7eff0;
}

/* Saved Projects / All Projects tables */
html[data-theme="dark"] .ps-page .ps-table-row:hover,
html[data-theme="dark"] .ps-page .ps-table-row.is-active {
  background: rgba(33,167,174,.14);
}
html[data-theme="dark"] .ps-page .ps-table-row-name {
  color: #e7eff0;
}
html[data-theme="dark"] .ps-page .ps-table-row-location {
  color: #9ab0b4;
}
html[data-theme="dark"] .ps-page .ps-row-action-btn {
  color: #7c9195;
}
html[data-theme="dark"] .ps-page .ps-row-action-btn:hover {
  background: #24393f;
  color: #69c8c2;
}
html[data-theme="dark"] .ps-page .ps-row-action-btn.ps-action-delete:hover {
  color: #ef6b6b;
}
html[data-theme="dark"] .ps-page .ps-row-action-btn.ps-action-save.is-saved {
  color: #fa9a3a;
}

/* ---------- Project Overview ---------- */
html[data-theme="dark"] body:has(.page-container) {
  background: var(--page-bg);
  color: var(--ink);
}
html[data-theme="dark"] .page-container .card {
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--border);
}
html[data-theme="dark"] .page-container .project-selector select {
  background: var(--surface);
  color: var(--ink);
  border-color: var(--border);
}
html[data-theme="dark"] .page-container .project-meta,
html[data-theme="dark"] .page-container .weather-desc,
html[data-theme="dark"] .page-container .empty-state {
  color: var(--muted);
}
html[data-theme="dark"] .page-container .chart-placeholder {
  border-color: #385057;
  color: var(--muted);
}
html[data-theme="dark"] .page-container button {
  background: #7c3aed;
  color: #f4efff;
}

/* ---------- Project Monitoring ---------- */
html[data-theme="dark"] .pm-page .pm-tabs,
html[data-theme="dark"] .pm-page .pm-empty-state,
html[data-theme="dark"] .pm-page .pm-card,
html[data-theme="dark"] .pm-page .pm-panel,
html[data-theme="dark"] .pm-page .pm-stat-card,
html[data-theme="dark"] .pm-page .pm-table-card {
  background: var(--si-surface);
  color: var(--si-ink);
  border-color: var(--si-border);
}
html[data-theme="dark"] .pm-page .pm-tab-icon,
html[data-theme="dark"] .opd-page .pm-tab-icon {
  background: #203238;
  color: var(--si-muted);
}
html[data-theme="dark"] .pm-page .pm-tab.is-active,
html[data-theme="dark"] .opd-page .pm-tab.is-active {
  background: rgba(33,167,174,.14);
  color: #dcebec;
}
html[data-theme="dark"] .pm-page .pm-tab.is-active .pm-tab-icon,
html[data-theme="dark"] .opd-page .pm-tab.is-active .pm-tab-icon {
  background: #0f6a71;
  color: #d9eeee;
}

/* ---------- System Information / Task Management / Task Maintenance ---------- */
html[data-theme="dark"] .si,
html[data-theme="dark"] .tm,
html[data-theme="dark"] .tkm {
  color: var(--si-ink);
}
html[data-theme="dark"] .si .si-card,
html[data-theme="dark"] .si .si-panel,
html[data-theme="dark"] .si .si-stat-card,
html[data-theme="dark"] .si .si-table-card,
html[data-theme="dark"] .si input,
html[data-theme="dark"] .si select,
html[data-theme="dark"] .si textarea,
html[data-theme="dark"] .tm .tm-card,
html[data-theme="dark"] .tm .tm-panel,
html[data-theme="dark"] .tm input,
html[data-theme="dark"] .tm select,
html[data-theme="dark"] .tm textarea,
html[data-theme="dark"] .tkm-task-card {
  background: var(--si-surface);
  color: var(--si-ink);
  border-color: var(--si-border);
}
html[data-theme="dark"] .si input::placeholder,
html[data-theme="dark"] .si textarea::placeholder,
html[data-theme="dark"] .tm input::placeholder,
html[data-theme="dark"] .tm textarea::placeholder {
  color: var(--si-muted);
}
html[data-theme="dark"] .si .si-table th,
html[data-theme="dark"] .tm .tm-table th {
  background: #203238;
  color: var(--si-muted);
}
html[data-theme="dark"] .si .si-table td,
html[data-theme="dark"] .tm .tm-table td {
  background: var(--si-surface);
  color: var(--si-ink);
  border-color: var(--si-border);
}
html[data-theme="dark"] .si .si-table tbody tr:hover td,
html[data-theme="dark"] .tm .tm-table tbody tr:hover td {
  background: rgba(33,167,174,.08);
}
html[data-theme="dark"] .tkm-range-row input[type="range"]::-webkit-slider-thumb,
html[data-theme="dark"] .tkm-range-row input[type="range"]::-moz-range-thumb {
  border-color: #d9eeee;
}

/* ---------- Battery Station ---------- */
html[data-theme="dark"] .bs-page .bs-card,
html[data-theme="dark"] .bs-page .bs-stat-card,
html[data-theme="dark"] .bs-page .bs-select,
html[data-theme="dark"] .bs-page .bs-slot,
html[data-theme="dark"] .bs-page .bs-table-card,
html[data-theme="dark"] .bs-page .bs-bat-card,
html[data-theme="dark"] .bs-page .bs-filter-select,
html[data-theme="dark"] .bs-page .bs-timeline-card,
html[data-theme="dark"] .bs-page .bs-empty,
html[data-theme="dark"] .bs-page input,
html[data-theme="dark"] .bs-page select,
html[data-theme="dark"] .bs-page textarea {
  background: var(--surface);
  color: var(--ink);
  border-color: var(--border);
}
html[data-theme="dark"] .bs-page .bs-slot {
  background: #1d3035;
}
html[data-theme="dark"] .bs-page .bs-slot:hover {
  border-color: #467077;
}
html[data-theme="dark"] .bs-page .bs-info-item,
html[data-theme="dark"] .bs-page .bs-energy-item,
html[data-theme="dark"] .bs-page .bs-chart-card,
html[data-theme="dark"] .bs-page .bs-chip-item,
html[data-theme="dark"] .bs-page .bs-hero-graphic-card,
html[data-theme="dark"] .bs-page .bs-attention-pill,
html[data-theme="dark"] .bs-page .bs-timeline-card {
  background: #1d3035;
}
html[data-theme="dark"] .bs-page .bs-summary-card {
  background: linear-gradient(155deg, var(--surface) 0%, #12242a 145%);
  border-color: var(--border);
}
html[data-theme="dark"] .bs-page .bs-summary-item {
  background: rgba(29, 48, 53, 0.72);
  border-color: var(--border);
}
html[data-theme="dark"] .bs-page .bs-src::after {
  background: #0b1a1d;
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.45);
}
html[data-theme="dark"] .bs-page .bs-src::before {
  border-top-color: #0b1a1d;
}
html[data-theme="dark"] .bs-page .bs-badge-unknown,
html[data-theme="dark"] .bs-page .bs-chip-icon-muted,
html[data-theme="dark"] .bs-page .bs-timeline-dot-unknown {
  background: #26383d;
}
html[data-theme="dark"] .bs-page .bs-batt-graphic-icon {
  border-color: #1d3035;
}
html[data-theme="dark"] .bs-page .bs-tabs {
  background: #16262a;
}
html[data-theme="dark"] .bs-page .bs-nodata-note {
  background: rgba(227, 162, 26, 0.12);
  border-color: rgba(227, 162, 26, 0.35);
  color: #E3A21A;
}

/* ---------- Add New Project ---------- */
html[data-theme="dark"] .ap-wrap,
html[data-theme="dark"] .ap-wrap {
  color: var(--ink);
}
html[data-theme="dark"] .ap-card,
html[data-theme="dark"] .ap-tab-header,
html[data-theme="dark"] .ap-tab-btn.active,
html[data-theme="dark"] .ap-add-inverter-btn,
html[data-theme="dark"] .ap-field input,
html[data-theme="dark"] .ap-field select,
html[data-theme="dark"] .ap-file-btn,
html[data-theme="dark"] .ap-modal,
html[data-theme="dark"] .ap-summary {
  background: var(--surface);
  color: var(--ink);
  border-color: var(--border);
}
html[data-theme="dark"] .ap-tab-header,
html[data-theme="dark"] .ap-file-btn {
  background: #203238;
}
html[data-theme="dark"] .ap-tab-btn.active {
  background: #1d3035;
  color: var(--primary);
}
html[data-theme="dark"] .ap-field input::placeholder {
  color: var(--muted);
}
html[data-theme="dark"] .ap-card-head h3,
html[data-theme="dark"] .ap-tab-btn.active,
html[data-theme="dark"] .ap-add-inverter-btn {
  color: #dcebec;
}

/* ---------- Admin View ---------- */
html[data-theme="dark"] .um-wrap,
html[data-theme="dark"] .um-head,
html[data-theme="dark"] .um-toolbar,
html[data-theme="dark"] .um-table-card,
html[data-theme="dark"] .um-stat-card {
  color: var(--ink);
}
html[data-theme="dark"] .um-toolbar,
html[data-theme="dark"] .um-table-card,
html[data-theme="dark"] .um-stat-card,
html[data-theme="dark"] .um-modal,
html[data-theme="dark"] .um-form-card {
  background: var(--surface);
  border-color: var(--border);
}
html[data-theme="dark"] .um-search input,
html[data-theme="dark"] .um-toolbar select,
html[data-theme="dark"] .um-modal input,
html[data-theme="dark"] .um-modal select,
html[data-theme="dark"] .um-modal textarea {
  background: var(--field-bg);
  color: var(--ink);
  border-color: var(--border);
}
html[data-theme="dark"] .um-search input:focus,
html[data-theme="dark"] .um-toolbar select:focus {
  background: var(--surface);
}
html[data-theme="dark"] .um-table th {
  background: #203238;
  color: var(--muted);
}
html[data-theme="dark"] .um-table td {
  background: var(--surface);
  color: var(--ink);
  border-color: var(--border);
}
html[data-theme="dark"] .um-table tbody tr:hover td {
  background: rgba(33,167,174,.08);
}
html[data-theme="dark"] .um-pill-muted {
  background: #203238;
  color: var(--muted);
}

/* ---------- Activity Log ---------- */
html[data-theme="dark"] .al-page .al-card,
html[data-theme="dark"] .al-page .al-toolbar,
html[data-theme="dark"] .al-page .al-table-card,
html[data-theme="dark"] .al-page input,
html[data-theme="dark"] .al-page select,
html[data-theme="dark"] .al-page textarea,
html[data-theme="dark"] .al-page .al-date-input {
  background: var(--surface);
  color: var(--ink);
  border-color: var(--border);
}
html[data-theme="dark"] .al-page .al-table thead tr {
  background: #0f6a71;
}
html[data-theme="dark"] .al-page .al-table td {
  background: var(--surface);
  color: var(--ink);
  border-color: var(--border);
}
html[data-theme="dark"] .al-page .al-table tbody tr:hover td {
  background: rgba(33,167,174,.08);
}
html[data-theme="dark"] .al-page .al-type-logout {
  background: #263238;
  color: var(--muted);
}

/* ---------- Admin Calculator ---------- */
html[data-theme="dark"] .sp-wrap,
html[data-theme="dark"] .sp-head,
html[data-theme="dark"] .sp-layout {
  color: var(--ink);
}
html[data-theme="dark"] .sp-stepper,
html[data-theme="dark"] .sp-form,
html[data-theme="dark"] .sp-accordion,
html[data-theme="dark"] .sp-card,
html[data-theme="dark"] .sp-panel,
html[data-theme="dark"] .sp-rab-card,
html[data-theme="dark"] .sp-summary-card,
html[data-theme="dark"] .sp-btn-outline,
html[data-theme="dark"] .sp-editable-cell {
  background: var(--surface);
  color: var(--ink);
  border-color: var(--border);
}
html[data-theme="dark"] .sp-accordion-btn {
  background: #16262b;
  color: var(--ink);
}
html[data-theme="dark"] .sp-accordion.is-open .sp-accordion-btn {
  background: rgba(33,167,174,.14);
  color: #dcebec;
}
html[data-theme="dark"] .sp-select-wrap select,
html[data-theme="dark"] .sp-number-wrap input[type="number"],
html[data-theme="dark"] .sp-field input,
html[data-theme="dark"] .sp-field textarea,
html[data-theme="dark"] .sp-field select {
  background: var(--field-bg);
  color: var(--ink);
  border-color: var(--border);
}
html[data-theme="dark"] .sp-select-wrap select:focus,
html[data-theme="dark"] .sp-number-wrap input[type="number"]:focus,
html[data-theme="dark"] .sp-field input:focus,
html[data-theme="dark"] .sp-field textarea:focus,
html[data-theme="dark"] .sp-field select:focus {
  background: var(--surface);
}
html[data-theme="dark"] .sp-rab-total-row td {
  background: rgba(33,167,174,.14);
  color: #dcebec;
}
html[data-theme="dark"] .sp-filter-chip.is-active {
  background: #0f6a71;
  border-color: #0f6a71;
  color: #dcebec;
}
html[data-theme="dark"] .sp-filter-chip {
  background: var(--surface-alt);
  border-color: var(--border);
  color: var(--ink);
}
html[data-theme="dark"] .sp-filter-chip:hover {
  background: var(--surface-raised);
}
html[data-theme="dark"] .sp-add-row-select {
  background: var(--surface-alt);
  border-color: var(--border);
  color: var(--ink);
}

/* ---------- Shared dark form/table safeguards for pages above ---------- */
html[data-theme="dark"] input,
html[data-theme="dark"] select,
html[data-theme="dark"] textarea {
  color-scheme: dark;
}
html[data-theme="dark"] input::placeholder,
html[data-theme="dark"] textarea::placeholder {
  color: #839b9f;
}
html[data-theme="dark"] option {
  background: #18272c;
  color: #e7eff0;
}

/* Keep status pills colorful, but use dark tints rather than light pastel blocks. */
html[data-theme="dark"] .pill-success { background: rgba(79,189,138,.14); color: #70d19f; }
html[data-theme="dark"] .pill-warning { background: rgba(232,179,74,.14); color: #efc466; }
html[data-theme="dark"] .pill-danger  { background: rgba(239,107,107,.14); color: #f08080; }
html[data-theme="dark"] .pill-info    { background: rgba(105,168,214,.14); color: #82bce3; }

/* Avoid bright white utility surfaces outside Settings/header. */
html[data-theme="dark"] .edash-content .modal-content,
html[data-theme="dark"] .edash-content .modal,
html[data-theme="dark"] .edash-content .dropdown-menu,
html[data-theme="dark"] .edash-content .popover {
  background: var(--surface);
  color: var(--ink);
  border-color: var(--border);
}

/* ============================================================
   Dark Mode Consistency Patch — v10
   Scope: Dashboard, Battery Station, shared header/sidebar,
   logout modal, Admin profile popup, and Login.
============================================================= */

/* ---------- Dashboard: remove remaining bright surfaces ---------- */
html[data-theme="dark"] .po-page {
  --po-surface: #18272c;
  --po-border: #2a3d42;
  --po-ink: #e7eff0;
  --po-muted: #9ab0b4;
  --po-primary-soft: rgba(33,167,174,.16);
  --po-primary-dark: #69c8c2;
  color: var(--po-ink);
}
html[data-theme="dark"] .po-page .po-card,
html[data-theme="dark"] .po-page .po-refresh-btn,
html[data-theme="dark"] .po-page .po-download-btn,
html[data-theme="dark"] .po-page .po-map-stat {
  background: #18272c;
  color: #e7eff0;
  border-color: #2a3d42;
}
html[data-theme="dark"] .po-page .po-map-stat {
  background: rgba(24,39,44,.94);
}
html[data-theme="dark"] .po-page .po-legend-row,
html[data-theme="dark"] .po-page .po-site-row,
html[data-theme="dark"] .po-page .po-poi-row {
  border-color: #2a3d42;
}
html[data-theme="dark"] .po-page .po-legend-row {
  background: #1d3035;
}
html[data-theme="dark"] .po-page .po-summary-item {
  background: #1d3035;
  border-color: #2a3d42;
}
html[data-theme="dark"] .po-page .po-map {
  background: #1b2c31;
}
html[data-theme="dark"] .po-page .po-title,
html[data-theme="dark"] .po-page .po-card-value,
html[data-theme="dark"] .po-page .po-site-name,
html[data-theme="dark"] .po-page .po-site-value,
html[data-theme="dark"] .po-page .po-poi-site,
html[data-theme="dark"] .po-page .po-map-stat-value,
html[data-theme="dark"] .po-page .po-map-stat-metric {
  color: #e7eff0;
}
html[data-theme="dark"] .po-page .po-subtitle,
html[data-theme="dark"] .po-page .po-card-eyebrow,
html[data-theme="dark"] .po-page .po-site-region,
html[data-theme="dark"] .po-page .po-poi-region,
html[data-theme="dark"] .po-page .po-poi-date,
html[data-theme="dark"] .po-page .po-map-stat-label,
html[data-theme="dark"] .po-page .po-legend-label {
  color: #9ab0b4;
}

/* ---------- Battery Station: dropdown/status controls and cards ---------- */
html[data-theme="dark"] .bs-page .bs-card,
html[data-theme="dark"] .bs-page .bs-stat-card,
html[data-theme="dark"] .bs-page .bs-table-card,
html[data-theme="dark"] .bs-page .bs-select,
html[data-theme="dark"] .bs-page .bs-slot,
html[data-theme="dark"] .bs-page .bs-schedule,
html[data-theme="dark"] .bs-page .bs-history,
html[data-theme="dark"] .bs-page .bs-panel,
html[data-theme="dark"] .bs-page .bs-dropdown,
html[data-theme="dark"] .bs-page .bs-filter,
html[data-theme="dark"] .bs-page .bs-status-select {
  background: #18272c;
  color: #e7eff0;
  border-color: #2a3d42;
}
html[data-theme="dark"] .bs-page .bs-slot,
html[data-theme="dark"] .bs-page .bs-table thead th {
  background: #1d3035;
}
html[data-theme="dark"] .bs-page .bs-table td,
html[data-theme="dark"] .bs-page .bs-table th {
  border-color: #2a3d42;
  color: #e7eff0;
}
html[data-theme="dark"] .bs-page .bs-table tbody tr:hover td {
  background: rgba(33,167,174,.08);
}

/* ---------- Shared header: notification + admin/profile popups ---------- */
html[data-theme="dark"] .edash-header {
  --hd-surface: #18272c;
  --hd-ink: #e7eff0;
  --hd-muted: #9ab0b4;
  --hd-border: #2a3d42;
  --hd-primary-soft: rgba(33,167,174,.16);
  --hd-success-soft: rgba(79,189,138,.14);
  --hd-warning-soft: rgba(232,179,74,.14);
  --hd-danger-soft: rgba(239,107,107,.14);
  --hd-info-soft: rgba(105,168,214,.14);
}
html[data-theme="dark"] .edash-header .hd-notif-panel,
html[data-theme="dark"] .edash-header .hd-profile-menu {
  background: #18272c;
  color: #e7eff0;
  border-color: #2a3d42;
}
html[data-theme="dark"] .edash-header .hd-notif-item,
html[data-theme="dark"] .edash-header .hd-profile-menu-row,
html[data-theme="dark"] .edash-header .hd-profile-menu-body {
  background: #18272c;
  color: #e7eff0;
}
html[data-theme="dark"] .edash-header .hd-notif-item.is-unread {
  background: #1d3035;
}
html[data-theme="dark"] .edash-header .hd-notif-item:hover,
html[data-theme="dark"] .edash-header .hd-profile-menu-row:hover {
  background: rgba(33,167,174,.12);
}
html[data-theme="dark"] .edash-header .hd-notif-panel-head,
html[data-theme="dark"] .edash-header .hd-profile-menu-divider {
  border-color: #2a3d42;
}
html[data-theme="dark"] .edash-header .hd-profile-menu-value,
html[data-theme="dark"] .edash-header .hd-profile-menu-text,
html[data-theme="dark"] .edash-header .hd-notif-item-title {
  color: #e7eff0;
}
html[data-theme="dark"] .edash-header .hd-profile-menu-label,
html[data-theme="dark"] .edash-header .hd-notif-item-msg,
html[data-theme="dark"] .edash-header .hd-notif-item-time {
  color: #9ab0b4;
}

/* ---------- Sidebar brand + red Logout ---------- */
html[data-theme="dark"] .edash-sidebar .sb-brand {
  background: #122227;
  border-bottom-color: #294047;
}
html[data-theme="dark"] .edash-sidebar .sb-nav-item-danger,
html[data-theme="dark"] .edash-sidebar .sb-nav-group-footer .sb-nav-item-danger {
  background: #d64545;
  border-color: #d64545;
  color: #fff;
}
html[data-theme="dark"] .edash-sidebar .sb-nav-item-danger .sb-nav-icon,
html[data-theme="dark"] .edash-sidebar .sb-nav-group-footer .sb-nav-item-danger .sb-nav-icon {
  background: rgba(255,255,255,.18);
}
html[data-theme="dark"] .edash-sidebar .sb-nav-item-danger .sb-nav-icon i,
html[data-theme="dark"] .edash-sidebar .sb-nav-group-footer .sb-nav-item-danger .sb-nav-icon i {
  color: #fff;
}
html[data-theme="dark"] .edash-sidebar .sb-nav-item-danger:hover,
html[data-theme="dark"] .edash-sidebar .sb-nav-group-footer .sb-nav-item-danger:hover {
  background: #b93a3a;
  border-color: #b93a3a;
  color: #fff;
}

/* ---------- Sidebar "OTHERS" group: Setting button (v11 — forced override) ----------
   The OTHERS panel and the Setting item are both rendering with the
   same solid teal fill (inherited from base/global CSS, not from
   this file), so they visually merge. Below forces the Setting
   item onto a dark neutral surface — visually distinct from the
   teal panel behind it AND from the red Logout — with !important
   so it wins regardless of base CSS specificity/order. Wildcard
   attribute selectors are added as a safety net in case the real
   class name differs from .sb-nav-item. */
html[data-theme="dark"] .edash-sidebar .sb-nav-group-footer > *:has(i, svg):not(.sb-nav-item-danger):not(.sb-nav-item-danger *) {
  background: #0f6a71 !important;
  border: 1px solid #0f6a71 !important;
  color: #e7eff0 !important;
  border-radius: 12px !important;
}
html[data-theme="dark"] .edash-sidebar .sb-nav-group-footer > *:has(i, svg):not(.sb-nav-item-danger):not(.sb-nav-item-danger *) i,
html[data-theme="dark"] .edash-sidebar .sb-nav-group-footer > *:has(i, svg):not(.sb-nav-item-danger):not(.sb-nav-item-danger *) svg {
  color: #d9eeee !important;
  fill: #d9eeee !important;
}
html[data-theme="dark"] .edash-sidebar .sb-nav-group-footer > *:has(i, svg):not(.sb-nav-item-danger):not(.sb-nav-item-danger *):hover {
  background: #0c565b !important;
  border-color: #0c565b !important;
}
html[data-theme="dark"] .edash-sidebar .sb-nav-group-footer > *:has(i, svg):not(.sb-nav-item-danger):not(.sb-nav-item-danger *):not(.disabled) {
  opacity: 1 !important;
  pointer-events: auto !important;
}

/* Safety net: any element literally containing the word "Setting"
   inside the OTHERS group, regardless of class name. Does not
   touch the "OTHERS" group label/heading itself. */
html[data-theme="dark"] .edash-sidebar [class*="sb-nav"][class*="setting" i],
html[data-theme="dark"] .edash-sidebar [data-key="setting" i] {
  background: #0f6a71 !important;
  border: 1px solid #0f6a71 !important;
  color: #e7eff0 !important;
}

/* ---------- Logout modal ---------- */
html[data-theme="dark"] .lg-overlay {
  --lg-surface: #18272c;
  --lg-ink: #e7eff0;
  --lg-muted: #9ab0b4;
  --lg-border: #2a3d42;
  --lg-danger-soft: rgba(239,107,107,.14);
}
html[data-theme="dark"] .lg-modal {
  background: #18272c;
  color: #e7eff0;
  border: 1px solid #2a3d42;
  box-shadow: 0 24px 60px rgba(0,0,0,.38);
}
html[data-theme="dark"] .lg-btn-secondary {
  background: #1d3035;
  color: #e7eff0;
  border-color: #2a3d42;
}
html[data-theme="dark"] .lg-btn-secondary:hover {
  background: #253b41;
  border-color: #385057;
}

/* ---------- Login page ---------- */
html[data-theme="dark"] body:has(.login-shell) {
  background: #101b1f;
  color: #e7eff0;
}
html[data-theme="dark"] .login-form-panel {
  background: #18272c;
  color: #e7eff0;
  scrollbar-color: #385057 transparent;
}
html[data-theme="dark"] .login-form-head p,
html[data-theme="dark"] .login-footnote {
  color: #9ab0b4;
}
html[data-theme="dark"] .login-form-head h1,
html[data-theme="dark"] .form-group label {
  color: #e7eff0;
}
html[data-theme="dark"] .input-control {
  background: #16262b;
  border-color: #2a3d42;
}
html[data-theme="dark"] .input-control input,
html[data-theme="dark"] .input-control select {
  color: #e7eff0;
  background: transparent;
}
html[data-theme="dark"] .input-control select option {
  background: #18272c;
  color: #e7eff0;
}

/* ============================================================
   Dark Mode Final Patch — Dashboard, Battery Station, Login
   Scope: ONLY the three remaining dark-mode gaps.
============================================================= */

/* ---------- Dashboard: eliminate remaining light surfaces ---------- */
html[data-theme="dark"] .po-page .po-row,
html[data-theme="dark"] .po-page .po-chart-wrap,
html[data-theme="dark"] .po-page .po-trend-legend,
html[data-theme="dark"] .po-page .po-sites-list,
html[data-theme="dark"] .po-page .po-poi-list {
  color: #e7eff0;
}
html[data-theme="dark"] .po-page .po-card,
html[data-theme="dark"] .po-page .po-map-stat,
html[data-theme="dark"] .po-page .po-refresh-btn,
html[data-theme="dark"] .po-page .po-download-btn {
  background: #18272c;
  color: #e7eff0;
  border-color: #2a3d42;
}
html[data-theme="dark"] .po-page .po-legend-row,
html[data-theme="dark"] .po-page .po-site-row,
html[data-theme="dark"] .po-page .po-poi-row {
  background: #1d3035;
  border-color: #2a3d42;
}
html[data-theme="dark"] .po-page .po-map-stat {
  background: rgba(24,39,44,.96);
}
html[data-theme="dark"] .po-page .po-map {
  background: #17282d;
}
html[data-theme="dark"] .po-page .po-chart-wrap svg line {
  stroke: #2a3d42 !important;
}
html[data-theme="dark"] .po-page .po-chart-wrap svg text {
  fill: #9ab0b4 !important;
}
html[data-theme="dark"] .po-page .po-chart-wrap svg rect[fill="#fff"],
html[data-theme="dark"] .po-page .po-chart-wrap svg rect[fill="#FFFFFF"] {
  fill: #18272c !important;
}
html[data-theme="dark"] .po-page .po-card-value,
html[data-theme="dark"] .po-page .po-site-value,
html[data-theme="dark"] .po-page .po-site-name,
html[data-theme="dark"] .po-page .po-poi-site,
html[data-theme="dark"] .po-page .po-map-stat-value,
html[data-theme="dark"] .po-page .po-map-stat-metric,
html[data-theme="dark"] .po-page .po-trend-legend-value {
  color: #e7eff0;
}

/* ---------- Map pin popup card (Dashboard hover tooltip + Project
   Selector click popup) — maplibregl.Popup renders its DOM straight
   into <body>, outside .po-page/.ps-page, and previously only got its
   background from maplibre's own CSS (always white) while its text
   inherited color: var(--ink) from body (catalog.css), which flips to
   a near-white token in dark mode. Result: near-invisible light text
   on a white card, exactly like the report. Force an explicit dark
   card here instead of relying on inheritance. */
html[data-theme="dark"] .maplibregl-popup-content {
  background: #18272c;
  color: #e7eff0;
  box-shadow: 0 4px 16px rgba(0,0,0,.45);
}
html[data-theme="dark"] .maplibregl-popup-content b,
html[data-theme="dark"] .maplibregl-popup-content .ps-popup-title {
  color: #e7eff0;
}
html[data-theme="dark"] .ps-popup-category {
  color: #9ab0b4;
}
html[data-theme="dark"] .maplibregl-popup-close-button {
  color: #9ab0b4;
}
/* Tint the little arrow to match the new dark card (only the color,
   not the border-style/width, so the pointer keeps its shape). */
html[data-theme="dark"] .maplibregl-popup-anchor-top .maplibregl-popup-tip,
html[data-theme="dark"] .maplibregl-popup-anchor-top-left .maplibregl-popup-tip,
html[data-theme="dark"] .maplibregl-popup-anchor-top-right .maplibregl-popup-tip {
  border-bottom-color: #18272c;
}
html[data-theme="dark"] .maplibregl-popup-anchor-bottom .maplibregl-popup-tip,
html[data-theme="dark"] .maplibregl-popup-anchor-bottom-left .maplibregl-popup-tip,
html[data-theme="dark"] .maplibregl-popup-anchor-bottom-right .maplibregl-popup-tip {
  border-top-color: #18272c;
}
html[data-theme="dark"] .maplibregl-popup-anchor-left .maplibregl-popup-tip {
  border-right-color: #18272c;
}
html[data-theme="dark"] .maplibregl-popup-anchor-right .maplibregl-popup-tip {
  border-left-color: #18272c;
}

/* ---------- Battery Station: Charging Schedule + Swap History ---------- */
html[data-theme="dark"] .bs-page .bs-schedule-row,
html[data-theme="dark"] .bs-page .bs-tt thead th,
html[data-theme="dark"] .bs-page thead .bs-tt-sticky {
  background: #1d3035;
  color: #e7eff0;
  border-color: #2a3d42;
}
html[data-theme="dark"] .bs-page .bs-schedule-row:hover {
  background: #20363b;
}
html[data-theme="dark"] .bs-page .bs-tt-scroll {
  background: #18272c;
  border-color: #2a3d42;
}
html[data-theme="dark"] .bs-page .bs-tt,
html[data-theme="dark"] .bs-page .bs-tt th,
html[data-theme="dark"] .bs-page .bs-tt td {
  border-color: #2a3d42;
}
html[data-theme="dark"] .bs-page .bs-tt-sticky {
  background: #18272c;
}
html[data-theme="dark"] .bs-page .bs-tt-swap-dot {
  background: #315158;
  box-shadow: 0 0 0 1px rgba(231,239,240,.28);
}
html[data-theme="dark"] .bs-page .skeleton {
  background: #203238;
}
html[data-theme="dark"] .bs-page .skeleton::after {
  background: linear-gradient(90deg, transparent, rgba(255,255,255,.10), transparent);
}

/* ---------- Login page: ensure the standalone login document receives
   the same dark theme as the authenticated application. ---------- */
html[data-theme="dark"] body:has(.login-shell) {
  background: #101b1f;
  color: #e7eff0;
}
html[data-theme="dark"] .login-shell {
  background: #101b1f;
}
html[data-theme="dark"] .login-form-panel {
  background: #18272c;
  color: #e7eff0;
}
html[data-theme="dark"] .login-form-head h1,
html[data-theme="dark"] .login-form-head p,
html[data-theme="dark"] .form-group label,
html[data-theme="dark"] .login-footnote {
  color: #e7eff0;
}
html[data-theme="dark"] .login-form-head p,
html[data-theme="dark"] .login-footnote {
  color: #9ab0b4;
}
html[data-theme="dark"] .input-control {
  background: #16262b;
  border-color: #2a3d42;
}
html[data-theme="dark"] .input-control input,
html[data-theme="dark"] .input-control select {
  color: #e7eff0;
}
html[data-theme="dark"] .input-control input::placeholder {
  color: #839b9f;
}
html[data-theme="dark"] .input-control__icon,
html[data-theme="dark"] .input-control__caret,
html[data-theme="dark"] .input-control__toggle {
  color: #9ab0b4;
}
html[data-theme="dark"] .input-control__toggle:hover {
  color: #69c8c2;
}
html[data-theme="dark"] .input-control select option {
  background: #18272c;
  color: #e7eff0;
}

/* ---------- Sidebar Dashboard quick-action: dark-mode fix ---------- */
html[data-theme="dark"] .edash-sidebar .sb-btn-primary,
html[data-theme="dark"] .edash-sidebar .sb-btn-secondary {
  background: #182c31;
  border-color: #294047;
  color: #dce8e9;
}
html[data-theme="dark"] .edash-sidebar .sb-btn-primary i,
html[data-theme="dark"] .edash-sidebar .sb-btn-secondary i {
  color: #69c8c2;
}
html[data-theme="dark"] .edash-sidebar .sb-btn-primary:hover,
html[data-theme="dark"] .edash-sidebar .sb-btn-secondary:hover {
  background: rgba(33,167,174,.14);
  border-color: rgba(105,200,194,.45);
  color: #e7eff0;
}
html[data-theme="dark"] .edash-sidebar .sb-btn.is-active {
  background:
    linear-gradient(rgba(33,167,174,.20), rgba(33,167,174,.20)) padding-box,
    linear-gradient(90deg, #21a7ae, #69c8c2) border-box;
  border-color: transparent;
  color: #e7eff0;
}
html[data-theme="dark"] .edash-sidebar .sb-btn.is-active i {
  color: #69c8c2;
}
/* ---------- Ticketing (WebDev) ---------- */
/* ticketing.css defines its own local token family (--tk-*) in :root
   and is not overridden anywhere for dark mode -- that's why the
   heading blends into the page background and cards stay white.
   Redefining the same --tk-* tokens here (mirrors the --al-*//*--si-*
   pattern above) fixes almost everything at once since ticketing.css
   consistently uses var(--tk-*) throughout. */
html[data-theme="dark"] {
  --tk-primary: #21a7ae;
  --tk-primary-dark: #16868d;
  --tk-primary-soft: rgba(33,167,174,.16);
  --tk-success: #4fbd8a;
  --tk-warning: #e8b34a;
  --tk-danger: #ef6b6b;
  --tk-info: #69a8d6;
  --tk-success-soft: rgba(79,189,138,.14);
  --tk-warning-soft: rgba(232,179,74,.14);
  --tk-danger-soft: rgba(239,107,107,.14);
  --tk-info-soft: rgba(105,168,214,.14);
  --tk-ink: #e7eff0;
  --tk-muted: #9ab0b4;
  --tk-border: #2a3d42;
  --tk-surface: #18272c;
  --tk-page-bg: #101b1f;

  --tk-heat-0: #182228;
  --tk-heat-1: #1c3a3c;
  --tk-heat-2: #1f5c5c;
  --tk-heat-3: #1f8e8a;
  --tk-heat-4: #21a7ae;
}

/* A few spots in ticketing.css hardcode #fff instead of var(--tk-surface)
   -- those need direct overrides since redefining the token above
   doesn't reach them. */
html[data-theme="dark"] .tk-alert-icon,
html[data-theme="dark"] .tk-pill-muted,
html[data-theme="dark"] .tk-toast {
  background: var(--tk-surface);
}
html[data-theme="dark"] .tk-view-btn:hover {
  background: rgba(33,167,174,.24);
}
html[data-theme="dark"] .tk-btn-secondary:hover {
  background: #203238;
}
html[data-theme="dark"] .tk-attach-remove {
  background: rgba(16, 27, 31, 0.72);
}

/* Priority tooltip references --tk-text/--tk-text-muted (never defined
   anywhere, including light mode) so it always falls back to its
   hardcoded light-mode colors -- fix directly for dark mode. */
html[data-theme="dark"] .tk-priority-info {
  color: var(--tk-muted);
}
html[data-theme="dark"] .tk-priority-info:hover::after,
html[data-theme="dark"] .tk-priority-info:focus::after {
  background: #0b1a1d;
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.45);
}
html[data-theme="dark"] .tk-priority-info:hover::before,
html[data-theme="dark"] .tk-priority-info:focus::before {
  border-top-color: #0b1a1d;
}