@import url('https://fonts.googleapis.com/css2?family=Archivo:ital,wght@0,400;0,500;0,600;0,700;0,800;1,400&display=swap');

/* ==========================================================================
   Elelevi backoffice — 2026 chrome

   The token block is copied verbatim from the design guide
   (elelevi-app/SKETCHES/design.md §2). Do not edit the values here; if the
   customer app's src/assets/main.css changes, re-copy the block.

   Everything below the token block is either a shared component from the guide
   (§5) or admin-only chrome that the customer app has no equivalent for.
   ========================================================================== */

:root {
    /* Accent - Elelevi green */
    --elv-accent: #237F52;
    --elv-accent-dark: #1a6340;
    --elv-accent-bright: #2e9c66;
    --elv-accent-soft: #eff7f2;
    --elv-accent-border: #cfe6da;

    /* Busy - the blue a charge point lights up while a car is on it.
       Status only: never a second brand colour. */
    --elv-busy: #2b51a0;
    --elv-busy-dark: #1e3a76;
    --elv-busy-bright: #3a68c4;
    --elv-busy-soft: #eef1f7;
    --elv-busy-border: #d3dcec;

    /* Text */
    --elv-ink: #14181a;
    --elv-ink-soft: #4a5257;
    --elv-muted: #79838a;

    /* Hairlines are warm grey, never black */
    --elv-line: #ebe9e4;
    --elv-line-strong: #ddd9d1;

    /* Surfaces */
    --elv-surface: #ffffff;
    --elv-surface-alt: #f7f6f3;
    --elv-ground: #f4f3ef;

    /* A whisper of elevation */
    --elv-shadow-sm: 0 1px 2px rgba(20, 24, 26, 0.04);
    --elv-shadow: 0 1px 2px rgba(20, 24, 26, 0.04), 0 12px 28px -16px rgba(20, 24, 26, 0.18);
    --elv-shadow-lg: 0 2px 4px rgba(20, 24, 26, 0.04), 0 24px 48px -20px rgba(20, 24, 26, 0.22);
    --elv-shadow-accent: 0 12px 32px -14px rgba(35, 127, 82, 0.55);

    /* The remaining status tones, from design.md §6. Never pick one by hand. */
    --elv-waiting: #c88a1d;
    --elv-waiting-soft: #fdf7ec;
    --elv-waiting-border: #f2e4c9;
    --elv-error: #c0392b;
    --elv-error-soft: #fdf1f0;
    --elv-error-border: #f3d6d3;
    --elv-offline: #6c757d;

    /* Layout metrics */
    --elv-nav-height: 68px;
    --elv-header-height: var(--elv-nav-height);

    /* Admin-only: the fixed left rail, and the mobile bar that replaces it */
    --elv-rail-width: 268px;
    --elv-topbar-height: 56px;

    /* Bootstrap: strip every radius */
    --bs-border-radius: 0;
    --bs-border-radius-sm: 0;
    --bs-border-radius-lg: 0;
    --bs-border-radius-xl: 0;
    --bs-border-radius-2xl: 0;
    --bs-border-radius-pill: 0;

    --bs-primary: #237F52;
    --bs-primary-rgb: 35, 127, 82;
    --bs-link-color: #237F52;
    --bs-link-hover-color: #1a6340;
    --bs-body-color: #14181a;
    --bs-body-font-family: 'Archivo', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif;

    /* Third party */
    --dp-border-radius: 0;
}

/* ==========================================================================
   Base
   ========================================================================== */
html, body {
    min-height: 100%;
    margin: 0;
    font-family: 'Archivo', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
    "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif,
    "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
    background-color: var(--elv-ground);
    color: var(--elv-ink);
    font-weight: 400;
    -webkit-font-smoothing: antialiased;
}

/* The browser's own [hidden] rule loses to any class that sets display, and
   plenty here do — .panel is a flex column. Script that hides a thing by
   setting el.hidden needs this to mean what it says. */
[hidden] {
    display: none !important;
}

/* ==========================================================================
   Typography
   ========================================================================== */
h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
    font-weight: 700;
    letter-spacing: -0.02em;
    color: var(--elv-ink);
}

h1, .h1 { font-weight: 800; letter-spacing: -0.035em; }
h2, .h2 { font-weight: 800; letter-spacing: -0.03em; }

/* Small uppercase label - a label, not a heading. Use sparingly. */
.eyebrow {
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--elv-muted);
}

.eyebrow.accent { color: var(--elv-accent); }
.eyebrow.light { color: rgba(255, 255, 255, 0.85); }

/* Every figure on screen gets this. Tabular figures stop numbers jittering. */
.numeric {
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.03em;
    font-weight: 800;
}

/* The suffix on a figure - "8.4 kWh" has to read as one thing */
.unit {
    font-size: 0.5em;
    font-weight: 600;
    letter-spacing: 0;
    color: var(--elv-muted);
}

a {
    text-decoration: none !important;
    color: var(--elv-accent);
    font-weight: 500;
}

a:hover { color: var(--elv-accent-dark); }

/* Strip the padding and ground highlight.js themes bring with them, so a code
   block sits on whatever surface it was put on. .receipt opts out: it *is* the
   surface. */
code, pre:not(.receipt), .hljs {
    padding: 0 !important;
    margin: 0 !important;
    background: transparent !important;
    font-variant-ligatures: none;
}

/* ==========================================================================
   Square corners, everywhere
   ========================================================================== */
.card,
.btn,
.form-control,
.form-select,
.input-group-text,
.modal-content,
.dropdown-menu,
.alert,
.badge,
.list-group,
.list-group-item,
.nav-link,
.progress,
.progress-bar,
.form-check-input,
.page-link,
.toast,
.rounded,
.rounded-circle,
.rounded-pill {
    border-radius: 0 !important;
}

/* ==========================================================================
   Panels and rows - the workhorse of every content view
   ========================================================================== */
.panel {
    display: flex;
    flex-direction: column;
    background: var(--elv-surface);
    border: 1px solid var(--elv-line);
    box-shadow: var(--elv-shadow);
}

.panel-head {
    background: var(--elv-surface);
    border-bottom: 1px solid var(--elv-line);
    padding: 1.125rem 1.5rem;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
}

.panel-title {
    font-size: 1rem;
    font-weight: 700;
    letter-spacing: -0.015em;
    margin: 0;
}

/* A panel head that is also the control for opening the panel. The whole head
   is the hit target — a chevron on its own is a small thing to aim at — and it
   keeps the head's one job of saying what the panel holds. */
button.panel-head {
    width: 100%;
    background: var(--elv-surface);
    border: 0;
    border-bottom: 1px solid var(--elv-line);
    font: inherit;
    color: inherit;
    text-align: left;
    cursor: pointer;
    transition: background-color 160ms ease;
}

button.panel-head:hover {
    background: var(--elv-surface-alt);
}

/* Closed there is nothing under the rule, so it would double up against the
   panel's own bottom border */
button.panel-head.collapsed {
    border-bottom: 0;
}

/* The chevron points the way the panel will move: down to roll it open, up to
   roll it closed. Bootstrap puts .collapsed on the trigger while the panel is
   shut, so the un-rotated (down) state is the closed one. */
.panel-chevron {
    flex: 0 0 auto;
    font-size: 0.75rem;
    color: var(--elv-muted);
    transform: rotate(180deg);
    transition: transform 200ms ease;
}

button.panel-head.collapsed .panel-chevron,
.panel-head-main.collapsed .panel-chevron,
.row-toggle.collapsed .panel-chevron {
    transform: none;
}

/* Panels that collapse on a phone ship expanded in the markup — right for a
   desktop, and right with no JS at all. On a narrow screen the head script
   stamps .is-narrow before paint and this holds the body shut until
   collapse-init has removed .show and added .collapse-ready, at which point
   the rule stops matching and Bootstrap owns the element again. */
.is-narrow [data-collapse-mobile]:not(.collapse-ready) {
    display: none;
}

/* Part of a panel head that is the control, used where the head also carries
   links and so cannot itself be a button. It takes the title and the slack
   beside it, so the hit target is most of the bar rather than a chevron. */
.panel-head-main {
    flex: 1 1 auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    min-width: 0;
    background: transparent;
    border: 0;
    padding: 0;
    font: inherit;
    color: inherit;
    text-align: left;
    cursor: pointer;
}

@media (max-width: 767.98px) {
    /* Stacked on a phone there is no row to keep even, and a scroller inside a
       page that already scrolls is a trap. */
    .panel-fixed {
        height: auto;
    }

    .panel-fixed .panel-scroll {
        overflow-y: visible;
    }
}

@media (prefers-reduced-motion: reduce) {
    .panel-chevron { transition: none; }
}

/* margin-top: auto so panels sitting in a row line their footers up */
.panel-foot {
    margin-top: auto;
    padding: 1rem 1.5rem;
    border-top: 1px solid var(--elv-line);
    background: var(--elv-surface-alt);
}

.panel-body {
    padding: 1.5rem;
}

/* A panel inside a panel keeps its hairline but drops its lift. Two stacked
   shadows read as clutter, and the inner one is not floating above anything —
   it is part of the panel it sits in. */
.panel .panel {
    box-shadow: none;
}

/* A panel divided into columns: the hairline between them is the panel's own
   rule continuing, so it runs edge to edge and turns horizontal when the
   columns stack. Use on a .row.g-0 inside a panel. */
.panel-split > [class*="col-"] + [class*="col-"] {
    border-top: 1px solid var(--elv-line);
}

@media (min-width: 992px) {
    .panel-split > [class*="col-"] + [class*="col-"] {
        border-top: 0;
        border-left: 1px solid var(--elv-line);
    }
}

.row-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 1.125rem 1.5rem;
    border-bottom: 1px solid var(--elv-line);
    transition: background-color 160ms ease;
}

.row-item:hover { background: var(--elv-surface-alt); }
.row-item.column { display: block; }
.row-item:last-child { border-bottom: 0; }

.row-text { min-width: 0; }

.row-title {
    font-weight: 600;
    font-size: 0.9375rem;
    letter-spacing: -0.01em;
    color: var(--elv-ink);
}

.row-meta {
    font-size: 0.8125rem;
    color: var(--elv-muted);
    margin-top: 0.2rem;
}

.row-value {
    font-size: 1.0625rem;
    white-space: nowrap;
}

/* A row whose value is a form control. The old views hardcoded a 400px width
   on every input; the control gets a real width here instead, and the row is
   allowed to wrap so a narrow panel stacks the label above the field rather
   than crushing it. */
.row-item .row-value > .form-control,
.row-item .row-value > .form-select,
.row-item .row-value > .input-group {
    width: 17rem;
    max-width: 100%;
}

.row-item:has(> .row-value > .form-control),
.row-item:has(> .row-value > .form-select),
.row-item:has(> .row-value > .input-group) {
    flex-wrap: wrap;
}

@media (max-width: 575.98px) {
    .row-item .row-value > .form-control,
    .row-item .row-value > .form-select,
    .row-item .row-value > .input-group,
    .row-item:has(> .row-value > .form-control) .row-value,
    .row-item:has(> .row-value > .form-select) .row-value,
    .row-item:has(> .row-value > .input-group) .row-value {
        width: 100%;
    }
}

/* A disclosure sitting in a list of rows, for fields that are set once at
   commissioning and would otherwise push the everyday ones off the screen.
   Shaped like a .row-item so the hairline rhythm carries through it, but
   quieter than a row title — it is a control, not a value. */
.row-toggle {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    width: 100%;
    padding: 1.125rem 1.5rem;
    border: 0;
    border-bottom: 1px solid var(--elv-line);
    background: none;
    text-align: left;
    font-size: 0.8125rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    color: var(--elv-muted);
    transition: background-color 160ms ease;
}

.row-toggle:hover { background: var(--elv-surface-alt); }
.row-toggle .panel-chevron { margin-left: auto; }

/* Closed there is nothing under the rule — same reason as .panel-head */
.row-toggle.collapsed { border-bottom: 0; }

/* Tiles sitting flush: the container colour shows through as hairlines, so no
   tile carries its own border and nothing doubles up against the panel edge */
.tile-grid {
    display: grid;
    gap: 1px;
    background: var(--elv-line);
}

/* A tile grid standing on its own rather than filling a panel needs its own
   outer hairline - the gaps only draw the ones between tiles */
.tile-grid.bordered {
    border: 1px solid var(--elv-line);
}

/* Explicit column counts, so the row always divides evenly - auto-fit silently
   orphans the last tile when the container narrows */
.tile-grid-4 { grid-template-columns: 1fr; }

@media (min-width: 576px) {
    .tile-grid-4 { grid-template-columns: repeat(2, 1fr); }
}

@media (min-width: 1200px) {
    .tile-grid-4 { grid-template-columns: repeat(4, 1fr); }
}

.tile {
    background: var(--elv-surface);
    padding: 1.125rem 1.5rem;
}

.tile .eyebrow { display: block; margin-bottom: 0.35rem; }

.tile-figure {
    font-size: 1.5rem;
    line-height: 1.15;
}

.card {
    background-color: var(--elv-surface);
    border: 1px solid var(--elv-line);
    box-shadow: var(--elv-shadow);
}

.card-body { padding: 1.5rem; }

/* ==========================================================================
   Tags - a tag states a fact, it is never clickable
   ========================================================================== */
.tag {
    display: inline-block;
    padding: 0.3rem 0.55rem;
    background: var(--elv-surface-alt);
    border: 1px solid var(--elv-line);
    color: var(--elv-ink-soft);
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    white-space: nowrap;
}

.tag-accent {
    background: var(--elv-accent-soft);
    border-color: var(--elv-accent-border);
    color: var(--elv-accent-dark);
}

/* Status tones, straight off design.md §6. Never hand-pick a status colour. */
.tag-busy {
    background: var(--elv-busy-soft);
    border-color: var(--elv-busy-border);
    color: var(--elv-busy-dark);
}

.tag-waiting {
    background: var(--elv-waiting-soft);
    border-color: var(--elv-waiting-border);
    color: #8a5f11;
}

.tag-error {
    background: var(--elv-error-soft);
    border-color: var(--elv-error-border);
    color: var(--elv-error);
}

/* The lamp. A moving state shows a dot, a steady state shows its icon. */
.dot {
    display: inline-block;
    width: 0.5rem;
    height: 0.5rem;
    background: currentColor;
    vertical-align: 0.02em;
}

/* Whichever mark the tag is carrying, it needs the same air before the word */
.tag > .dot,
.tag > i {
    margin-right: 0.35rem;
}

.tone-free, .tone-done { color: var(--elv-accent); }
.tone-busy { color: var(--elv-busy); }
.tone-waiting { color: var(--elv-waiting); }
.tone-error { color: var(--elv-error); }
.tone-offline { color: var(--elv-offline); }

/* ==========================================================================
   Side-panel lists

   The four panels along the top of Transactions each answer a small question
   with a handful of rows. At that size a graphic is decoration: five status
   counts and three hardware vendors are read off the numbers, and a bar drawn
   beside them adds ink without adding an answer. So they are lists.

   What they do need is to not stretch the page — the list scrolls inside the
   panel, and the row of panels stays a row.
   ========================================================================== */

/* Denser than a full-width .row-item, which is sized for a page-wide table */
.row-item.compact {
    padding: 0.625rem 1.25rem;
}

/* A row of side panels is one height, so the row is predictable and one long
   list cannot stretch its neighbours: the height is on the panel, and the list
   inside scrolls. Capping the list instead would let a panel end up taller than
   its list and leave dead space under it. */
.panel-fixed {
    height: 20rem;
    /* A box with a height contains what it holds. Normally the list inside is
       what scrolls and this never fires; it is here so that if anything breaks
       the flex chain between the panel and that list, the panel scrolls itself
       rather than spilling the whole list over the boxes below. auto, not
       hidden — a broken chain should still leave the content reachable. */
    overflow: auto;
}

/* Except when it is shut, where a fixed height would hold a 20rem box with
   nothing in it. The row is .align-items-start, so a short panel here stays
   short instead of being stretched back by its neighbours. The height does not
   animate: mid-transition the body is .collapsing rather than .collapse, so
   this stops matching and the panel takes its full height for that moment. */
.panel-fixed:has(> .collapse:not(.show)) {
    height: auto;
}

/* The collapse wrapper is now the flex child between the panel and the list, so
   it has to pass the sizing through or the list cannot scroll. */
.panel-fixed > .collapse.show,
.panel-fixed > .collapsing {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
}

/* Fills whatever the head and the footer leave, and scrolls. min-height: 0 is
   what lets a flex item shrink below its content — without it the list pushes
   the panel open instead of scrolling. */
.panel-scroll {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}

/* The count on the right of a panel head. The figure keeps ink weight; any
   words qualifying it stay muted, so a column of these reads as numbers.
   Not .unit — that is half the parent's size, which at body size is 7px. */
.panel-count {
    font-size: 0.8125rem;
    color: var(--elv-muted);
    white-space: nowrap;
}

.panel-count .numeric {
    font-size: 0.9375rem;
    color: var(--elv-ink);
}

/* .table-empty is sized for a page-wide table; this is its side-panel size */
.panel-empty {
    padding: 1.5rem 1.25rem;
    color: var(--elv-muted);
    font-size: 0.875rem;
}

/* The label above a chart that has no title of its own */
.figure-label {
    display: block;
    margin-bottom: 0.5rem;
}

.truncate {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Bootstrap ships no min-width utility, and a flex child will not shrink below
   its content without one — which is what stops .truncate working */
.min-w-0 {
    min-width: 0 !important;
}

/* Tabular figures without .numeric's weight. For things that are read rather
   than compared — timestamps, identifiers, reference numbers — where the
   digits still need to stop jittering but the value is not the headline. */
.tnum {
    font-variant-numeric: tabular-nums;
}

/* A receipt, a payload, anything the charge point or the terminal printed.
   Monospace on an inset, hairline bordered — never the black box it was. */
.receipt {
    background: var(--elv-surface-alt);
    border: 1px solid var(--elv-line);
    padding: 1rem;
    margin: 0;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.8125rem;
    line-height: 1.5;
    white-space: pre-wrap;
    word-break: break-word;
    overflow-x: auto;
    color: var(--elv-ink-soft);
}

/* ==========================================================================
   Buttons
   ========================================================================== */
.btn {
    font-weight: 600;
    letter-spacing: -0.005em;
    border-width: 1px;
    padding: 0.5rem 1rem;
    transition: background-color 160ms ease, color 160ms ease, border-color 160ms ease,
    box-shadow 160ms ease, transform 160ms ease;
}

/* A press should feel physical */
.btn:active { transform: translateY(1px); }

.btn-primary {
    background-color: var(--elv-accent) !important;
    border-color: var(--elv-accent) !important;
    color: #ffffff !important;
    box-shadow: 0 1px 2px rgba(20, 24, 26, 0.08), 0 8px 18px -10px rgba(35, 127, 82, 0.7);
}

.btn-primary:hover, .btn-primary:focus {
    background-color: var(--elv-accent-dark) !important;
    border-color: var(--elv-accent-dark) !important;
    box-shadow: 0 1px 2px rgba(20, 24, 26, 0.1), 0 12px 22px -10px rgba(35, 127, 82, 0.8);
}

.btn-outline-primary {
    background-color: var(--elv-surface);
    color: var(--elv-accent);
    border: 1px solid var(--elv-accent);
}

.btn-outline-primary:hover {
    background-color: var(--elv-accent-soft);
    border: 1px solid var(--elv-accent);
    color: var(--elv-accent-dark);
}

/* Neutral secondary - soft edge, lifts on hover */
.btn-outline-ink {
    background-color: var(--elv-surface);
    color: var(--elv-ink);
    border: 1px solid var(--elv-line-strong);
    box-shadow: var(--elv-shadow-sm);
}

.btn-outline-ink:hover {
    background-color: var(--elv-surface-alt);
    border-color: #c9c4b9;
    color: var(--elv-ink);
    box-shadow: var(--elv-shadow);
}

.btn-outline-danger {
    background-color: var(--elv-surface);
    color: var(--elv-error);
    border: 1px solid var(--elv-error-border);
}

.btn-outline-danger:hover {
    background-color: var(--elv-error-soft);
    border-color: var(--elv-error);
    color: var(--elv-error);
}

.btn-light {
    background-color: var(--elv-surface-alt);
    border-color: transparent;
    color: var(--elv-ink);
}

.btn-light:hover {
    background-color: #eeece7;
    border-color: transparent;
}

.btn-link { text-decoration: none !important; }

.btn-sm { padding: 0.3rem 0.65rem; font-size: 0.8125rem; }

/* ==========================================================================
   Forms
   ========================================================================== */
.form-control, .form-select {
    border: 1px solid var(--elv-line-strong);
    padding: 0.5rem 0.75rem;
    background-color: var(--elv-surface);
    transition: border-color 160ms ease, box-shadow 160ms ease;
}

.form-control:focus, .form-select:focus {
    border-color: var(--elv-accent);
    box-shadow: 0 0 0 3px rgba(35, 127, 82, 0.12);
}

.form-check-input:checked {
    background-color: var(--elv-accent);
    border-color: var(--elv-accent);
}

.form-check-input:focus {
    border-color: var(--elv-accent);
    box-shadow: none;
}

.form-label {
    font-size: 0.8125rem;
    font-weight: 600;
    letter-spacing: -0.01em;
    margin-bottom: 0.35rem;
}

.form-text {
    font-size: 0.8125rem;
    color: var(--elv-muted);
}

.input-group-text {
    background: var(--elv-surface-alt);
    border-color: var(--elv-line-strong);
    color: var(--elv-muted);
    font-size: 0.8125rem;
    font-weight: 600;
}

/* ==========================================================================
   Tables - the backoffice is table-heavy in a way the customer app is not,
   so this is admin-only. Same hairlines, same warm hover as .row-item.
   ========================================================================== */
.table {
    --bs-table-bg: transparent;
    color: var(--elv-ink);
    background-color: transparent;
    margin-bottom: 0;
    font-size: 0.875rem;
    vertical-align: middle;
}

.table > thead > tr > th {
    background: var(--elv-surface-alt);
    border-bottom: 1px solid var(--elv-line);
    color: var(--elv-muted);
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    white-space: nowrap;
    padding: 0.75rem 1rem;
}

.table > tbody > tr > td {
    border-bottom: 1px solid var(--elv-line);
    padding: 0.8125rem 1rem;
    color: var(--elv-ink);
}

.table > tbody > tr:last-child > td { border-bottom: 0; }

.table-hover > tbody > tr:hover > * {
    --bs-table-accent-bg: transparent;
    background-color: var(--elv-surface-alt);
    color: var(--elv-ink);
}

.table-striped > tbody > tr:nth-of-type(odd) > * {
    --bs-table-accent-bg: transparent;
    background-color: var(--elv-surface-alt);
    color: var(--elv-ink);
}

/* A sortable heading. The label itself is the link, so the target is the
   width of the column rather than a caret someone has to aim at, and the
   heading keeps the same size and weight as the ones that do not sort. */
.table > thead > tr > th > .th-sort {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    color: inherit;
    text-decoration: none;
    letter-spacing: inherit;
}

.table > thead > tr > th > .th-sort:hover { color: var(--elv-ink); }
.table > thead > tr > th > .th-sort.is-active { color: var(--elv-ink); }

/* The caret on the column actually sorted is solid; the neutral glyph on the
   others is faint enough to read as "you could" rather than "this is". */
.table > thead > tr > th > .th-sort > i { font-size: 0.85em; opacity: 0.3; }
.table > thead > tr > th > .th-sort:hover > i { opacity: 0.6; }
.table > thead > tr > th > .th-sort.is-active > i { opacity: 1; }

/* A right aligned column keeps its numbers at the right edge, so its heading
   has to sit there too */
.table > thead > tr > th.text-end > .th-sort {
    width: 100%;
    justify-content: flex-end;
}

/* A table inside a panel sits flush against its edges */
/* A collapsible panel puts its body inside a .collapse, so the table is a
   grandchild rather than a child. Without the second selector the table keeps
   its full width, pushes the panel wide and scrolls the whole page sideways. */
.panel > .table-wrap,
.panel > .collapse > .table-wrap { overflow-x: auto; }
.panel > .table-wrap > .table > thead > tr > th:first-child,
.panel > .table-wrap > .table > tbody > tr > td:first-child,
.panel > .collapse > .table-wrap > .table > thead > tr > th:first-child,
.panel > .collapse > .table-wrap > .table > tbody > tr > td:first-child { padding-left: 1.5rem; }
.panel > .table-wrap > .table > thead > tr > th:last-child,
.panel > .table-wrap > .table > tbody > tr > td:last-child,
.panel > .collapse > .table-wrap > .table > thead > tr > th:last-child,
.panel > .collapse > .table-wrap > .table > tbody > tr > td:last-child { padding-right: 1.5rem; }

/* The wrapper is a flex item now; let it shrink under its content so the
   scroller inside it is what scrolls, not the page. */
.panel > .collapse { min-width: 0; }

/* An empty table says so rather than showing a bare rule */
.table-empty {
    padding: 3rem 1.5rem;
    text-align: center;
    color: var(--elv-muted);
    font-size: 0.9375rem;
}

/* The event log's cells sit at the top of their row. A payload can be many
   times the height of the two columns labelling it, and a tag floating in the
   middle of that gap stops reading as the label of the row it belongs to. */
.evt-log > tbody > tr > td {
    vertical-align: top;
}

/* An OCPP payload, rendered inside a cell of the event log.

   A payload is a small document inside a table cell. What it holds really is
   tabular - measurands, configuration keys, per-connector setpoints - so it
   keeps its tables, only at a size that does not compete with the log around
   it, and with a border of its own so it reads as a block on the row rather
   than as more of the same table. */
.evt-payload {
    max-width: 64rem;
}

.evt-payload > * + * {
    margin-top: 0.75rem;
}

.evt-payload .table {
    font-size: 0.8125rem;
    border: 1px solid var(--elv-line);
}

.evt-payload .table > thead > tr > th {
    padding: 0.35rem 0.6rem;
    font-size: 0.625rem;
}

.evt-payload .table > tbody > tr > td {
    padding: 0.35rem 0.6rem;
}

.evt-payload .receipt {
    font-size: 0.75rem;
}

/* Payloads run to hundreds of lines. Past a screenful the raw block scrolls
   rather than pushing the next event out of reach. */
.evt-raw {
    max-height: 26rem;
    overflow-y: auto;
}

/* ==========================================================================
   Address search and map - the location editor's geocoder
   ========================================================================== */

/* The results hang under the input, so the wrapper is what they hang from */
.address-search {
    position: relative;
}

.address-results {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    z-index: 1001;
    max-height: 15rem;
    overflow-y: auto;
    background: var(--elv-surface);
    border: 1px solid var(--elv-line);
    box-shadow: var(--elv-shadow);
}

/* With nothing in it the box was still drawing its own border as a hairline
   strip under the field */
.address-results:empty {
    display: none;
}

/* Buttons, not list items: a result is something you activate, and a <li> with
   a click handler cannot be reached from the keyboard */
.address-results button {
    display: block;
    width: 100%;
    padding: 0.5rem 0.75rem;
    background: none;
    border: 0;
    border-bottom: 1px solid var(--elv-line);
    font: inherit;
    font-size: 0.8125rem;
    color: var(--elv-ink);
    text-align: left;
    cursor: pointer;
}

.address-results button:last-child {
    border-bottom: 0;
}

.address-results button:hover,
.address-results button:focus {
    background: var(--elv-surface-alt);
}

/* The map is a control the operator sets a value with, so it wears an input's
   hairline rather than floating on the panel */
.map-canvas {
    height: 24rem;
    border: 1px solid var(--elv-line);
}

/* ==========================================================================
   Tabs - flat, sitting on a rule, 2px accent underline scaling in from the left
   ========================================================================== */
.elv-tabs {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    border-bottom: 1px solid var(--elv-line);
    margin-bottom: 1.5rem;
    padding: 0;
    list-style: none;
}

.elv-tabs::-webkit-scrollbar { display: none; }

.elv-tabs a,
.elv-tabs button {
    position: relative;
    display: block;
    appearance: none;
    background: transparent;
    border: 0;
    padding: 0.75rem 0.125rem;
    margin-right: 1.75rem;
    font-family: inherit;
    font-size: 0.9375rem;
    font-weight: 500;
    color: var(--elv-muted);
    letter-spacing: -0.01em;
    white-space: nowrap;
    transition: color 160ms ease;
}

.elv-tabs a::after,
.elv-tabs button::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: -1px;
    height: 2px;
    background: var(--elv-accent);
    transform: scaleX(0);
    transform-origin: left;
    transition: transform 220ms cubic-bezier(0.4, 0, 0.2, 1);
}

.elv-tabs a:hover,
.elv-tabs button:hover { color: var(--elv-ink); }

.elv-tabs a.active,
.elv-tabs button.active {
    color: var(--elv-ink);
    font-weight: 600;
}

.elv-tabs a.active::after,
.elv-tabs button.active::after { transform: scaleX(1); }

.elv-tabs .tab-filler { flex: 1 1 auto; }

/* ==========================================================================
   Dropdowns, modals, alerts, badges
   ========================================================================== */
.dropdown-menu {
    border: 1px solid var(--elv-line);
    box-shadow: var(--elv-shadow-lg);
    padding: 0.25rem;
}

.dropdown-item {
    padding: 0.6rem 1rem;
    font-weight: 500;
    font-size: 0.9375rem;
}

.dropdown-item:active, .dropdown-item:focus, .dropdown-item:hover {
    background-color: var(--elv-accent-soft);
    color: var(--elv-ink);
}

.dropdown-divider { border-top-color: var(--elv-line); }

.modal-content {
    background-color: var(--elv-surface) !important;
    border: 1px solid var(--elv-line);
    box-shadow: 0 32px 80px -24px rgba(20, 24, 26, 0.35);
}

.modal-header {
    border-bottom: 1px solid var(--elv-line);
    background: var(--elv-surface);
    padding: 1.25rem 1.5rem;
}

.modal-header h4, .modal-title {
    margin: 0;
    font-size: 1.1875rem;
}

.modal-body { padding: 1.5rem; }

.modal-footer {
    border-top: 1px solid var(--elv-line);
    background: var(--elv-surface-alt);
    padding: 1rem 1.5rem;
}

.modal-backdrop { background: rgba(20, 24, 26, 0.35); }
.modal-backdrop.show { opacity: 1; }
.modal.fade.show { backdrop-filter: blur(6px); }

.alert {
    font-size: 0.9375rem !important;
    line-height: 1.55rem !important;
    color: var(--elv-ink) !important;
    border: 1px solid var(--elv-line);
    border-left: 3px solid var(--elv-line-strong);
    padding: 1rem 1.25rem;
}

.alert h5 { font-weight: 700 !important; color: var(--elv-ink) !important; }

.alert-success {
    background-color: var(--elv-accent-soft);
    border-color: var(--elv-accent-border);
    border-left-color: var(--elv-accent);
}

.alert-info {
    background-color: var(--elv-surface-alt);
    border-color: var(--elv-line);
    border-left-color: var(--elv-ink-soft);
}

.alert-danger {
    background-color: var(--elv-error-soft);
    border-color: var(--elv-error-border);
    border-left-color: var(--elv-error);
}

.alert-warning {
    background-color: var(--elv-waiting-soft);
    border-color: var(--elv-waiting-border);
    border-left-color: var(--elv-waiting);
}

.badge {
    font-weight: 700;
    letter-spacing: 0.02em;
    padding: 0.4em 0.6em;
}

.list-group-item {
    border-color: var(--elv-line);
    background-color: transparent;
}

hr {
    margin: 1.25rem 0;
    height: 1px;
    border: 0;
    opacity: 1;
    background: var(--elv-line);
}

/* Bootstrap colour overrides */
.bg-primary { background-color: var(--elv-accent) !important; }
.border-primary { border-color: var(--elv-accent) !important; }
.text-primary { color: var(--elv-accent) !important; }
.bg-success { background-color: var(--elv-accent) !important; }
.text-success { color: var(--elv-accent) !important; }
.text-muted { color: var(--elv-muted) !important; }
.text-danger { color: var(--elv-error) !important; }

/* ==========================================================================
   Chrome: the fixed left rail

   Admin-only. The customer app has a single top navbar because it has five
   destinations; the backoffice has fourteen, so they live in a rail grouped
   under .eyebrow section labels.
   ========================================================================== */
.adm-rail {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 1030;
    display: flex;
    flex-direction: column;
    width: var(--elv-rail-width);
    height: 100vh;
    background: var(--elv-surface);
    border-right: 1px solid var(--elv-line);
}

.adm-rail-head {
    flex: 0 0 auto;
    padding: 1.25rem 1.25rem 1rem;
    border-bottom: 1px solid var(--elv-line);
}

.adm-brand {
    display: block;
    margin-bottom: 0.875rem;
}

.adm-brand img {
    height: 22px;
    width: auto;
    display: block;
}

/* Manager switcher. It is the one thing in the rail that changes what every
   other item points at, so it sits above the rule, not in the list. */
.adm-switch {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    width: 100%;
    padding: 0.5rem 0.625rem;
    background: var(--elv-surface-alt);
    border: 1px solid var(--elv-line);
    color: var(--elv-ink);
    font-size: 0.875rem;
    font-weight: 600;
    letter-spacing: -0.01em;
    text-align: left;
    transition: border-color 160ms ease, background-color 160ms ease;
}

.adm-switch:hover {
    background: var(--elv-surface);
    border-color: var(--elv-line-strong);
    color: var(--elv-ink);
}

.adm-switch-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.adm-switch i {
    flex: 0 0 auto;
    font-size: 0.6875rem;
    color: var(--elv-muted);
}

/* One manager: nothing to switch to, so it reads as a label, not a control */
.adm-switch.is-static {
    background: transparent;
    border-color: transparent;
    padding-left: 0;
    cursor: default;
}

.adm-nav {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 0.5rem 0 1rem;
}

.adm-nav-section {
    padding: 1rem 1.25rem 0.375rem;
}

.adm-nav-link {
    position: relative;
    display: flex;
    align-items: center;
    gap: 0.625rem;
    padding: 0.5rem 1.25rem;
    font-size: 0.875rem;
    font-weight: 500;
    letter-spacing: -0.01em;
    color: var(--elv-ink-soft);
    transition: background-color 160ms ease, color 160ms ease;
}

.adm-nav-link i {
    flex: 0 0 1.125rem;
    width: 1.125rem;
    font-size: 0.9375rem;
    text-align: center;
    color: var(--elv-muted);
    transition: color 160ms ease;
}

.adm-nav-link:hover {
    background: var(--elv-surface-alt);
    color: var(--elv-ink);
}

.adm-nav-link:hover i { color: var(--elv-ink-soft); }

/* The active item is marked by a 2px accent bar, the rail's equivalent of the
   navbar underline in the customer app */
.adm-nav-link.active {
    background: var(--elv-surface-alt);
    color: var(--elv-ink);
    font-weight: 600;
}

.adm-nav-link.active::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 2px;
    background: var(--elv-accent);
}

.adm-nav-link.active i { color: var(--elv-accent); }

.adm-nav-link.disabled {
    color: var(--elv-muted);
    opacity: 0.5;
    pointer-events: none;
}

/* Count on the right of a nav item - open maintenance items, say */
.adm-nav-count {
    margin-left: auto;
    font-size: 0.6875rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--elv-muted);
}

.adm-rail-foot {
    flex: 0 0 auto;
    border-top: 1px solid var(--elv-line);
    padding: 0.875rem 1.25rem;
}

.adm-user {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    min-width: 0;
}

.adm-user-text { min-width: 0; }

.adm-user-name {
    font-size: 0.8125rem;
    font-weight: 600;
    letter-spacing: -0.01em;
    color: var(--elv-ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.adm-user-meta {
    font-size: 0.75rem;
    color: var(--elv-muted);
}

/* ==========================================================================
   Chrome: the mobile top bar and the offcanvas the rail folds into
   ========================================================================== */
.adm-topbar {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 1030;
    display: flex;
    align-items: center;
    gap: 0.75rem;
    height: var(--elv-topbar-height);
    padding: 0 1rem;
    background: rgba(255, 255, 255, 0.9);
    backdrop-filter: saturate(180%) blur(12px);
    -webkit-backdrop-filter: saturate(180%) blur(12px);
    border-bottom: 1px solid var(--elv-line);
}

.adm-topbar-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    background: var(--elv-surface);
    border: 1px solid var(--elv-line-strong);
    color: var(--elv-ink);
}

.adm-topbar-title {
    font-size: 0.9375rem;
    font-weight: 700;
    letter-spacing: -0.015em;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.offcanvas { border-right: 1px solid var(--elv-line); }
.offcanvas .adm-rail { position: static; width: 100%; height: 100%; border-right: 0; }

/* ==========================================================================
   Chrome: the content area
   ========================================================================== */
.adm-main {
    min-height: 100vh;
    padding-top: var(--elv-topbar-height);
}

@media (min-width: 992px) {
    .adm-main {
        margin-left: var(--elv-rail-width);
        padding-top: 0;
    }
}

/* Fills the width beside the rail. No max-width: the backoffice is tables, and
   a session table wants every column it can get - capping the page just leaves
   dead ground on the right of a wide monitor. Anything that should not run to
   full measure is narrowed where it is used, not here.

   Never hardcode this padding in a view - a banner can appear above the
   content and everything below has to move with it. */
.adm-page {
    padding: 1.75rem 1.5rem 3rem;
}

@media (max-width: 767.98px) {
    .adm-page { padding: 1.25rem 1rem 2.5rem; }
}

/* The one header every view gets: where it is, what it is, what you can do */
.adm-page-head {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 1rem;
    margin-bottom: 1.75rem;
}

.adm-page-title {
    font-size: 1.75rem;
    font-weight: 800;
    letter-spacing: -0.035em;
    margin: 0;
}

.adm-page-lede {
    font-size: 0.9375rem;
    color: var(--elv-muted);
    margin: 0.35rem 0 0;
}

.adm-page-actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
}

/* ==========================================================================
   Public pages: login and shared location reports
   ========================================================================== */
.pub-shell {
    min-height: 100vh;
    background: var(--elv-surface-alt);
    color: var(--elv-ink);
}

.pub-main {
    min-height: 100vh;
    padding: 2rem 1rem 3rem;
}

.pub-page {
    width: min(100%, 76rem);
    margin: 0 auto;
}

.pub-page.pub-page-narrow {
    width: min(100%, 28rem);
}

.pub-brandbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: 1.5rem;
}

.pub-brand {
    display: inline-flex;
    align-items: center;
    gap: 0.75rem;
    color: inherit;
    text-decoration: none;
}

.pub-brand:hover {
    color: inherit;
    opacity: 1;
}

.pub-brand img {
    display: block;
    height: 2rem;
    width: auto;
}

.pub-page-head {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 1rem;
    margin-bottom: 1.75rem;
}

.pub-page-title {
    font-size: 1.75rem;
    font-weight: 800;
    letter-spacing: -0.035em;
    margin: 0;
}

.pub-page-lede {
    font-size: 0.9375rem;
    color: var(--elv-muted);
    margin: 0.35rem 0 0;
}

.pub-page-actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
}

@media (max-width: 767.98px) {
    .pub-main {
        padding: 1.25rem 1rem 2.5rem;
    }

    .pub-page-title {
        font-size: 1.5rem;
    }
}

/* A named part of a longer page. Sits on the ground between panels, carrying
   its own tools on the right - the way .panel-head does inside a panel. */
.adm-section {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 1rem;
    margin: 2.75rem 0 1.25rem;
}

.adm-section:first-child { margin-top: 0; }

/* A section heading that is also the control for the block under it. Same
   layout as .adm-section — the button styling is stripped back so the heading
   still reads as a heading, not as a control. */
button.adm-section {
    width: 100%;
    background: transparent;
    border: 0;
    font: inherit;
    color: inherit;
    text-align: left;
    cursor: pointer;
}

.adm-section-title {
    font-size: 1.125rem;
    font-weight: 700;
    letter-spacing: -0.02em;
    margin: 0;
}

/* Breadcrumb above the title, quiet and small. The size is pinned on the items
   as well as the list because the crumbs are yielded into it from a view. */
.adm-crumbs,
.adm-crumbs li,
.adm-crumbs a {
    font-size: 0.8125rem;
    line-height: 1.4;
}

.adm-crumbs {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin: 0 0 0.5rem;
    padding: 0;
    list-style: none;
}

.adm-crumbs li { display: flex; align-items: center; gap: 0.4rem; color: var(--elv-muted); }
.adm-crumbs li + li::before { content: '/'; color: var(--elv-line-strong); }
.adm-crumbs a { color: var(--elv-muted); font-weight: 500; }
.adm-crumbs a:hover { color: var(--elv-ink); }

/* ==========================================================================
   The non-production banner. It has to be impossible to mistake for the
   product, so it is the one place a flat colour block is allowed.
   ========================================================================== */
.adm-env {
    background: var(--elv-waiting);
    color: #ffffff;
    text-align: center;
    padding: 0.5rem 1rem;
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.09em;
    text-transform: uppercase;
}

/* ==========================================================================
   Motion - mirrors the charge point's lamp, never emphasis
   ========================================================================== */
.blink {
    animation-name: toggle;
    animation-duration: 2s;
    animation-timing-function: linear;
    animation-iteration-count: infinite;
}

@keyframes toggle {
    from { visibility: hidden; }
    50% { visibility: hidden; }
    to { visibility: visible; }
}

.breathe {
    animation-name: breathe;
    animation-duration: 2.6s;
    animation-timing-function: ease-in-out;
    animation-iteration-count: infinite;
}

@keyframes breathe {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.25; }
}

@media (prefers-reduced-motion: reduce) {
    .blink, .breathe { animation: none; }
}

/* ==========================================================================
   Status bars - a period of connector history drawn as one line.

   Used two ways by the analytics page: as a timeline, where width is when,
   and as a proportional split, where width is only how much. Neither carries
   its meaning in colour alone - the caller always names the bands beside it.
   ========================================================================== */
.status-bar {
    display: flex;
    width: 100%;
    height: 22px;
    border-radius: 4px;
    overflow: hidden;
    background: var(--elv-surface-alt);
    border: 1px solid var(--elv-line);
}

.status-bar.is-empty {
    background: repeating-linear-gradient(
        45deg,
        var(--elv-surface-alt) 0 6px,
        var(--elv-surface) 6px 12px
    );
}

.status-bar-seg {
    background: var(--seg, var(--elv-offline));
    min-width: 1px;
    /* A hairline between neighbours so two adjacent stretches of the same
       colour still read as two, without a border eating a 1px segment */
    box-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.35);
}

.status-bar-seg:last-child { box-shadow: none; }

/* Nothing was recorded here. Must not resemble any status. */
.status-bar-seg.is-blank,
.status-bar-chip.is-blank {
    background: repeating-linear-gradient(
        45deg,
        var(--elv-surface-alt) 0 4px,
        var(--elv-surface) 4px 8px
    );
}

/* An error was live during this stretch, whatever the status claimed. The
   stripes sit over the status colour rather than replacing it - both facts
   are true at once and the bar has to carry both. */
.status-bar-seg.has-error,
.status-bar-chip.has-error {
    background-image: repeating-linear-gradient(
        45deg,
        rgba(255, 255, 255, 0.55) 0 3px,
        rgba(255, 255, 255, 0) 3px 7px
    );
    box-shadow: inset 0 0 0 1px var(--elv-error);
}

.status-bar-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem 1rem;
    margin-top: 0.5rem;
    font-size: 0.8125rem;
    color: var(--elv-ink-soft);
}

.status-bar-key {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}

.status-bar-chip {
    width: 10px;
    height: 10px;
    border-radius: 2px;
    background: var(--seg, var(--elv-offline));
    flex: none;
}

/* The time axis under a timeline. Five labels, both ends included, spread so
   the first sits at the left edge and the last at the right. */
.status-bar-axis {
    display: flex;
    justify-content: space-between;
    margin-top: 0.35rem;
    font-size: 0.75rem;
    color: var(--elv-muted);
}

.status-bar-axis span:first-child { text-align: left; }
.status-bar-axis span:last-child { text-align: right; }

/* One connector's row on the station view: label, then the bar taking the
   rest. Stacks under md, where a 22px bar beside a label is unreadable. */
.status-row {
    display: grid;
    grid-template-columns: 8.5rem 1fr;
    gap: 0.75rem;
    align-items: center;
    padding: 0.75rem 0;
    border-bottom: 1px solid var(--elv-line);
}

.status-row:last-child { border-bottom: 0; }

@media (max-width: 767.98px) {
    .status-row { grid-template-columns: 1fr; gap: 0.4rem; }
}
