/*!
 * open-dashboard v1.0.6
 * Framework-agnostic dashboard layout + component kit. No build step.
 * All classes are prefixed `od-`, all CSS variables `--od-`, all data
 * attributes `data-od-*`, all custom DOM events `od:*`.
 */

/* ============================== 1. Design tokens ============================== */
:root {
    --od-bg: #F6F7F9;
    --od-panel: #FFFFFF;
    --od-border: #E6E8EB;
    --od-border-light: #F1F2F5;
    --od-text: #111723;
    --od-text-secondary: #5B6472;
    --od-text-muted: #8A93A2;
    --od-text-muted-2: #9AA3B1;

    --od-accent: #2A6FDB;
    --od-accent-hover: #205BBD;
    --od-accent-bg: #EAF1FC;
    /* A separate token from --od-accent because --od-accent itself is tuned
       for borders/buttons/backgrounds, not for reading as text: on a white
       panel it only just clears WCAG AA (~4.8:1), and on a dark panel it
       falls under the 4.5:1 minimum entirely (~3.9:1). Use this token (not
       --od-accent) wherever the accent color paints readable text, e.g. an
       active tab or an open accordion summary — --od-accent-hover gives a
       comfortable ~6.4:1 on white, and the dark-theme block below overrides
       this to a lighter blue for the same margin on a dark panel. */
    --od-accent-text: var(--od-accent-hover);

    --od-success: #1F8A5B;
    --od-success-bg: #E8F4EE;
    --od-danger: #D14343;
    --od-danger-bg: #FBEBEB;
    --od-warning: #9A6700;
    --od-warning-bg: #FFF6E5;

    --od-dark: #11161F;
    --od-dark-text: #DFE6F0;
    --od-dark-border: #2B3340;

    --od-row-hover: #F4F8FF;
    --od-nav-hover: #F2F4F7;

    /* Modal/Drawer backdrop. In light mode a dark, fairly translucent
       overlay reads as "dimmed" because it pulls both the light page
       background AND any bright colored content (chart bars, pills) toward
       the same dark shade. In dark mode the page is already close to this
       color, so at the same opacity the plain background barely changes
       while colorful content still shows through at close to full strength
       — a ghostly "echo" of shapes behind the modal. The dark-theme blocks
       below use a near-black, higher-opacity overlay instead, which mutes
       colorful content enough to actually read as dimmed. */
    --od-overlay: rgba(17, 22, 31, .45);

    --od-radius-sm: 6px;
    --od-radius: 8px;
    --od-radius-lg: 12px;

    --od-font-sans: 'Inter', 'Manrope', system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    --od-font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

    --od-sidebar-width: 252px;
    --od-topbar-height: 58px;
}

/* Dark theme — three states, resolved in this order:
   1. No preference either way: light tokens above (the :root default).
   2. OS/browser prefers dark AND nothing has opted into light explicitly:
      applied automatically via the media query below.
   3. `data-od-theme` set explicitly (by OpenDashboard.Theme, see od-theme.js,
      or by hand): wins over both — "light" forces light even in a dark OS,
      "dark" forces dark even in a light OS. */
@media (prefers-color-scheme: dark) {
    :root:not([data-od-theme="light"]) {
        --od-bg: #0E1116;
        --od-panel: #161B22;
        --od-border: #262C36;
        --od-border-light: #1D222B;
        --od-text: #E7EBF1;
        --od-text-secondary: #A6AFBC;
        --od-text-muted: #757F8C;
        --od-text-muted-2: #8A93A2;

        --od-accent-bg: rgba(42, 111, 219, .16);
        --od-accent-text: #58A6FF;
        --od-success-bg: rgba(31, 138, 91, .16);
        --od-danger-bg: rgba(209, 67, 67, .16);
        --od-warning-bg: rgba(154, 103, 0, .18);

        --od-row-hover: rgba(42, 111, 219, .08);
        --od-nav-hover: rgba(255, 255, 255, .05);
        --od-overlay: rgba(2, 4, 8, .72);
    }
}
[data-od-theme="dark"] {
    --od-bg: #0E1116;
    --od-panel: #161B22;
    --od-border: #262C36;
    --od-border-light: #1D222B;
    --od-text: #E7EBF1;
    --od-text-secondary: #A6AFBC;
    --od-text-muted: #757F8C;
    --od-text-muted-2: #8A93A2;

    --od-accent-bg: rgba(42, 111, 219, .16);
    --od-accent-text: #58A6FF;
    --od-success-bg: rgba(31, 138, 91, .16);
    --od-danger-bg: rgba(209, 67, 67, .16);
    --od-warning-bg: rgba(154, 103, 0, .18);

    --od-row-hover: rgba(42, 111, 219, .08);
    --od-nav-hover: rgba(255, 255, 255, .05);
    --od-overlay: rgba(2, 4, 8, .72);
}

/* ============================== 2. Reset & base ============================== */
/* Scoped to our own component roots (never the bare `*` selector) so this
   never changes box-sizing for the host page's own, unrelated elements —
   listed explicitly because .od-modal/.od-dropdown/.od-toast-region are
   commonly mounted outside .od-shell (e.g. appended straight to <body>). */
.od-shell, .od-panel, .od-stat-grid, .od-modal, .od-drawer, .od-dropdown, .od-toast-region, .od-accordion, .od-tabs, [data-od-table], .od-alert, .od-switch, .od-segmented, .od-guest-shell, .od-command-palette, .od-context-menu,
.od-shell *, .od-panel *, .od-stat-grid *, .od-modal *, .od-drawer *, .od-dropdown *, .od-toast-region *, .od-accordion *, .od-tabs *, [data-od-table] *, .od-alert *, .od-switch *, .od-segmented *, .od-guest-shell *, .od-command-palette *, .od-context-menu * {
    box-sizing: border-box;
}
.od-shell { margin: 0; }
/* A <ul>/<ol> a consumer writes into a panel/modal/etc. body (prose, a doc
   list, a numbered how-to) otherwise keeps the browser's default ~40px
   left padding, which then STACKS with any margin/padding the consumer
   adds themselves for their own indent — scoped the same way as the
   box-sizing reset above, so a host page's own unrelated lists elsewhere
   are never touched. */
:where(.od-shell, .od-panel, .od-stat-grid, .od-modal, .od-drawer, .od-dropdown, .od-toast-region, .od-accordion, .od-tabs, [data-od-table], .od-alert, .od-switch, .od-segmented, .od-guest-shell, .od-command-palette, .od-context-menu) :is(ul, ol) {
    margin: 0;
    padding: 0;
}
.od-sr-only { position: absolute !important; width: 1px !important; height: 1px !important; padding: 0 !important; margin: -1px !important; overflow: hidden !important; clip: rect(0, 0, 0, 0) !important; white-space: nowrap !important; border: 0 !important; }
.od-mono { font-family: var(--od-font-mono); }
.od-muted { color: var(--od-text-muted); font-size: 13px; }

.od-shell, .od-guest-shell, .od-shell a, .od-guest-shell a, .od-modal a, .od-drawer a, .od-dropdown a, .od-command-palette a, .od-context-menu a { color: inherit; }
.od-shell a, .od-guest-shell a, .od-modal a, .od-drawer a, .od-dropdown a, .od-command-palette a, .od-context-menu a { text-decoration: none; }
.od-shell h1, .od-guest-shell h1, .od-modal h1, .od-drawer h1 { font-size: 22px; font-weight: 800; letter-spacing: -.4px; margin: 0; }
.od-shell h2, .od-guest-shell h2, .od-modal h2, .od-drawer h2 { font-size: 14.5px; font-weight: 800; margin: 0; }
/* Typography/color are inherited properties, so this also reaches every
   descendant — but .od-modal/.od-drawer/.od-dropdown/.od-toast-region are
   commonly mounted as siblings of .od-shell (not inside it), so each root
   needs its own copy rather than relying on inheritance from .od-shell. */
.od-shell, .od-modal, .od-drawer, .od-dropdown, .od-toast-region, .od-alert, .od-switch, .od-segmented, .od-guest-shell, .od-command-palette, .od-context-menu {
    font-family: var(--od-font-sans); -webkit-font-smoothing: antialiased; color: var(--od-text);
}
.od-shell, .od-guest-shell { background: var(--od-bg); }
/* .od-shell/.od-guest-shell fill the viewport exactly (100dvh), but the
   underlying <body>/<html> stay their default (usually white) — visible
   for a moment during elastic overscroll (trackpad/mobile "bounce" past
   the end of a scrollable area). Only applies when one of our shells is
   actually on the page, so this never paints a background for a host page
   that doesn't use open-dashboard's layout at all. */
html:has(.od-shell), html:has(.od-guest-shell),
body:has(.od-shell), body:has(.od-guest-shell) {
    background: var(--od-bg);
}
/* The browser's default <body> margin (usually 8px) otherwise leaves a
   sliver of the host page's own background showing around the shell, and
   on .od-guest-shell it's enough on its own to push the page into
   scrolling even though the visible content fits. Same :has() scoping as
   the background rule above — never touches a host page's <body> unless
   one of our shells is actually on it. */
body:has(.od-shell), body:has(.od-guest-shell) {
    margin: 0;
}
/* .od-shell manages its own internal scrolling — the sidebar/topbar stay
   fixed while .od-main scrolls — so the outer document must never scroll
   on top of that (some content lengths otherwise leave the page itself
   scrollable by a second, redundant amount past .od-shell's own 100dvh
   box, effectively two independent scrollbars for the same content).
   .od-guest-shell only sets a min-height, so its page is meant to scroll
   normally and is deliberately excluded here. */
html:has(.od-shell), body:has(.od-shell) {
    height: 100%;
    overflow: hidden;
}

/* ============================== 3. Layout shell ============================== */
.od-shell { display: flex; height: 100vh; height: 100dvh; width: 100%; overflow: hidden; }

.od-sidebar-toggle { display: none; }

.od-sidebar {
    width: var(--od-sidebar-width); flex-shrink: 0; background: var(--od-panel);
    border-right: 1px solid var(--od-border); display: flex; flex-direction: column;
    min-height: 0; overflow-y: auto; overscroll-behavior: contain;
}
.od-sidebar-burger, .od-sidebar-overlay { display: none; }

.od-brand { display: flex; align-items: center; gap: 10px; padding: 18px 18px 16px; }
.od-brand-mark { width: 30px; height: 30px; border-radius: var(--od-radius); background: var(--od-text); color: var(--od-panel); display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: 16px; flex-shrink: 0; }
.od-brand-name { font-weight: 800; font-size: 15.5px; }

.od-nav-group { padding: 6px 12px; }
.od-section-label { font-size: 10.5px; font-weight: 700; letter-spacing: .8px; color: var(--od-text-muted-2); text-transform: uppercase; padding: 8px 8px 6px; }
.od-nav-list { display: flex; flex-direction: column; gap: 2px; }
.od-nav-link { display: flex; align-items: center; gap: 10px; padding: 7px 8px; border-radius: var(--od-radius); color: var(--od-text-secondary); font-weight: 600; font-size: 13.5px; cursor: pointer; }
.od-nav-link:hover { background: var(--od-nav-hover); }
.od-nav-link.is-active { background: var(--od-nav-hover); color: var(--od-text); }
.od-nav-link.is-disabled { color: var(--od-text-muted-2); cursor: default; }
.od-nav-icon { width: 24px; height: 24px; border-radius: var(--od-radius-sm); color: #fff; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 12px; flex-shrink: 0; }
.od-nav-count { font-size: 11.5px; font-weight: 700; color: var(--od-text-secondary); background: var(--od-border-light); padding: 1px 7px; border-radius: 999px; margin-left: auto; }

.od-sidebar-footer { display: flex; align-items: center; gap: 4px; margin-top: auto; padding: 14px 16px; border-top: 1px solid var(--od-border-light); }
.od-avatar { width: 28px; height: 28px; border-radius: 999px; background: var(--od-accent-bg); display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 12px; color: var(--od-accent-text); flex-shrink: 0; }
.od-account-link { flex: 1; min-width: 0; display: flex; align-items: center; gap: 9px; padding: 6px 8px; border-radius: var(--od-radius); }
.od-account-link:hover { background: var(--od-nav-hover); }
.od-account-link.is-active { background: var(--od-accent-bg); }
.od-account-link > div:last-child { min-width: 0; }
.od-account-name { font-size: 12.5px; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.od-account-role { font-size: 10.5px; color: var(--od-text-muted-2); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.od-main-col { flex: 1; display: flex; flex-direction: column; min-width: 0; }
.od-topbar { height: var(--od-topbar-height); flex-shrink: 0; background: var(--od-panel); border-bottom: 1px solid var(--od-border); display: flex; align-items: center; padding: 0 24px; gap: 16px; }
.od-topbar-title { font-size: 15px; font-weight: 700; flex: 1; min-width: 0; }
.od-main { flex: 1; overflow-y: auto; overscroll-behavior: contain; padding: 26px 30px 40px; }

/* Off-canvas sidebar on small screens — CSS-only checkbox toggle, no JS. */
@media (max-width: 820px) {
    .od-sidebar-burger { display: inline-flex; align-items: center; justify-content: center; width: 36px; height: 36px; margin-right: 6px; border-radius: var(--od-radius); cursor: pointer; color: var(--od-text-secondary); font-size: 18px; flex-shrink: 0; }
    .od-sidebar-burger:hover { background: var(--od-nav-hover); color: var(--od-text); }
    .od-sidebar { position: fixed; top: 0; left: 0; height: 100dvh; z-index: 45; transform: translateX(-100%); transition: transform .22s ease; box-shadow: 6px 0 30px rgba(17, 22, 31, .16); }
    .od-sidebar-toggle:checked ~ .od-sidebar { transform: translateX(0); }
    .od-sidebar-toggle:checked ~ .od-sidebar-overlay { display: block; position: fixed; inset: 0; background: rgba(17, 22, 31, .35); z-index: 44; }
    .od-main { padding: 18px 16px 32px; }
    .od-topbar { padding: 0 14px; }
    .od-panel-header { align-items: flex-start; }
}

/* ============================== 4. Panel ============================== */
.od-panel { position: relative; background: var(--od-panel); border: 1px solid var(--od-border); border-radius: var(--od-radius-lg); margin-bottom: 20px; }
.od-panel-body { padding: 18px; }
.od-panel-header { padding: 13px 18px; border-bottom: 1px solid var(--od-border-light); font-size: 13px; font-weight: 800; display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 8px 12px; }
.od-panel-header > div { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* ============================== 5. Stat grid ============================== */
.od-stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 14px; margin-bottom: 20px; }
.od-stat-tile { background: var(--od-panel); border: 1px solid var(--od-border); border-radius: var(--od-radius-lg); padding: 15px 16px; }
.od-stat-label { display: flex; align-items: center; gap: 6px; font-size: 11px; font-weight: 700; letter-spacing: .4px; color: var(--od-text-muted-2); text-transform: uppercase; }
.od-stat-label .od-i { font-size: 13px; }
.od-stat-value { font-size: 27px; font-weight: 800; letter-spacing: -.5px; margin-top: 6px; }
.od-stat-sub { font-size: 12px; color: var(--od-text-muted); font-weight: 600; margin-top: 2px; }

/* ============================== 6. Pill / badge ============================== */
.od-pill { display: inline-flex; align-items: center; gap: 6px; font-size: 11.5px; font-weight: 700; padding: 3px 10px; border-radius: 999px; white-space: nowrap; }
.od-pill-dot { width: 6px; height: 6px; border-radius: 999px; background: currentColor; }
.od-pill-success { color: var(--od-success); background: var(--od-success-bg); }
.od-pill-neutral { color: var(--od-text-secondary); background: var(--od-border-light); }
.od-pill-danger { color: var(--od-danger); background: var(--od-danger-bg); }
.od-pill-warning { color: var(--od-warning); background: var(--od-warning-bg); }
.od-pill-accent { color: var(--od-accent-text); background: var(--od-accent-bg); }

/* ============================== 7. Buttons ============================== */
.od-btn, .od-btn-danger, .od-btn-ghost, .od-btn-neutral {
    display: inline-flex; align-items: center; justify-content: center; gap: 6px; margin: 0;
    font-family: inherit; font-weight: 700; font-size: 13px; line-height: 1.2; white-space: nowrap;
    padding: 8px 14px; border: 1px solid transparent; border-radius: var(--od-radius);
    cursor: pointer; text-decoration: none; background: none; color: inherit;
}
.od-btn { background: var(--od-accent); color: #fff; }
.od-btn:hover { background: var(--od-accent-hover); }
.od-btn-danger { background: transparent; color: var(--od-danger); border-color: color-mix(in srgb, var(--od-danger) 30%, transparent); }
.od-btn-danger:hover { background: var(--od-danger-bg); }
.od-btn-ghost { background: var(--od-panel); border-color: var(--od-border); color: var(--od-text-secondary); }
.od-btn-ghost:hover { background: var(--od-nav-hover); color: var(--od-text); }
.od-btn-neutral { background: transparent; color: var(--od-text-secondary); border-color: var(--od-border); }
.od-btn-neutral:hover { background: var(--od-nav-hover); color: var(--od-text); }
.od-btn-sm { padding: 4px 10px; font-size: 12px; }
.od-btn-icon { width: 32px; height: 32px; padding: 0; flex-shrink: 0; }

/* ============================== 8. Form fields (CSS only, no validation logic) ============================== */
.od-field { margin-bottom: 14px; }
.od-field > label { display: block; font-size: 12.5px; font-weight: 700; color: var(--od-text-secondary); margin-bottom: 5px; }
.od-input, .od-select, .od-textarea {
    width: 100%; font-family: inherit; font-size: 13.5px; padding: 8px 11px;
    border: 1px solid var(--od-border); border-radius: var(--od-radius); background: var(--od-panel); color: var(--od-text);
}
.od-input:focus, .od-select:focus, .od-textarea:focus { outline: none; border-color: var(--od-accent); }
.od-checkbox-row { display: flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 500; color: var(--od-text-secondary); }
.od-field-error { color: var(--od-danger); font-size: 12px; font-weight: 600; margin-top: 5px; }
.od-field.has-error .od-input, .od-field.has-error .od-select, .od-field.has-error .od-textarea { border-color: var(--od-danger); }
.od-banner { padding: 10px 14px; border-radius: var(--od-radius); font-size: 13.5px; font-weight: 600; margin-bottom: 16px; }
.od-banner-error { background: var(--od-danger-bg); color: var(--od-danger); }
.od-banner-success { background: var(--od-success-bg); color: var(--od-success); }

/* ============================== 9. Table ============================== */
/* [data-od-table] clips its own corners (rather than relying on a parent
   .od-panel's overflow) so that a flush toolbar + table can sit directly
   inside a panel without clipping unrelated content (e.g. a dropdown menu)
   placed elsewhere in that same panel. */
[data-od-table] { border-radius: var(--od-radius-lg); overflow: hidden; }
.od-table-wrap { overflow-x: auto; }
.od-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.od-table th { text-align: left; font-size: 11px; font-weight: 700; letter-spacing: .5px; color: var(--od-text-muted-2); text-transform: uppercase; padding: 11px 16px; background: var(--od-border-light); border-bottom: 1px solid var(--od-border); }
.od-table td { padding: 12px 16px; border-bottom: 1px solid var(--od-border-light); vertical-align: middle; }
.od-table tbody tr:hover { background: var(--od-row-hover); }
.od-table tbody tr:last-child td { border-bottom: none; }
.od-table th[data-od-sort] { cursor: pointer; user-select: none; }
.od-table th[data-od-sort] .od-sort-icon { display: inline-block; margin-left: 4px; opacity: .4; font-size: 10px; }
.od-table th[data-od-sort].is-sorted .od-sort-icon { opacity: 1; color: var(--od-accent); }
.od-table-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 13px 16px; border-bottom: 1px solid var(--od-border-light); }
.od-table-search { position: relative; display: flex; align-items: center; min-width: 220px; }
.od-table-search input { width: 100%; font-family: inherit; font-size: 13px; padding: 7px 11px; border: 1px solid var(--od-border); border-radius: var(--od-radius); background: var(--od-panel); color: var(--od-text); }
.od-table-search input:focus { outline: none; border-color: var(--od-accent); }
.od-table-empty { padding: 32px 16px; text-align: center; color: var(--od-text-muted); font-size: 13px; }
.od-table-footer { display: flex; justify-content: flex-end; padding: 12px 16px; border-top: 1px solid var(--od-border-light); }
.od-pagination { display: inline-flex; gap: 4px; }
.od-pagination-btn { display: inline-flex; align-items: center; justify-content: center; min-width: 30px; height: 30px; padding: 0 8px; border: 1px solid var(--od-border); border-radius: var(--od-radius); font: inherit; font-size: 13px; font-weight: 700; color: var(--od-text-secondary); background: var(--od-panel); cursor: pointer; }
.od-pagination-btn:hover:not(:disabled) { background: var(--od-nav-hover); }
.od-pagination-btn.is-active { background: var(--od-accent); border-color: var(--od-accent); color: #fff; }
.od-pagination-btn.is-active:hover { background: var(--od-accent-hover); border-color: var(--od-accent-hover); }
.od-pagination-btn:disabled { opacity: .45; cursor: not-allowed; }

/* ============================== 10. Tabs ============================== */
.od-tabs { min-width: 0; }
.od-tabs-nav { display: flex; flex-wrap: wrap; gap: 4px; border-bottom: 1px solid var(--od-border); margin-bottom: 20px; }
.od-tab-button { appearance: none; display: inline-flex; align-items: center; gap: 7px; padding: 10px 16px; border: 0; border-bottom: 2px solid transparent; margin: 0 0 -1px; background: transparent; font: inherit; font-size: 13px; font-weight: 700; color: var(--od-text-muted); cursor: pointer; user-select: none; }
.od-tab-button:hover { color: var(--od-text); background: var(--od-nav-hover); }
.od-tab-button:focus-visible { outline: 2px solid var(--od-accent); outline-offset: -2px; border-radius: 5px 5px 0 0; }
.od-tab-button.is-active, .od-tab-button[aria-selected="true"] { color: var(--od-accent-text); border-bottom-color: var(--od-accent); }
.od-tab-button:disabled { opacity: .5; cursor: not-allowed; }
.od-tab-count { font-size: 11px; font-weight: 700; color: var(--od-text-secondary); background: var(--od-border-light); border-radius: 999px; padding: 1px 7px; }
.od-tab-panel { display: none; }
.od-tab-panel.is-active { display: block; }
.od-tabs:not(.od-tabs-initialized) .od-tab-panel:first-child { display: block; }

/* ============================== 11. Accordion / collapse (native <details>) ============================== */
.od-accordion { border: 1px solid var(--od-border); border-radius: var(--od-radius-lg); overflow: hidden; }
.od-accordion-item { border-bottom: 1px solid var(--od-border-light); }
.od-accordion-item:last-child { border-bottom: 0; }
.od-accordion-item > summary { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 13px 16px; cursor: pointer; list-style: none; font-weight: 800; font-size: 13.5px; color: var(--od-text); outline: none; }
.od-accordion-item > summary::-webkit-details-marker { display: none; }
.od-accordion-item > summary::after { content: "\002B"; font-size: 18px; font-weight: 400; color: var(--od-text-muted); transition: transform .15s ease; }
.od-accordion-item[open] > summary::after { transform: rotate(45deg); }
.od-accordion-item:hover > summary { background: var(--od-nav-hover); }
.od-accordion-item[open] > summary { color: var(--od-accent-text); }
.od-accordion-body { padding: 0 16px 16px; color: var(--od-text-secondary); font-size: 13.5px; line-height: 1.6; }

/* ============================== 12. Dropdown ============================== */
.od-dropdown { position: relative; display: inline-block; }
.od-dropdown-menu { position: absolute; top: calc(100% + 6px); left: 0; min-width: 180px; background: var(--od-panel); border: 1px solid var(--od-border); border-radius: var(--od-radius); box-shadow: 0 12px 28px rgba(17, 22, 31, .14); padding: 6px; z-index: 30; }
.od-dropdown-menu[hidden] { display: none; }
.od-dropdown-menu--right { left: auto; right: 0; }
.od-dropdown-item { display: flex; align-items: center; gap: 8px; width: 100%; padding: 8px 10px; border: 0; border-radius: var(--od-radius-sm); background: transparent; font: inherit; font-size: 13px; font-weight: 600; color: var(--od-text-secondary); text-align: left; cursor: pointer; }
.od-dropdown-item:hover { background: var(--od-nav-hover); color: var(--od-text); }
.od-dropdown-item--danger { color: var(--od-danger); }
.od-dropdown-item--danger:hover { background: var(--od-danger-bg); }
.od-dropdown-separator { height: 1px; margin: 6px 4px; background: var(--od-border-light); }

/* ============================== 13. Modal ============================== */
.od-modal { position: fixed; inset: 0; z-index: 60; display: none; align-items: center; justify-content: center; padding: 20px; }
.od-modal.is-open { display: flex; }
.od-modal-overlay { position: absolute; inset: 0; width: 100%; height: 100%; padding: 0; border: 0; background: var(--od-overlay); cursor: default; }
.od-modal-dialog { position: relative; z-index: 1; width: 100%; max-width: 480px; max-height: 88vh; display: flex; flex-direction: column; background: var(--od-panel); border-radius: var(--od-radius-lg); box-shadow: 0 24px 70px rgba(17, 22, 31, .25); overflow: hidden; }
.od-modal-dialog--wide { max-width: 640px; }
.od-modal-header { padding: 15px 20px; border-bottom: 1px solid var(--od-border-light); display: flex; align-items: center; justify-content: space-between; flex-shrink: 0; }
.od-modal-header h2 { font-size: 15px; }
.od-modal-close { background: none; border: none; font-size: 22px; line-height: 1; color: var(--od-text-muted); cursor: pointer; padding: 0 2px; }
.od-modal-close:hover { color: var(--od-text); }
.od-modal-body { padding: 20px; overflow-y: auto; }
.od-modal-footer { padding: 14px 20px; border-top: 1px solid var(--od-border-light); display: flex; justify-content: flex-end; gap: 8px; flex-shrink: 0; }
body.od-modal-open { overflow: hidden; }

/* ============================== 14. Toast ============================== */
.od-toast-region { position: fixed; z-index: 80; bottom: 20px; right: 20px; display: flex; flex-direction: column; gap: 10px; max-width: 340px; }
.od-toast { display: flex; align-items: flex-start; gap: 10px; padding: 12px 14px; border-radius: var(--od-radius); background: var(--od-dark); color: var(--od-dark-text); box-shadow: 0 12px 30px rgba(17, 22, 31, .25); font-size: 13px; line-height: 1.45; opacity: 0; transform: translateY(8px); transition: opacity .18s ease, transform .18s ease; }
.od-toast.is-visible { opacity: 1; transform: translateY(0); }
.od-toast-dot { width: 8px; height: 8px; border-radius: 999px; margin-top: 4px; flex-shrink: 0; background: var(--od-text-muted); }
.od-toast--success .od-toast-dot { background: var(--od-success); }
.od-toast--danger .od-toast-dot { background: var(--od-danger); }
.od-toast--warning .od-toast-dot { background: var(--od-warning); }
.od-toast-message { flex: 1; min-width: 0; }
.od-toast-close { flex-shrink: 0; background: none; border: 0; color: inherit; opacity: .6; cursor: pointer; font-size: 15px; line-height: 1; padding: 0; }
.od-toast-close:hover { opacity: 1; }

/* ============================== 15. Misc utilities ============================== */
.od-code { background: var(--od-dark); color: var(--od-dark-text); border-radius: var(--od-radius-lg); padding: 14px 16px; overflow-x: auto; font-family: var(--od-font-mono); font-size: 12px; line-height: 1.55; white-space: pre; margin: 0; }
.od-divider { height: 1px; background: var(--od-border-light); border: 0; margin: 16px 0; }

/* ============================== 16. Breadcrumbs ============================== */
.od-breadcrumbs { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; font-size: 12.5px; font-weight: 600; color: var(--od-text-muted); margin-bottom: 16px; }
.od-breadcrumbs a { color: var(--od-text-secondary); }
.od-breadcrumbs a:hover { color: var(--od-text); text-decoration: underline; }
.od-breadcrumbs .is-current { color: var(--od-text); font-weight: 700; }
.od-breadcrumb-sep { color: var(--od-text-muted-2); }

/* ============================== 17. Empty state ============================== */
.od-empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; padding: 48px 24px; text-align: center; color: var(--od-text-muted); }
.od-empty-state-icon { width: 44px; height: 44px; display: flex; align-items: center; justify-content: center; margin-bottom: 4px; border-radius: 999px; background: var(--od-nav-hover); color: var(--od-text-muted); font-size: 20px; }
.od-empty-state-title { font-size: 14px; font-weight: 800; color: var(--od-text); }
.od-empty-state-desc { font-size: 13px; max-width: 340px; line-height: 1.5; }
.od-empty-state-actions { margin-top: 6px; display: flex; gap: 8px; }

/* ============================== 18. Theme toggle ============================== */
/* Optional icon-swap helper for a <button data-od-theme-toggle>: give it two
   children (e.g. <span class="od-theme-icon-light">) and od-theme.js keeps
   aria-pressed in sync with the effective theme so exactly one shows. */
[data-od-theme-toggle] .od-theme-icon-dark { display: none; }
[data-od-theme-toggle][aria-pressed="true"] .od-theme-icon-light { display: none; }
[data-od-theme-toggle][aria-pressed="true"] .od-theme-icon-dark { display: inline; }

/* ============================== 19. Alert ============================== */
.od-alert { display: flex; align-items: flex-start; gap: 10px; padding: 12px 14px; border-radius: var(--od-radius); font-size: 13.5px; line-height: 1.5; margin-bottom: 16px; }
.od-alert-icon { flex-shrink: 0; font-size: 15px; line-height: 1.3; }
.od-alert-body { flex: 1; min-width: 0; }
.od-alert-title { font-weight: 800; margin-bottom: 2px; }
.od-alert-close { flex-shrink: 0; background: none; border: 0; opacity: .6; cursor: pointer; font-size: 15px; line-height: 1; color: inherit; padding: 0; }
.od-alert-close:hover { opacity: 1; }
.od-alert-info { background: var(--od-accent-bg); color: var(--od-accent-text); }
.od-alert-success { background: var(--od-success-bg); color: var(--od-success); }
.od-alert-warning { background: var(--od-warning-bg); color: var(--od-warning); }
.od-alert-danger { background: var(--od-danger-bg); color: var(--od-danger); }

/* ============================== 20. Switch ============================== */
.od-switch { position: relative; display: inline-flex; align-items: center; gap: 8px; cursor: pointer; font-size: 13px; font-weight: 600; color: var(--od-text-secondary); user-select: none; }
.od-switch input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.od-switch-track { position: relative; width: 36px; height: 20px; flex-shrink: 0; border-radius: 999px; background: var(--od-border); transition: background .15s ease; }
.od-switch-track::before { content: ""; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: #fff; transition: transform .15s ease; box-shadow: 0 1px 2px rgba(17, 22, 31, .2); }
.od-switch input:checked ~ .od-switch-track { background: var(--od-accent); }
.od-switch input:checked ~ .od-switch-track::before { transform: translateX(16px); }
.od-switch input:focus-visible ~ .od-switch-track { outline: 2px solid var(--od-accent); outline-offset: 2px; }
.od-switch input:disabled ~ .od-switch-track { opacity: .5; cursor: not-allowed; }
.od-switch:has(input:disabled) { cursor: not-allowed; opacity: .7; }

/* ============================== 21. Progress bar ============================== */
.od-progress { height: 6px; border-radius: 999px; background: var(--od-border-light); overflow: hidden; }
.od-progress-bar { height: 100%; border-radius: 999px; background: var(--od-accent); transition: width .2s ease; }
.od-progress-bar--success { background: var(--od-success); }
.od-progress-bar--danger { background: var(--od-danger); }
.od-progress-bar--warning { background: var(--od-warning); }

/* ============================== 22. Skeleton loader ============================== */
.od-skeleton { position: relative; overflow: hidden; border-radius: var(--od-radius-sm); background: var(--od-border-light); }
.od-skeleton::after { content: ""; position: absolute; inset: 0; transform: translateX(-100%); background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .6), transparent); animation: od-skeleton-shimmer 1.5s infinite; }
@media (prefers-color-scheme: dark) {
    :root:not([data-od-theme="light"]) .od-skeleton::after { background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .08), transparent); }
}
[data-od-theme="dark"] .od-skeleton::after { background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .08), transparent); }
@keyframes od-skeleton-shimmer { 100% { transform: translateX(100%); } }
.od-skeleton-text { height: 12px; margin-bottom: 8px; border-radius: 4px; }
.od-skeleton-text:last-child { margin-bottom: 0; width: 70%; }
.od-skeleton-title { height: 18px; width: 40%; margin-bottom: 12px; border-radius: 5px; }
.od-skeleton-avatar { width: 40px; height: 40px; border-radius: 999px; }

/* ============================== 23. Tooltip ============================== */
/* Pure CSS — `title`-like, but styled and instant. Put the tooltip text in
   data-od-tooltip on any element; needs no other markup. CSS alone can only
   flip up/down near the top of the topbar (below) — it has no way to know
   how close an element sits to the left/right/bottom edge of the actual
   viewport, so a long tooltip on an edge element can still clip there. Add
   od-tooltip.js (optional) to fix that too: it measures the trigger and
   viewport on hover/focus and writes the --od-tooltip-shift/position
   corrections this CSS reads. --od-tooltip-shift defaults to 0px (no JS =
   plain centered tooltip, same as before). */
[data-od-tooltip] { position: relative; }
[data-od-tooltip]::after {
    content: attr(data-od-tooltip); position: absolute; bottom: calc(100% + 6px); left: 50%;
    width: max-content; max-width: 220px; transform: translateX(calc(-50% + var(--od-tooltip-shift, 0px))) translateY(4px);
    padding: 5px 9px; border-radius: var(--od-radius-sm);
    background: var(--od-dark); color: var(--od-dark-text); font-family: var(--od-font-sans);
    font-size: 11.5px; font-weight: 600; line-height: 1.4; white-space: normal; text-align: center;
    opacity: 0; visibility: hidden;
    transition: opacity .12s ease, transform .12s ease; pointer-events: none; z-index: 50;
}
[data-od-tooltip]:hover::after, [data-od-tooltip]:focus-visible::after { opacity: 1; visibility: visible; transform: translateX(calc(-50% + var(--od-tooltip-shift, 0px))) translateY(0); }
[data-od-tooltip-position="bottom"]::after { bottom: auto; top: calc(100% + 6px); transform: translateX(calc(-50% + var(--od-tooltip-shift, 0px))) translateY(-4px); }
[data-od-tooltip-position="bottom"]:hover::after, [data-od-tooltip-position="bottom"]:focus-visible::after { transform: translateX(calc(-50% + var(--od-tooltip-shift, 0px))) translateY(0); }
/* .od-topbar sits at the very top of the viewport, so a tooltip opening
   upward from it would render off-screen — default to opening downward
   there instead. `data-od-tooltip-position="top"` opts back into "up" for a
   topbar element that has room (e.g. a tall custom topbar). */
.od-topbar [data-od-tooltip]:not([data-od-tooltip-position="top"])::after { bottom: auto; top: calc(100% + 6px); transform: translateX(calc(-50% + var(--od-tooltip-shift, 0px))) translateY(-4px); }
.od-topbar [data-od-tooltip]:not([data-od-tooltip-position="top"]):hover::after, .od-topbar [data-od-tooltip]:not([data-od-tooltip-position="top"]):focus-visible::after { transform: translateX(calc(-50% + var(--od-tooltip-shift, 0px))) translateY(0); }

/* ============================== 24. Avatar group ============================== */
.od-avatar-group { display: flex; align-items: center; }
.od-avatar-group .od-avatar { border: 2px solid var(--od-panel); margin-left: -8px; }
.od-avatar-group .od-avatar:first-child { margin-left: 0; }
.od-avatar-group-more { width: 28px; height: 28px; flex-shrink: 0; border-radius: 999px; border: 2px solid var(--od-panel); margin-left: -8px; background: var(--od-border-light); color: var(--od-text-secondary); display: flex; align-items: center; justify-content: center; font-size: 10.5px; font-weight: 700; }

/* ============================== 25. Kbd ============================== */
.od-kbd { display: inline-flex; align-items: center; justify-content: center; padding: 2px 6px; border: 1px solid var(--od-border); border-bottom-width: 2px; border-radius: 5px; background: var(--od-nav-hover); color: var(--od-text-secondary); font-family: var(--od-font-mono); font-size: 11px; font-weight: 600; line-height: 1.4; }

/* ============================== 26. Segmented control ============================== */
.od-segmented { display: inline-flex; gap: 2px; padding: 3px; border-radius: var(--od-radius); background: var(--od-nav-hover); }
.od-segmented-btn { appearance: none; border: 0; padding: 6px 14px; border-radius: var(--od-radius-sm); background: transparent; font: inherit; font-size: 12.5px; font-weight: 700; color: var(--od-text-secondary); cursor: pointer; }
.od-segmented-btn:hover { color: var(--od-text); }
.od-segmented-btn.is-active { background: var(--od-panel); color: var(--od-text); box-shadow: 0 1px 2px rgba(15, 23, 42, .08); }
.od-segmented-btn:disabled { opacity: .5; cursor: not-allowed; }

/* ============================== 27. Bar chart ============================== */
/* A plain CSS bar chart — set each bar's value via inline `style="height:N%"`.
   Pair with `data-od-tooltip` (see Tooltip above) to show the exact value. */
.od-bars { display: flex; align-items: flex-end; gap: 6px; height: 140px; }
.od-bars-col { flex: 1; min-width: 0; height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; gap: 8px; }
.od-bars-fill { width: 100%; max-width: 28px; min-height: 3px; border-radius: 4px 4px 0 0; background: var(--od-accent); transition: height .2s ease; }
.od-bars-fill--success { background: var(--od-success); }
.od-bars-fill--danger { background: var(--od-danger); }
.od-bars-fill--warning { background: var(--od-warning); }
.od-bars-label { font-size: 10.5px; font-weight: 600; color: var(--od-text-muted); white-space: nowrap; }

/* ============================== 28. Timeline ============================== */
.od-timeline { display: flex; flex-direction: column; }
.od-timeline-item { position: relative; padding: 0 0 20px 22px; }
.od-timeline-item::before { content: ""; position: absolute; top: 3px; left: 0; width: 9px; height: 9px; border-radius: 50%; background: var(--od-accent); }
.od-timeline-item::after { content: ""; position: absolute; top: 14px; left: 4px; bottom: 0; width: 1px; background: var(--od-border); }
.od-timeline-item:last-child { padding-bottom: 0; }
.od-timeline-item:last-child::after { display: none; }
.od-timeline-item--success::before { background: var(--od-success); }
.od-timeline-item--danger::before { background: var(--od-danger); }
.od-timeline-item--warning::before { background: var(--od-warning); }
.od-timeline-time { font-size: 11px; font-weight: 700; color: var(--od-text-muted); }
.od-timeline-title { font-size: 13px; font-weight: 700; color: var(--od-text); margin-top: 2px; }
.od-timeline-desc { font-size: 12.5px; color: var(--od-text-secondary); margin-top: 2px; line-height: 1.5; }

/* ============================== 29. List group ============================== */
.od-list { display: flex; flex-direction: column; }
.od-list-item { display: flex; align-items: center; gap: 12px; padding: 11px 2px; border-bottom: 1px solid var(--od-border-light); }
.od-list-item:last-child { border-bottom: 0; }
.od-list-icon { width: 34px; height: 34px; flex-shrink: 0; border-radius: var(--od-radius); background: var(--od-accent-bg); color: var(--od-accent-text); display: flex; align-items: center; justify-content: center; font-size: 15px; }
.od-list-body { flex: 1; min-width: 0; }
.od-list-title { font-size: 13px; font-weight: 700; color: var(--od-text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.od-list-desc { font-size: 12px; color: var(--od-text-muted); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.od-list-meta { flex-shrink: 0; font-size: 11px; color: var(--od-text-muted); white-space: nowrap; }

/* ============================== 30. Notification badge dot ============================== */
/* Add alongside any icon button classes: <button class="od-btn od-btn-ghost od-btn-icon od-badge-dot">
   Uses ::before (not ::after) so it can be combined with data-od-tooltip on
   the same element — an element can only have one ::after, and the Tooltip
   component (above) already owns that one. */
.od-badge-dot { position: relative; }
.od-badge-dot::before { content: ""; position: absolute; top: 3px; right: 3px; width: 9px; height: 9px; border-radius: 50%; background: var(--od-danger); border: 2px solid var(--od-panel); }

/* ============================== 31. Guest / auth shell ============================== */
/* A centered single-card layout for login/register/forgot-password pages —
   used instead of .od-shell where there's no sidebar/topbar yet. */
.od-guest-shell { min-height: 100vh; min-height: 100dvh; display: flex; align-items: center; justify-content: center; padding: 24px; }
.od-guest-panel { width: 100%; max-width: 360px; }
.od-guest-brand { display: flex; align-items: center; gap: 10px; justify-content: center; margin-bottom: 22px; }

/* ============================== 32. Stepper ============================== */
/* Presentational only — toggle .is-active/.is-complete yourself as the user
   moves through your form's steps (pair with your own Next/Back buttons). */
.od-stepper { display: flex; align-items: flex-start; }
.od-step { position: relative; flex: 1; min-width: 0; display: flex; flex-direction: column; align-items: center; text-align: center; }
.od-step:not(:last-child)::after { content: ""; position: absolute; top: 15px; left: 50%; width: 100%; height: 2px; background: var(--od-border); z-index: 0; }
.od-step.is-complete:not(:last-child)::after { background: var(--od-success); }
.od-step-circle { position: relative; z-index: 1; width: 30px; height: 30px; flex-shrink: 0; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 13px; font-weight: 800; background: var(--od-panel); border: 2px solid var(--od-border); color: var(--od-text-muted); }
.od-step.is-active .od-step-circle { border-color: var(--od-accent); color: var(--od-accent-text); }
.od-step.is-complete .od-step-circle { background: var(--od-success); border-color: var(--od-success); color: #fff; }
.od-step-label { margin-top: 8px; font-size: 12px; font-weight: 700; color: var(--od-text-muted); }
.od-step.is-active .od-step-label { color: var(--od-text); }
.od-step.is-complete .od-step-label { color: var(--od-text-secondary); }

/* ============================== 33. Sidebar submenu (collapsible nav) ============================== */
/* Native <details>/<summary> — same zero-JS mechanism as the Accordion —
   styled to sit inline in .od-nav-list next to plain .od-nav-link items. */
.od-nav-collapsible > summary.od-nav-toggle { display: flex; align-items: center; gap: 10px; padding: 7px 8px; border-radius: var(--od-radius); color: var(--od-text-secondary); font-weight: 600; font-size: 13.5px; cursor: pointer; list-style: none; }
.od-nav-collapsible > summary.od-nav-toggle::-webkit-details-marker { display: none; }
.od-nav-collapsible > summary.od-nav-toggle:hover { background: var(--od-nav-hover); }
.od-nav-collapsible > summary.od-nav-toggle::after { content: "\276F"; margin-left: auto; font-size: 10px; color: var(--od-text-muted-2); transition: transform .15s ease; }
.od-nav-collapsible[open] > summary.od-nav-toggle::after { transform: rotate(90deg); }
.od-nav-sublist { display: flex; flex-direction: column; gap: 2px; margin: 2px 0 4px; padding-left: 34px; }
.od-nav-sublist .od-nav-link { font-size: 12.5px; padding: 6px 8px; }

/* ============================== 34. Confirm dialog ============================== */
/* No CSS of its own — OpenDashboard.Confirm (od-confirm.js) builds a plain
   .od-modal at call time and reuses all of the Modal component's styles. */

/* ============================== 35. Spinner & button loading state ============================== */
.od-spinner { display: inline-block; width: 16px; height: 16px; flex-shrink: 0; border: 2px solid var(--od-border); border-top-color: var(--od-accent); border-radius: 50%; animation: od-spin .6s linear infinite; }
.od-spinner--sm { width: 12px; height: 12px; border-width: 2px; }
.od-spinner--lg { width: 24px; height: 24px; border-width: 3px; }
@keyframes od-spin { to { transform: rotate(360deg); } }
/* Toggle yourself when an action starts/ends — this never guesses when your
   async call is "done", it just renders whichever state you set. Add
   `disabled` alongside the class for real (not just visual) blocking. */
.od-btn.is-loading, .od-btn-ghost.is-loading, .od-btn-neutral.is-loading, .od-btn-danger.is-loading { pointer-events: none; opacity: .75; cursor: wait; }
.od-btn.is-loading::before, .od-btn-ghost.is-loading::before, .od-btn-neutral.is-loading::before, .od-btn-danger.is-loading::before {
    content: ""; width: 13px; height: 13px; flex-shrink: 0; border-radius: 50%;
    border: 2px solid currentColor; border-right-color: transparent; border-top-color: transparent;
    animation: od-spin .6s linear infinite;
}

/* ============================== 36. Copy to clipboard ============================== */
/* No markup of its own — od-copy.js adds this state to whatever trigger
   (button, icon button, link) you already have. */
[data-od-copy].is-copied, [data-od-copy-target].is-copied { color: var(--od-success) !important; border-color: var(--od-success) !important; }

/* ============================== 37. Icon ============================== */
/* Sizing/alignment for your OWN inline SVG icon — stroke="currentColor" (or
   fill="currentColor" for a solid glyph) makes it track the surrounding
   text color automatically, including through dark mode, pills, nav
   active-states, etc. For a ready-made set with zero inline SVG markup at
   all, see open-dashboard-icons.css (the .od-i-* classes) instead. */
.od-icon { width: 1em; height: 1em; display: inline-block; vertical-align: -0.125em; flex-shrink: 0; }

/* The .od-i-* icon set has moved to its own file, open-dashboard-icons.css —
   the data-URI icons made this file disproportionately large, and most
   pages don't need the whole set. Link that file too (optional) if you use
   the .od-i-* classes; see
   the Icons section of the README and demo/icons.html. */

/* ============================== 38. Period bar (date range filter) ============================== */
.od-period-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 20px; }
.od-period-link { display: inline-flex; align-items: center; padding: 6px 12px; border-radius: var(--od-radius); border: 1px solid var(--od-border); background: var(--od-panel); font-size: 12.5px; font-weight: 700; color: var(--od-text-secondary); cursor: pointer; }
.od-period-link:hover { background: var(--od-nav-hover); color: var(--od-text); }
.od-period-link.is-active { background: var(--od-accent); border-color: var(--od-accent); color: #fff; }
.od-period-custom { display: flex; align-items: center; gap: 6px; margin-left: auto; flex-wrap: wrap; }
.od-period-custom .od-input { width: auto; padding: 5px 9px; font-size: 12.5px; }

/* ============================== 39. Rank list ============================== */
/* A ranked row list with a proportional background bar — "top pages",
   "top referrers", any leaderboard. Set each row's bar width yourself via
   inline `style="width:N%"`, computed as value/max(values)*100. */
.od-rank-list { display: flex; flex-direction: column; }
.od-rank-row { position: relative; display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 9px 16px; overflow: hidden; font-size: 13.5px; border-bottom: 1px solid var(--od-border-light); }
.od-rank-row:last-child { border-bottom: none; }
.od-rank-bar { position: absolute; top: 0; bottom: 0; left: 0; z-index: 0; background: var(--od-accent-bg); }
.od-rank-label, .od-rank-value { position: relative; z-index: 1; }
.od-rank-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
a.od-rank-label:hover { color: var(--od-accent-text); text-decoration: underline; }
.od-rank-value { flex-shrink: 0; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--od-text-secondary); }

/* ============================== 41. Drawer ============================== */
/* A side panel — same contract as Modal (content already in the page, no
   fetching), just sliding in from an edge instead of appearing centered.
   z-index sits just below .od-modal (60) so a confirm/edit modal opened
   from inside a drawer still layers on top of it. */
.od-drawer { position: fixed; inset: 0; z-index: 55; display: none; }
.od-drawer.is-open { display: block; }
.od-drawer-overlay { position: absolute; inset: 0; width: 100%; height: 100%; padding: 0; border: 0; background: var(--od-overlay); opacity: 0; transition: opacity .2s ease; cursor: default; }
.od-drawer.is-open .od-drawer-overlay { opacity: 1; }
.od-drawer-panel { position: fixed; top: 0; right: 0; z-index: 1; width: 420px; max-width: 92vw; height: 100vh; height: 100dvh; display: flex; flex-direction: column; background: var(--od-panel); border-left: 1px solid var(--od-border); box-shadow: -8px 0 30px rgba(17, 22, 31, .16); transform: translateX(100%); transition: transform .22s ease; }
.od-drawer.is-open .od-drawer-panel { transform: translateX(0); }
.od-drawer-panel--wide { width: 640px; }
.od-drawer-panel--left { top: 0; right: auto; left: 0; border-left: 0; border-right: 1px solid var(--od-border); box-shadow: 8px 0 30px rgba(17, 22, 31, .16); transform: translateX(-100%); }
.od-drawer.is-open .od-drawer-panel--left { transform: translateX(0); }
.od-drawer-header { padding: 15px 20px; border-bottom: 1px solid var(--od-border-light); display: flex; align-items: center; justify-content: space-between; flex-shrink: 0; }
.od-drawer-header h2 { font-size: 15px; }
.od-drawer-close { background: none; border: none; font-size: 22px; line-height: 1; color: var(--od-text-muted); cursor: pointer; padding: 0 2px; }
.od-drawer-close:hover { color: var(--od-text); }
.od-drawer-body { padding: 20px; overflow-y: auto; flex: 1; }
.od-drawer-footer { padding: 14px 20px; border-top: 1px solid var(--od-border-light); display: flex; justify-content: flex-end; gap: 8px; flex-shrink: 0; }
body.od-drawer-open { overflow: hidden; }

/* ============================== 42. Chip / tag ============================== */
.od-chip { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; padding: 4px 10px; border-radius: 999px; background: var(--od-nav-hover); color: var(--od-text-secondary); white-space: nowrap; }
/* The remove button's own hit area already reads as right-side padding, so
   a chip that has one gets a tighter right pad than the default above. */
.od-chip:has(.od-chip-remove) { padding-right: 6px; }
.od-chip-remove { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; border-radius: 999px; border: 0; background: none; color: inherit; opacity: .6; cursor: pointer; font-size: 13px; line-height: 1; padding: 0; }
.od-chip-remove:hover { opacity: 1; background: color-mix(in srgb, currentColor 15%, transparent); }
.od-chip--accent { background: var(--od-accent-bg); color: var(--od-accent-text); }
.od-chip--success { background: var(--od-success-bg); color: var(--od-success); }
.od-chip--danger { background: var(--od-danger-bg); color: var(--od-danger); }

/* ============================== 43. Description list ============================== */
.od-desc-list { display: flex; flex-direction: column; }
.od-desc-row { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; padding: 10px 0; border-bottom: 1px solid var(--od-border-light); font-size: 13.5px; }
.od-desc-row:last-child { border-bottom: none; }
.od-desc-label { flex-shrink: 0; color: var(--od-text-muted); font-weight: 600; }
.od-desc-value { text-align: right; font-weight: 600; min-width: 0; }
.od-desc-list--tight .od-desc-row { padding: 6px 0; }

/* ============================== 44. Top loading bar ============================== */
/* Indeterminate progress hint for full-page navigations — this is a
   classic multi-page app, every real navigation is a browser page load,
   never an AJAX swap — or for a manual long-running action. Add the
   element once, near the top of <body>; od-loading-bar.js toggles
   `.is-active` automatically for same-tab link clicks inside .od-shell/
   .od-guest-shell, or call OpenDashboard.LoadingBar yourself. */
.od-loading-bar { position: fixed; top: 0; left: 0; width: 100%; height: 3px; z-index: 90; background: transparent; pointer-events: none; overflow: hidden; }
.od-loading-bar::before { content: ''; position: absolute; top: 0; left: 0; height: 100%; width: 40%; background: var(--od-accent); opacity: 0; transform: translateX(-100%); }
.od-loading-bar.is-active::before { opacity: 1; animation: od-loading-bar-slide 1.1s ease-in-out infinite; }
@keyframes od-loading-bar-slide {
    0% { transform: translateX(-100%); }
    50% { transform: translateX(60%); }
    100% { transform: translateX(250%); }
}

/* ============================== 45. Mini calendar ============================== */
/* Rendered entirely by od-calendar.js from plain date math (no fetching,
   no server contract) — the element starts empty, this is just the
   styling for the grid the script builds. */
.od-calendar { width: 100%; max-width: 280px; }
.od-calendar-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.od-calendar-title { font-size: 13px; font-weight: 800; }
.od-calendar-nav { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; border-radius: var(--od-radius-sm); border: 1px solid var(--od-border); background: var(--od-panel); color: var(--od-text-secondary); cursor: pointer; font-size: 14px; line-height: 1; padding: 0; }
.od-calendar-nav:hover { background: var(--od-nav-hover); color: var(--od-text); }
.od-calendar-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; text-align: center; }
.od-calendar-dow { font-size: 10.5px; font-weight: 700; color: var(--od-text-muted-2); text-transform: uppercase; padding: 4px 0; }
.od-calendar-day { display: flex; align-items: center; justify-content: center; aspect-ratio: 1; border: 0; border-radius: var(--od-radius-sm); background: none; font: inherit; font-size: 12.5px; font-weight: 600; color: var(--od-text); cursor: pointer; padding: 0; }
.od-calendar-day:hover { background: var(--od-nav-hover); }
.od-calendar-day--muted { color: var(--od-text-muted-2); }
.od-calendar-day--today { border: 1px solid var(--od-accent); color: var(--od-accent-text); font-weight: 800; }
.od-calendar-day--active, .od-calendar-day--active:hover { background: var(--od-accent); color: #fff; font-weight: 800; }

/* ============================== 46. Command palette ============================== */
.od-command-palette { position: fixed; inset: 0; z-index: 70; display: none; align-items: flex-start; justify-content: center; padding: 14vh 20px 20px; }
.od-command-palette.is-open { display: flex; }
.od-command-overlay { position: absolute; inset: 0; width: 100%; height: 100%; padding: 0; border: 0; background: var(--od-overlay); cursor: default; }
.od-command-panel { position: relative; z-index: 1; width: 100%; max-width: 560px; max-height: 60vh; display: flex; flex-direction: column; background: var(--od-panel); border-radius: var(--od-radius-lg); box-shadow: 0 24px 70px rgba(17, 22, 31, .3); overflow: hidden; }
.od-command-search { display: flex; align-items: center; gap: 8px; padding: 14px 16px; border-bottom: 1px solid var(--od-border-light); flex-shrink: 0; }
.od-command-search input { flex: 1; min-width: 0; border: 0; background: none; font: inherit; font-size: 14.5px; color: var(--od-text); outline: none; }
.od-command-search input::placeholder { color: var(--od-text-muted); }
.od-command-list { overflow-y: auto; padding: 6px; }
.od-command-item { display: flex; align-items: center; justify-content: space-between; gap: 10px; width: 100%; padding: 10px 12px; border: 0; border-radius: var(--od-radius); background: transparent; font: inherit; font-size: 13.5px; font-weight: 600; color: var(--od-text); text-align: left; text-decoration: none; cursor: pointer; }
.od-command-item[hidden] { display: none; }
.od-command-item:hover, .od-command-item.is-active { background: var(--od-nav-hover); }
.od-command-item-hint { flex-shrink: 0; font-size: 11.5px; font-weight: 700; color: var(--od-text-muted); }
.od-command-empty { padding: 20px 12px; text-align: center; font-size: 13px; color: var(--od-text-muted); }

/* ============================== 47. File dropzone ============================== */
.od-dropzone { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; padding: 32px 20px; border: 2px dashed var(--od-border); border-radius: var(--od-radius-lg); text-align: center; cursor: pointer; transition: border-color .15s ease, background .15s ease; }
.od-dropzone:hover, .od-dropzone.is-dragover { border-color: var(--od-accent); background: var(--od-accent-bg); }
.od-dropzone-icon { width: 32px; height: 32px; margin-bottom: 6px; color: var(--od-text-muted); }
.od-dropzone-title { font-size: 13.5px; font-weight: 700; color: var(--od-text); }
.od-dropzone-hint { font-size: 12px; color: var(--od-text-muted); }

/* ============================== 48. Context menu ============================== */
.od-context-menu { position: fixed; z-index: 75; min-width: 180px; background: var(--od-panel); border: 1px solid var(--od-border); border-radius: var(--od-radius); box-shadow: 0 12px 28px rgba(17, 22, 31, .18); padding: 6px; display: none; }
.od-context-menu.is-open { display: block; }
.od-context-menu-item { display: flex; align-items: center; gap: 8px; width: 100%; padding: 8px 10px; border: 0; border-radius: var(--od-radius-sm); background: transparent; font: inherit; font-size: 13px; font-weight: 600; color: var(--od-text-secondary); text-align: left; cursor: pointer; }
.od-context-menu-item:hover { background: var(--od-nav-hover); color: var(--od-text); }
.od-context-menu-item--danger { color: var(--od-danger); }
.od-context-menu-item--danger:hover { background: var(--od-danger-bg); }
.od-context-menu-divider { height: 1px; margin: 6px 4px; background: var(--od-border-light); }

/* ============================== 49. Sparkline ============================== */
/* An inline SVG line chart — plot the points yourself (e.g. from
   `value / max(values) * 100`), same "no chart library" approach as Bar
   chart, just a continuous line instead of discrete bars. Sizes to
   whatever width/height you give the `<svg>`. */
.od-sparkline { display: block; width: 100%; height: 32px; overflow: visible; }
.od-sparkline polyline { fill: none; stroke: var(--od-accent); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; vector-effect: non-scaling-stroke; }
.od-sparkline--success polyline { stroke: var(--od-success); }
.od-sparkline--danger polyline { stroke: var(--od-danger); }
.od-sparkline--warning polyline { stroke: var(--od-warning); }

/* ============================== 50. Donut chart ============================== */
/* Set the fill with the --value custom property (0-100) — same
   inline-style pattern as Progress bar, just a ring instead of a bar.
   CSS conic-gradient, no chart library involved. */
.od-donut { --value: 0; --od-donut-color: var(--od-accent); position: relative; width: 96px; height: 96px; flex-shrink: 0; border-radius: 50%; display: flex; align-items: center; justify-content: center; background: conic-gradient(var(--od-donut-color) calc(var(--value) * 1%), var(--od-border-light) 0); }
.od-donut::before { content: ''; position: absolute; inset: 12px; border-radius: 50%; background: var(--od-panel); }
.od-donut-label { position: relative; z-index: 1; font-size: 16px; font-weight: 800; }
.od-donut--sm { width: 56px; height: 56px; }
.od-donut--sm::before { inset: 7px; }
.od-donut--sm .od-donut-label { font-size: 12px; }
.od-donut--success { --od-donut-color: var(--od-success); }
.od-donut--danger { --od-donut-color: var(--od-danger); }
.od-donut--warning { --od-donut-color: var(--od-warning); }

/* ============================== 51. Rating ============================== */
/* Read-only display, same "set a fraction inline" pattern as Progress
   bar — set --value to the filled width (e.g. 3.5/5 = 70%). CSS only:
   two stacked identical star strings, the top one clipped by width. */
.od-rating { --value: 0%; position: relative; display: inline-block; font-size: 16px; line-height: 1; letter-spacing: 2px; }
.od-rating-stars { display: block; color: var(--od-border); }
.od-rating-stars--filled { position: absolute; top: 0; left: 0; overflow: hidden; width: var(--value); color: var(--od-warning); white-space: nowrap; }

/* ============================== 52. OTP / PIN input ============================== */
.od-otp { display: flex; gap: 8px; }
.od-otp-input { width: 44px; height: 52px; border: 1px solid var(--od-border); border-radius: var(--od-radius); background: var(--od-panel); color: var(--od-text); font: inherit; font-size: 20px; font-weight: 700; text-align: center; }
.od-otp-input:focus { outline: none; border-color: var(--od-accent); box-shadow: 0 0 0 3px var(--od-accent-bg); }

/* ============================== 53. Split button ============================== */
/* Composes with the existing Dropdown component — the caret is a normal
   dropdown trigger, just visually joined to the button next to it. */
.od-split-btn { display: inline-flex; }
.od-split-btn > .od-btn:first-child, .od-split-btn > .od-btn-ghost:first-child, .od-split-btn > .od-btn-neutral:first-child, .od-split-btn > .od-btn-danger:first-child { border-top-right-radius: 0; border-bottom-right-radius: 0; }
.od-btn.od-split-btn-caret, .od-btn-ghost.od-split-btn-caret, .od-btn-neutral.od-split-btn-caret, .od-btn-danger.od-split-btn-caret { border-top-left-radius: 0; border-bottom-left-radius: 0; border-left: 1px solid color-mix(in srgb, currentColor 25%, transparent); padding-left: 10px; padding-right: 10px; }

/* ============================== 54. Trend indicator ============================== */
/* Color is sentiment (is this change good or bad), not direction — for
   a metric like "Errors" a downward arrow is the good outcome, so
   direction (the arrow glyph, written by you) and sentiment (the
   modifier class) are deliberately independent. */
.od-trend { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; font-weight: 700; }
.od-trend--positive { color: var(--od-success); }
.od-trend--negative { color: var(--od-danger); }
.od-trend--neutral { color: var(--od-text-muted); }
.od-trend-context { font-weight: 600; color: var(--od-text-muted); }

/* ============================== 55. Range slider ============================== */
.od-range { -webkit-appearance: none; appearance: none; width: 100%; height: 6px; margin: 8px 0; border-radius: 999px; background: var(--od-border-light); outline: none; cursor: pointer; }
.od-range::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 18px; height: 18px; border-radius: 50%; background: var(--od-accent); border: 3px solid var(--od-panel); box-shadow: 0 0 0 1px var(--od-border); cursor: pointer; }
.od-range::-moz-range-track { height: 6px; border-radius: 999px; background: var(--od-border-light); }
.od-range::-moz-range-thumb { width: 18px; height: 18px; border-radius: 50%; background: var(--od-accent); border: 3px solid var(--od-panel); box-shadow: 0 0 0 1px var(--od-border); cursor: pointer; }
.od-range:focus-visible { box-shadow: 0 0 0 3px var(--od-accent-bg); border-radius: 999px; }
.od-range:disabled { opacity: .5; cursor: not-allowed; }

/* ============================== 56. Vertical stepper ============================== */
/* Add alongside .od-stepper to switch the same markup from a horizontal
   row to a vertical list (checkout/onboarding flows). */
.od-stepper--vertical { flex-direction: column; align-items: stretch; }
.od-stepper--vertical .od-step { flex: none; flex-direction: row; align-items: flex-start; text-align: left; padding-bottom: 28px; }
.od-stepper--vertical .od-step:last-child { padding-bottom: 0; }
.od-stepper--vertical .od-step:not(:last-child)::after { top: 30px; bottom: 0; left: 15px; width: 2px; height: auto; }
.od-stepper--vertical .od-step-label { margin-top: 0; margin-left: 12px; padding-top: 4px; }

/* ============================== 57. Panel loading overlay ============================== */
/* Add/remove this element yourself (or toggle `hidden`) while a panel's
   content is loading — CSS only, no bundled behavior, same as Alert's
   dismiss button. Needs `.od-panel` to be a positioning context, which
   it already is. */
.od-panel-loading { position: absolute; inset: 0; z-index: 5; display: flex; align-items: center; justify-content: center; background: color-mix(in srgb, var(--od-panel) 80%, transparent); border-radius: inherit; }
.od-panel-loading[hidden] { display: none; }

/* ============================== 58. Sortable list (drag to reorder) ============================== */
.od-sortable { display: flex; flex-direction: column; gap: 8px; }
.od-sortable-item { display: flex; align-items: center; gap: 10px; padding: 10px 12px; border: 1px solid var(--od-border); border-radius: var(--od-radius); background: var(--od-panel); font-size: 13.5px; font-weight: 600; }
.od-sortable-item.is-dragging { opacity: .5; }
.od-sortable-item.is-drag-over { border-color: var(--od-accent); }
/* A 2x3 grid of dots drawn with box-shadow rather than a unicode glyph
   (e.g. vertical-ellipsis characters) — glyph metrics vary enough across
   fonts/OSes to leave unpredictable, often oversized gaps; this is a
   fixed, predictable size regardless of font. */
.od-sortable-handle { flex-shrink: 0; position: relative; width: 12px; height: 16px; cursor: grab; touch-action: none; }
.od-sortable-handle::before { content: ''; position: absolute; top: 1px; left: 1px; width: 3px; height: 3px; border-radius: 50%; background: var(--od-text-muted); box-shadow: 7px 0 0 var(--od-text-muted), 0 5px 0 var(--od-text-muted), 7px 5px 0 var(--od-text-muted), 0 10px 0 var(--od-text-muted), 7px 10px 0 var(--od-text-muted); }

/* ============================== 59. Page header ============================== */
/* A page/section title row — an <h1> (+ optional .od-muted description) on
   the left, optional trailing actions on the right. Wraps below on narrow
   screens. Place it as the first thing inside .od-main. */
.od-page-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; flex-wrap: wrap; margin-bottom: 20px; }
.od-page-header .od-muted { margin-top: 4px; }
.od-page-header-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* ============================== 60. Nav status dot ============================== */
/* A small live/inactive indicator inline in a sidebar .od-nav-link — for a
   list of monitored items (sites, servers, environments) rather than plain
   navigation. Distinct from .od-badge-dot (a corner badge on an icon
   button) and .od-pill-dot (a dot inside a pill). Unmodified reads as
   inactive/neutral; add .is-live for the "on" state. */
.od-nav-dot { width: 8px; height: 8px; border-radius: 999px; background: var(--od-border); flex-shrink: 0; }
.od-nav-dot.is-live { background: var(--od-success); }

/* ============================== 61. Button block modifier ============================== */
/* Add alongside any button variant (.od-btn, .od-btn-ghost, .od-btn-neutral,
   .od-btn-danger) to stretch it to the width of its container — e.g. a
   submit button under a narrow guest-shell form. */
.od-btn-block { width: 100%; }

/* ============================== 62. Form field help & actions ============================== */
.od-field-help { font-size: 12px; color: var(--od-text-muted); margin-top: 5px; }
.od-form-actions { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-top: 4px; }

/* ============================== 63. Grid utility ============================== */
/* An equal-width, auto-wrapping grid for placing panels side by side —
   collapses to a single column on narrow screens on its own, no media
   query needed. For an asymmetric split (e.g. a wide chart + a narrow
   sidebar panel), write your own grid-template-columns instead. */
.od-grid-2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 20px; }
