*, *::before, *::after { box-sizing: border-box; }

:root {
    /* Map toolbar height — referenced by .map-toolbar (sets it) and .pane-map
       (adds it to the pane height so the viewport below comes out square). Keep
       them in sync via this variable. 39px = padding 7+7 + button 24 + border 1. */
    --map-toolbar-height: 39px;
}

html, body {
    margin: 0;
    padding: 0;
    min-height: 100vh;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    background: #0e1014;
    color: #d8dde6;
    -webkit-font-smoothing: antialiased;
}

/* ---------- App shell: top menu bar + page body ---------- */

.layout {
    display: flex;
    flex-direction: column;
    height: 100vh;
}

.topbar {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 20px;
    height: 48px;
    padding: 0 18px;
    background: #14171d;
    border-bottom: 1px solid #1f2330;
}

.topbar-brand {
    font-size: 14px;
    font-weight: 700;
    letter-spacing: 0.3px;
    color: #d8dde6;
    text-decoration: none;
}

.topbar-nav {
    display: flex;
    align-items: center;
    gap: 4px;
}

.topbar-account {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 10px;
}

.topbar-link {
    font-size: 13px;
    color: #8c95a4;
    text-decoration: none;
    padding: 6px 10px;
    border-radius: 6px;
    /* Reserve space for the active underline so the link doesn't jump 2px
       when it gains the .active class. */
    border-bottom: 2px solid transparent;
    margin-bottom: -2px;
}

.topbar-link:hover {
    background: #1c2030;
    color: #d8dde6;
}

/* Applied by Blazor's <NavLink> when the current URL matches. The brighter
   text + blue underline makes the current page unambiguous in the topbar. */
.topbar-link.active {
    color: #d8dde6;
    background: #1c2030;
    border-bottom-color: #3b82f6;
}

.topbar-user {
    max-width: 220px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 12px;
    color: #6b7280;
}

.topbar-signout {
    background: none;
    border: 1px solid #374151;
    color: #8c95a4;
    border-radius: 6px;
    padding: 5px 11px;
    font-size: 12px;
    cursor: pointer;
}

.topbar-signout:hover {
    background: #1c2030;
    color: #d8dde6;
}

.layout-body {
    flex: 1;
    min-height: 0;
    overflow: auto;
}

/* --------- Site-wide warning banner (BridgeMissingBanner.razor) --------- */
/* Amber "attention needed" strip that sits between the top nav and the page
   body when at least one connected server is missing the RustyRconBridge
   plugin. Not blocking — the operator can dismiss for the session or opt out
   permanently via localStorage. */
.bridge-warning {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
    padding: 10px 20px;
    background: rgba(234, 179, 8, 0.12);
    border-bottom: 1px solid rgba(234, 179, 8, 0.4);
    color: #fef3c7;
    font-size: 13px;
    /* Keep the banner behind hovering modals but above pane content. */
    position: relative;
    z-index: 5;
}

.bridge-warning-body {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    flex: 1;
    min-width: 0;
}

.bridge-warning-icon {
    color: #fbbf24;
    font-size: 16px;
    line-height: 1.4;
    flex-shrink: 0;
}

.bridge-warning-text {
    flex: 1;
    min-width: 0;
}

.bridge-warning-title {
    font-weight: 600;
    color: #fde68a;
}

.bridge-warning-detail {
    margin-top: 2px;
    color: #fef3c7;
    line-height: 1.4;
}

.bridge-warning-servers {
    display: inline;
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, monospace;
    color: #fef3c7;
}

.bridge-warning-actions {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
}

/* "Don't show again" — subdued ghost button so the primary dismiss × still
   reads as the default action. */
.bridge-warning-forget {
    background: transparent;
    border: 1px solid rgba(251, 191, 36, 0.35);
    color: #fde68a;
    border-radius: 4px;
    padding: 4px 10px;
    font-size: 12px;
    cursor: pointer;
}
.bridge-warning-forget:hover {
    background: rgba(251, 191, 36, 0.1);
    border-color: rgba(251, 191, 36, 0.55);
}

.bridge-warning-dismiss {
    background: transparent;
    border: none;
    color: #fef3c7;
    font-size: 20px;
    line-height: 1;
    padding: 2px 8px;
    cursor: pointer;
    opacity: 0.75;
}
.bridge-warning-dismiss:hover {
    opacity: 1;
}

.app {
    display: grid;
    grid-template-columns: 450px 1fr;
    height: 100%;
}

/* Server-list sidebar collapsed — the content area takes the whole width. */
.app.sidebar-collapsed {
    grid-template-columns: 1fr;
}

/* ---------- Sidebar ---------- */

.sidebar {
    background: #14171d;
    border-right: 1px solid #1f2330;
    padding: 20px 16px;
    overflow-y: auto;
}

.sidebar h1 {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 1.5px;
    color: #6b7280;
    margin: 0 0 16px 6px;
    font-weight: 700;
}

.empty {
    font-size: 13px;
    color: #6b7280;
    line-height: 1.6;
    margin: 8px 6px;
}

.empty code {
    background: #1c2030;
    padding: 1px 5px;
    border-radius: 3px;
    color: #b8becf;
    font-size: 12px;
}

.server-row {
    display: block;
    width: 100%;
    text-align: left;
    background: none;
    border: 1px solid transparent;
    border-radius: 8px;
    padding: 10px 12px;
    margin-bottom: 4px;
    cursor: pointer;
    color: inherit;
    font: inherit;
    transition: background-color 0.1s ease;
}

.server-row:hover { background: #1c2030; }
.server-row:focus-visible { outline: 2px solid #3b82f6; outline-offset: 1px; }

.server-row.active {
    background: #1f2937;
    border-color: #374151;
}

.server-name {
    font-weight: 600;
    margin-bottom: 6px;
    font-size: 14px;
}

.server-meta {
    font-size: 12px;
    color: #8c95a4;
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px 8px;
}

.host {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    margin-left: auto;
}

/* ---------- Player-count chip ---------- */

.count-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 11px;
    font-weight: 700;
    color: #b8becf;
    background: #1c2030;
    border-radius: 4px;
    padding: 2px 7px;
    white-space: nowrap;
}

.count-icon {
    width: 11px;
    height: 11px;
    flex: none;
}

/* ---------- State chip ---------- */

.state {
    text-transform: uppercase;
    font-size: 10px;
    letter-spacing: 0.5px;
    padding: 2px 7px;
    border-radius: 4px;
    font-weight: 700;
    white-space: nowrap;
}
.state-connected    { background: rgba(34,197,94,.15);  color: #4ade80; }
.state-connecting   { background: rgba(234,179,8,.18);  color: #fbbf24; }
.state-disconnected { background: rgba(239,68,68,.15);  color: #f87171; }

/* ---------- Content panel ---------- */

.content {
    display: flex;
    flex-direction: column;
    overflow: hidden;
    position: relative;
}

/* When the sidebar is collapsed, keep the header text clear of the ☰ button. */
.sidebar-collapsed .server-header {
    padding-left: 62px;
}

.placeholder {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #6b7280;
    font-size: 14px;
}

.server-header {
    padding: 18px 24px;
    border-bottom: 1px solid #1f2330;
    display: flex;
    justify-content: space-between;
    align-items: center;
    background: #11141a;
}

.server-header h2 {
    margin: 0;
    font-size: 18px;
    font-weight: 600;
}

/* ---------- Event feed ---------- */

/* Wrapper takes the flex space so the inner <ul> can scroll independently and
   the "Jump to bottom" button can be positioned over the feed's bottom-right. */
.event-feed-wrapper {
    flex: 1;
    min-height: 0;
    position: relative;
    overflow: hidden;
}

.event-feed {
    height: 100%;
    overflow-y: auto;
    list-style: none;
    margin: 0;
    padding: 12px 24px;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 13px;
    line-height: 1.6;
}

/* Pill-shaped chip floating over the bottom-right of the feed, shown only when
   autoscroll is off (i.e. the user has scrolled up). Clicking it snaps back to
   the latest event and re-enables autoscroll. */
.autoscroll-resume {
    position: absolute;
    bottom: 14px;
    right: 24px;
    background: #1c2030;
    color: #d8dde6;
    border: 1px solid #374151;
    border-radius: 16px;
    padding: 6px 14px;
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    z-index: 5;
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.55);
}

.autoscroll-resume:hover {
    background: #232a3a;
    border-color: #3b82f6;
    color: #ffffff;
}

.autoscroll-resume:focus-visible {
    outline: 2px solid #3b82f6;
    outline-offset: 2px;
}

.event {
    padding: 1px 0;
    color: #b8becf;
    word-break: break-word;
}

.event .time {
    color: #4b5563;
    margin-right: 10px;
}

.event-chat                 { color: #67e8f9; }
.event-chat .chat-user      { font-weight: 600; color: #22d3ee; margin-right: 4px; }

.event-warning              { color: #fbbf24; }
.event-error                { color: #f87171; }
.event-connstate            { color: #94a3b8; font-style: italic; }
.event-generic              { color: #b8becf; }

.event .console-type        { color: #6b7280; margin-right: 6px; }

.event-empty {
    color: #4b5563;
    font-style: italic;
    list-style: none;
}

/* ---------- Command bar ---------- */

.command-bar {
    display: flex;
    gap: 8px;
    padding: 12px 24px;
    border-top: 1px solid #1f2330;
    background: #11141a;
}

.command-bar input {
    flex: 1;
    background: #0e1014;
    border: 1px solid #1f2937;
    border-radius: 6px;
    padding: 8px 12px;
    color: #d8dde6;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 13px;
    outline: none;
}

.command-bar input:focus {
    border-color: #3b82f6;
}

.command-bar input:disabled {
    opacity: 0.5;
}

.command-bar button {
    background: #2563eb;
    border: none;
    border-radius: 6px;
    color: white;
    padding: 8px 18px;
    cursor: pointer;
    font-weight: 600;
    font-size: 13px;
}

.command-bar button:hover:not(:disabled) { background: #3b82f6; }
.command-bar button:disabled              { opacity: 0.4; cursor: not-allowed; }

/* ---------- Error banner ---------- */

.error-banner {
    background: rgba(239,68,68,.12);
    color: #f87171;
    border-top: 1px solid rgba(239,68,68,.3);
    padding: 8px 24px;
    font-size: 13px;
}

/* ---------- Sidebar user bar ---------- */

.user-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 8px 10px 12px;
    margin-bottom: 8px;
    border-bottom: 1px solid #1f2330;
}

.user-name {
    font-size: 13px;
    color: #b8becf;
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.logout-button {
    background: none;
    border: 1px solid #374151;
    color: #8c95a4;
    border-radius: 6px;
    padding: 4px 10px;
    font-size: 12px;
    cursor: pointer;
    white-space: nowrap;
}

.logout-button:hover {
    background: #1c2030;
    color: #d8dde6;
}

/* ---------- Login page ---------- */

.login-page {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    padding: 24px;
}

.login-card {
    width: 100%;
    max-width: 360px;
    background: #14171d;
    border: 1px solid #1f2330;
    border-radius: 12px;
    padding: 32px 28px;
    display: flex;
    flex-direction: column;
}

.login-title {
    margin: 0;
    font-size: 22px;
    font-weight: 700;
}

.login-sub {
    margin: 4px 0 20px;
    font-size: 13px;
    color: #6b7280;
}

.login-field {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-bottom: 14px;
}

.login-field span {
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: #8c95a4;
}

.login-field input {
    background: #0e1014;
    border: 1px solid #1f2937;
    border-radius: 6px;
    padding: 9px 12px;
    color: #d8dde6;
    font-size: 14px;
    outline: none;
}

.login-field input:focus {
    border-color: #3b82f6;
}

.login-remember {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 13px;
    color: #b8becf;
    margin-bottom: 20px;
}

.login-button {
    background: #2563eb;
    border: none;
    border-radius: 6px;
    color: white;
    padding: 10px;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
}

.login-button:hover {
    background: #3b82f6;
}

.login-error {
    background: rgba(239,68,68,.12);
    color: #f87171;
    border: 1px solid rgba(239,68,68,.3);
    border-radius: 6px;
    padding: 8px 12px;
    font-size: 13px;
    margin-bottom: 16px;
}

.login-card .validation-message {
    color: #f87171;
    font-size: 12px;
}

.login-validation {
    list-style: none;
    margin: 0 0 14px;
    padding: 0;
    color: #f87171;
    font-size: 12px;
}

/* AccessDenied additions — the login card gets a "requested path" line
   under the error, a hint paragraph, and a Back / Sign-in-as-different
   button pair. Same colour family as the login form. */
.login-return-url {
    margin-top: 8px;
    font-size: 12px;
}
.login-return-url code {
    background: rgba(0, 0, 0, 0.25);
    color: #fbbf24;
    padding: 2px 6px;
    border-radius: 4px;
    /* URLs can be long — wrap rather than blow the card width out. */
    word-break: break-all;
}

.login-hint {
    color: #b8becf;
    font-size: 13px;
    margin: 0 0 16px;
}

.login-actions {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

/* "Don't have an account? Sign up" link under the login form, and its
   sibling on the sign-up page ("Already have an account? Sign in"). */
.login-alt,
.signup-alt {
    margin: 16px 0 0;
    text-align: center;
    color: #94a3b8;
    font-size: 13px;
}
.login-alt a,
.signup-alt a {
    color: #60a5fa;
    text-decoration: none;
    margin-left: 4px;
}
.login-alt a:hover,
.signup-alt a:hover {
    text-decoration: underline;
}

/* ---------- Sign-up page (S4a) ---------- */

/* Same centred-card shape as .login-page, but the card is wider to fit
   the tier picker + extra fields. Reuses login's colour palette so the
   two anonymous pages read as siblings. */
.signup-page {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    padding: 24px;
}
.signup-card {
    width: 100%;
    max-width: 560px;
    background: #14171d;
    border: 1px solid #1f2330;
    border-radius: 12px;
    padding: 32px 28px;
    display: flex;
    flex-direction: column;
}
.signup-title {
    margin: 0;
    font-size: 22px;
    font-weight: 700;
}
.signup-sub {
    margin: 4px 0 20px;
    font-size: 13px;
    color: #6b7280;
}
.signup-error {
    background: rgba(239, 68, 68, 0.12);
    color: #f87171;
    border: 1px solid rgba(239, 68, 68, 0.3);
    border-radius: 6px;
    padding: 8px 12px;
    font-size: 13px;
    margin-bottom: 16px;
}
.signup-validation {
    list-style: none;
    margin: 0 0 14px;
    padding: 0;
    color: #f87171;
    font-size: 12px;
}

/* Tier picker — three tiles in a row on wide screens, stacking on narrow
   ones. Only the "Free" tile is interactive for S4a; the other two carry
   a "Coming soon" badge and don't accept clicks. */
.signup-tier-picker {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 10px;
    margin-bottom: 20px;
}
@media (max-width: 620px) {
    .signup-tier-picker {
        grid-template-columns: 1fr;
    }
}
.signup-tier {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    border: 1px solid #1f2330;
    background: #0f1218;
    border-radius: 8px;
    padding: 12px;
    cursor: pointer;
    transition: border-color 120ms ease, background 120ms ease;
}
.signup-tier input[type="radio"] {
    margin-top: 3px;
    accent-color: #60a5fa;
}
.signup-tier-body {
    flex: 1 1 auto;
    min-width: 0;
}
.signup-tier-name {
    font-weight: 600;
    font-size: 14px;
    color: #f8fafc;
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
}
.signup-tier-price {
    color: #94a3b8;
    font-size: 12px;
    margin: 2px 0 6px;
}
.signup-tier-details {
    color: #cbd5e1;
    font-size: 12px;
    line-height: 1.4;
}
.signup-tier-selected {
    border-color: rgba(96, 165, 250, 0.5);
    background: rgba(96, 165, 250, 0.06);
}
.signup-tier-disabled {
    opacity: 0.55;
    cursor: not-allowed;
}
.signup-tier-badge {
    display: inline-block;
    padding: 1px 6px;
    border-radius: 3px;
    background: rgba(245, 158, 11, 0.15);
    color: #fbbf24;
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

/* Shown under the tier picker when ?plan=pro|enterprise pre-selected a
   tier that isn't wired up yet — same amber palette as the plan-banner
   warning family so the tone matches the rest of the app. */
.signup-plan-notice {
    background: rgba(245, 158, 11, 0.12);
    color: #fbbf24;
    border: 1px solid rgba(245, 158, 11, 0.35);
    border-radius: 6px;
    padding: 10px 12px;
    font-size: 12px;
    line-height: 1.4;
    margin: 0 0 16px;
}

/* Fields — same shape as login-field, but sign-up has more of them so a
   tighter margin keeps the card compact. */
.signup-field {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-bottom: 12px;
}
.signup-field span {
    color: #94a3b8;
    font-size: 12px;
    font-weight: 500;
    letter-spacing: 0.02em;
    text-transform: uppercase;
}
.signup-field input {
    background: #0f1218;
    border: 1px solid #1f2330;
    border-radius: 6px;
    padding: 8px 10px;
    color: #e5e7eb;
    font-size: 14px;
    outline: none;
    transition: border-color 120ms ease;
}
.signup-field input:focus {
    border-color: #60a5fa;
}

.signup-terms {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    margin: 4px 0 8px;
    color: #cbd5e1;
    font-size: 13px;
    line-height: 1.4;
}
.signup-terms input[type="checkbox"] {
    margin-top: 3px;
    accent-color: #60a5fa;
}

.signup-hint {
    color: #64748b;
    font-size: 12px;
    margin: 0 0 16px;
    line-height: 1.4;
}

.signup-button {
    background: #60a5fa;
    color: #0f1218;
    font-weight: 600;
    border: none;
    border-radius: 6px;
    padding: 10px 14px;
    font-size: 14px;
    cursor: pointer;
    transition: background 120ms ease;
}
.signup-button:hover {
    background: #93c5fd;
}

/* ---- Paddle checkout landing (S4b) --------------------------------------
   /checkout renders a single dark card the operator clicks through to open
   Paddle.js's overlay. Same visual language as the plan-banner / login-card
   family — dark background, 720px max-width, generous padding — so it
   doesn't jar against the rest of the admin chrome. */

.checkout-card {
    background: #0f1420;
    border: 1px solid rgba(148, 163, 184, 0.2);
    border-radius: 10px;
    padding: 32px;
    max-width: 720px;
    margin-top: 24px;
    color: #d8dde6;
}
.checkout-card h2 {
    margin: 0 0 12px 0;
    font-size: 20px;
    color: #e6edf5;
}
.checkout-card p {
    color: #b8becf;
    line-height: 1.5;
    margin: 0 0 16px 0;
}
/* Global .btn / .btn-primary chrome (promoted from .checkout-card scope
   during S4c so BillingCard buttons on /account get the same styling).
   The dashboard's older kick/ban buttons use their own .player-action-*
   family, so this class doesn't conflict with them — .btn is only
   applied where the base is opted-into explicitly. */
.btn {
    display: inline-block;
    padding: 10px 20px;
    border-radius: 6px;
    text-decoration: none;
    font-size: 14px;
    font-weight: 500;
    background: transparent;
    border: 1px solid rgba(148, 163, 184, 0.35);
    color: #cbd5e1;
    cursor: pointer;
    margin-right: 8px;
}
.btn:hover {
    border-color: rgba(148, 163, 184, 0.6);
    color: #e6edf5;
}
.btn.btn-primary {
    background: #60a5fa;
    border-color: #60a5fa;
    color: #0f1420;
    font-weight: 600;
}
.btn.btn-primary:hover {
    background: #93c5fd;
}
.btn:disabled {
    opacity: 0.6;
    cursor: not-allowed;
}

.checkout-price {
    font-size: 24px;
    font-weight: 600;
    color: #e6edf5;
    margin: 4px 0 20px 0 !important;
}

.checkout-open-button {
    /* Paddle.js opens an overlay so the button doesn't need to be
       full-width — keep it comfortable but not oversized. */
    min-width: 200px;
    padding: 12px 24px !important;
    font-size: 15px !important;
}

.checkout-hint {
    font-size: 12px;
    color: #7c8494 !important;
    margin-top: 16px !important;
    font-style: italic;
}

/* State variants: red-tinted for "Paddle not configured", amber for
   "checkout succeeded but tier not synced yet", green for "confirmed". */
.checkout-card-error {
    border-color: rgba(239, 68, 68, 0.4);
    background: rgba(239, 68, 68, 0.05);
}
.checkout-card-ok {
    border-color: rgba(34, 197, 94, 0.4);
    background: rgba(34, 197, 94, 0.05);
}
.checkout-card-success {
    border-color: rgba(34, 197, 94, 0.4);
    background: rgba(34, 197, 94, 0.05);
    text-align: center;
}

/* Tier picker on the "unknown tier" fallback — a stack of link-buttons. */
.checkout-tier-list {
    display: flex;
    flex-direction: column;
    gap: 12px;
    margin-top: 16px;
}
.checkout-tier-link {
    display: flex !important;
    align-items: center;
    justify-content: space-between;
    padding: 16px 20px !important;
    text-align: left !important;
    margin-right: 0 !important;
}
.checkout-tier-link-name {
    font-size: 16px;
    font-weight: 600;
    color: #e6edf5;
}
.checkout-tier-link-price {
    font-size: 14px;
    color: #94a3b8;
}

/* /checkout/success — spinner while the webhook is still processing. */
.checkout-spinner {
    width: 36px;
    height: 36px;
    border: 3px solid rgba(148, 163, 184, 0.25);
    border-top-color: #60a5fa;
    border-radius: 50%;
    margin: 0 auto 20px auto;
    animation: rr-spin 900ms linear infinite;
}
@keyframes rr-spin {
    to { transform: rotate(360deg); }
}

/* ---- S4c: /account billing card + past-due banner ---------------------- */

/* .billing-card is layered on top of .editor-panel (background + border +
   radius + padding come from there). No layout overrides — the card
   inherits the .admin wrapper's width so it lines up with the sibling
   Preferences + Commands cards on /account. */
.billing-card-owner-only {
    background: rgba(148, 163, 184, 0.06);
    border-color: rgba(148, 163, 184, 0.25);
}

.billing-current { margin-bottom: 20px; }
.billing-tier-row {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    margin-bottom: 8px;
}
.billing-tier-label {
    color: #94a3b8;
    font-size: 13px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}
.billing-tier-pill {
    padding: 4px 12px;
    border-radius: 999px;
    font-weight: 600;
    font-size: 14px;
}
/* Per-tier accent — Free is muted; Pro/Enterprise get warmer colors so the
   currently-active tier reads at a glance. */
.billing-tier-free       { background: rgba(148, 163, 184, 0.18); color: #cbd5e1; }
.billing-tier-pro        { background: rgba(96, 165, 250, 0.18);  color: #93c5fd; }
.billing-tier-enterprise { background: rgba(168, 85, 247, 0.18);  color: #c4b5fd; }

.billing-status-pill {
    padding: 3px 10px;
    border-radius: 999px;
    font-size: 12px;
    font-weight: 500;
    text-transform: capitalize;
}
.billing-status-active   { background: rgba(34, 197, 94, 0.18);  color: #86efac; }
.billing-status-past-due { background: rgba(251, 146, 60, 0.20); color: #fdba74; }
.billing-status-paused   { background: rgba(234, 179, 8, 0.18);  color: #fbbf24; }
.billing-status-canceled { background: rgba(148, 163, 184, 0.18); color: #cbd5e1; }
/* "Canceling on <date>" — Paddle-hosted cancel scheduled at end
   of billing period. Amber to match past-due (both are
   "something needs attention") with warmer tone to distinguish.
   text-transform: none because the date already has proper casing. */
.billing-status-canceling { background: rgba(251, 191, 36, 0.20); color: #fcd34d; text-transform: none; }

.billing-hint {
    color: #94a3b8;
    font-size: 13px;
    margin: 4px 0 0 0;
}
.billing-hint-warn { color: #fbbf24; }

.billing-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    align-items: center;
    margin-top: 16px;
}
.billing-action-button {
    /* Padding + font-size match the global .btn-primary rule (7px 14px,
       13px font) so ALL billing-card buttons render at identical height
       regardless of whether they're the primary (Change plan) or neutral
       (Manage payment / Cancel / Reactivate) variant. Before this
       alignment the primary button was slightly shorter than its
       siblings because the later .btn-primary rule further down in
       app.css overrode padding/font-size but the neutral buttons
       weren't touched by it. */
    padding: 7px 14px;
    font-size: 13px;
    min-width: 180px;
    text-align: center;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
}
/* Price chip inside the Upgrade button — same dark tone as the button
   text, but font-weight 500 (vs 600 on the label) makes the price read
   as secondary. Previous try at 65% opacity muddied against the light
   blue background. */
.billing-action-price {
    color: #0f1420;
    opacity: 0.75;
    font-weight: 500;
    font-size: 13px;
}

/* Top-bar Free-trial countdown pill (S4c follow-up). Amber tint so it
   catches the eye without being alarming; brighter as the trial gets
   closer to expiring is a future refinement. Placed left of the user
   email to sit between navigation and identity. */
.topbar-trial-pill {
    display: inline-flex;
    align-items: center;
    padding: 4px 12px;
    margin-right: 10px;
    border-radius: 999px;
    background: rgba(251, 146, 60, 0.15);
    color: #fdba74;
    font-size: 12px;
    font-weight: 600;
    text-decoration: none;
    letter-spacing: 0.2px;
    transition: background 120ms ease;
}
.topbar-trial-pill:hover {
    background: rgba(251, 146, 60, 0.25);
    color: #fed7aa;
}

/* ---- S5a tenant switcher (top bar) --------------------------------- */
/* Only rendered for logins that belong to more than one account, so this
   whole block is dead CSS for the single-tenant operator. Positioned
   relative so the dropdown can anchor to it. */
.tenant-switcher {
    position: relative;
    margin-right: 10px;
}
.tenant-switcher-button {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    max-width: 200px;
    padding: 4px 10px;
    border: 1px solid #334155;
    border-radius: 6px;
    background: #1e293b;
    color: #e2e8f0;
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    transition: border-color 120ms ease, background 120ms ease;
}
.tenant-switcher-button:hover {
    border-color: #475569;
    background: #263449;
}
.tenant-switcher-current {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.tenant-switcher-caret {
    font-size: 10px;
    color: #94a3b8;
}
/* Full-viewport transparent catcher — a click anywhere outside the menu
   closes it. Keeps the dismiss behaviour in C# instead of needing a
   document-level JS listener. z-index sits just under the menu. */
.tenant-switcher-backdrop {
    position: fixed;
    inset: 0;
    z-index: 40;
}
.tenant-switcher-menu {
    position: absolute;
    top: calc(100% + 6px);
    right: 0;
    z-index: 41;
    min-width: 240px;
    padding: 6px;
    border: 1px solid #334155;
    border-radius: 8px;
    background: #0f172a;
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);
}
.tenant-switcher-menu-head {
    padding: 4px 8px 6px;
    color: #64748b;
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}
.tenant-switcher-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    width: 100%;
    padding: 7px 8px;
    border: 0;
    border-radius: 6px;
    background: transparent;
    color: #e2e8f0;
    font-size: 13px;
    text-align: left;
}
.tenant-switcher-item-button {
    cursor: pointer;
    transition: background 120ms ease;
}
.tenant-switcher-item-button:hover {
    background: #1e293b;
}
.tenant-switcher-item-current {
    color: #94a3b8;
}
.tenant-switcher-item-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.tenant-switcher-item-badge,
.tenant-switcher-item-owner {
    flex: none;
    padding: 1px 7px;
    border-radius: 999px;
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.3px;
}
.tenant-switcher-item-badge {
    background: rgba(148, 163, 184, 0.15);
    color: #94a3b8;
}
.tenant-switcher-item-owner {
    background: rgba(56, 189, 248, 0.15);
    color: #7dd3fc;
}

/* ---- S5b organization card (/account) ------------------------------ */
/* Hints and the loading line reuse .editor-help; the input reuses
   .editor-field + .sched-input. Only the read-only name display needs a
   rule of its own. */
.org-card-readonly-name {
    margin: 0 0 8px;
    font-size: 16px;
    font-weight: 600;
}

/* ---- S5b invitations (/users) -------------------------------------- */
.invite-muted {
    color: #94a3b8;
    font-size: 13px;
}

/* ---- S5b accept-invite page (/accept-invite) ----------------------- */
/* Public page, so it renders outside the app chrome — same centred dark
   card family as the login / sign-up / select-tenant pages. */
.accept-invite-page {
    display: flex;
    justify-content: center;
    padding: 80px 20px 40px;
}
.accept-invite-card {
    width: 100%;
    max-width: 480px;
    padding: 28px;
    border: 1px solid #1e293b;
    border-radius: 12px;
    background: #0f172a;
}
.accept-invite-card h1 {
    margin: 0 0 8px;
    font-size: 22px;
}
.accept-invite-sub {
    margin: 0 0 20px;
    color: #94a3b8;
    font-size: 14px;
    line-height: 1.5;
}
/* Amber rather than red: being signed in as the wrong person is a
   "here's what to do next" state, not a failure. */
.accept-invite-warn {
    margin: 0 0 16px;
    padding: 12px 14px;
    border: 1px solid rgba(251, 146, 60, 0.4);
    border-radius: 8px;
    background: rgba(251, 146, 60, 0.1);
    color: #fdba74;
    font-size: 13px;
    line-height: 1.5;
}

/* ---- S5a tenant picker page (/select-tenant) ----------------------- */
/* Sibling of .login-card / .signup-card — same centred dark-card shape,
   sized between the two (480px) because the content is a short list. */
.select-tenant-page {
    display: flex;
    justify-content: center;
    padding: 80px 20px 40px;
}
.select-tenant-card {
    width: 100%;
    max-width: 480px;
    padding: 28px;
    border: 1px solid #1e293b;
    border-radius: 12px;
    background: #0f172a;
}
.select-tenant-card h1 {
    margin: 0 0 8px;
    font-size: 22px;
}
.select-tenant-sub {
    margin: 0 0 20px;
    color: #94a3b8;
    font-size: 14px;
    line-height: 1.5;
}
.select-tenant-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.select-tenant-option {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    width: 100%;
    padding: 14px 16px;
    border: 1px solid #334155;
    border-radius: 8px;
    background: #1e293b;
    color: #e2e8f0;
    font-size: 14px;
    text-align: left;
    cursor: pointer;
    transition: border-color 120ms ease, background 120ms ease;
}
.select-tenant-option:hover {
    border-color: #3b82f6;
    background: #263449;
}
.select-tenant-option-name {
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.select-tenant-option-meta {
    display: flex;
    flex: none;
    align-items: center;
    gap: 8px;
}
.select-tenant-option-tier {
    color: #94a3b8;
    font-size: 12px;
}
.select-tenant-option-owner {
    padding: 1px 7px;
    border-radius: 999px;
    background: rgba(56, 189, 248, 0.15);
    color: #7dd3fc;
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.3px;
}
.select-tenant-hint {
    margin: 20px 0 0;
    color: #64748b;
    font-size: 12px;
}
/* Cancel is a destructive action — dim it slightly + red hover so a
   distracted click doesn't read as "yes I want this". */
.billing-action-danger {
    color: #fca5a5;
    border-color: rgba(239, 68, 68, 0.4);
}
.billing-action-danger:hover {
    border-color: rgba(239, 68, 68, 0.8);
    background: rgba(239, 68, 68, 0.08);
}

/* Scoped override for the "primary" (upgrade / change plan) button
   inside the /account billing card — turns the solid blue fill into
   an outline. Same weight-of-attention as the other .billing-action-button
   siblings (Manage payment / Cancel), just tinted blue so it still
   reads as the recommended action. Kept scoped to .billing-action-button
   so the .btn-primary elsewhere (Sign up form's submit, Checkout page's
   Open-checkout button, Trial-expired page's Upgrade CTA) stays solid
   blue — those are landing-page hero calls-to-action where the visual
   weight is appropriate. */
.billing-action-button.btn-primary,
.btn.billing-action-button.btn-primary {
    background: transparent;
    color: #93c5fd;
    border: 1px solid rgba(96, 165, 250, 0.55);
}
.billing-action-button.btn-primary:hover:not(:disabled),
.btn.billing-action-button.btn-primary:hover:not(:disabled) {
    background: rgba(96, 165, 250, 0.10);
    border-color: rgba(96, 165, 250, 0.85);
    color: #bfdbfe;
}
/* Price chip inside the outline button — dark text was tuned for the
   solid-blue background; on the outline variant we need a lighter
   colour that reads against the dark card. Matches the button label
   colour so the price reads as related-but-secondary. */
.billing-action-button.btn-primary .billing-action-price {
    color: #93c5fd;
}

/* Generic modal chrome — introduced with S4c's billing-cancel confirm
   dialog. The dashboard's kick/ban and other older modals use their own
   backdrop class families (`.player-action-backdrop` etc.); this pair is
   the reusable one for future confirm dialogs. */
.modal-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.55);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1000;
    padding: 24px;
}
.modal-card {
    background: #0f1420;
    border: 1px solid rgba(148, 163, 184, 0.25);
    border-radius: 10px;
    color: #d8dde6;
    max-width: 640px;
    width: 100%;
    padding: 24px 28px;
    box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4);
}
.modal-actions {
    display: flex;
    gap: 10px;
    justify-content: flex-end;
    flex-wrap: wrap;
    margin-top: 20px;
}

/* Billing-cancel-modal — variant of .modal-card sized down since the
   copy is short. Danger-styled action link on the right. */
.billing-cancel-modal {
    max-width: 520px;
    padding: 28px 32px;
}
.billing-cancel-modal h3 {
    margin: 0 0 12px 0;
    font-size: 18px;
    color: #e6edf5;
}
.billing-cancel-modal p {
    color: #b8becf;
    line-height: 1.5;
    margin: 0 0 20px 0;
}
.btn-danger {
    background: #dc2626;
    border-color: #dc2626;
    color: #fff;
}
.btn-danger:hover {
    background: #ef4444;
    border-color: #ef4444;
}

/* Past-due banner — sits under the top nav, above every page.
   Amber tint matches Bridge-missing conventions but slightly warmer
   red-orange since past-due is a real problem, not a "you might want
   to know". Not dismissable. */
.past-due-banner {
    background: rgba(251, 146, 60, 0.12);
    border-bottom: 1px solid rgba(251, 146, 60, 0.35);
    color: #fdba74;
    padding: 10px 24px;
}
.past-due-banner-inner {
    display: flex;
    align-items: center;
    gap: 12px;
    max-width: 1300px;
    margin: 0 auto;
    flex-wrap: wrap;
}
.past-due-banner-icon {
    font-size: 18px;
    flex-shrink: 0;
}
.past-due-banner-text {
    flex: 1;
    display: flex;
    gap: 8px;
    align-items: baseline;
    flex-wrap: wrap;
    min-width: 0;
}
.past-due-banner-text strong {
    color: #fed7aa;
}
.past-due-banner-text span {
    color: #fcd9b3;
    font-size: 14px;
}
.past-due-banner-action {
    background: #ea580c;
    color: #fff;
    border: none;
    padding: 8px 16px;
    border-radius: 6px;
    text-decoration: none;
    font-weight: 600;
    font-size: 13px;
    flex-shrink: 0;
}
.past-due-banner-action:hover {
    background: #f97316;
}
.past-due-banner-hint {
    color: #fcd9b3;
    font-size: 13px;
    font-style: italic;
    flex-shrink: 0;
}

/* ==== P2 — Changelog catch-up modal ============================
   Builds on the generic .modal-backdrop / .modal-card / .modal-actions
   chrome above. Wider than a confirm dialog because it can hold several
   releases' notes, and the body scrolls rather than the page so the
   acknowledge button stays reachable. */
.changelog-card {
    max-width: 720px;
}
.changelog-title {
    margin: 0 0 18px;
    font-size: 19px;
    color: #e5e9f0;
}
.changelog-entries {
    display: flex;
    flex-direction: column;
    gap: 20px;
    /* Caps the card at roughly two-thirds of the viewport so a long
       catch-up scrolls internally instead of pushing the button off
       screen. */
    max-height: 60vh;
    overflow-y: auto;
    padding-right: 6px;
}
.changelog-entry {
    border-left: 3px solid rgba(96, 165, 250, 0.45);
    padding-left: 14px;
}
.changelog-entry-head {
    display: flex;
    align-items: baseline;
    gap: 10px;
    flex-wrap: wrap;
    margin-bottom: 6px;
}
.changelog-entry-date {
    font-weight: 600;
    color: #dbeafe;
    font-size: 14px;
}
.changelog-entry-build {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 11px;
    color: #7f8ea3;
    background: rgba(148, 163, 184, 0.10);
    border-radius: 4px;
    padding: 1px 6px;
}
/* <pre> preserves the author's own line breaks without a markdown
   parser. pre-wrap so long lines still wrap instead of forcing a
   horizontal scrollbar; the font is the body font, not monospace —
   these are release notes, not code. */
.changelog-entry-notes {
    margin: 0;
    white-space: pre-wrap;
    word-break: break-word;
    font-family: inherit;
    font-size: 14px;
    line-height: 1.55;
    color: #c3cad6;
}
.changelog-dismiss {
    background: #2563eb;
    color: #fff;
    border: none;
    padding: 9px 20px;
    border-radius: 6px;
    font-weight: 600;
    font-size: 13px;
    cursor: pointer;
}
.changelog-dismiss:hover:not(:disabled) {
    background: #3b82f6;
}
.changelog-dismiss:disabled {
    opacity: 0.6;
    cursor: default;
}

/* Maintenance banner (P1) — platform-wide announcement, above the other
   two banners. Deliberately slate-blue rather than amber: this is
   information, not a fault. Amber is already carrying bridge-missing and
   past-due, and a third amber stripe would make all three easy to tune
   out. Dismissable, so it gets a button rather than a CTA link. */
.maintenance-banner {
    background: rgba(96, 165, 250, 0.10);
    border-bottom: 1px solid rgba(96, 165, 250, 0.30);
    color: #bfdbfe;
    padding: 10px 24px;
}
.maintenance-banner-inner {
    display: flex;
    align-items: center;
    gap: 12px;
    max-width: 1300px;
    margin: 0 auto;
    flex-wrap: wrap;
}
.maintenance-banner-icon {
    font-size: 16px;
    flex-shrink: 0;
}
.maintenance-banner-text {
    flex: 1;
    display: flex;
    gap: 8px;
    align-items: baseline;
    flex-wrap: wrap;
    min-width: 0;
}
.maintenance-banner-text strong {
    color: #dbeafe;
}
.maintenance-banner-text span {
    color: #c7dcfb;
    font-size: 14px;
}
/* The date line reads as metadata next to the operator's own message,
   so it's muted and monospaced-ish rather than competing with it. */
.maintenance-banner-when {
    color: #93c5fd !important;
    font-size: 13px !important;
    white-space: nowrap;
}
.maintenance-banner-dismiss {
    background: transparent;
    color: #bfdbfe;
    border: 1px solid rgba(96, 165, 250, 0.45);
    padding: 5px 12px;
    border-radius: 6px;
    font-size: 12px;
    cursor: pointer;
    flex-shrink: 0;
}
.maintenance-banner-dismiss:hover {
    background: rgba(96, 165, 250, 0.15);
    color: #dbeafe;
}

/* ==== S4c follow-up 4 — Danger zone (DeleteAccountCard) =========
   Sits at the bottom of /account. Red-tinted heading so the section
   reads distinct from the neutral panels above it, but no full-card
   red flood — the destructive UI hasn't been activated yet. Actual
   destructive buttons carry the .btn-danger red on top of this. */
.danger-zone {
    border-top: 1px solid rgba(248, 113, 113, 0.35);
}
.danger-zone h2 {
    color: #fca5a5;
}
.danger-zone-help {
    color: #94a3b8;
    font-size: 13px;
    margin: 0 0 12px 0;
}
.danger-zone-actions {
    display: flex;
    gap: 12px;
    flex-wrap: wrap;
    margin-top: 12px;
}
/* Blocked state: paid subscription still active. Refuse copy in
   amber, not red — it's an instruction, not a failure. */
.danger-zone-blocked {
    background: rgba(251, 146, 60, 0.10);
    border: 1px solid rgba(251, 146, 60, 0.35);
    color: #fdba74;
    padding: 12px 16px;
    border-radius: 8px;
    font-size: 14px;
}
/* Scheduled state — deletion is pending. Slightly warmer background
   so it reads distinct from the plain "not yet scheduled" state. */
.danger-zone-scheduled {
    background: rgba(248, 113, 113, 0.06);
}
.danger-zone-scheduled-status {
    font-weight: 600;
    color: #fca5a5;
    font-size: 15px;
    margin: 0 0 8px 0;
}
/* Confirm modal — reuses .modal-backdrop / .modal-card / .modal-actions
   family, adds a final-warning paragraph so a stray click isn't the
   only thing standing between the operator and permanent data loss. */
.danger-zone-confirm-modal {
    max-width: 520px;
}
.danger-zone-confirm-final {
    color: #fca5a5;
    font-weight: 600;
    font-size: 14px;
    margin-top: 12px;
}

/* ==== S4d — /verify-email gate ==================================
   Blocking page shown to any signed-in user whose email isn't
   verified. Same centred-card shape as /trial-expired and
   /account-deleted so the three "you can't proceed yet" screens
   read as a family. */
.verify-email {
    max-width: 520px;
    margin: 80px auto;
    padding: 0 24px;
}
.verify-email-card {
    background: #1a2338;
    border-radius: 12px;
    padding: 36px 32px;
    color: #e2e8f0;
}
.verify-email-card h1 {
    color: #f8fafc;
    margin: 0 0 12px 0;
    font-size: 22px;
}
.verify-email-intro {
    color: #cbd5e1;
    line-height: 1.6;
    margin: 0 0 22px 0;
    font-size: 15px;
}
.verify-email-field {
    margin-bottom: 16px;
}
.verify-email-field label {
    display: block;
    font-size: 13px;
    color: #94a3b8;
    margin-bottom: 6px;
}
/* Monospace + wide letter-spacing so a 6-character code is easy to
   read back against the mail. Uppercase transform means the user can
   type lowercase and still see it match what they were sent — the
   service normalises server-side either way. */
.verify-email-input {
    width: 100%;
    box-sizing: border-box;
    background: #0f1420;
    border: 1px solid rgba(148, 163, 184, 0.35);
    border-radius: 8px;
    padding: 14px 16px;
    color: #f8fafc;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 24px;
    font-weight: 600;
    letter-spacing: 8px;
    text-align: center;
    text-transform: uppercase;
}
.verify-email-input:focus {
    outline: none;
    border-color: rgba(96, 165, 250, 0.7);
}
.verify-email-input::placeholder {
    color: #475569;
    letter-spacing: 8px;
}
.verify-email-info {
    color: #86efac;
    font-size: 13px;
    margin: 0 0 12px 0;
}
.verify-email-success {
    color: #86efac;
    font-size: 15px;
    line-height: 1.6;
    margin: 0 0 20px 0;
}
.verify-email-actions {
    display: flex;
    gap: 12px;
    flex-wrap: wrap;
    margin-top: 4px;
}
.verify-email-help {
    color: #64748b;
    font-size: 12px;
    line-height: 1.5;
    margin: 18px 0 0 0;
}
/* Sign-out escape hatch at the bottom. Deliberately understated —
   it's the "wrong address, let me start over" exit, not a primary
   action. */
.verify-email-signout {
    margin-top: 24px;
    padding-top: 16px;
    border-top: 1px solid rgba(148, 163, 184, 0.15);
}
.verify-email-signout-button {
    background: none;
    border: none;
    color: #64748b;
    font-size: 13px;
    cursor: pointer;
    padding: 0;
    text-decoration: underline;
}
.verify-email-signout-button:hover {
    color: #94a3b8;
}

/* ==== S4c follow-up 4 — /account-deleted public landing =========
   Bare page shown after immediate self-service deletion. Reuses the
   trial-expired layout family for consistency; no top-nav renders
   because the user is signed out. */
.account-deleted {
    max-width: 640px;
    margin: 80px auto;
    padding: 0 24px;
    text-align: center;
}
.account-deleted-card {
    background: #1a2338;
    border-radius: 12px;
    padding: 40px 32px;
    color: #e2e8f0;
}
.account-deleted-card h1 {
    color: #f8fafc;
    margin: 0 0 16px 0;
}
.account-deleted-card p {
    color: #cbd5e1;
    line-height: 1.6;
    margin: 12px 0;
}
.account-deleted-hint {
    color: #94a3b8;
    font-style: italic;
}
.account-deleted-actions {
    display: flex;
    gap: 12px;
    justify-content: center;
    margin-top: 24px;
    flex-wrap: wrap;
}

/* Trial-expired: non-owner variant sits between the lead paragraph and
   the actions row. Uses the .plan-banner-warn colour family so it reads
   consistent with the over-cap warnings elsewhere. */
.trial-expired-owner-only {
    background: rgba(251, 146, 60, 0.12);
    border: 1px solid rgba(251, 146, 60, 0.35);
    color: #fdba74;
    padding: 12px 16px;
    border-radius: 8px;
    margin: 16px 0;
    font-size: 14px;
}

/* Ghost button variant of .login-button — same size + shape, less
   emphasis so the primary "Back to dashboard" reads as the default. */
.login-secondary {
    background: transparent;
    border: 1px solid rgba(148, 163, 184, 0.35);
    border-radius: 6px;
    color: #cbd5e1;
    padding: 10px;
    font-size: 14px;
    font-weight: 500;
    text-align: center;
    text-decoration: none;
    cursor: pointer;
}
.login-secondary:hover {
    background: rgba(148, 163, 184, 0.08);
    color: #f1f5f9;
}
/* The primary "Back to dashboard" link renders as .login-button — keep
   its underline off so it looks the same as the form button. */
.login-actions a.login-button {
    text-decoration: none;
    text-align: center;
    display: block;
}

/* ---------- Sidebar heading + manage link ---------- */

.sidebar-heading {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin: 0 6px 12px;
}

.sidebar-heading h1 {
    margin: 0;
}

.sidebar-heading-actions {
    display: flex;
    align-items: center;
    gap: 10px;
}

.sidebar-collapse-btn {
    border: 1px solid #374151;
    background: none;
    color: #8c95a4;
    border-radius: 6px;
    padding: 4px 9px;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    line-height: 1;
    cursor: pointer;
}

.sidebar-collapse-btn:hover {
    background: #1c2030;
    color: #d8dde6;
}

/* Floating button to reopen the sidebar, shown over the content when collapsed. */
.sidebar-show-btn {
    position: absolute;
    top: 13px;
    left: 14px;
    z-index: 20;
    width: 36px;
    height: 36px;
    border: 1px solid #1f2330;
    background: #14171d;
    color: #b8becf;
    border-radius: 8px;
    font-size: 16px;
    line-height: 1;
    cursor: pointer;
}

.sidebar-show-btn:hover {
    background: #1c2030;
    color: #d8dde6;
}

/* Match the visual treatment of .sidebar-collapse-btn so the two controls
   in the sidebar heading read as a single button cluster. Anchor (not
   button) so it still navigates as a link — we just paint it like a
   button. */
.manage-link {
    display: inline-block;
    border: 1px solid #374151;
    background: none;
    color: #8c95a4;
    border-radius: 6px;
    padding: 4px 9px;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    line-height: 1;
    text-decoration: none;
}

.manage-link:hover {
    background: #1c2030;
    color: #d8dde6;
    text-decoration: none;
}

/* ---------- Server-management admin page ---------- */

.admin {
    /* Widened from the original 1170px so per-server config pages
       (Servers, Bans, Raid detection, Triggers, ...) can accommodate
       wider layouts — the dashboard (Home.razor) has its own outer
       chrome and is unaffected by this rule. */
    max-width: 1300px;
    margin: 0 auto;
    padding: 28px 24px 48px;
}

/* .admin-header is a 3-row flex layout using display:contents on
   .admin-title so the DOM stays hierarchical (back-link + h1 + sub
   nested inside a title div) but layout treats them as siblings of
   the action cluster. Rows:
     [0] back-link                          — full-width
     [1] h1                    | actions    — same row, actions right
     [2] sub (descriptive text)             — full-width, no line-cap
   The full-width sub row was the point of the 2026-07-13 restructure —
   long descriptions used to be trapped inside .admin-title's flex
   width and got squeezed by the action-cluster on the right, forcing
   an unreadable multi-line wrap next to buttons. */
.admin-header {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 4px 16px;
    margin-bottom: 20px;
}

/* display:contents flattens .admin-title — its children (.back-link,
   h1, .admin-sub) become direct flex children of .admin-header without
   the Razor markup needing to change. Modern-browser support is
   universal; the accessibility caveat about display:contents dropping
   the element from the a11y tree is fine here because .admin-title is
   purely presentational (no ARIA role, no landmark). */
.admin-title {
    display: contents;
}

.back-link {
    display: inline-block;
    font-size: 13px;
    color: #8c95a4;
    text-decoration: none;
}

/* Only the back-link inside .admin-header takes a full row above the
   title — other .back-link usages (there aren't any right now but
   the class is generic) keep the default inline-block behaviour. */
.admin-header .back-link {
    flex-basis: 100%;
    order: 0;
    margin: 0 0 2px;
}

.back-link:hover {
    color: #d8dde6;
}

.admin-title h1 {
    order: 1;
    flex: 1 1 auto;
    min-width: 0;
    margin: 0;
    font-size: 22px;
}

.admin-sub {
    order: 2;
    flex-basis: 100%;
    margin: 4px 0 0;
    font-size: 13px;
    color: #6b7280;
    /* No max-width cap now — the sub owns its own full-width row so a
       long description never squeezes the h1 or the action cluster. */
}

.admin-error {
    background: rgba(239,68,68,.12);
    color: #f87171;
    border: 1px solid rgba(239,68,68,.3);
    border-radius: 6px;
    padding: 9px 14px;
    font-size: 13px;
    margin-bottom: 16px;
}

.admin-status {
    background: rgba(34,197,94,.12);
    color: #4ade80;
    border: 1px solid rgba(34,197,94,.3);
    border-radius: 6px;
    padding: 9px 14px;
    font-size: 13px;
    margin-bottom: 16px;
}

/* Plan-tier over-cap banner. Amber (warn), never dismissable — the
   operator needs to fix the situation or upgrade before the banner
   goes away. Distinct from .admin-error (red) which is a per-action
   failure, and from .admin-status (green) which is a success. */
.plan-banner {
    border-radius: 6px;
    padding: 10px 14px;
    font-size: 13px;
    margin-bottom: 16px;
    line-height: 1.4;
}
.plan-banner-warn {
    background: rgba(245,158,11,.12);
    color: #fbbf24;
    border: 1px solid rgba(245,158,11,.35);
}

/* Trial-expired landing page. Simple centred card — no admin chrome
   because the page is intentionally outside the normal /admin surface
   (no back-link, no h1-with-sub layout). */
.trial-expired {
    max-width: 640px;
    margin: 80px auto;
    padding: 0 20px;
}
.trial-expired-card {
    background: rgba(30, 41, 59, 0.6);
    border: 1px solid rgba(148, 163, 184, 0.15);
    border-radius: 10px;
    padding: 32px;
    text-align: center;
}
.trial-expired-card h1 {
    margin: 0 0 12px 0;
    color: #f8fafc;
    font-size: 24px;
}
.trial-expired-lead {
    color: #cbd5e1;
    font-size: 15px;
    line-height: 1.6;
    margin: 0 0 16px 0;
}
.trial-expired-meta {
    color: #94a3b8;
    font-size: 13px;
    margin: 0 0 24px 0;
}
.trial-expired-actions {
    display: flex;
    gap: 12px;
    justify-content: center;
    margin: 0 0 20px 0;
}
.trial-expired-help {
    color: #64748b;
    font-size: 12px;
    margin: 0;
}

/* ---------- Buttons ---------- */

.btn-primary, .btn-secondary, .btn-danger {
    border-radius: 6px;
    padding: 7px 14px;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    border: 1px solid transparent;
    /* Short labels like "+ New ban" were wrapping when the admin-header flexbox
       squeezed the button — keep button labels on a single line. */
    white-space: nowrap;
    flex-shrink: 0;
}

.btn-primary {
    background: #2563eb;
    color: white;
}
.btn-primary:hover:not(:disabled) { background: #3b82f6; }

.btn-secondary {
    background: none;
    border-color: #374151;
    color: #b8becf;
}
.btn-secondary:hover:not(:disabled) { background: #1c2030; }

.btn-danger {
    background: none;
    border-color: rgba(239,68,68,.4);
    color: #f87171;
}
.btn-danger:hover:not(:disabled) { background: rgba(239,68,68,.12); }

.btn-primary:disabled, .btn-secondary:disabled, .btn-danger:disabled {
    opacity: 0.45;
    cursor: not-allowed;
}

/* ---------- Editor panel ---------- */

.editor-panel {
    background: #14171d;
    border: 1px solid #1f2330;
    border-radius: 10px;
    padding: 20px 22px;
    margin-bottom: 22px;
}

.editor-panel h2 {
    margin: 0 0 16px;
    font-size: 16px;
}

.editor-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 14px 18px;
}

.editor-field {
    display: flex;
    flex-direction: column;
    gap: 5px;
}

.editor-field span {
    font-size: 12px;
    color: #8c95a4;
}

.editor-field input {
    background: #0e1014;
    border: 1px solid #1f2937;
    border-radius: 6px;
    padding: 8px 11px;
    color: #d8dde6;
    font-size: 13px;
    outline: none;
}

.editor-field input:focus {
    border-color: #3b82f6;
}

.editor-field input:disabled {
    opacity: 0.55;
}

.editor-checkbox {
    grid-column: 1 / -1;
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 13px;
    color: #b8becf;
}

.editor-checkbox > span {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.editor-subheading {
    margin: 22px 0 6px;
    font-size: 13px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: #6b7280;
    font-weight: 600;
}

.editor-help {
    margin: 0 0 10px;
    font-size: 12px;
    color: #8c95a4;
    line-height: 1.45;
}

.editor-checkbox .editor-help {
    margin: 0;
}

.editor-validation {
    list-style: none;
    margin: 14px 0 0;
    padding: 0;
    color: #f87171;
    font-size: 12px;
}

.editor-actions {
    display: flex;
    gap: 10px;
    margin-top: 18px;
}

/* ---------- Server table ---------- */

.server-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
}

.server-table th {
    text-align: left;
    text-transform: uppercase;
    font-size: 11px;
    letter-spacing: 0.5px;
    color: #6b7280;
    padding: 8px 12px;
    border-bottom: 1px solid #1f2330;
}

.server-table td {
    padding: 12px;
    border-bottom: 1px solid #1a1d27;
    vertical-align: middle;
}

.srv-name {
    font-weight: 600;
    color: #d8dde6;
}

.srv-id {
    font-size: 11px;
    color: #6b7280;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

.srv-addr {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    color: #b8becf;
}

.table-empty {
    text-align: center;
    color: #6b7280;
    padding: 28px 12px;
}

.actions-col {
    text-align: right;
    white-space: nowrap;
}

.actions-col button {
    margin-left: 6px;
}

.confirm-text {
    font-size: 13px;
    color: #f87171;
    margin-right: 4px;
}

.toggle {
    border-radius: 6px;
    padding: 4px 12px;
    font-size: 12px;
    font-weight: 700;
    cursor: pointer;
    border: 1px solid transparent;
}

.toggle.on {
    background: rgba(34,197,94,.15);
    color: #4ade80;
}

.toggle.off {
    background: #1c2030;
    color: #8c95a4;
}

.toggle:disabled {
    opacity: 0.45;
    cursor: not-allowed;
}

.state-disabled {
    background: #1c2030;
    color: #8c95a4;
}

/* ---------- Access-denied page ---------- */

.access-denied {
    max-width: 460px;
    margin: 80px auto;
    padding: 32px;
    text-align: center;
}

.access-denied h1 {
    margin: 0 0 8px;
    font-size: 22px;
}

.access-denied p {
    margin: 0 0 16px;
    color: #8c95a4;
    font-size: 14px;
}

.access-denied a {
    color: #3b82f6;
    text-decoration: none;
    font-size: 14px;
}

.access-denied a:hover {
    text-decoration: underline;
}

/* ---------- Users & Roles page ---------- */

.role-select {
    background: #0e1014;
    border: 1px solid #1f2937;
    border-radius: 6px;
    padding: 5px 8px;
    color: #d8dde6;
    font-size: 13px;
    font-family: inherit;
    cursor: pointer;
}

.role-select:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

.pw-reset-input {
    background: #0e1014;
    border: 1px solid #1f2937;
    border-radius: 6px;
    padding: 6px 10px;
    margin-right: 6px;
    color: #d8dde6;
    font-size: 13px;
}

.pw-reset-input:focus {
    outline: none;
    border-color: #3b82f6;
}

.you-tag {
    margin-left: 8px;
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: #4ade80;
    background: rgba(34, 197, 94, 0.15);
    border-radius: 3px;
    padding: 1px 6px;
    vertical-align: middle;
}

/* ---------- Dashboard panes (player list + console) ---------- */

.header-meta {
    display: flex;
    align-items: center;
    gap: 10px;
}

.panes {
    flex: 1;
    min-height: 0;
    display: grid;
    /* Left column tracks the square map's size (same clamp as .pane-map height)
       so the map fills it without side letterboxing; the console takes the rest. */
    grid-template-columns: clamp(320px, 60vh, 680px) minmax(0, 1fr);
}

.pane {
    display: flex;
    flex-direction: column;
    min-height: 0;
    overflow: hidden;
}

/* Left column of the dashboard: map pane stacked above the player list. */
.pane-left {
    display: flex;
    flex-direction: column;
    min-height: 0;
    overflow: hidden;
    border-right: 1px solid #1f2330;
}

.pane-players {
    flex: 1;
    min-height: 0;
}

/* When the user clicks the player-list ↑ chip the pane covers the map.
   The map keeps polling and rendering in the background; we just hide it so
   re-opening it doesn't have to re-fetch anything. */
.pane-left.players-expanded .pane-map {
    display: none;
}

.pane-title {
    padding: 10px 16px;
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 1px;
    color: #6b7280;
    border-bottom: 1px solid #1f2330;
    background: #11141a;
}

.pane-scroll {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
}

/* ---------- Player table ---------- */

.player-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
}

.player-table thead th {
    position: sticky;
    top: 0;
    background: #14171d;
    text-align: left;
    text-transform: uppercase;
    font-size: 10px;
    letter-spacing: 0.5px;
    color: #6b7280;
    padding: 8px 14px;
    border-bottom: 1px solid #1f2330;
}

.player-table td {
    padding: 8px 14px;
    border-bottom: 1px solid #1a1d27;
    white-space: nowrap;
}

.p-name {
    font-weight: 600;
    color: #d8dde6;
}

.p-steam, .p-ip {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    color: #8c95a4;
    font-size: 12px;
}

.p-time {
    color: #b8becf;
}

.player-empty {
    text-align: center;
    color: #4b5563;
    font-style: italic;
    padding: 24px 14px !important;
    white-space: normal !important;
}

/* ---------- Player list: List / Teams view ---------- */

.pane-title-flex {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding-top: 6px;
    padding-bottom: 6px;
}

.player-view-toggle {
    display: flex;
    gap: 3px;
}

/* Right side of the player-list header — groups the List/Teams toggle and the
   expand/contract chips, with a slight gap between the two groups. */
.player-toolbar {
    display: flex;
    align-items: center;
    gap: 10px;
}

.player-size-toggle {
    display: flex;
    gap: 3px;
}

/* Compact, icon-only variant of .view-chip — same height/border, no text. */
.view-chip-icon {
    padding: 4px 6px;
    line-height: 0;
}

.view-chip-icon svg {
    width: 12px;
    height: 12px;
    display: block;
}

.view-chip-icon:disabled {
    opacity: 0.35;
    cursor: not-allowed;
}

.view-chip-icon:hover:not(:disabled) {
    color: #d8dde6;
    background: #1c2030;
}

.view-chip {
    font-family: inherit;
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    border: 1px solid #1f2330;
    border-radius: 4px;
    background: #14171d;
    color: #6b7280;
    padding: 4px 11px;
    cursor: pointer;
}

.view-chip.on {
    background: #1f2937;
    color: #d8dde6;
    border-color: #374151;
}

.view-chip:hover:not(.on) { color: #b8becf; }
.view-chip:focus-visible { outline: 2px solid #3b82f6; outline-offset: 1px; }

/* Team-leader (parent) row. */
.player-row-leader td {
    background: rgba(251, 191, 36, 0.05);
}

.leader-badge {
    display: inline-block;
    font-size: 9px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    color: #0e1014;
    background: #fbbf24;
    border-radius: 3px;
    padding: 1px 5px;
    margin-right: 7px;
    vertical-align: middle;
}

/* Nested team-member row — indented, with a continuous tree spine down the
   left of the group. */
.player-table td.p-name-nested {
    padding-left: 34px;
    position: relative;
}

.p-name-nested::before {
    content: "";
    position: absolute;
    left: 20px;
    top: 0;
    bottom: 0;
    border-left: 1px solid #2a3142;
}

/* Offline team leader shown as a group head. */
.p-offline { color: #6b7280; }

.p-offline-tag {
    font-size: 9px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    color: #6b7280;
    margin-left: 7px;
}

/* Inline flag glyph next to a player's display name — passive indicator
   that this SteamID has at least one visible flag. Not clickable; the
   operator opens the info modal via the Info button on the same row to
   actually view / manage flags. Amber matches the highlight star to
   signal "operator attention worth noting". */
.p-name-flag {
    display: inline-flex;
    align-items: center;
    margin-left: 6px;
    color: #f59e0b;
    vertical-align: middle;
}
.p-name-flag svg {
    width: 12px;
    height: 12px;
    display: block;
}

/* ---------- Player row: flag, action buttons, icons sub-row ---------- */

/* Flag image before the IP address — fetched from flagcdn.com. */
.p-flag {
    width: 16px;
    height: 12px;
    margin-right: 8px;
    vertical-align: middle;
    border-radius: 2px;
    box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.4);
}

/* Same footprint as .p-flag, so the IP text doesn't jump when the lookup
   resolves and the flag image is swapped in. */
.p-flag-placeholder {
    display: inline-block;
    width: 16px;
    height: 12px;
    margin-right: 8px;
    vertical-align: middle;
    border-radius: 2px;
    background: #1f2330;
}

.p-ip-addr {
    vertical-align: middle;
}

/* Action buttons live in the per-player icons sub-row (alongside ping / VPN /
   etc.), pushed to the right edge via the .p-row-actions wrapper. The shared
   .p-action-btn rules keep the icon-only look — transparent surface, hover
   colour comes from the per-variant rules below. */
.p-action-btn {
    background: transparent;
    border: 1px solid transparent;
    color: #6b7280;
    padding: 3px;
    border-radius: 4px;
    cursor: pointer;
    line-height: 0;
    vertical-align: middle;
}

.p-action-btn svg {
    width: 14px;
    height: 14px;
    display: block;
}

.p-action-btn:hover {
    background: #14171d;
    color: #d8dde6;
    border-color: #2a3142;
}

.p-action-kick:hover {
    color: #facc15;
    border-color: rgba(250, 204, 21, 0.4);
}

.p-action-ban:hover {
    color: #f87171;
    border-color: rgba(248, 113, 113, 0.4);
}

/* Highlight (star) toggle — blue palette so the button reads distinctly
   from Kick (amber) and Ban (red). The .active modifier paints the star
   solid so the on/off state reads at a glance even on a packed roster. */
.p-action-highlight:hover {
    color: #3b82f6;
    border-color: rgba(59, 130, 246, 0.4);
}

.p-action-highlight.active {
    color: #3b82f6;
    background: rgba(59, 130, 246, 0.12);
    border-color: rgba(59, 130, 246, 0.5);
}

.p-action-highlight.active:hover {
    background: rgba(59, 130, 246, 0.2);
}

/* Ping pill tiers — green at healthy latency, amber as it climbs, red when
   it's actually rough. The chip width is variable; let the icon column stay
   tight. */
.p-icon-group.p-icon-good {
    color: #6ee7b7;
    border-color: rgba(110, 231, 183, 0.3);
}

.p-icon-group.p-icon-good svg {
    color: #6ee7b7;
}

.p-icon-group.p-icon-warn-soft {
    color: #fbbf24;
    border-color: rgba(251, 191, 36, 0.3);
}

.p-icon-group.p-icon-warn-soft svg {
    color: #fbbf24;
}

/* Main row visually pairs with its icons sub-row: drop the border between
   them so the two rows read as one record. */
.player-row-main-with-icons td {
    border-bottom-color: transparent;
}

/* Sub-row of small badges (VPN/Proxy + dummy VAC / game bans). */
/* Sub-row cell stays a regular <td> (display: table-cell) so it contributes
   to column-width calculations normally — the previous version put
   display:flex on the td itself, which stripped its table-cell role and
   squished the player-name column. The flex layout now lives on the inner
   .p-icons-inner div. */
.player-row-icons td.p-icons {
    padding: 4px 14px 10px 14px;
}

/* Pills left, action buttons right, all on ONE line. The pills keep the
   default flex-shrink:1 so they compress (ellipsising via the pill's own
   overflow rules) before the layout breaks; .p-row-actions is flex-shrink:0
   so the highlight/kick/ban trio stays at full size and glued to the right
   edge regardless of cell width. */
.p-icons-inner {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: nowrap;
    /* Clip overflow inside the flex container rather than letting it spill
       past the cell edge into adjacent columns. */
    overflow: hidden;
    /* min-width: 0 lets the flex container actually shrink — without it the
       inner flex would resist below its content's min size, defeating the
       flex-shrink:1 we want on the pills. */
    min-width: 0;
}

/* Pills can shrink, but their content gets ellipsised rather than wrapped
   to two lines so the row height stays predictable. min-width:0 unlocks the
   flex item from min-content sizing. */
.p-icon-group {
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Wraps the highlight/kick/ban trio. margin-left:auto in the flex container
   pushes the group to the far right edge; flex-shrink:0 protects the buttons
   from ever being squeezed smaller than their icon size. */
.p-row-actions {
    margin-left: auto;
    display: flex;
    gap: 4px;
    flex-shrink: 0;
}

.p-icon-group {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    background: #11141a;
    border: 1px solid #1f2330;
    border-radius: 4px;
    padding: 2px 7px;
    color: #8c95a4;
    font-size: 11px;
    line-height: 1.4;
}

.p-icon-group svg {
    width: 11px;
    height: 11px;
    color: #6b7280;
}

.p-icon-group.p-icon-warn {
    color: #f87171;
    border-color: rgba(239, 68, 68, 0.35);
}

.p-icon-group.p-icon-warn svg {
    color: #f87171;
}

.p-icon-group.p-icon-loading {
    color: #6b7280;
    font-style: italic;
}

.p-icon-group.p-icon-muted {
    color: #6b7280;
}

/* "Not in a team" divider between the team groups and the solo players. */
.roster-section td {
    padding: 5px 14px;
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: #4b5563;
    background: #11141a;
    border-bottom: 1px solid #1f2330;
}

/* ---------- Console filter bar ---------- */

.filter-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    padding: 8px 16px;
    border-bottom: 1px solid #1f2330;
    background: #11141a;
}

.filter-bar-label {
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: #4b5563;
    margin-right: 2px;
}

.filter-chip {
    font-family: inherit;
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    border-radius: 4px;
    padding: 3px 9px;
    cursor: pointer;
    border: 1px solid transparent;
    transition: background-color 0.1s ease, color 0.1s ease, border-color 0.1s ease;
}

.filter-chip.on {
    background: #1f2937;
    color: #b8becf;
    border-color: #374151;
}

.filter-chip.off {
    background: #14171d;
    color: #4b5563;
    border-color: #1f2330;
    text-decoration: line-through;
}

.filter-chip:hover {
    border-color: #3b82f6;
}

.filter-chip:focus-visible {
    outline: 2px solid #3b82f6;
    outline-offset: 1px;
}

/* Team-chat badge in the console feed. */
.event-chat .chat-channel {
    font-size: 9px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: #0e1014;
    background: #22d3ee;
    border-radius: 3px;
    padding: 0 4px;
    margin-right: 5px;
}

/* ---------- Map image upload (server editor) ---------- */

.map-image-section {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-top: 18px;
    padding-top: 18px;
    border-top: 1px solid #1f2330;
}

.map-image-label {
    font-size: 12px;
    color: #8c95a4;
}

.map-image-hint {
    margin: 0;
    font-size: 13px;
    color: #6b7280;
    line-height: 1.5;
}

.map-image-current {
    display: flex;
    align-items: flex-start;
    gap: 12px;
}

.map-image-preview {
    width: 240px;
    height: 240px;
    object-fit: contain;
    background: #0e1014;
    border: 1px solid #1f2937;
    border-radius: 8px;
}

.map-image-actions {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 12px;
}

/* ---------- Live map pane (dashboard) ---------- */

.pane-map {
    flex: 0 0 auto;
    /* Pane height = toolbar + square viewport. The viewport's side length
       matches the left grid column's width (the same clamp), so the SVG
       inside is exactly square and `preserveAspectRatio="xMidYMid meet"`
       doesn't have to letterbox the grid + map on the sides. */
    height: calc(clamp(320px, 60vh, 680px) + var(--map-toolbar-height));
    border-bottom: 1px solid #1f2330;
}

.map-view {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
}

.map-toolbar {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 7px 14px;
    background: #11141a;
    border-bottom: 1px solid #1f2330;
    /* Explicit height so .pane-map's calc(... + var(--map-toolbar-height))
       below the toolbar produces a perfectly square viewport. */
    height: var(--map-toolbar-height);
}

.map-toolbar-title {
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 1px;
    color: #6b7280;
}

.map-status {
    display: flex;
    align-items: center;
    gap: 5px;
    font-size: 12px;
    color: #b8becf;
}

.map-status-dim { color: #6b7280; }
.map-status-sep { color: #374151; }

.map-controls {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 4px;
}

.map-btn {
    background: #1c2030;
    border: 1px solid #2a3142;
    color: #b8becf;
    border-radius: 5px;
    min-width: 26px;
    height: 24px;
    padding: 0 7px;
    font-size: 13px;
    font-weight: 700;
    line-height: 1;
    cursor: pointer;
}

.map-btn-text {
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.4px;
}

.map-btn:hover:not(:disabled) {
    background: #232a3a;
    border-color: #3b82f6;
}

.map-btn:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

/* Active state for toggle buttons in the map toolbar (e.g. Grid). */
.map-btn-on {
    background: #1f2937;
    color: #d8dde6;
    border-color: #3b82f6;
}

.map-zoom-label {
    min-width: 40px;
    text-align: center;
    font-size: 11px;
    color: #8c95a4;
    font-variant-numeric: tabular-nums;
}

.map-viewport {
    flex: 1;
    min-height: 0;
    overflow: hidden;
    position: relative;
    background: #0b0d11;
    user-select: none;
}

/* map-interact.js drives pan/zoom by mutating the <svg viewBox>. The browser
   re-rasterises the embedded raster image at every zoom level, so the dashboard
   stays as crisp as the enlarge modal. Blazor renders the initial viewBox and
   then the JS owns it — Blazor's dot re-renders never clobber it because the
   source markup doesn't change.
   `position: relative` puts the SVG into the same positioned context as the
   loading skeleton so DOM order decides who paints on top — the SVG comes after
   the skeleton in markup, so the SVG covers it once the image paints. */
.map-canvas {
    display: block;
    width: 100%;
    height: 100%;
    position: relative;
}

/* Spinner + label painted behind the SVG. While the embedded raster <image>
   is fetching, the SVG is transparent and this overlay shows through; the
   instant the image paints (whether from the network or instantly from cache)
   it covers the overlay without needing a load-event handshake. Transparent
   background lets the viewport's dark backdrop sit beneath. */
.map-loading {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 14px;
    pointer-events: none;
    background: transparent;
}

.map-loading-spinner {
    width: 36px;
    height: 36px;
    border: 3px solid #1f2330;
    border-top-color: #3b82f6;
    border-radius: 50%;
    animation: map-spin 0.9s linear infinite;
}

.map-loading-text {
    font-size: 13px;
    color: #b8becf;
    letter-spacing: 0.2px;
}

@keyframes map-spin {
    to { transform: rotate(360deg); }
}

/* Rust-style coordinate grid overlaid on the map (MapGrid.razor). pointer-events
   are off so it never blocks dot hovers or drag-to-pan. */
.map-grid {
    pointer-events: none;
}

.map-grid line {
    /* Near-black to match Rust's in-game coordinate grid. The map render
       is mostly warm terrain colours, so a dark ink reads better than the
       previous light overlay and matches what operators see in-game. */
    stroke: rgba(0, 0, 0, 0.6);
    /* Constant 1 CSS pixel at every zoom level. `vector-effect:
       non-scaling-stroke` prevents the SVG viewBox scaling from thickening
       the line as you zoom in — earlier we multiplied stroke-width by
       (1 / zoom), which had the reverse effect and made the grid thick at
       reset zoom and thin at high zoom. A hair-thin uniform 1px reads as a
       reference overlay without hiding the terrain underneath. */
    stroke-width: 1;
    vector-effect: non-scaling-stroke;
}

/* font-size and stroke-width are set per-element by MapGrid.razor so they can
   scale with the per-map cell. CSS class selectors win over SVG presentation
   attributes in SVG 2, so don't add those two declarations back here unless you
   also remove them from BuildLabels. */
.map-grid-label {
    /* Rust's in-game grid uses near-black labels with a subtle light halo
       so they read against the varied warm terrain of the rendered map.
       Fill = near-black ink; stroke = translucent white halo painted
       *behind* the fill (via paint-order: stroke), giving the label a
       readable outline over both light sand and dark forest regions. */
    fill: rgba(0, 0, 0, 0.9);
    stroke: rgba(255, 255, 255, 0.55);
    paint-order: stroke;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}

/* Player dots — status colour, filled (active) vs hollow (sleeper). Each dot is
   counter-scaled by 1/zoom (--map-inv-zoom, published by map-interact.js) so it
   keeps a constant on-screen size as the map zooms. --map-dot-pe is flipped to
   "none" while dragging so tooltips don't flicker under the cursor. */
.map-dot {
    cursor: pointer;
    pointer-events: var(--map-dot-pe, auto);
    transform: scale(var(--map-inv-zoom, 1));
    transform-box: fill-box;
    transform-origin: center;
}

.map-dot.active {
    fill: #4ade80;
    stroke: #0b0d11;
}

/* Online sleeper — player is still on the RCON session but their in-world
   body is asleep. Rendered as a two-tone half-circle: left half green
   (matches Active — they're still connected), right half gray (matches
   Sleeper — but the body is asleep). Reads clearly at every zoom without
   depending on a small overlaid glyph.

   The wrapping SVG is a two-<g> stack: outer positions, inner class="map-dot
   online-sleeper" carries the counter-scale + hover events; the two half
   paths and the outline circle are siblings inside the inner group. Each
   arc omits its own stroke (fill only) so the shared black outline sits on
   top as a single crisp ring — an internal border between the halves would
   otherwise appear at the seam. */
.map-dot.online-sleeper .map-dot-half-active {
    fill: #4ade80;
    stroke: none;
}
.map-dot.online-sleeper .map-dot-half-sleeper {
    fill: rgba(148, 163, 184, 0.55);
    stroke: none;
}
.map-dot.online-sleeper .map-dot-online-sleeper-outline {
    fill: none;
    stroke: #0b0d11;
    stroke-width: 3;
}

.map-dot.sleeper {
    fill: rgba(148, 163, 184, 0.18);
    stroke: #94a3b8;
}

.map-dot:hover {
    stroke: #fde047;
}
/* Online-sleeper hover — the child <path>s have explicit stroke:none, so the
   parent group's inherited stroke doesn't paint anything visible; drive the
   hover-yellow through the outline <circle> instead. */
.map-dot.online-sleeper:hover .map-dot-online-sleeper-outline {
    stroke: #fde047;
}

/* Highlighted dots — blue palette to match the player-row highlight button.
   Sonar-style pulse on stroke-width + opacities (NOT CSS filter, which Safari
   renders inconsistently on SVG). These three SVG presentation properties
   animate cleanly across every modern browser, and they don't touch
   `transform`, so the existing inverse-zoom counter-scale on .map-dot stays
   intact and the dot's centre never shifts off-grid. */
.map-dot.map-dot-highlighted {
    fill: #3b82f6 !important;
    stroke: #3b82f6 !important;
    animation: map-dot-pulse 1.1s ease-in-out infinite;
}

@keyframes map-dot-pulse {
    0%, 100% {
        stroke-width: 3;
        stroke-opacity: 0.55;
        fill-opacity: 1;
    }
    50% {
        stroke-width: 10;
        stroke-opacity: 0.12;
        fill-opacity: 0.72;
    }
}

/* Empty-state messages drawn over the viewport. */
/* RETIRED — every overlay state is now a .map-overlay-pill. Kept only
   because the class name is generic enough that a future component might
   reasonably reach for it; if you do, note that bare low-contrast text over
   the map artwork is unreadable, which is why the awaiting states moved to
   pills. Prefer .map-overlay-pill-neutral. */
.map-overlay {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 24px;
    font-size: 13px;
    line-height: 1.6;
    color: #6b7280;
    pointer-events: none;
}

.map-overlay-bottom {
    inset: auto 0 0 0;
    padding: 8px 14px;
    font-size: 12px;
    color: #8c95a4;
    background: rgba(11, 13, 17, 0.85);
}

/* Centered pill for terminal states (e.g. server disconnected) — sits
   over any cached map image, so it needs an opaque-enough background
   to read against the artwork. */
.map-overlay-pill {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    padding: 8px 16px;
    font-size: 13px;
    font-weight: 600;
    letter-spacing: 0.02em;
    border-radius: 999px;
    pointer-events: none;
    white-space: nowrap;
    box-shadow: 0 6px 24px rgba(0, 0, 0, 0.45);
}

.map-overlay-pill-offline {
    background: rgba(120, 26, 32, 0.94);
    border: 1px solid rgba(220, 90, 96, 0.6);
    color: #ffd6d8;
}

/* Neutral (transient / informational) pill — "awaiting map data",
   "awaiting map image". Deliberately grey rather than amber or red:
   waiting for a wipe's render to finish is normal operation, not a fault.
   The near-opaque background is the whole point — these states usually
   render on top of the previous map image or the loading spinner, and the
   old bare-text version was invisible against terrain. */
.map-overlay-pill-neutral {
    background: rgba(20, 23, 29, 0.94);
    border: 1px solid rgba(148, 163, 184, 0.4);
    color: #cbd5e1;
    font-weight: 500;
}

/* Hover tooltip — floats at the cursor, above the modal too. */
.map-tip {
    position: fixed;
    z-index: 60;
    pointer-events: none;
    display: flex;
    align-items: center;
    gap: 8px;
    background: #1c2030;
    border: 1px solid #2a3142;
    border-radius: 6px;
    padding: 5px 9px;
    font-size: 12px;
    white-space: nowrap;
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.5);
}

.map-tip-name {
    font-weight: 600;
    color: #d8dde6;
}

.map-tip-status {
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.4px;
}

.map-tip-active { color: #4ade80; }
/* Online-sleeper tooltip reuses the .map-tip-active pill above (they're
   still online!) and appends this small "SLEEPING" tag after it — so the
   status reads "Active SLEEPING" at a glance, matching the same pattern
   as the half-and-half map dot. */
.map-tip-sleeping-suffix {
    margin-left: 4px;
    color: #94a3b8;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
}
.map-tip-sleeper { color: #94a3b8; }

/* ---------- Enlarge modal ---------- */

.map-modal-backdrop {
    position: fixed;
    inset: 0;
    z-index: 50;
    background: rgba(0, 0, 0, 0.72);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 32px;
}

.map-modal {
    width: min(92vw, 1180px);
    height: min(88vh, 980px);
    background: #14171d;
    border: 1px solid #1f2330;
    border-radius: 12px;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.6);
}

.map-modal-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 12px 16px;
    border-bottom: 1px solid #1f2330;
    background: #11141a;
}

.map-modal-title {
    font-size: 14px;
    font-weight: 600;
    color: #d8dde6;
}

.map-modal-close {
    background: none;
    border: 1px solid #374151;
    color: #8c95a4;
    border-radius: 6px;
    width: 28px;
    height: 28px;
    font-size: 13px;
    line-height: 1;
    cursor: pointer;
}

.map-modal-close:hover {
    background: #1c2030;
    color: #d8dde6;
}

.map-modal-body {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

/* ---------- Schedules page ---------- */

.sched-editor {
    max-width: 1230px;
}

.sched-loading {
    color: #6b7280;
    font-size: 13px;
}

.sched-inline-error {
    color: #f87171;
    font-size: 12px;
    margin-top: 8px;
}

.sched-hint {
    margin: 0 0 12px;
    font-size: 13px;
    color: #8c95a4;
    line-height: 1.55;
}

.sched-sub-hint {
    margin-top: 4px;
    font-size: 12px;
    color: #6b7280;
}

.sched-sub-hint code {
    background: #1c2030;
    padding: 1px 5px;
    border-radius: 3px;
    color: #b8becf;
    font-size: 11.5px;
}

.sched-sub {
    font-size: 11px;
    color: #6b7280;
    margin-top: 2px;
}

/* Schedule list table: pad rows a touch more than the server table. */
.sched-table td {
    vertical-align: top;
    padding-top: 14px;
    padding-bottom: 14px;
}

.sched-targets {
    max-width: 220px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: #b8becf;
}

/* ---------- Schedule editor: shared inputs ---------- */

.sched-input,
.sched-select {
    background: #0e1014;
    border: 1px solid #1f2937;
    border-radius: 6px;
    padding: 8px 11px;
    color: #d8dde6;
    font-family: inherit;
    font-size: 13px;
    outline: none;
}

.sched-input:focus,
.sched-select:focus {
    border-color: #3b82f6;
}

.sched-input:disabled,
.sched-select:disabled {
    opacity: 0.55;
    cursor: not-allowed;
}

.sched-input-num {
    width: 110px;
}

.sched-input-time {
    width: 140px;
}

.sched-input-mono {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

.sched-textarea {
    resize: vertical;
    min-height: 56px;
    font-family: inherit;
}

.sched-select-inline {
    padding: 4px 8px;
    font-size: 12px;
    width: auto;
}

/* Compact icon-only button used by step move/remove and condition-child remove. */
.sched-icon-btn {
    background: #1c2030;
    border: 1px solid #2a3142;
    color: #b8becf;
    border-radius: 5px;
    width: 26px;
    height: 26px;
    padding: 0;
    font-size: 13px;
    line-height: 1;
    cursor: pointer;
}

.sched-icon-btn:hover:not(:disabled) {
    background: #232a3a;
    border-color: #3b82f6;
}

.sched-icon-btn:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

.sched-icon-btn.danger:hover:not(:disabled) {
    border-color: rgba(239, 68, 68, 0.5);
    color: #f87171;
    background: rgba(239, 68, 68, 0.08);
}

.btn-xs {
    padding: 4px 10px;
    font-size: 12px;
}

/* ---------- Recurrence section + next-5 preview ---------- */

.recurrence-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 280px;
    gap: 22px;
}

.recurrence-controls {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.next-occ-panel {
    background: #0b0d11;
    border: 1px solid #1f2330;
    border-radius: 8px;
    padding: 14px 16px;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.next-occ-title {
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 1px;
    color: #6b7280;
    margin-bottom: 4px;
}

.next-occ-item {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
    padding: 6px 0;
    border-bottom: 1px solid #1a1d27;
    font-size: 13px;
}

.next-occ-item:last-child {
    border-bottom: none;
}

.next-occ-when {
    color: #d8dde6;
    font-variant-numeric: tabular-nums;
}

.next-occ-rel {
    font-size: 11px;
    color: #6b7280;
}

.next-occ-empty {
    color: #f87171;
    font-size: 13px;
    font-style: italic;
}

/* ---------- Weekday chips (reused by recurrence + DayOfWeek condition) ---------- */

.weekday-row {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
}

.weekday-chip {
    display: inline-flex;
    align-items: center;
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    color: #6b7280;
    background: #14171d;
    border: 1px solid #1f2330;
    border-radius: 4px;
    padding: 4px 10px;
    cursor: pointer;
    user-select: none;
}

.weekday-chip input {
    display: none;
}

.weekday-chip.on {
    background: #1f2937;
    color: #d8dde6;
    border-color: #3b82f6;
}

.weekday-chip:hover:not(.on) {
    color: #b8becf;
}

/* ---------- Day-of-month chips (DayOfMonth condition) ---------- */

.monthday-row {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(34px, 1fr));
    gap: 4px;
    max-width: 360px;
}

.monthday-chip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 11px;
    font-weight: 700;
    color: #6b7280;
    background: #14171d;
    border: 1px solid #1f2330;
    border-radius: 4px;
    padding: 4px 0;
    cursor: pointer;
    user-select: none;
    font-variant-numeric: tabular-nums;
}

.monthday-chip input {
    display: none;
}

.monthday-chip.on {
    background: #1f2937;
    color: #d8dde6;
    border-color: #3b82f6;
}

.monthday-chip:hover:not(.on) {
    color: #b8becf;
}

/* ---------- Targets grid ---------- */

.targets-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 8px;
}

.target-chip {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 8px 12px;
    background: #0e1014;
    border: 1px solid #1f2937;
    border-radius: 8px;
    cursor: pointer;
    user-select: none;
}

.target-chip.on {
    background: rgba(59, 130, 246, 0.08);
    border-color: #3b82f6;
}

.target-chip input {
    accent-color: #3b82f6;
}

.target-name {
    font-size: 13px;
    font-weight: 600;
    color: #d8dde6;
}

.target-id {
    font-size: 11px;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    color: #6b7280;
    margin-left: auto;
}

/* ---------- Step list ---------- */

.steps-empty {
    color: #6b7280;
    font-size: 13px;
    font-style: italic;
    padding: 14px 4px 18px;
}

.steps-list {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-bottom: 14px;
}

.step-card {
    background: #0e1014;
    border: 1px solid #1f2330;
    border-left: 3px solid #1f2330;
    border-radius: 8px;
    overflow: hidden;
}

.step-card.condition {
    border-left-color: #fbbf24;
}

.step-card.action {
    border-left-color: #3b82f6;
}

.step-card.ifbranch {
    border-left-color: #a78bfa;
}

.step-card-head {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 12px;
    background: #11141a;
    border-bottom: 1px solid #1f2330;
}

.step-num {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: #1f2937;
    color: #b8becf;
    font-size: 11px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

.step-kind-badge {
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    padding: 2px 8px;
    border-radius: 4px;
}

.step-kind-badge.condition {
    color: #fbbf24;
    background: rgba(251, 191, 36, 0.15);
}

.step-kind-badge.action {
    color: #60a5fa;
    background: rgba(59, 130, 246, 0.15);
}

.step-kind-badge.ifbranch {
    color: #c4b5fd;
    background: rgba(167, 139, 250, 0.18);
}

/* If/else branch card body: three rows — IF + condition, THEN + sub-list,
   ELSE + sub-list — each keyword sits left of its content. */
.if-branch {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.if-branch-row {
    display: flex;
    align-items: flex-start;
    gap: 12px;
}

.if-keyword {
    flex: 0 0 auto;
    min-width: 48px;
    padding-top: 6px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 1px;
    color: #a78bfa;
    text-transform: uppercase;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

.if-branch-cond,
.if-branch-body {
    flex: 1;
    min-width: 0;
}

.step-move {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 4px;
}

.step-card-body {
    padding: 14px 16px;
}

.add-step-bar {
    display: flex;
    gap: 10px;
}

/* ---------- Action editor ---------- */

.action-editor {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.action-type-field {
    max-width: 280px;
}

.action-row {
    display: flex;
    gap: 14px;
    flex-wrap: wrap;
}

.action-row .editor-field {
    min-width: 0;
}

.action-grow {
    flex: 1;
    min-width: 220px;
}

/* Token chip — used in the token-reference modal and the few remaining
   inline mentions ("supports {tokens}" hint text on the trigger Command
   action). The old .tokens-hint inline chip row is gone; the
   .tokens-cta + MessageTokensModal pattern replaced it so chips live in
   one place. */
.token-chip {
    background: #1c2030;
    color: #b8becf;
    border: 1px solid #2a3142;
    border-radius: 4px;
    padding: 2px 7px;
    font-size: 11.5px;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    cursor: help;
}

/* Help text + "Show available tags" button below a chat-message textarea.
   Replaces the older .tokens-hint inline chip list — now the chips live
   in the modal so we have one place to maintain (and translate). */
.tokens-cta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    font-size: 12px;
    color: #8c95a4;
    margin-top: 2px;
}

.tokens-cta-text { flex: 1; min-width: 240px; line-height: 1.45; }
.tokens-cta-text code {
    background: #1c2030;
    border: 1px solid #2a3142;
    border-radius: 3px;
    padding: 0 4px;
    font-size: 11px;
    color: #b8becf;
}

.tokens-cta-btn {
    text-transform: uppercase;
    font-size: 11px;
    letter-spacing: 0.5px;
    padding: 5px 11px;
    flex-shrink: 0;
}

/* ---------- Token reference modal ---------- */
/* Backdrop + panel mirror the existing logs-modal / plugins-perms-modal
   look so admin modals all feel consistent. */
.tokens-modal-backdrop {
    position: fixed; inset: 0;
    background: rgba(5, 8, 12, 0.6);
    display: flex; align-items: center; justify-content: center;
    z-index: 200;
    padding: 24px;
}

.tokens-modal-panel {
    background: #14171d;
    border: 1px solid #1f2330;
    border-radius: 10px;
    width: min(640px, 100%);
    max-height: min(80vh, 720px);
    overflow-y: auto;
    padding: 18px 22px 16px;
    box-shadow: 0 24px 64px rgba(0, 0, 0, 0.5);
}

.tokens-modal-header {
    display: flex; align-items: center; justify-content: space-between;
    border-bottom: 1px solid #1f2330;
    padding-bottom: 10px;
    margin-bottom: 12px;
}

.tokens-modal-header h2 { margin: 0; font-size: 15px; }

.tokens-modal-intro {
    margin: 0 0 16px;
    font-size: 12.5px;
    color: #8c95a4;
    line-height: 1.5;
}

.tokens-modal-group {
    margin-bottom: 18px;
    transition: opacity 120ms ease;
}

.tokens-modal-group-dim { opacity: 0.45; }

.tokens-modal-group-title {
    margin: 0 0 8px;
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: #d8dde6;
    font-weight: 600;
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 10px;
}

.tokens-modal-applies {
    color: #6b7280;
    font-size: 11px;
    font-weight: 500;
    letter-spacing: 0.3px;
    text-transform: none;
}

.tokens-modal-na {
    color: #f87171;
    font-weight: 500;
}

.tokens-modal-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.tokens-modal-list li {
    display: flex;
    align-items: baseline;
    gap: 10px;
    padding: 4px 0;
}

.tokens-modal-list .token-chip { cursor: default; flex-shrink: 0; }

.tokens-modal-desc {
    color: #b8becf;
    font-size: 12.5px;
    line-height: 1.4;
}

.tokens-modal-actions {
    display: flex;
    justify-content: flex-end;
    padding-top: 6px;
    border-top: 1px solid #1f2330;
}

/* ---------- Condition editor (recursive AND/OR tree) ---------- */

.cond-group {
    background: #0b0d11;
    border: 1px solid #1f2330;
    border-radius: 8px;
    padding: 12px 14px;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.cond-group-head {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.cond-label {
    font-size: 12px;
    color: #8c95a4;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    font-weight: 700;
}

.cond-children {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-left: 8px;
    padding-left: 10px;
    border-left: 1px dashed #2a3142;
}

.cond-child {
    display: flex;
    align-items: flex-start;
    gap: 6px;
}

.cond-child-body {
    flex: 1;
    min-width: 0;
}

.cond-group-actions {
    display: flex;
    gap: 8px;
}

.cond-check {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    background: #14171d;
    border: 1px solid #1f2330;
    border-radius: 6px;
    padding: 8px 10px;
}

.cond-unit {
    font-size: 12px;
    color: #8c95a4;
}

.cond-empty {
    font-size: 12px;
    color: #6b7280;
    font-style: italic;
}

/* NOT toggle on each condition node (group or leaf). The "on" colour is reused
   for the surrounding container's border so a negated subtree is easy to spot. */
.cond-not {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.6px;
    color: #6b7280;
    text-transform: uppercase;
    background: #14171d;
    border: 1px solid #2a3142;
    border-radius: 4px;
    padding: 2px 7px;
    cursor: pointer;
    user-select: none;
}

.cond-not input {
    accent-color: #f87171;
}

.cond-not:has(input:checked) {
    color: #f87171;
    border-color: rgba(239, 68, 68, 0.5);
    background: rgba(239, 68, 68, 0.1);
}

.cond-group.negated,
.cond-check.negated {
    border-color: rgba(239, 68, 68, 0.45);
    box-shadow: inset 3px 0 0 rgba(239, 68, 68, 0.35);
}

/* ---------- Triggers page ---------- */

/* Event chip in the triggers table — same compact pill style as the audit-log
   category badges so the table reads consistently. */
.trigger-event-chip {
    display: inline-block;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    background: #1f2937;
    color: #93c5fd;
    border-radius: 3px;
    padding: 1px 7px;
}

.trigger-event-onconnect { background: rgba(59, 130, 246, 0.15); color: #93c5fd; }
.trigger-event-onwake { background: rgba(16, 185, 129, 0.15); color: #6ee7b7; }

/* ---------- Trigger editor sections ---------- */

.trigger-event-section,
.trigger-targets-section,
.trigger-condition-section,
.trigger-steps-section {
    margin-top: 22px;
}

.trigger-event-section h2,
.trigger-targets-section h2,
.trigger-condition-section h2,
.trigger-steps-section h2 {
    margin: 0 0 10px;
    font-size: 14px;
    color: #d8dde6;
    display: flex;
    align-items: center;
    gap: 10px;
}

.trigger-event-options {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.trigger-event-option {
    display: grid;
    grid-template-columns: auto 1fr;
    grid-template-rows: auto auto;
    column-gap: 10px;
    align-items: start;
    padding: 10px 12px;
    background: #14171d;
    border: 1px solid #1f2330;
    border-radius: 8px;
    cursor: pointer;
}

.trigger-event-option.on {
    border-color: rgba(59, 130, 246, 0.45);
    background: rgba(59, 130, 246, 0.07);
}

.trigger-event-option input[type="radio"] {
    grid-row: 1 / 3;
    margin-top: 2px;
    accent-color: #3b82f6;
}

.trigger-event-name {
    grid-column: 2;
    font-weight: 600;
    color: #d8dde6;
}

.trigger-event-hint {
    grid-column: 2;
    color: #8c95a4;
    font-size: 12px;
    line-height: 1.4;
}

.trigger-target-list {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.trigger-target-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 10px;
    background: #14171d;
    border: 1px solid #1f2330;
    border-radius: 999px;
    color: #b8becf;
    font-size: 12px;
    cursor: pointer;
}

.trigger-target-chip.on {
    background: rgba(59, 130, 246, 0.12);
    border-color: rgba(59, 130, 246, 0.45);
    color: #d8dde6;
}

.trigger-target-chip input { accent-color: #3b82f6; }

/* Ban-template editor inside a Trigger Action step. Reuses the .ban-duration
   styling from the Bans page editor. */
.ban-template-editor {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.ban-template-channels {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px;
    padding: 10px 12px;
    background: #14171d;
    border: 1px solid #1f2330;
    border-radius: 6px;
}

.ban-template-channels .cond-label { margin-right: 4px; }

.editor-error {
    color: #f87171;
    font-size: 12px;
    margin-top: 4px;
}

/* ---------- Bans page ---------- */

/* Section headings under the page header — Rusty RCON bans and Server bans. */
.bans-section-title {
    margin: 32px 0 4px;
    font-size: 17px;
    color: #d8dde6;
}

.bans-section-sub {
    margin: 0 0 16px;
    font-size: 12.5px;
    color: #8c95a4;
    max-width: 720px;
}

.bans-section-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-top: 32px;
}

.bans-section-header .bans-section-title {
    margin: 0;
}

/* One (toggle, value) identifier slot in the ban editor — visually grouped so
   it's obvious the checkbox controls the input next to it. */
.ban-identifier {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 10px 12px;
    background: #14171d;
    border: 1px solid #1f2330;
    border-radius: 6px;
}

.ban-identifier .editor-checkbox {
    grid-column: auto;
    margin: 0;
    font-size: 12.5px;
}

.ban-identifier input {
    background: #0e1014;
    border: 1px solid #1f2937;
    border-radius: 6px;
    padding: 7px 10px;
    color: #d8dde6;
    font-size: 13px;
    outline: none;
}

.ban-identifier input:focus { border-color: #3b82f6; }

/* The "Reason" field spans both columns of the editor grid. */
.editor-field-wide {
    grid-column: 1 / -1;
}

/* Identifier value cell in the table: dim when not enabled (the value is
   stored as a draft, but it doesn't currently participate in matching). */
.ban-id-on {
    color: #d8dde6;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12.5px;
}

.ban-id-off {
    color: #6b7280;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12.5px;
    text-decoration: line-through;
}

.ban-id-empty {
    color: #4b5263;
}

/* Server-bans per-server group: one card per connected server. */
.bans-server-group {
    margin-top: 18px;
    padding: 14px 16px 6px;
    background: #14171d;
    border: 1px solid #1f2330;
    border-radius: 8px;
}

.bans-server-title {
    margin: 0 0 8px;
    font-size: 14px;
    color: #d8dde6;
}

.bans-plugin-hint {
    margin: 0 0 10px;
    font-size: 12.5px;
    color: #f8b06b;
}

/* Per-server "still asking the server" hint, while the RCON call is in
   flight. Distinct from the warm-up / plugin-missing hints — this one is
   neutral and just tells the user that work is happening. */
.bans-loading-hint {
    margin: 0 0 10px;
    font-size: 12.5px;
    color: #8c95a4;
    font-style: italic;
}

/* Small inline spinner that sits next to the server name while its bans
   fetch is in flight. Pure CSS so it doesn't need a re-render to animate. */
.bans-server-spinner {
    display: inline-block;
    width: 12px;
    height: 12px;
    margin-left: 8px;
    border: 2px solid #2a3142;
    border-top-color: #3b82f6;
    border-radius: 50%;
    vertical-align: middle;
    animation: bans-spinner-rotate 0.9s linear infinite;
}

@keyframes bans-spinner-rotate {
    to { transform: rotate(360deg); }
}

/* ---------- Player action (Kick / Ban) confirm modal ---------- */

.player-action-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.55);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1000;
}

.player-action-modal {
    width: 420px;
    max-width: calc(100vw - 32px);
    background: #14171d;
    border: 1px solid #1f2330;
    border-radius: 10px;
    padding: 22px 24px 18px;
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.6);
}

.player-action-title {
    margin: 0 0 8px;
    font-size: 16px;
    color: #d8dde6;
}

.player-action-meta {
    margin: 0 0 12px;
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.player-action-pill {
    background: #0e1014;
    border: 1px solid #1f2937;
    border-radius: 4px;
    padding: 2px 7px;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 11.5px;
    color: #b8becf;
}

.player-action-hint {
    margin: 0 0 14px;
    color: #8c95a4;
    font-size: 12.5px;
    line-height: 1.45;
}

.player-action-hint a {
    color: #93c5fd;
    text-decoration: underline;
}

.player-action-hint code {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12px;
    background: #0e1014;
    border: 1px solid #1f2937;
    border-radius: 3px;
    padding: 1px 4px;
}

.player-action-field {
    display: flex;
    flex-direction: column;
    gap: 5px;
    margin-bottom: 14px;
}

.player-action-field span {
    font-size: 12px;
    color: #8c95a4;
}

.player-action-field input {
    background: #0e1014;
    border: 1px solid #1f2937;
    border-radius: 6px;
    padding: 8px 11px;
    color: #d8dde6;
    font-size: 13px;
    outline: none;
}

.player-action-field input:focus {
    border-color: #3b82f6;
}

.player-action-error {
    margin: 0 0 12px;
    padding: 8px 11px;
    border-radius: 6px;
    background: rgba(239, 68, 68, 0.1);
    border: 1px solid rgba(239, 68, 68, 0.35);
    color: #f87171;
    font-size: 12.5px;
}

.player-action-buttons {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
}

/* ---------- Logs page ---------- */

.logs-toolbar {
    display: flex;
    align-items: flex-end;
    gap: 14px;
    margin: 16px 0;
    flex-wrap: wrap;
}

.logs-toolbar-field {
    display: flex;
    flex-direction: column;
    gap: 5px;
    font-size: 12px;
    color: #8c95a4;
    min-width: 220px;
}

.logs-toolbar-field .sched-select {
    min-width: 260px;
}

.logs-toolbar-actions {
    display: flex;
    gap: 8px;
    margin-left: auto;
}

/* Anchor-based "buttons" (the downloads) — they need to look the same as
   .btn-secondary, so reuse padding/border, just override link-default
   text-decoration and inline alignment. */
.logs-toolbar-actions a.btn-secondary {
    text-decoration: none;
    display: inline-flex;
    align-items: center;
}

/* Disabled appearance for the download anchors (no native :disabled on <a>). */
.btn-disabled {
    opacity: 0.45;
    cursor: not-allowed;
    pointer-events: none;
}

/* The log viewer scrolls independently of the rest of the page so a big day
   doesn't push the toolbar off the top. The reused .event styles already
   handle the per-line look. */
.logs-viewer {
    margin-top: 6px;
    background: #14171d;
    border: 1px solid #1f2330;
    border-radius: 6px;
    max-height: calc(100vh - 320px);
    overflow-y: auto;
}

/* Static (non-scrolling) variant of .event-feed for the Logs viewer — the
   feed in the dashboard wants 100% height so it can scroll inside its pane,
   but the Logs viewer is the scroll container itself. */
.event-feed-static {
    height: auto;
    overflow: visible;
    padding: 4px 0;
}

.logs-viewer .table-empty {
    padding: 28px 14px;
}

/* Two-up layout: the server log and user log share the page vertically.
   Each section gets its own toolbar and viewer; the viewers are split so
   neither dominates the screen, and each has an Expand chip that opens a
   modal for full-screen reading. */
.logs-sections {
    display: flex;
    flex-direction: column;
    gap: 24px;
}

.logs-section {
    display: flex;
    flex-direction: column;
}

.logs-section-head {
    display: flex;
    align-items: center;
    gap: 10px;
}

.logs-section-head .bans-section-title {
    margin: 32px 0 0;
    flex: 0 0 auto;
}

.logs-section-head .view-chip-icon {
    margin-top: 32px;
}

/* Half-height viewer for the in-page two-up layout. Modal viewer fills the
   modal body so a busy day reads top-to-bottom without an extra scroll. */
.logs-viewer-split {
    max-height: calc(50vh - 180px);
    min-height: 180px;
}

.logs-viewer-modal {
    max-height: none;
    height: 100%;
    border: 0;
    border-radius: 0;
    background: transparent;
}

/* Audit-line spans — match the dashboard / formatter colours so the in-page
   viewer is identical to the downloadable HTML. */
.audit-category {
    display: inline-block;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    background: #1f2937;
    color: #93c5fd;
    border-radius: 3px;
    padding: 1px 5px;
    margin-right: 6px;
}

.audit-category-ban { background: rgba(239, 68, 68, 0.15); color: #f87171; }
.audit-category-server { background: rgba(59, 130, 246, 0.15); color: #93c5fd; }
.audit-category-schedule { background: rgba(168, 85, 247, 0.15); color: #c4b5fd; }
.audit-category-setting { background: rgba(234, 179, 8, 0.15); color: #fbbf24; }
.audit-category-user { background: rgba(16, 185, 129, 0.15); color: #6ee7b7; }
.audit-category-dashboard { background: rgba(148, 163, 184, 0.15); color: #cbd5e1; }
.audit-category-trigger { background: rgba(34, 211, 238, 0.15); color: #67e8f9; }
.audit-category-plugin { background: rgba(244, 114, 182, 0.15); color: #f9a8d4; }
.audit-category-permissiongroup { background: rgba(132, 204, 22, 0.15); color: #bef264; }
.audit-category-billing { background: rgba(251, 146, 60, 0.15); color: #fdba74; }

.audit-actor { color: #fbbf24; }
.audit-verb { color: #d8dde6; }
.audit-target { color: #b8becf; }
.audit-details { color: #8c95a4; }

/* ---------- Logs: expand modal ---------- */

.logs-modal-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.6);
    display: flex;
    align-items: stretch;
    justify-content: center;
    padding: 24px;
    z-index: 1000;
}

.logs-modal {
    flex: 1;
    max-width: 1400px;
    background: #14171d;
    border: 1px solid #1f2330;
    border-radius: 10px;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

.logs-modal-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 12px 20px;
    border-bottom: 1px solid #1f2330;
    background: #11141a;
}

.logs-modal-title {
    font-weight: 700;
    color: #d8dde6;
    font-size: 14px;
}

.logs-modal-body {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 12px 20px 20px;
}

/* ---------- Activity page ---------- */

.activity-toolbar {
    display: flex;
    align-items: flex-end;
    gap: 16px;
    margin: 18px 0 22px;
    flex-wrap: wrap;
}

.activity-range-chips {
    display: flex;
    gap: 4px;
    margin-left: auto;
}

/* One card per chart. Border + background mirror the other admin panels so
   the page reads as part of the same admin family. */
.chart {
    background: #14171d;
    border: 1px solid #1f2330;
    border-radius: 8px;
    padding: 14px 16px 8px;
    margin-bottom: 18px;
}

.chart-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    margin-bottom: 6px;
    flex-wrap: wrap;
}

.chart-head-actions {
    display: flex;
    align-items: center;
}

.chart-title {
    margin: 0;
    font-size: 13px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.6px;
    color: #d8dde6;
}

/* Sits on its own row right under the chart head — frees the head's
   top-right corner for range chips on multi-series charts. */
.chart-legend {
    display: flex;
    gap: 14px;
    font-size: 12px;
    color: #b8becf;
    margin-bottom: 4px;
}

.chart-legend-item {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

.chart-legend-swatch {
    width: 10px;
    height: 10px;
    border-radius: 2px;
    display: inline-block;
}

/* Per-chart range chips — same look as the player-view chips on the dashboard. */
.chart-range-chips {
    display: flex;
    gap: 3px;
}

/* The wrapper is the positioning context for the hover tooltip. The SVG
   inside fills 100% of the wrapper but never stretches its own aspect ratio
   thanks to preserveAspectRatio="xMidYMid meet". Width is controlled by the
   parent .chart card — in the 2×2 grid each column sets the available width. */
.chart-svg-wrap {
    position: relative;
    margin-left: auto;
    margin-right: auto;
}

/* Two columns on wide screens, single column on narrow. Each chart card
   sits in its own grid cell so the SVG aspect-ratio is preserved without
   the chart elongating to fill the full page width. */
.activity-charts-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 20px;
    margin: 16px 0 24px;
}

@media (max-width: 980px) {
    .activity-charts-grid {
        grid-template-columns: 1fr;
    }
}

/* Health chart: dropdown sits next to the range chips in the header. */
.chart-head-controls {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}

.metric-select {
    background: #14171d;
    border: 1px solid #1f2330;
    border-radius: 4px;
    color: #d8dde6;
    font-family: inherit;
    font-size: 11px;
    padding: 4px 8px;
    cursor: pointer;
}

.metric-select:focus { outline: 2px solid #3b82f6; outline-offset: 1px; }

.chart-svg {
    display: block;
    width: 100%;
    height: auto;
}

.chart-grid {
    stroke: #1f2330;
    stroke-width: 1;
}

.chart-line {
    stroke-linejoin: round;
    stroke-linecap: round;
    pointer-events: none;  /* hover is delivered by the overlay rects */
}

.chart-point {
    pointer-events: none;
    transition: r 0.08s ease;
}

.chart-point-hover {
    /* Subtle glow on the hovered point — picked up via the .chart-point-hover
       class set by the LineChart when hoveredIndex matches. */
    filter: drop-shadow(0 0 4px currentColor);
}

.chart-hover-zone {
    cursor: default;
}

.chart-hover-line {
    stroke: #2a3142;
    stroke-width: 1;
    stroke-dasharray: 2 3;
    pointer-events: none;
}

.chart-axis-label {
    fill: #6b7280;
    font: 10px ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

.chart-axis-label-x { text-anchor: middle; }
.chart-axis-label-y { text-anchor: end; }

.chart-empty {
    color: #6b7280;
    font-style: italic;
    padding: 28px 14px;
    text-align: center;
}

/* Detailed hover tooltip — Blazor-managed, positioned via percentage so it
   tracks the SVG's responsive scaling without a JS measure step. */
.chart-tooltip {
    position: absolute;
    top: 8px;
    transform: translateX(-50%);
    background: #0e1014;
    border: 1px solid #2a3142;
    border-radius: 6px;
    padding: 8px 10px;
    font-size: 12px;
    color: #d8dde6;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.55);
    pointer-events: none;  /* never block the hover overlay rects */
    min-width: 140px;
    max-width: 220px;
    z-index: 5;
}

.chart-tooltip-title {
    font-weight: 700;
    color: #d8dde6;
    margin-bottom: 4px;
    font-size: 12px;
}

.chart-tooltip-row {
    display: grid;
    grid-template-columns: 10px 1fr auto;
    align-items: center;
    gap: 6px;
    line-height: 1.5;
}

.chart-tooltip-swatch {
    width: 8px;
    height: 8px;
    border-radius: 2px;
    display: inline-block;
}

.chart-tooltip-label {
    color: #8c95a4;
}

.chart-tooltip-value {
    color: #d8dde6;
    font-weight: 600;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 11.5px;
}

/* ---------- Activity page: active-players list ---------- */

.activity-list-head {
    margin-top: 24px;
}

.activity-list-toolbar {
    display: flex;
    align-items: flex-end;
    gap: 14px;
    margin: 12px 0 16px;
    flex-wrap: wrap;
}

.activity-list-toolbar .sched-input {
    color-scheme: dark;
    width: 180px;
}

/* "Online now" pill in the Active players table — replaces the Last Seen
   timestamp when the player's latest session is still open. */
.player-online-now {
    display: inline-block;
    padding: 1px 8px;
    border-radius: 999px;
    background: rgba(16, 185, 129, 0.15);
    color: #6ee7b7;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.2px;
}

/* The bans tables share the .server-table base; tighten the row padding a
   touch so a long ban list stays scannable. */
.bans-table td {
    padding: 9px 12px;
}

/* Expired bans stay in the table for the admin to review and delete, but they
   no longer match anyone — dim the whole row so it's obvious at a glance that
   it's inert. */
.bans-row-expired td {
    color: #5a6273;
}

.bans-row-expired .srv-name,
.bans-row-expired .ban-id-on {
    color: #6b7280;
}

/* Expires-column flavours. */
.ban-expires-forever {
    color: #b8becf;
    font-weight: 500;
}

.ban-expires-active {
    color: #d8dde6;
    font-size: 12.5px;
}

.ban-expires-expired {
    color: #f87171;
    font-size: 12.5px;
    font-weight: 500;
}

/* ---------- Ban editor: duration picker ---------- */

.ban-duration {
    display: flex;
    flex-direction: column;
    gap: 8px;
    border: 1px solid #1f2330;
    border-radius: 8px;
    padding: 12px 14px;
    background: #14171d;
}

.ban-duration legend {
    color: #8c95a4;
    font-size: 12px;
    padding: 0 4px;
}

.ban-duration-option {
    display: flex;
    align-items: center;
    gap: 8px;
    color: #b8becf;
    font-size: 13px;
}

.ban-duration-option input[type="radio"] {
    accent-color: #3b82f6;
}

.ban-duration-input-days {
    width: 80px;
    background: #0e1014;
    border: 1px solid #1f2937;
    border-radius: 6px;
    padding: 6px 9px;
    color: #d8dde6;
    font-size: 13px;
    outline: none;
}

.ban-duration-input-until {
    background: #0e1014;
    border: 1px solid #1f2937;
    border-radius: 6px;
    padding: 6px 9px;
    color: #d8dde6;
    font-size: 13px;
    outline: none;
    color-scheme: dark;
}

.ban-duration-input-days:focus,
.ban-duration-input-until:focus {
    border-color: #3b82f6;
}

.ban-duration-input-days:disabled,
.ban-duration-input-until:disabled {
    opacity: 0.45;
    cursor: not-allowed;
}

.ban-duration-tz {
    color: #6b7280;
    font-size: 11.5px;
}

/* ---------- Plugins page (server-scoped plugin management) ---------- */

.plugins-toolbar {
    display: flex;
    align-items: flex-end;
    gap: 14px;
    margin: 12px 0 18px;
    flex-wrap: wrap;
}

.plugins-framework-badge {
    display: inline-flex;
    align-items: baseline;
    gap: 8px;
    padding: 4px 10px;
    border-radius: 4px;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.4px;
    text-transform: uppercase;
    background: rgba(148, 163, 184, 0.15);
    color: #cbd5e1;
    border: 1px solid rgba(148, 163, 184, 0.25);
}

.plugins-framework-carbon {
    background: rgba(34, 197, 94, 0.15);
    color: #86efac;
    border-color: rgba(34, 197, 94, 0.3);
}

.plugins-framework-oxide {
    background: rgba(59, 130, 246, 0.15);
    color: #93c5fd;
    border-color: rgba(59, 130, 246, 0.3);
}

.plugins-framework-version {
    font-weight: 400;
    text-transform: none;
    letter-spacing: 0;
    opacity: 0.7;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 11px;
}

.plugins-empty-state {
    background: #14171d;
    border: 1px solid #1f2330;
    border-radius: 8px;
    padding: 22px 24px;
    margin: 8px 0 18px;
    color: #b8becf;
}

.plugins-empty-state h3 {
    margin: 0 0 8px;
    font-size: 14px;
    color: #d8dde6;
}

.plugins-empty-state p {
    margin: 0 0 8px;
    font-size: 13px;
    line-height: 1.5;
}

.plugins-empty-state p:last-child { margin-bottom: 0; }

.plugins-empty-state code {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12px;
    background: rgba(255, 255, 255, 0.05);
    padding: 1px 5px;
    border-radius: 3px;
}

/* ----- Collapsible sections ----- */

.plugins-section {
    background: #14171d;
    border: 1px solid #1f2330;
    border-radius: 8px;
    margin-bottom: 14px;
    overflow: hidden;
}

.plugins-section-header {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 16px;
    cursor: pointer;
    user-select: none;
    background: #181c24;
    border-bottom: 1px solid transparent;
}

.plugins-section:has(.plugins-table) .plugins-section-header,
.plugins-section:has(.plugins-section-empty) .plugins-section-header {
    border-bottom-color: #1f2330;
}

.plugins-section-header:hover { background: #1c2129; }

.plugins-section-toggle {
    color: #8c95a4;
    font-size: 12px;
    width: 12px;
    text-align: center;
}

.plugins-section-header h2 {
    margin: 0;
    font-size: 14px;
    color: #d8dde6;
    font-weight: 600;
    flex: 1;
}

.plugins-section-count {
    color: #8c95a4;
    font-weight: 400;
    margin-left: 4px;
}

.plugins-section-empty {
    margin: 0;
    padding: 18px 22px;
    color: #6b7280;
    font-size: 13px;
}

/* ----- Plugin rows ----- */

.plugins-table {
    width: 100%;
    border-collapse: collapse;
}

.plugins-table thead th {
    text-align: left;
    padding: 10px 16px;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.6px;
    color: #6b7280;
    border-bottom: 1px solid #1f2330;
    font-weight: 500;
}

.plugins-table tbody td {
    padding: 12px 16px;
    border-bottom: 1px solid #1a1d27;
    vertical-align: top;
    font-size: 13px;
}

.plugins-table tbody tr:last-child td { border-bottom: 0; }

.plugins-table tbody tr:hover { background: #181c24; }

.plugins-row-bridge { background: rgba(244, 114, 182, 0.05); }
.plugins-row-bridge:hover { background: rgba(244, 114, 182, 0.08); }

.plugins-name {
    font-weight: 600;
    color: #d8dde6;
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

.plugins-description {
    color: #8c95a4;
    font-size: 12px;
    margin-top: 3px;
    line-height: 1.45;
}

.plugins-version,
.plugins-author {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    color: #b8becf;
    font-size: 12.5px;
}

.plugins-filename {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    color: #b8becf;
    font-size: 12.5px;
}

.plugins-tag {
    display: inline-block;
    padding: 1px 6px;
    border-radius: 3px;
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    vertical-align: middle;
}

.plugins-tag-bridge {
    background: rgba(244, 114, 182, 0.18);
    color: #f9a8d4;
}

.plugins-tag-core {
    background: rgba(148, 163, 184, 0.18);
    color: #cbd5e1;
}

/* ----- Plugins page modal (Disable confirm + bridge warning) ----- */

.plugins-modal-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.55);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1000;
}

.plugins-modal-panel {
    width: 480px;
    max-width: calc(100vw - 32px);
    background: #14171d;
    border: 1px solid #1f2330;
    border-radius: 10px;
    padding: 22px 24px 18px;
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.6);
}

.plugins-modal-panel-danger {
    border-color: rgba(244, 114, 182, 0.5);
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.6),
                0 0 0 1px rgba(244, 114, 182, 0.2);
}

.plugins-modal-panel h2 {
    margin: 0 0 12px;
    font-size: 16px;
    color: #d8dde6;
}

.plugins-modal-panel-danger h2 { color: #f9a8d4; }

.plugins-modal-panel p {
    margin: 0 0 10px;
    color: #b8becf;
    font-size: 13px;
    line-height: 1.5;
}

.plugins-modal-panel code {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12px;
    background: rgba(255, 255, 255, 0.05);
    padding: 1px 5px;
    border-radius: 3px;
}

.plugins-modal-actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    margin-top: 16px;
}

/* ---------- Per-plugin permissions modal (Phase B) ---------- */

.plugins-perms-modal {
    width: 640px;
    max-width: calc(100vw - 32px);
    max-height: calc(100vh - 64px);
    overflow-y: auto;
}

.plugins-perms-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 14px;
    border-bottom: 1px solid #1f2330;
    padding-bottom: 12px;
}

.plugins-perms-header h2 {
    margin: 0 0 4px;
    font-size: 16px;
    color: #d8dde6;
}

.plugins-perms-server {
    margin: 0;
    color: #8c95a4;
    font-size: 12px;
}

.plugins-perms-close {
    background: transparent;
    border: 1px solid #2a3142;
    border-radius: 4px;
    color: #b8becf;
    font-size: 18px;
    line-height: 1;
    width: 28px;
    height: 28px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
}

.plugins-perms-close:hover:not(:disabled) {
    border-color: #3a4458;
    color: #d8dde6;
}

.plugins-perms-close:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

/* ----- Permission rows ----- */

.plugins-perms-list {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-bottom: 18px;
}

.plugins-perm-row {
    background: #0e1014;
    border: 1px solid #1f2330;
    border-radius: 6px;
    padding: 10px 12px;
}

.plugins-perm-name {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12.5px;
    color: #d8dde6;
    margin-bottom: 8px;
    font-weight: 600;
}

.plugins-perm-grantees {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.plugins-perm-empty {
    color: #6b7280;
    font-size: 12px;
    font-style: italic;
}

.plugins-perm-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 2px 4px 2px 6px;
    border-radius: 4px;
    font-size: 12px;
    border: 1px solid;
}

.plugins-perm-chip-group {
    background: rgba(59, 130, 246, 0.1);
    color: #93c5fd;
    border-color: rgba(59, 130, 246, 0.3);
}

.plugins-perm-chip-user {
    background: rgba(168, 85, 247, 0.1);
    color: #c4b5fd;
    border-color: rgba(168, 85, 247, 0.3);
}

.plugins-perm-chip-icon {
    font-weight: 700;
    font-size: 10px;
    width: 14px;
    height: 14px;
    border-radius: 2px;
    background: rgba(255, 255, 255, 0.1);
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.plugins-perm-chip-label {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 11.5px;
}

.plugins-perm-chip-x {
    background: transparent;
    border: 0;
    color: inherit;
    opacity: 0.6;
    cursor: pointer;
    padding: 0 4px;
    font-size: 14px;
    line-height: 1;
    border-radius: 3px;
}

.plugins-perm-chip-x:hover:not(:disabled) {
    background: rgba(239, 68, 68, 0.25);
    color: #fca5a5;
    opacity: 1;
}

.plugins-perm-chip-x:disabled {
    opacity: 0.3;
    cursor: not-allowed;
}

/* ----- Grant section ----- */

.plugins-perms-grant {
    border-top: 1px solid #1f2330;
    padding-top: 14px;
    margin-top: 6px;
}

.plugins-perms-grant h3 {
    margin: 0 0 10px;
    font-size: 13px;
    color: #d8dde6;
    font-weight: 600;
}

.plugins-perms-grant-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 10px;
    margin-bottom: 8px;
}

.plugins-perms-field {
    display: flex;
    flex-direction: column;
    gap: 4px;
    font-size: 12px;
    color: #b8becf;
}

.plugins-perms-field > span {
    color: #8c95a4;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.4px;
}

.plugins-perms-target {
    grid-column: span 2;
    margin: 0;
    padding: 8px 10px;
    background: #0e1014;
    border: 1px solid #1f2330;
    border-radius: 6px;
}

.plugins-perms-target legend {
    color: #8c95a4;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    padding: 0 6px;
}

.plugins-perms-target-option {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-right: 14px;
    font-size: 12.5px;
    color: #b8becf;
    cursor: pointer;
}

.plugins-perms-target-option.on { color: #d8dde6; }

/* ---------- Raids page ---------- */

.raids-toolbar {
    display: flex;
    align-items: flex-end;
    gap: 12px;
    margin: 12px 0 18px;
    flex-wrap: wrap;
}

.raids-search-field input {
    min-width: 200px;
}

.raids-apply {
    align-self: flex-end;
}

.raids-table {
    width: 100%;
    border-collapse: collapse;
    background: #14171d;
    border: 1px solid #1f2330;
    border-radius: 8px;
    overflow: hidden;
}

.raids-table thead th {
    text-align: left;
    padding: 10px 14px;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.6px;
    color: #6b7280;
    border-bottom: 1px solid #1f2330;
    font-weight: 500;
    background: #181c24;
}

.raids-table tbody td {
    padding: 10px 14px;
    border-bottom: 1px solid #1a1d27;
    vertical-align: top;
    font-size: 12.5px;
}

.raids-table tbody tr:last-child td { border-bottom: 0; }
.raids-table tbody tr:hover { background: #181c24; }

/* A red left border highlights offline raids — the more interesting case
   from a moderation point of view since the defender can't react. */
.raid-row-offline td:first-child {
    border-left: 3px solid rgba(239, 68, 68, 0.6);
    padding-left: 11px;
}
.raid-row-online td:first-child {
    border-left: 3px solid rgba(34, 197, 94, 0.4);
    padding-left: 11px;
}

.raid-time {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12px;
    color: #b8becf;
}

.raid-player {
    color: #d8dde6;
    font-weight: 500;
}

.raid-steamid {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    color: #6b7280;
    font-size: 10.5px;
    margin-top: 2px;
}

.raid-defender-state {
    display: inline-block;
    margin-top: 3px;
    padding: 1px 6px;
    border-radius: 3px;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    font-weight: 600;
}

.raid-defender-state.on {
    background: rgba(34, 197, 94, 0.15);
    color: #86efac;
}

.raid-defender-state.off {
    background: rgba(239, 68, 68, 0.15);
    color: #fca5a5;
}

.raid-weapon,
.raid-entity,
.raid-location {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 11.5px;
    color: #b8becf;
}

.raid-damage {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12px;
    color: #fbbf24;
    font-weight: 600;
}

.raids-overflow {
    margin: 10px 0 0;
    color: #6b7280;
    font-size: 12px;
    font-style: italic;
}

/* ---------- Grouped raid sessions (added with 0.11.0 raid-loot batch) ---------- */

/* The session start-time chip mirrors .raid-time; the sub-line is a small
   muted "ended at ..." tag so a long-running session's end is visible
   without cluttering the main timestamp. */
.raid-session-span {
    margin-top: 3px;
    color: #6b7280;
    font-size: 10.5px;
    font-style: italic;
}

/* Activity column — two pills stacked horizontally. The loot pill lights up
   amber when there's any loot to view; otherwise it fades so an operator can
   see at a glance which sessions have takeaways worth drilling into. */
.raid-session-counts {
    display: inline-flex;
    gap: 6px;
    align-items: center;
}

.raid-count-pill {
    display: inline-flex;
    align-items: center;
    padding: 2px 8px;
    border-radius: 12px;
    font-size: 11px;
    font-weight: 600;
    background: #1a1e26;
    color: #b8becf;
    border: 1px solid #262b36;
}

.raid-count-damage {
    /* Rocket-orange to reinforce the damage semantics of the pill. */
    background: rgba(239, 68, 68, 0.12);
    color: #fca5a5;
    border-color: rgba(239, 68, 68, 0.24);
}

.raid-count-loot.has-loot {
    background: rgba(251, 191, 36, 0.16);
    color: #fde68a;
    border-color: rgba(251, 191, 36, 0.32);
}

/* The grouped-list Actions column has two secondary buttons side-by-side —
   give them a bit of breathing room so they don't stack unintentionally on
   narrower widths. */
.raids-sessions-table .actions-col {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
}

/* ---------- Raid-session detail modals (events + stolen loot) ---------- */

/* Wider than the schedule/trigger import panel — the events + loot tables
   carry more columns and the modal is a browse-focused view rather than a
   form. Reuses .import-modal-* for the backdrop + header. */
.raids-detail-panel {
    max-width: 900px;
    width: 90vw;
    max-height: 80vh;
    overflow-y: auto;
}

/* Trim table chrome inside the modal — the outer panel already has padding. */
.raids-detail-table {
    margin-top: 12px;
}

/* Loot summary rows: icon + item name + a muted shortname below it. Same
   shape as the raid player-name / SteamID pattern, with a leading item
   thumbnail from the Rust catalog. */
.raid-loot-item-cell {
    display: flex;
    align-items: center;
    gap: 10px;
}
.raid-loot-icon {
    width: 32px;
    height: 32px;
    object-fit: contain;
    flex-shrink: 0;
    image-rendering: -webkit-optimize-contrast;
}
/* Reserved slot for a loot item whose shortname isn't in the catalog —
   keeps the row's column alignment consistent even when the icon
   would 404. */
.raid-loot-icon-missing {
    display: inline-block;
    background: transparent;
}
.raid-loot-item-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
}
.raid-loot-item {
    color: #d8dde6;
    font-weight: 500;
}

.raid-loot-shortname {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    color: #6b7280;
    font-size: 10.5px;
    margin-top: 2px;
}

.raid-loot-container-list {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    color: #94a3b8;
    font-size: 11px;
}

/* ---------- Trigger violation window (OnRaidDamage) ---------- */

.trigger-violation-section {
    margin: 18px 0;
    padding: 14px 16px;
    background: #14171d;
    border: 1px solid #1f2330;
    border-radius: 8px;
}

.trigger-violation-section h2 {
    margin: 0 0 6px;
    font-size: 14px;
    color: #d8dde6;
}

.editor-field-narrow {
    max-width: 160px;
}

/* Highlight the raid-only token chips so they're visually distinct from
   the player tokens — useful when scanning the hint row on an
   OnRaidDamage trigger. */
.token-chip-raid {
    background: rgba(239, 68, 68, 0.12) !important;
    color: #fca5a5 !important;
    border-color: rgba(239, 68, 68, 0.25) !important;
}

/* ---------- Import / Export (Triggers + Schedules pages) ---------- */

/* Header button cluster: Import / Export all / New. Ordered to sit on
   the same row as the h1 (row index 1) and right-aligned via
   `margin-left: auto` — because .admin-header is now a wrapping flex
   with the h1 pushing to grow, the action cluster naturally sits
   opposite it. Row 2 (the sub description) never squeezes it. */
.admin-header-actions {
    order: 1;
    margin-left: auto;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

/* Render <a class="btn-secondary"> exactly like a <button class="btn-secondary">
   so the Export anchor doesn't look out of place next to Import + New. The
   existing .btn-secondary rule covers most of this; these few lines just
   undo the default anchor underline + colour. */
a.btn-secondary {
    text-decoration: none;
    color: inherit;
    display: inline-flex;
    align-items: center;
}

a.btn-secondary:hover { text-decoration: none; }

/* ----- Import modal ----- */

.import-modal-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.55);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1000;
}

.import-modal-panel {
    width: 720px;
    max-width: calc(100vw - 32px);
    max-height: calc(100vh - 64px);
    overflow-y: auto;
    background: #14171d;
    border: 1px solid #1f2330;
    border-radius: 10px;
    padding: 22px 24px 18px;
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.6);
}

/* Truncated one-line description shown under the row's primary name in the
   Schedules and Triggers lists. The muted colour + smaller size keeps it
   visually secondary, and the ellipsis + title tooltip means a paragraph
   in the DB won't stretch the row. */
.list-description {
    color: #94a3b8;
    font-size: 12px;
    margin-top: 2px;
    max-width: 320px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* -------- New picker modal (create schedule / trigger) -------- */
.new-picker-panel {
    width: 560px;
}

.new-picker-body {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding-top: 4px;
}

/* Base card. Every option in the picker (scratch + each template) uses
   this. The scratch card gets an accent border below to signal it's the
   primary action. */
.new-picker-card {
    display: block;
    width: 100%;
    text-align: left;
    padding: 14px 16px;
    background: #1a1f28;
    border: 1px solid #2a303c;
    border-radius: 8px;
    cursor: pointer;
    color: inherit;
    font: inherit;
    transition: background-color 120ms ease, border-color 120ms ease;
}
.new-picker-card:hover {
    background: #1f2632;
    border-color: #3a4152;
}

.new-picker-scratch {
    border-color: rgba(59, 130, 246, 0.5);
}
.new-picker-scratch:hover {
    background: rgba(59, 130, 246, 0.08);
    border-color: rgba(59, 130, 246, 0.75);
}

.new-picker-card-title {
    font-weight: 600;
    color: #e5e7eb;
    font-size: 14px;
}

.new-picker-card-hint {
    color: #94a3b8;
    font-size: 12px;
    margin-top: 4px;
}

.new-picker-card-desc {
    color: #cbd5e1;
    font-size: 12.5px;
    margin-top: 6px;
    line-height: 1.4;
    /* Cap to a couple of lines so a novella-length description doesn't
       stretch the picker row height out. */
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.new-picker-card-meta {
    display: flex;
    justify-content: space-between;
    gap: 12px;
    margin-top: 8px;
    font-size: 11px;
    color: #64748b;
    font-variant-numeric: tabular-nums;
}


/* Divider between "start from scratch" and the template list — visually
   parallel to the Filters dropdown's "Map filter" divider, but with a
   subtler tone so the scratch card stays the visual anchor. */
.new-picker-divider {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 6px 2px 2px;
    color: #64748b;
    font-size: 10.5px;
    text-transform: uppercase;
    letter-spacing: 0.7px;
}
.new-picker-divider::before,
.new-picker-divider::after {
    content: "";
    flex: 1;
    height: 1px;
    background: rgba(148, 163, 184, 0.18);
}

.new-picker-empty {
    color: #94a3b8;
    font-size: 12.5px;
    margin: 6px 4px 0;
    line-height: 1.5;
}

.import-modal-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 14px;
    border-bottom: 1px solid #1f2330;
    padding-bottom: 12px;
}

.import-modal-header h2 {
    margin: 0;
    font-size: 16px;
    color: #d8dde6;
}

.import-modal-hint {
    margin: 0 0 14px;
    color: #b8becf;
    font-size: 13px;
    line-height: 1.5;
}

.import-modal-hint code {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12px;
    background: rgba(255, 255, 255, 0.05);
    padding: 1px 5px;
    border-radius: 3px;
}

.import-modal-warn {
    color: #fbbf24;
    font-size: 12px;
}

/* "Reading file…" state for the schedules / triggers import modal — fills
   the gap between the OS file dialog closing and PrepareAsync returning.
   On a slow link this can be several seconds; without a spinner the modal
   looks dead. */
.import-modal-preparing {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 24px 0 8px;
    gap: 12px;
}

.import-modal-preparing .import-modal-hint {
    margin: 0;
    text-align: center;
}

.import-modal-spinner {
    width: 28px;
    height: 28px;
    border: 3px solid rgba(148, 163, 184, 0.25);
    border-top-color: #93c5fd;
    border-radius: 50%;
    animation: import-modal-spin 0.9s linear infinite;
}
@keyframes import-modal-spin {
    to { transform: rotate(360deg); }
}

/* The InputFile element has to stay in the DOM while the upload stream is
   being read (Blazor's JS interop looks up the file on the live element's
   _blazorFilesById map). Removing it mid-flight throws JSException. We hide
   it visually during the preparing state instead. */
.import-modal-input-hidden {
    display: none;
}

.import-preview-table {
    width: 100%;
    border-collapse: collapse;
    margin: 12px 0;
    background: #0e1014;
    border: 1px solid #1f2330;
    border-radius: 6px;
    overflow: hidden;
}

.import-preview-table thead th {
    text-align: left;
    padding: 8px 12px;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: #6b7280;
    border-bottom: 1px solid #1f2330;
    font-weight: 500;
    background: #14171d;
}

.import-preview-table tbody td {
    padding: 8px 12px;
    border-bottom: 1px solid #1a1d27;
    font-size: 12.5px;
    vertical-align: top;
}

.import-preview-table tbody tr:last-child td { border-bottom: 0; }

.import-preview-from {
    color: #6b7280;
    font-size: 11px;
    margin-top: 2px;
}

.import-modal-overwrite {
    margin: 14px 0;
    color: #b8becf;
    font-size: 13px;
}

.import-modal-actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    margin-top: 8px;
}

/* ---------- Player info modal (per-player drill-in) ---------- */

/* Info icon in the player row action cluster. Colour is the neutral
   blue-grey of the existing p-icon-muted family rather than the
   action-y red of kick/ban — info is a "look first" affordance. */
.p-action-info {
    color: #93c5fd;
}

.p-action-info:hover:not(:disabled) {
    background: rgba(59, 130, 246, 0.12);
    color: #bfdbfe;
}

.player-info-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.55);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1000;
}

.player-info-modal {
    width: 640px;
    max-width: calc(100vw - 32px);
    max-height: calc(100vh - 64px);
    overflow-y: auto;
    background: #14171d;
    border: 1px solid #1f2330;
    border-radius: 10px;
    padding: 22px 24px 18px;
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.6);
}

/* Embedded mode (Investigation page) — same card look but no fixed
   positioning, wider, and no shadow (the page background handles depth). */
.player-info-embedded {
    display: block;
    margin-top: 12px;
}
.player-info-embedded-inner {
    width: 100%;
    max-width: 960px;
    background: #14171d;
    border: 1px solid #1f2330;
    border-radius: 10px;
    padding: 22px 24px 18px;
}

/* Investigation page toolbar — server picker + copy-link button, in the
   same shape as the /activity list toolbar. */
.investigation-toolbar {
    display: flex;
    align-items: flex-end;
    gap: 14px;
    margin-top: 8px;
    margin-bottom: 4px;
    flex-wrap: wrap;
}

/* Raid detection per-server settings page (/settings/raid-detection) —
   picker + copy-from toolbar, two toggles, two shortname checkbox lists
   with a free-text custom add per section. */
.raid-cfg-toolbar {
    display: flex;
    align-items: flex-end;
    gap: 14px;
    flex-wrap: wrap;
}
.raid-cfg-toggle {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 4px 0;
    color: #cbd0dc;
    cursor: pointer;
}
.raid-cfg-list {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
    gap: 6px 14px;
    margin-top: 8px;
}
.raid-cfg-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 4px 6px;
    border-radius: 4px;
    cursor: pointer;
    color: #cbd0dc;
}
.raid-cfg-row:hover {
    background: #1a1e28;
}
.raid-cfg-row-custom {
    border-left: 2px solid #f59e0b;
    padding-left: 4px;
}
.raid-cfg-name {
    flex: 1;
}
.raid-cfg-shortname {
    font-size: 11px;
    color: #7a828e;
    background: #1a1e28;
    padding: 1px 6px;
    border-radius: 3px;
}

/* Small pill next to the shortname count on category rows. Building-block
   categories get a matching "Building" tag so operators know the grade
   filter section applies to them. */
.raid-cfg-count {
    font-size: 11px;
    color: #7a828e;
    background: #1a1e28;
    padding: 1px 8px;
    border-radius: 999px;
    min-width: 20px;
    text-align: center;
}
.raid-cfg-bb-badge {
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.3px;
    text-transform: uppercase;
    color: #f59e0b;
    background: rgba(245, 158, 11, 0.14);
    padding: 1px 8px;
    border-radius: 999px;
}
/* Collapsible <details> block for the custom-shortname free-text list.
   Keeps the primary UI (category checkboxes) short — operators who need
   to add exotic shortnames open the details on demand. */
.raid-cfg-custom-toggle {
    margin-top: 12px;
    border-top: 1px dashed #1f2330;
    padding-top: 10px;
}
.raid-cfg-custom-toggle > summary {
    cursor: pointer;
    color: #a4a9b6;
    font-size: 13px;
    padding: 4px 0;
}
.raid-cfg-custom-toggle > summary:hover {
    color: #cbd0dc;
}
.raid-cfg-remove {
    background: transparent;
    border: none;
    color: #f59e0b;
    font-size: 16px;
    line-height: 1;
    padding: 0 4px;
    cursor: pointer;
}
.raid-cfg-remove:hover {
    color: #ff6b6b;
}
.raid-cfg-add {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 12px;
}
.raid-cfg-add input {
    flex: 1;
    max-width: 320px;
}

/* Small square thumbnail next to each raid-detection category row —
   uses the icon of the first shortname the category expands to
   (see CategoryIconUrl in RaidDetectionSettings.razor). 26px matches
   the height of the checkbox + label baseline so rows stay a single
   line. Icon is contained (not cropped) so long items like a rocket
   launcher render as the full silhouette. */
.raid-cfg-icon {
    width: 26px;
    height: 26px;
    object-fit: contain;
    flex-shrink: 0;
    image-rendering: -webkit-optimize-contrast;
}
/* Reserved slot for a category whose first-shortname icon isn't in
   the catalog — keeps the row's column alignment intact even when the
   image would 404. Same footprint as .raid-cfg-icon, no visible
   content. */
.raid-cfg-icon-missing {
    display: inline-block;
    background: transparent;
}

/* ---- Rust item picker (RustItemPicker.razor) ----
   Reusable "chip list + autocomplete search" widget. The picker is
   currently only used from the raid-detection settings page, but the
   markup is decoupled — it can be dropped into any settings surface
   without page-specific overrides. */
.item-picker {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-top: 8px;
}
.item-picker-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}
.item-picker-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 3px 3px 6px;
    border-radius: 999px;
    background: #1a1e28;
    border: 1px solid #2a3142;
    color: #cbd0dc;
    font-size: 12px;
    max-width: 260px;
}
.item-picker-chip-icon {
    width: 22px;
    height: 22px;
    object-fit: contain;
    flex-shrink: 0;
    border-radius: 3px;
}
.item-picker-chip-icon-missing {
    /* Empty placeholder for unknown shortnames — keeps chip layout
       stable without rendering a broken image. */
    display: inline-block;
    background: transparent;
}
.item-picker-chip-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    flex: 1;
}
.item-picker-chip-remove {
    background: transparent;
    border: none;
    color: #7a828e;
    font-size: 18px;
    line-height: 1;
    padding: 0 6px;
    cursor: pointer;
    border-radius: 999px;
}
.item-picker-chip-remove:hover {
    color: #ff6b6b;
}
.item-picker-chip-remove:disabled {
    cursor: not-allowed;
    opacity: 0.5;
}

/* Search input + dropdown. The dropdown is a plain flow element (not
   absolute-positioned) — inside a <details> block it needs to push
   the surrounding UI down rather than float on top of unrelated
   content, and there's no z-index competition in the raid-detection
   layout. If we ever put the picker inline in a tighter container,
   revisit the positioning here. */
.item-picker-search {
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.item-picker-input {
    max-width: 320px;
}
.item-picker-suggestions {
    display: flex;
    flex-direction: column;
    max-width: 520px;
    max-height: 320px;
    overflow-y: auto;
    background: #12141c;
    border: 1px solid #232837;
    border-radius: 6px;
}
.item-picker-suggestion,
.item-picker-suggestion-empty {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 6px 10px;
    background: transparent;
    border: none;
    text-align: left;
    color: #cbd0dc;
    cursor: pointer;
    font: inherit;
    border-bottom: 1px solid #171b25;
}
.item-picker-suggestion:last-child {
    border-bottom: none;
}
.item-picker-suggestion:hover:not(:disabled) {
    background: #1a1e28;
}
.item-picker-suggestion:disabled {
    cursor: not-allowed;
    opacity: 0.6;
}
.item-picker-suggestion-icon {
    width: 28px;
    height: 28px;
    object-fit: contain;
    flex-shrink: 0;
    border-radius: 3px;
}
.item-picker-suggestion-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
    flex: 1;
}
.item-picker-suggestion-name {
    color: #cbd0dc;
    font-size: 13px;
    line-height: 1.2;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.item-picker-suggestion-shortname {
    color: #7a828e;
    font-size: 11px;
    line-height: 1.2;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.item-picker-suggestion-already {
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.3px;
    color: #7a828e;
    background: #1a1e28;
    padding: 1px 8px;
    border-radius: 999px;
}
/* "Add 'foo.bar' as raw shortname" fallback — dashed border tells the
   operator this is the escape-hatch option rather than a catalog hit. */
.item-picker-suggestion-raw {
    border-top: 1px dashed #2a3142;
    color: #a4a9b6;
    font-style: italic;
}
.item-picker-suggestion-empty {
    color: #7a828e;
    font-size: 12px;
    font-style: italic;
    cursor: default;
}

/* Investigations LIST page (/investigations) — plural, the aggregate view.
   Shares the .server-table look with Bans / Servers / Activity. */
.investigations-summary {
    color: #a4a9b6;
    font-size: 13px;
    margin: 6px 0 12px;
}
.investigations-table .investigations-note {
    max-width: 420px;
    color: #cbd0dc;
    font-size: 13px;
    white-space: pre-wrap;
    word-break: break-word;
}
.investigations-latest {
    line-height: 1.35;
}
.investigations-latest-author {
    color: #7a828e;
    font-size: 12px;
    margin-top: 2px;
}
/* Small badge next to the flag count when >1 distinct operator has flagged
   the player. Corroboration is a stronger signal than one person's stack. */
.investigations-corroborated {
    display: inline-block;
    margin-left: 8px;
    padding: 1px 8px;
    border-radius: 999px;
    background: rgba(245, 158, 11, 0.14);
    color: #f59e0b;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.3px;
    text-transform: uppercase;
    vertical-align: middle;
}

.player-info-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 16px;
    border-bottom: 1px solid #1f2330;
    padding-bottom: 14px;
}

.player-info-name-block { flex: 1; min-width: 0; }

.player-info-name {
    font-size: 18px;
    color: #d8dde6;
    font-weight: 600;
}

.player-info-steamid {
    display: flex;
    align-items: center;
    gap: 10px;
    color: #8c95a4;
    font-size: 12.5px;
    margin-top: 4px;
    flex-wrap: wrap;
}

.player-info-steam-link {
    color: #93c5fd;
    text-decoration: none;
    font-size: 12px;
}
.player-info-steam-link:hover { text-decoration: underline; }

.player-info-sub {
    color: #8c95a4;
    font-size: 12px;
    margin-top: 4px;
}

.player-info-sub-inline {
    color: #8c95a4;
    font-size: 12px;
    font-weight: 400;
    margin-left: 6px;
}

.player-info-close {
    background: transparent;
    border: 1px solid #2a3142;
    border-radius: 4px;
    color: #b8becf;
    font-size: 18px;
    line-height: 1;
    width: 28px;
    height: 28px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}
.player-info-close:hover:not(:disabled) {
    border-color: #3a4458;
    color: #d8dde6;
}

.player-info-section {
    margin-bottom: 14px;
}

.player-info-section h3 {
    margin: 0 0 8px;
    font-size: 12px;
    color: #6b7280;
    text-transform: uppercase;
    letter-spacing: 0.6px;
    font-weight: 500;
}

.player-info-pills {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.pi-pill {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 6px 10px;
    background: #0e1014;
    border: 1px solid #1f2330;
    border-radius: 6px;
    min-width: 84px;
}

.pi-pill.muted { color: #b8becf; }
.pi-pill.warn  {
    background: rgba(234, 179, 8, 0.10);
    border-color: rgba(234, 179, 8, 0.25);
    color: #fbbf24;
}
.pi-pill.danger {
    background: rgba(239, 68, 68, 0.10);
    border-color: rgba(239, 68, 68, 0.30);
    color: #fca5a5;
}

.pi-pill-label {
    font-size: 10.5px;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    color: #6b7280;
}

.pi-pill-value {
    font-size: 13px;
    font-weight: 600;
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

.pi-pill-sub {
    font-size: 11px;
    color: #6b7280;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

.player-info-loading {
    color: #6b7280;
    font-size: 12.5px;
    font-style: italic;
    padding: 4px 0;
}

.player-info-actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    margin-top: 16px;
    padding-top: 12px;
    border-top: 1px solid #1f2330;
}

/* Reusable monospace text helper — used by the player-info modal for the
   SteamID and IP, but cheap to keep general. */
.mono {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/* ---------- Reports page ---------- */

.reports-view-toggle {
    display: inline-flex;
    gap: 4px;
    align-self: flex-end;
    background: #14171d;
    border: 1px solid #1f2330;
    border-radius: 6px;
    padding: 2px;
}

.reports-view-toggle .view-chip {
    border: 0;
    background: transparent;
    color: #8c95a4;
    padding: 4px 12px;
    border-radius: 4px;
    cursor: pointer;
    font-size: 12px;
    font-weight: 500;
}

.reports-view-toggle .view-chip:hover { color: #d8dde6; }

.reports-view-toggle .view-chip.on {
    background: #2a3142;
    color: #d8dde6;
}

/* ----- Grouped view ----- */

.reports-groups {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.report-group {
    background: #14171d;
    border: 1px solid #1f2330;
    border-radius: 8px;
    overflow: hidden;
}

.report-group-head {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 14px;
    cursor: pointer;
    user-select: none;
    background: #181c24;
}

.report-group-head:hover { background: #1c2129; }

.report-group-toggle {
    color: #8c95a4;
    font-size: 12px;
    width: 12px;
    text-align: center;
}

.report-group-name { flex: 1; min-width: 0; }

.report-group-display {
    color: #d8dde6;
    font-weight: 600;
    font-size: 14px;
}

.report-group-steamid {
    color: #6b7280;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 11px;
    margin-top: 2px;
}

.report-group-unknown {
    color: #6b7280;
    font-style: italic;
    font-size: 13px;
}

.report-group-stats {
    display: flex;
    align-items: center;
    gap: 12px;
}

.report-group-count {
    background: rgba(239, 68, 68, 0.15);
    color: #fca5a5;
    font-weight: 700;
    border-radius: 4px;
    padding: 2px 10px;
    font-size: 13px;
    min-width: 28px;
    text-align: center;
}

.report-group-last {
    color: #6b7280;
    font-size: 11.5px;
}

.report-list {
    list-style: none;
    margin: 0;
    padding: 0;
    border-top: 1px solid #1f2330;
}

.report-card {
    padding: 10px 16px;
    border-bottom: 1px solid #1a1d27;
    font-size: 12.5px;
}

.report-card:last-child { border-bottom: 0; }

.report-card-head {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 4px;
}

.report-time {
    color: #6b7280;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 11.5px;
}

.report-type {
    background: rgba(168, 85, 247, 0.15);
    color: #c4b5fd;
    border-radius: 3px;
    padding: 1px 6px;
    font-size: 10.5px;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    font-weight: 600;
}

.report-server {
    color: #8c95a4;
    font-size: 11.5px;
    margin-left: auto;
}

.report-card-by {
    color: #b8becf;
    margin-bottom: 4px;
}

.report-reporter-id {
    color: #6b7280;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 11px;
    margin-left: 4px;
}

.report-subject {
    color: #d8dde6;
    font-weight: 500;
    margin-bottom: 4px;
}

.report-message {
    color: #b8becf;
    background: #0e1014;
    border-left: 2px solid #2a3142;
    padding: 6px 10px;
    border-radius: 0 3px 3px 0;
    white-space: pre-wrap;
    word-break: break-word;
    font-size: 12px;
}

.report-message-inline {
    color: #8c95a4;
    font-size: 11.5px;
    font-style: italic;
    margin-top: 3px;
    /* Truncate very long bodies in the flat table — the Grouped view shows full text. */
    max-width: 360px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ---------- Report dashboard toast ---------- */

/* Slightly different left-edge colour from raid toasts so the two stack
   distinctly in the same panel. */
.report-toast {
    border-left: 3px solid rgba(168, 85, 247, 0.7);
}

.report-toast-badge {
    background: rgba(168, 85, 247, 0.18) !important;
    color: #c4b5fd !important;
}

.report-toast-no-target {
    color: #6b7280;
}

/* ---------- Phase 3: live raid notifications (dashboard) ---------- */

/* Fixed bottom-right panel; layered above the dashboard but below any modal. */
.raid-toast-panel {
    position: fixed;
    bottom: 20px;
    right: 20px;
    z-index: 950;
    display: flex;
    flex-direction: column;
    gap: 10px;
    max-width: 360px;
    pointer-events: none; /* Container is invisible; individual toasts re-enable. */
}

.raid-toast {
    pointer-events: auto;
    background: #14171d;
    border: 1px solid #1f2330;
    border-radius: 8px;
    padding: 10px 14px 12px 14px;
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.55);
    color: #d8dde6;
    position: relative;
    animation: raid-toast-in 220ms ease-out;
}

.raid-toast.online {
    border-left: 3px solid rgba(34, 197, 94, 0.7);
}

.raid-toast.offline {
    border-left: 3px solid rgba(239, 68, 68, 0.75);
}

@keyframes raid-toast-in {
    from { transform: translateX(20px); opacity: 0; }
    to   { transform: translateX(0); opacity: 1; }
}

.raid-toast-dismiss {
    position: absolute;
    top: 4px;
    right: 6px;
    background: transparent;
    border: 0;
    color: #6b7280;
    font-size: 16px;
    line-height: 1;
    cursor: pointer;
    padding: 2px 6px;
    border-radius: 3px;
}

.raid-toast-dismiss:hover {
    color: #d8dde6;
    background: rgba(255, 255, 255, 0.05);
}

.raid-toast-head {
    display: flex;
    align-items: baseline;
    gap: 8px;
    margin-bottom: 4px;
}

.raid-toast-badge {
    background: rgba(251, 191, 36, 0.18);
    color: #fbbf24;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    padding: 2px 6px;
    border-radius: 3px;
}

.raid-toast-time {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 11.5px;
    color: #8c95a4;
}

.raid-toast-body {
    font-size: 13px;
    margin-bottom: 6px;
    word-break: break-word;
}

.raid-toast-arrow {
    color: #6b7280;
    margin: 0 4px;
}

.raid-toast-offline {
    display: inline-block;
    margin-left: 8px;
    padding: 1px 5px;
    background: rgba(239, 68, 68, 0.18);
    color: #fca5a5;
    border-radius: 3px;
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.4px;
}

.raid-toast-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
}

.raid-toast-pill {
    background: #0e1014;
    border: 1px solid #1f2937;
    border-radius: 3px;
    padding: 1px 6px;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 11px;
    color: #b8becf;
}

/* Map pulse: an amber ring that grows + fades repeatedly while it's on the
   map. The element is taken off the DOM after the 30s TTL in Home.razor, so
   the animation just loops in the meantime. fill-rule:evenodd would help if
   we layered multiple rings but a single circle with growing stroke is fine
   for a single highlight beat. */
.map-raid-pulse {
    fill: rgba(251, 191, 36, 0.15);
    stroke: rgba(251, 191, 36, 0.95);
    stroke-width: 3;
    pointer-events: none;
    animation: map-raid-pulse 1.6s ease-out infinite;
    transform-box: fill-box;
    transform-origin: center;
}

@keyframes map-raid-pulse {
    0%   { opacity: 1;   stroke-width: 4;  fill-opacity: 0.35; }
    60%  { opacity: 0.5; stroke-width: 12; fill-opacity: 0.15; }
    100% { opacity: 0;   stroke-width: 18; fill-opacity: 0;    }
}

/* Monument overlay markers — small filled squares with a dark outline so
   they read cleanly on any map backdrop. Category-specific fills mirror
   the classification in MapView.MonumentCategoryOf. Each marker is wrapped
   in a <g translate="…"> so the same CSS counter-scale used for player
   dots keeps its on-screen size constant regardless of zoom. */
.map-monument {
    cursor: help;
    /* Inverse of the current zoom, so the marker keeps its on-screen size
       at any zoom level. The wwwroot/js/map-interact.js controller sets
       --map-inv-zoom on the SVG root; player dots use the same variable. */
    transform: scale(var(--map-inv-zoom, 1));
    transform-box: fill-box;
    transform-origin: center;
}

.map-monument-marker {
    stroke: rgba(15, 23, 42, 0.95);
    stroke-width: 1.5;
    fill: #94a3b8; /* slate default */
    pointer-events: all;
}

/* One fill per Filters-dropdown bucket. Kept in the same order as the
   dropdown so the categorisation is obvious when the map is annotated. */
.map-monument-large  .map-monument-marker { fill: #ef4444; } /* red — big monuments + safe zones */
.map-monument-small  .map-monument-marker { fill: #f59e0b; } /* amber — mid-size POIs, roadside */
.map-monument-tiny   .map-monument-marker { fill: #eab308; } /* mustard — utility structures */
.map-monument-caves  .map-monument-marker { fill: #78716c; } /* stone grey — caves + labs */
.map-monument-unique .map-monument-marker { fill: #a855f7; } /* purple — bunker entrances + catch-all */

/* Monument tooltip variant — same floating chip as the player tooltip,
   with a badge for safe zones so it's obvious at a glance. */
.map-tip-monument .map-tip-name {
    /* Slightly brighter than the player tooltip so monument names don't
       fade into the dark map behind them. */
    color: #e5e7eb;
}

.map-tip-safezone {
    background: rgba(34, 197, 94, 0.18);
    color: #86efac;
}

/* ---------- Map filter dropdown ---------- */
/* Anchor for the floating panel — position:relative so the panel and the
   backdrop position off the "Filters" button rather than the whole document. */
.map-filter {
    position: relative;
    display: inline-block;
}

/* Full-viewport backdrop — transparent, but captures any click outside the
   panel so we can close the dropdown without JS interop. Rendered underneath
   the panel in DOM order; z-index below keeps it below map-tips too. */
.map-filter-backdrop {
    position: fixed;
    inset: 0;
    background: transparent;
    z-index: 20;
}

/* The dropdown itself. Absolutely positioned below the Filters button (top:
   100% + a small gap), right-aligned so it doesn't spill off narrow screens
   where the button is near the toolbar's right edge. */
.map-filter-panel {
    position: absolute;
    top: calc(100% + 6px);
    right: 0;
    z-index: 21;
    min-width: 200px;
    padding: 8px 4px;
    background: #1e293b;
    border: 1px solid rgba(148, 163, 184, 0.25);
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
    display: flex;
    flex-direction: column;
    gap: 2px;
}

/* One checkbox row. Padding gives it a comfortable click target and the
   hover flash matches other dropdowns in the app. */
.map-filter-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 10px;
    border-radius: 4px;
    cursor: pointer;
    color: #e2e8f0;
    font-size: 13px;
    user-select: none;
}
.map-filter-row:hover {
    background: rgba(148, 163, 184, 0.1);
}
.map-filter-row input[type="checkbox"] {
    accent-color: #3b82f6;
    cursor: pointer;
}
/* Disabled row (e.g. Underground with no image) — the whole label greys out. */
.map-filter-row input[type="checkbox"]:disabled + span,
.map-filter-row:has(input[type="checkbox"]:disabled) {
    color: #64748b;
    cursor: not-allowed;
}

/* Divider between the Underground toggle (a data-source choice) and the
   overlay toggles below. The label text sits ON the divider line so the
   section header reads visually as a category. */
.map-filter-divider {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 6px 8px 4px;
    color: #94a3b8;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.6px;
}
.map-filter-divider::before,
.map-filter-divider::after {
    content: "";
    flex: 1;
    height: 1px;
    background: rgba(148, 163, 184, 0.25);
}

/* ---------- Permission groups page ---------- */

/* Inline chips for "system" / "locked" beside group names. Same flavour as
   trigger-event-chip but the Groups page wanted two distinct colours. */
.group-tag {
    display: inline-block;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    border-radius: 3px;
    padding: 1px 7px;
    margin-left: 6px;
    vertical-align: middle;
}

.group-tag-system { background: rgba(59, 130, 246, 0.15); color: #93c5fd; }
.group-tag-locked { background: rgba(234, 179, 8, 0.15); color: #fbbf24; }
.group-tag-owner { background: rgba(34, 197, 94, 0.15); color: #86efac; }
/* S5d — marks a permission group this account created, as opposed to the
   shared Admin / Moderator / Viewer. Teal so it reads as a neutral
   classification next to the blue "system" and amber "locked" tags. */
.group-tag-own { background: rgba(45, 212, 191, 0.15); color: #5eead4; }

.perm-count {
    font-variant-numeric: tabular-nums;
    color: #b8becf;
}

/* Permission matrix: one block per Permissions.Area, each with a list of
   checkbox rows. The .row is a label so the entire row is clickable. */
.perm-matrix {
    display: flex;
    flex-direction: column;
    gap: 18px;
    margin-top: 8px;
}

.perm-matrix-area {
    border: 1px solid #1f2330;
    border-radius: 8px;
    background: #0e1117;
    padding: 12px 14px;
}

.perm-matrix-area-title {
    margin: 0 0 8px;
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: #6b7280;
    font-weight: 600;
}

.perm-matrix-rows {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.perm-matrix-row {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 6px 8px;
    border-radius: 6px;
    cursor: pointer;
    color: #b8becf;
    font-size: 13px;
}

.perm-matrix-row:hover { background: rgba(255, 255, 255, 0.03); }

.perm-matrix-row input[type="checkbox"] {
    margin-top: 2px;
    flex-shrink: 0;
}

.perm-matrix-row input[type="checkbox"]:disabled {
    /* Read-only Admin group renders checked but greyed; the cursor signals
       the row isn't editable. */
    opacity: 0.6;
    cursor: not-allowed;
}

.perm-matrix-row:has(input:disabled) {
    cursor: not-allowed;
}

.perm-matrix-label {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.perm-matrix-name {
    color: #d8dde6;
    font-weight: 500;
}

.perm-matrix-desc {
    color: #8c95a4;
    font-size: 12px;
    line-height: 1.4;
}

.perm-matrix-key {
    color: #6b7280;
    font-size: 11px;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

/* ===== Blazor reconnect modal =============================================
   Replaces the default washed-out white overlay with a dark, high-contrast
   card. Blazor toggles the state classes (components-reconnect-show /
   -hide / -failed / -rejected) on #components-reconnect-modal; the matching
   .rr-reconnect-*-only helpers below decide which heading / body / button
   pair is visible per state. */

#components-reconnect-modal.rr-reconnect-modal {
    /* Default state — hidden. Visibility is driven by the state classes
       Blazor adds; without one of them, stay out of the way. */
    display: none;
    position: fixed;
    inset: 0;
    z-index: 10000;
    /* Much darker than the framework default (which is a barely-tinted
       white). Plain operators couldn't read the text before. */
    background: rgba(8, 10, 14, 0.78);
    backdrop-filter: blur(2px);
    -webkit-backdrop-filter: blur(2px);
    justify-content: center;
    align-items: center;
}

/* Blazor adds .components-reconnect-show / -failed / -rejected to flip
   the modal on. -hide takes priority and forces it back out of view. */
#components-reconnect-modal.components-reconnect-show,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-rejected {
    display: flex;
}
#components-reconnect-modal.components-reconnect-hide {
    display: none !important;
}

#components-reconnect-modal .rr-reconnect-card {
    background: #14171d;
    border: 1px solid #2a2f3b;
    border-radius: 10px;
    padding: 28px 32px;
    max-width: 420px;
    width: calc(100% - 48px);
    box-shadow: 0 16px 48px rgba(0, 0, 0, 0.55);
    text-align: center;
    color: #f1f5f9;
    font: 14px/1.55 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

#components-reconnect-modal .rr-reconnect-title {
    margin: 0 0 8px;
    font-size: 18px;
    /* Brighter than the surrounding body so the headline reads at a glance. */
    color: #ffffff;
    font-weight: 600;
}

#components-reconnect-modal .rr-reconnect-body {
    margin: 0 0 16px;
    color: #cbd5e1;
}

#components-reconnect-modal .rr-reconnect-button {
    appearance: none;
    border: 1px solid #3b82f6;
    background: #3b82f6;
    color: #ffffff;
    padding: 8px 18px;
    border-radius: 6px;
    font: inherit;
    font-weight: 500;
    cursor: pointer;
    margin: 4px 6px 0;
    transition: background 0.15s ease, border-color 0.15s ease;
}
#components-reconnect-modal .rr-reconnect-button:hover {
    background: #2563eb;
    border-color: #2563eb;
}

/* Soft spinner shown above the title while we're still trying to reconnect.
   Hidden in the failed/rejected states where retry/reload buttons are the
   focus instead. */
#components-reconnect-modal .rr-reconnect-spinner {
    width: 32px;
    height: 32px;
    margin: 0 auto 14px;
    border: 3px solid rgba(148, 163, 184, 0.25);
    border-top-color: #93c5fd;
    border-radius: 50%;
    animation: rr-reconnect-spin 0.9s linear infinite;
}
@keyframes rr-reconnect-spin {
    to { transform: rotate(360deg); }
}

/* Per-state visibility for the heading / body / button trios. Each
   element carries a .rr-reconnect-{state}-only class and is hidden by
   default; the matching state class on the parent lights it up. */
#components-reconnect-modal .rr-reconnect-show-only,
#components-reconnect-modal .rr-reconnect-failed-only,
#components-reconnect-modal .rr-reconnect-rejected-only {
    display: none;
}
#components-reconnect-modal.components-reconnect-show .rr-reconnect-show-only,
#components-reconnect-modal.components-reconnect-failed .rr-reconnect-failed-only,
#components-reconnect-modal.components-reconnect-rejected .rr-reconnect-rejected-only {
    display: block;
}
#components-reconnect-modal.components-reconnect-show .rr-reconnect-failed-only,
#components-reconnect-modal.components-reconnect-show .rr-reconnect-rejected-only {
    display: none;
}
/* The spinner is only meaningful while we're actively trying to reconnect. */
#components-reconnect-modal.components-reconnect-failed .rr-reconnect-spinner,
#components-reconnect-modal.components-reconnect-rejected .rr-reconnect-spinner {
    display: none;
}
/* In the show (still-trying) state, the Reload button is hidden — only the
   spinner + text are appropriate. The Retry button is failed-only; we
   suppress it everywhere else with the -only class already. */
#components-reconnect-modal.components-reconnect-show .rr-reconnect-button:not(.rr-reconnect-failed-only):not(.rr-reconnect-rejected-only) {
    display: none;
}

