﻿/* Design tokens — locked in by /plan-design-review (plan §13). Every new
   component MUST reference these; no hardcoded hex values outside this
   block. If you find yourself picking a new color, add it here first. */
:root {
    --bg: #f3f4f6;
    --surface: #ffffff;
    --border: #e5e7eb;
    --border-soft: #f3f4f6;
    --border-hover: #d1d5db;

    --ink: #1f2937;
    --ink-strong: #111827;
    /* Darkened from #6b7280 (2026-08-18): that value scored 4.39:1 on the
       page background, just under the 4.5 readability norm, and it is the
       colour every secondary line on every page uses. #656c78 scores 4.81
       on --bg and 5.29 on --surface and still reads as muted. */
    --ink-muted: #656c78;
    --ink-faint: #9ca3af;
    --ink-subtle: #4b5563;
    --ink-dim: #374151;

    --accent: #2563eb;
    --accent-dark: #1d4ed8;
    --accent-soft: #eff6ff;

    --work-bg: #dbeafe;
    --work-ink: #1e40af;
    --private-bg: #dcfce7;
    --private-ink: #166534;

    --pri-now-bg: #fee2e2;
    --pri-now-ink: #991b1b;
    --pri-high-bg: #ffedd5;
    --pri-high-ink: #9a3412;
    --pri-med-bg: #e5e7eb;
    --pri-med-ink: #374151;
    --pri-low-bg: #f3f4f6;
    --pri-low-ink: #6b7280;

    /* Waiting-on badge escalation. fresh <3d, warn 3-7d, stale >7d. */
    --wait-fresh-bg: #dcfce7;
    --wait-fresh-ink: #166534;
    --wait-warn-bg: #fef3c7;
    --wait-warn-ink: #92400e;
    --wait-stale-bg: #fee2e2;
    --wait-stale-ink: #b91c1c;
    --wait-gray: #6b7280;

    /* Billable-hours accent (Phase 5.8). Same amber family as the
       wait-warn because both communicate "attention". */
    --billable-bg: #fef3c7;
    --billable-border: #fde68a;
    --billable-ink: #92400e;
    --billable-ink-strong: #78350f;

    /* Overdue/urgent chip — reuses the danger family. */
    --overdue-bg: #fee2e2;
    --overdue-ink: #b91c1c;

    /* Today chip — same as wait-warn, softer "upcoming" signal. */
    --today-bg: #fef3c7;
    --today-ink: #92400e;

    /* State dots on todo groups. */
    --state-open: #3b82f6;
    --state-progress: #8b5cf6;
    --state-waiting: #f59e0b;
    --state-done: #10b981;
    --state-cancel: #9ca3af;

    --radius: 0.75rem;
    --radius-sm: 0.5rem;
    --radius-pill: 999px;
    --shadow-sm: 0 1px 3px rgba(0,0,0,0.04);
    --shadow-md: 0 4px 12px rgba(0,0,0,0.2);
    --shadow-fab: 0 4px 12px rgba(37,99,235,0.4);
    --focus-ring: 0 0 0 3px rgba(37,99,235,0.35);

    --font-head: Inter, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    --font-body: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

/* ───────────────────────────────────────────────────────────────────────
   Dark mode tokens (Phase 8). Applied when the <html> element has
   data-theme="dark" set. Theme bootstrap script in <head> picks the
   value from localStorage so the first paint is correct — no flash
   of wrong theme on load. Every component MUST use the var(--*) names
   above and skip hardcoded hex.
   ───────────────────────────────────────────────────────────────────────*/
[data-theme="dark"] {
    --bg: #0f172a;
    --surface: #1f2937;
    --border: #374151;
    --border-soft: #1e293b;
    --border-hover: #4b5563;

    --ink: #e5e7eb;
    --ink-strong: #f9fafb;
    --ink-muted: #9ca3af;
    --ink-faint: #6b7280;
    --ink-subtle: #d1d5db;
    --ink-dim: #9ca3af;

    --accent: #60a5fa;
    --accent-dark: #3b82f6;
    --accent-soft: #1e3a8a;

    --work-bg: #1e3a8a;
    --work-ink: #bfdbfe;
    --private-bg: #14532d;
    --private-ink: #bbf7d0;

    --pri-now-bg: #7f1d1d;
    --pri-now-ink: #fecaca;
    --pri-high-bg: #7c2d12;
    --pri-high-ink: #fed7aa;
    --pri-med-bg: #374151;
    --pri-med-ink: #e5e7eb;
    --pri-low-bg: #1f2937;
    --pri-low-ink: #9ca3af;

    --wait-fresh-bg: #14532d;
    --wait-fresh-ink: #86efac;
    --wait-warn-bg: #78350f;
    --wait-warn-ink: #fde68a;
    --wait-stale-bg: #7f1d1d;
    --wait-stale-ink: #fecaca;

    --billable-bg: #78350f;
    --billable-border: #b45309;
    --billable-ink: #fde68a;
    --billable-ink-strong: #fef3c7;

    --overdue-bg: #7f1d1d;
    --overdue-ink: #fecaca;
    --today-bg: #78350f;
    --today-ink: #fde68a;

    --state-open: #60a5fa;
    --state-progress: #a78bfa;
    --state-waiting: #fbbf24;
    --state-done: #34d399;
    --state-cancel: #9ca3af;

    --shadow-sm: 0 1px 3px rgba(0,0,0,0.3);
    --shadow-md: 0 4px 12px rgba(0,0,0,0.5);
    --shadow-fab: 0 4px 12px rgba(96,165,250,0.4);
    --focus-ring: 0 0 0 3px rgba(96,165,250,0.45);
}

/* Body-level theme coupling. Keeps body bg + text in sync with tokens so
   the page doesn't have a bright-white border when the rest is dark. */
html { background: var(--bg); color-scheme: light dark; }
[data-theme="dark"] { color-scheme: dark; }
body { background: var(--bg); color: var(--ink); }

/* Phase 8 a11y: make keyboard focus universally visible. :focus-visible
   only lights up on keyboard navigation, so pointer users don't see
   halos everywhere. Overrides Bootstrap's default outlines with our
   token-driven ring. */
:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
    border-radius: inherit;
}
button:focus-visible,
a:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[role="button"]:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
}

html, body {
    font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

a, .btn-link {
    color: #006bb7;
}

.btn-primary {
    color: #fff;
    background-color: #1b6ec2;
    border-color: #1861ac;
}

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
  box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}

.content {
    padding-top: 1.1rem;
}

/* V1.23: trim the article's px-4 (Bootstrap = 1.5rem each side) on
   mobile so cards/lists use more of the viewport. Below the sidebar
   breakpoint (641px) the page is the only column anyway, so the extra
   gutter was wasted whitespace. Selector is .content.px-4 with !important
   because px-4 is a Bootstrap utility carrying its own !important. */
@media (max-width: 640px) {
    .content.px-4 {
        padding-left: 0.5rem !important;
        padding-right: 0.5rem !important;
    }
}

/* Account pages (admin Accounts / Edit / Register / ChangePassword /
   ResetPassword / ForgotPassword) live in VSITodo.Server and render
   SSR — they can't ship per-page .razor.css. Match .todos-page exactly:
   only padding-bottom, no max-width, no margin-auto. The article
   wrapper above (<article class="content px-4">) carries the side
   gutter, and the page content fills the column the way Todos does. */
.accounts-page,
.edit-page,
.account-form-page {
    padding-bottom: 4rem;
}

/* Accounts list table — fills the page column. */
.accounts-page .table {
    width: 100%;
}

/* ===== Usage panel (Phase 19) on /Account/Edit/{id} =====
   Four cards on a 2-col grid at desktop width, collapsing to 1 col on
   mobile. Each card is a dl with the label tone-muted on the left and
   the figure heavier on the right. Read-only; nothing here is
   editable, so the muted card chrome differentiates it from the
   action sections (Roles, Reset password, Delete) below it. */
.usage-panel .usage-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1rem;
    margin-top: 0.5rem;
}

/* ===== Admin usage dashboard (/admin/usage) ===== */
.usage-dashboard .stat-tiles {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 0.75rem;
    margin: 1rem 0;
}

.usage-dashboard .stat-tile {
    background: #f8fafc;
    border: 1px solid #e2e8f0;
    border-radius: 8px;
    padding: 0.75rem 1rem;
    display: flex;
    flex-direction: column;
}

.usage-dashboard .stat-tile-warn {
    background: #fef9c3;
    border-color: #facc15;
}

.usage-dashboard .stat-tile-cost {
    background: #ecfdf5;
    border-color: #6ee7b7;
}

.usage-dashboard .stat-num {
    font-size: 1.5rem;
    font-weight: 700;
    color: #0f172a;
    line-height: 1.1;
}

.usage-dashboard .stat-label {
    font-size: 0.75rem;
    color: #64748b;
    margin-top: 0.25rem;
}

.usage-panel .usage-card {
    background: #f8fafc;
    border: 1px solid #e2e8f0;
    border-radius: 8px;
    padding: 0.875rem 1rem;
}

.usage-panel .usage-card h3 {
    margin: 0 0 0.5rem 0;
    letter-spacing: 0.04em;
}

.usage-panel .usage-card dl {
    display: grid;
    grid-template-columns: minmax(8.5rem, max-content) 1fr;
    gap: 0.25rem 0.75rem;
    margin: 0;
    font-size: 0.9rem;
}

.usage-panel .usage-card dt {
    color: #64748b;
    font-weight: 500;
}

.usage-panel .usage-card dd {
    margin: 0;
    color: #0f172a;
    word-break: break-word;
}

@media (max-width: 720px) {
    .usage-panel .usage-grid {
        grid-template-columns: 1fr;
    }
    .usage-panel .usage-card dl {
        grid-template-columns: 1fr;
        gap: 0.1rem 0;
    }
    .usage-panel .usage-card dt {
        margin-top: 0.4rem;
    }
}

html[data-theme="dark"] .usage-panel .usage-card {
    background: #1f2937;
    border-color: #374151;
}
html[data-theme="dark"] .usage-panel .usage-card dt { color: #94a3b8; }
html[data-theme="dark"] .usage-panel .usage-card dd { color: #e5e7eb; }

/* Password input with the eye-toggle button. The button absolute-
   positions inside the form-floating wrapper at the right edge.
   Form-floating's label already sits at the right z-index so we
   only need to make space for the button on the input itself. */
.pw-field-wrap {
    position: relative;
}
.pw-field-wrap > .pw-input {
    padding-right: 2.75rem; /* room for the eye button */
}
.pw-eye-btn {
    position: absolute;
    top: 50%;
    right: 0.5rem;
    transform: translateY(-50%);
    width: 2rem;
    height: 2rem;
    border: 0;
    background: transparent;
    color: #475569;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 999px;
    z-index: 5;
}
.pw-eye-btn:hover {
    background: rgba(15, 23, 42, 0.05);
    color: #0f172a;
}
.pw-eye-btn:focus-visible {
    outline: 2px solid #2563eb;
    outline-offset: 2px;
}
/* Hide the alternate icon depending on visibility state. The script
   below toggles `data-visible` on the wrapper. */
.pw-eye-btn .pw-eye-on { display: inline; }
.pw-eye-btn .pw-eye-off { display: none; }
.pw-field-wrap[data-visible="true"] .pw-eye-btn .pw-eye-on { display: none; }
.pw-field-wrap[data-visible="true"] .pw-eye-btn .pw-eye-off { display: inline; }

h1:focus {
    outline: none;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid #e50000;
}

.validation-message {
    color: #e50000;
}

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

.darker-border-checkbox.form-check-input {
    border-color: #929292;
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
    text-align: start;
}

/* WebAssembly boot indicator. Shown on every fresh HTML response until a
   WASM-rendered component signals ready via window.vsiTodo.markWasmReady().
   The 400ms fade-in delay means users with warm caches never see it. */
.app-boot-pill {
    position: fixed;
    right: 1rem;
    bottom: 1rem;
    z-index: 2000;
    background: var(--ink-strong, #111827);
    color: #fff;
    padding: 0.5rem 0.875rem;
    border-radius: 999px;
    font-size: 0.875rem;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    opacity: 0;
    pointer-events: none;
    animation: app-boot-fade-in 220ms ease-out 400ms forwards;
    transition: opacity 220ms ease-out;
    box-shadow: 0 6px 20px rgba(0,0,0,0.18);
}
html.wasm-ready .app-boot-pill {
    opacity: 0 !important;
    animation: none;
    visibility: hidden;
}
.app-boot-pill-spinner {
    width: 16px;
    height: 16px;
    border: 2px solid rgba(255,255,255,0.25);
    border-top-color: #fff;
    border-radius: 50%;
    animation: app-boot-spin 800ms linear infinite;
}
@keyframes app-boot-fade-in { to { opacity: 0.95; } }
@keyframes app-boot-spin { to { transform: rotate(360deg); } }

/* Full-screen boot splash. Server-rendered in App.razor so it paints with
   the first byte of HTML — the user sees the brand + spinner immediately,
   even while the WebAssembly runtime is still downloading on a slow link.
   Only shown on app routes (html.app-route); /account pages stay usable and
   use the corner pill instead. Fades out the instant WASM signals ready. */
.app-splash {
    display: none;
}
html.app-route .app-splash {
    display: flex;
    position: fixed;
    inset: 0;
    z-index: 3000;
    align-items: center;
    justify-content: center;
    background: linear-gradient(160deg, #2563eb 0%, #1e40af 100%);
    color: #fff;
    opacity: 1;
    /* fade-out tuned to feel instant once WASM is ready */
    transition: opacity 280ms ease-out, visibility 0s linear 280ms;
}
/* On app routes the full splash replaces the corner pill. */
html.app-route .app-boot-pill {
    display: none;
}
/* WASM booted — fade the splash away and let pointer events through. */
html.wasm-ready .app-splash {
    opacity: 0 !important;
    visibility: hidden;
    pointer-events: none;
}
.app-splash-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.85rem;
    padding: 2rem;
    text-align: center;
}
.app-splash-logo {
    width: 96px;
    height: 96px;
    border-radius: 22px;
    box-shadow: 0 10px 30px rgba(0,0,0,0.25);
    /* subtle entrance so it doesn't read as a frozen image */
    animation: app-splash-pop 320ms ease-out both;
}
.app-splash-name {
    font-size: 1.35rem;
    font-weight: 700;
    letter-spacing: 0.01em;
}
.app-splash-text {
    font-size: 0.9rem;
    opacity: 0.85;
}
.app-splash-spinner {
    width: 30px;
    height: 30px;
    border: 3px solid rgba(255,255,255,0.3);
    border-top-color: #fff;
    border-radius: 50%;
    animation: app-boot-spin 800ms linear infinite;
}
@keyframes app-splash-pop {
    from { transform: scale(0.9); opacity: 0; }
    to   { transform: scale(1);   opacity: 1; }
}
/* Dark theme — match the app's dark surface instead of bright blue. */
html[data-theme="dark"].app-route .app-splash {
    background: linear-gradient(160deg, #0f172a 0%, #1e293b 100%);
}
/* Respect reduced-motion: keep the spinner subtle, drop the pop. */
@media (prefers-reduced-motion: reduce) {
    .app-splash-logo { animation: none; }
    .app-splash-spinner { animation-duration: 1400ms; }
}

/* Shared inline loading indicator (LoadingSpinner.razor). Used on every
   data-loading view so a slow fetch shows visible motion instead of a blank
   or static screen. Sits above the skeleton placeholders on list pages and
   stands alone on pages without skeletons. */
.app-loading {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.6rem;
    padding: 1.5rem 1rem;
    color: var(--ink-muted);
    font-size: 0.95rem;
}
.app-loading-spinner {
    width: 22px;
    height: 22px;
    border: 3px solid rgba(37, 99, 235, 0.25);
    border-top-color: #2563eb;
    border-radius: 50%;
    animation: app-boot-spin 800ms linear infinite;
    flex: 0 0 auto;
}
.app-loading-text { font-weight: 500; }
@media (prefers-reduced-motion: reduce) {
    .app-loading-spinner { animation-duration: 1400ms; }
}

/* Inline per-row action buttons (Done / Tomorrow / Next week).
   Shared by /todos cards, /clients/{id} todo rows, and /week rows.
   Lives outside scoped CSS so the same look applies everywhere. */
.todo-actions-inline {
    display: flex;
    gap: 0.35rem;
    padding-top: 0.5rem;
    border-top: 1px dashed #e5e7eb;
    flex-wrap: nowrap;
}

.action-inline {
    flex: 1 1 auto;
    min-height: 34px;
    padding: 0.3rem 0.7rem;
    border-radius: 999px;
    border: 1px solid #d1d5db;
    background: #fff;
    color: #374151;
    font-weight: 600;
    font-size: 0.8125rem;
    cursor: pointer;
    transition: background-color 120ms, border-color 120ms, color 120ms;
}

.action-inline:hover {
    border-color: #9ca3af;
    background: #f9fafb;
}

.action-inline.action-done {
    background: #2563eb;
    color: #fff;
    border-color: #2563eb;
}

.action-inline.action-done:hover {
    background: #1d4ed8;
    border-color: #1d4ed8;
}

.action-inline.action-restore {
    background: #eff6ff;
    color: #2563eb;
    border-color: #2563eb;
}

.action-inline.action-restore:hover {
    background: #dbeafe;
}

/* /week's row-item is a CSS grid; the action row needs to span the full
   row instead of squeezing into the last column. */
.row-item .todo-actions-inline.row-actions {
    grid-column: 1 / -1;
    margin-top: 0.35rem;
}

/* ============================================================
   Development feature (VSIDirigent orchestrator)
   ============================================================ */
/* No max-width: the jobs grid is the widest thing in the app and the opdracht
   column is the one you actually read, so it gets every pixel the window has.
   Capping the page at 1200px left a third of a wide monitor empty while the
   opdracht was clipped after a handful of words. */
.development-page {
    padding: 1rem;
    width: 100%;
}

.development-header h1,
.development-detail-header h1 {
    margin: 0;
}

.development-create {
    background: var(--bs-body-bg, #fff);
    border: 1px solid rgba(0, 0, 0, 0.1);
    border-radius: 8px;
    padding: 1rem;
    margin: 1rem 0 1.5rem;
    max-width: 720px;
}

.development-field {
    margin-bottom: 0.75rem;
}

.development-field label {
    display: block;
    font-weight: 600;
    margin-bottom: 0.25rem;
}

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

.development-subtitle {
    margin-top: 1rem;
}

/* Status badge — shared by the grid and the detail header. */
.dev-status {
    display: inline-block;
    padding: 0.15rem 0.55rem;
    border-radius: 12px;
    font-size: 0.8rem;
    font-weight: 600;
    color: #fff;
    white-space: nowrap;
}

.dev-status-queued { background: #6c757d; }
.dev-status-validating { background: #0dcaf0; color: #053; }
.dev-status-running { background: #0b5ed7; }
.dev-status-completed { background: #198754; }
.dev-status-failed { background: #dc3545; }
.dev-status-cancelled { background: #495057; }
.dev-status-rolledback { background: #6f42c1; }
.dev-status-rejected { background: #fd7e14; color: #3d2000; }
/* Reviewed = done + acknowledged. Calm violet, success-like (never red). */
.dev-status-reviewed { background: #7048e8; }
/* Paused = parked on purpose. Amber, and the hex is copied verbatim from
   VSIDirigent's .dg-badge--paused so the same job reads the same colour in
   both apps. It pulses (see below) because a parked task must be
   unmistakable — it is the one state where nothing will ever happen unless
   you act. */
.dev-status-paused { background: #d98e04; }
/* Awaiting review = filed but NOT on its way. Purple, and the hex is copied
   verbatim from VSIDirigent's .dg-badge--review so the same job reads the same
   colour in both apps. Deliberately does NOT pulse: it is blocking, not urgent,
   and a second pulsing chip next to Running/Paused would be noise. */
.dev-status-review { background: #7c4dff; }

/* ── Applicatierechten page ─────────────────────────────────────────────────
   Superadmin-only grid: pick an application + a user, tick the rights. The
   form is a single column on a phone and a label/field pair on a desktop. */
.rights-page {
    padding: 0 12px 32px;
}

.rights-section {
    margin-top: 24px;
}

.rights-form {
    display: grid;
    gap: 10px;
    max-width: 720px;
}

.rights-form label {
    font-weight: 600;
}

/* 48px minimum touch target on the two selects — this page is usable from a
   phone, where the whole point is approving something while away from a desk. */
.rights-form .form-select {
    min-height: 48px;
}

.rights-checks {
    border: 1px solid #dee2e6;
    border-radius: 10px;
    padding: 12px 14px;
}

.rights-checks legend {
    font-size: 1rem;
    font-weight: 600;
    width: auto;
    padding: 0 6px;
}

.rights-check {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 8px 0;
    font-weight: 400;
}

.rights-check input {
    width: 22px;
    height: 22px;
    margin-top: 2px;
    flex: 0 0 auto;
}

.rights-check-hint {
    display: block;
    color: var(--ink-muted);
    font-size: 0.9rem;
}

.rights-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
}

.rights-actions .btn {
    min-height: 48px;
}

.rights-table code {
    word-break: break-all;
}

.rights-warn {
    display: inline-block;
    margin-left: 6px;
    color: #b3541e;
    font-size: 0.85rem;
}

/* ── Review panel (job detail) ──────────────────────────────────────────────
   Sits above the metadata on a job that is waiting for approval, so the
   decision is the first thing a reviewer sees and the command it is judged on
   is a few lines below. */
.dev-review-panel {
    border: 1px solid #7c4dff;
    border-left-width: 4px;
    border-radius: 10px;
    background: #f5f2ff;
    padding: 14px 16px;
    margin-bottom: 16px;
}

/* The same panel after the decision: neutral, because it is a record now and
   no longer asks anything of the reader. */
.dev-review-panel--done {
    border-color: #adb5bd;
    background: #f6f7f9;
}

.dev-review-title {
    font-weight: 600;
    margin-bottom: 6px;
}

.dev-review-by {
    font-weight: 400;
    color: var(--ink-muted);
    margin-left: 8px;
}

.dev-review-hint {
    margin: 0 0 10px;
    color: #3d4453;
}

/* Wraps on a phone so the two buttons keep their 48px touch target instead of
   being squeezed onto one line. */
.dev-review-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 8px;
}

.dev-review-actions .btn {
    min-height: 48px;
}

.dev-review-reject {
    margin-top: 12px;
}

.dev-review-reject label {
    display: block;
    font-weight: 600;
    margin-bottom: 4px;
}

.dev-review-message {
    margin-top: 10px;
    font-weight: 500;
}

/* Running is the only live state: pulse it so the overview shows at a glance
   that something is actually happening, instead of a static blue chip that
   looks the same as a stalled job. Brightness + halo, no size change — a
   growing chip would reflow the row on every beat. */
.dev-status-running {
    animation: dev-status-pulse 1.4s ease-in-out infinite;
}

@keyframes dev-status-pulse {
    0%, 100% {
        background: #0b5ed7;
        box-shadow: 0 0 0 0 rgba(13, 110, 253, 0.55);
    }

    50% {
        background: #2472e8;
        box-shadow: 0 0 0 4px rgba(13, 110, 253, 0);
    }
}

/* Paused pulses too, but slower and in amber, so a parked task stands apart
   from both the calm grey "Queued" and the busy blue "Running" at a glance.
   Timing and keyframes match VSIDirigent's badge. */
.dev-status-paused {
    animation: dev-status-paused-pulse 1.9s ease-in-out infinite;
}

@keyframes dev-status-paused-pulse {
    0%, 100% {
        opacity: 1;
        box-shadow: 0 0 0 0 rgba(217, 142, 4, 0.65);
    }

    50% {
        opacity: 0.7;
        box-shadow: 0 0 0 5px rgba(217, 142, 4, 0);
    }
}

/* Users who asked the OS for less motion get the plain chip. */
@media (prefers-reduced-motion: reduce) {
    .dev-status-running {
        animation: none;
    }

    /* Keep the amber — only the motion goes. */
    .dev-status-paused {
        animation: none;
    }

    .dev-heartbeat::before {
        animation: none;
    }
}

/* ── VSI Dirigent health ─────────────────────────────────────────────────
   Warning surface for "the orchestrator is not actually running". Kept out of
   the .razor files per project convention. */

/* The ⚠ next to a status chip whose job has stopped moving. Deliberately NOT
   animated: the running chip already pulses, and a pulsing warning next to a
   pulsing status reads as decoration instead of a problem. */
.dev-status-stale {
    display: inline-block;
    margin-left: 0.35rem;
    color: #b02a37;
    font-size: 0.95rem;
    line-height: 1;
    cursor: help;
    vertical-align: middle;
}

/* Levensteken naast de statuschip ("Laatste heartbeat 13 sec geleden"). Bewust
   rustig: het staat NAAST de chip, niet in plaats daarvan, dus gedempte tekst met
   een klein kloppend bolletje. Wordt de heartbeat oud, dan kleurt het amber — dat
   is het moment waarop het de aandacht verdient. */
.dev-heartbeat {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    /* Geen marge: .development-compact-header is een flexrij met een eigen gap. */
    font-size: 0.78rem;
    font-weight: 500;
    color: var(--ink-muted);
    white-space: nowrap;
    vertical-align: middle;
    cursor: help;
}

.dev-heartbeat::before {
    content: "";
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: #b02a37;
    animation: dev-heartbeat-beat 1.6s ease-in-out infinite;
}

.dev-heartbeat--stale {
    color: #b06a04;
    font-weight: 600;
}

.dev-heartbeat--stale::before {
    background: #d98e04;
    animation: none;
}

@keyframes dev-heartbeat-beat {
    0%, 100% {
        transform: scale(1);
        opacity: 0.85;
    }

    50% {
        transform: scale(1.35);
        opacity: 1;
    }
}

/* Claude-gebruikslimiet naast de statuschip ("Limiet bijna vol · 7d"). Vervangt de
   "Claude rate-limit: …"-REGELS die de agent-uitvoer vulden: die gaan over het account
   van de ontwikkelmachine en niet over deze taak, dus ze herhaalden zich eindeloos.
   Zelfde compacte maatvoering als de statuschip ernaast, zodat deze regel niet hoger
   wordt: dat was de uitdrukkelijke voorwaarde bij het verplaatsen. */
.dev-ratelimit {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.1rem 0.5rem;
    border-radius: 999px;
    border: 1px solid transparent;
    font-size: 0.78rem;
    font-weight: 600;
    color: var(--ink-muted);
    white-space: nowrap;
    vertical-align: middle;
    cursor: help;
}

/* Venster bijna vol ("allowed_warning"): het gaat nog door, maar niet lang meer. */
.dev-ratelimit--warning {
    background: rgba(217, 142, 4, 0.15);
    border-color: #d98e04;
    color: #8a5403;
}

/* Venster op: verzoeken worden geweigerd, daarom worden taken vastgehouden. */
.dev-ratelimit--blocked {
    background: rgba(176, 42, 55, 0.15);
    border-color: #b02a37;
    color: #b02a37;
}

/* On the detail page the marker carries its own text, so it becomes a chip. */
.dev-status-stale--inline {
    padding: 0.15rem 0.55rem;
    border-radius: 12px;
    background: #f8d7da;
    border: 1px solid #f1aeb5;
    font-size: 0.78rem;
    font-weight: 600;
    white-space: nowrap;
}

.dirigent-health {
    display: flex;
    gap: 0.75rem;
    align-items: flex-start;
    margin: 0 0 1rem;
    padding: 0.85rem 1rem;
    border-radius: 10px;
    border: 1px solid transparent;
    background: #fff3cd;
    border-color: #ffe69c;
    color: #664d03;
}

/* Offline / disabled: nothing is being picked up at all — the loud one. */
.dirigent-health--offline {
    background: #f8d7da;
    border-color: #f1aeb5;
    color: #58151c;
}

/* Online but with frozen job rows: something is off, but the host is there. */
.dirigent-health--stale {
    background: #fff3cd;
    border-color: #ffe69c;
    color: #664d03;
}

/* Planned states — informative, not alarming. */
.dirigent-health--stopped,
.dirigent-health--paused,
.dirigent-health--unknown {
    background: #e7f1ff;
    border-color: #b6d4fe;
    color: #052c65;
}

.dirigent-health__icon {
    font-size: 1.35rem;
    line-height: 1.2;
    flex: 0 0 auto;
}

.dirigent-health__body {
    flex: 1 1 auto;
    min-width: 0;
}

.dirigent-health__message {
    margin: 0;
    font-weight: 600;
}

.dirigent-health__hint,
.dirigent-health__meta,
.dirigent-health__result {
    margin: 0.35rem 0 0;
    font-size: 0.85rem;
}

.dirigent-health__meta {
    opacity: 0.8;
}

.dirigent-health__result {
    font-weight: 600;
}

.dirigent-health__jobs {
    margin: 0.4rem 0 0;
    padding-left: 1.1rem;
    font-size: 0.85rem;
}

.dirigent-health__jobs li {
    margin-bottom: 0.15rem;
}

.dirigent-health__job-app {
    margin-left: 0.4rem;
    font-weight: 600;
}

.dirigent-health__job-quiet {
    margin-left: 0.4rem;
    opacity: 0.85;
}

.dirigent-health__actions {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    flex: 0 0 auto;
}

/* 48px minimum touch target — this banner is read on a phone between
   meetings, which is exactly when you need to know the agent stopped. */
.dirigent-health__btn {
    min-height: 48px;
    padding: 0.4rem 0.85rem;
    border-radius: 8px;
    border: 1px solid currentColor;
    background: transparent;
    color: inherit;
    font-size: 0.85rem;
    font-weight: 600;
    cursor: pointer;
}

.dirigent-health__btn:disabled {
    opacity: 0.55;
    cursor: default;
}

.dirigent-health__btn--danger {
    background: #b02a37;
    border-color: #b02a37;
    color: #fff;
}

/* On a phone the action column would squeeze the text to a few characters. */
@media (max-width: 700px) {
    .dirigent-health {
        flex-wrap: wrap;
    }

    .dirigent-health__actions {
        flex-direction: row;
        flex-wrap: wrap;
        width: 100%;
    }

    .dirigent-health__btn {
        flex: 1 1 auto;
    }
}

/* Priority chip — High stands out, Low is muted, Normal is plain. */
.dev-prio {
    display: inline-block;
    padding: 0.1rem 0.5rem;
    border-radius: 10px;
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.02em;
}

.dev-prio-high {
    background: #fff3bf;
    color: #7a5600;
    border: 1px solid #ffe066;
}

.dev-prio-normal {
    background: #f1f3f5;
    color: #5c636a;
}

.dev-prio-low {
    background: #e9ecef;
    color: #5c636a;
}

/* ---- Job detail ---- */
/* ───────────────────────── Job-detail toolbar ───────────────────────────
   The single action row above the job. It used to be a hedge of Bootstrap
   outline buttons in three different colours and two different heights, which
   read as clutter. Now it is ONE pill language:

     • every control is a .dev-pill-btn — same height, same radius, same type
       scale, so the row has one baseline instead of several;
     • controls that belong together (step through the overview, step through
       the finished jobs) sit in a .dev-pill-seg segmented control, so a group
       reads as one object with a label in the middle naming what the arrows
       walk;
     • exactly ONE pill is accent-coloured ("Lopend") — the one thing you press
       repeatedly. Everything else is quiet, so the accent still means
       something;
     • .dev-toolbar__gap pushes the act-on-this-job controls to the right on a
       wide screen, keeping "move between jobs" and "do something to this job"
       visually apart.
   All colours come from the theme tokens, so dark mode follows for free. */
.dev-toolbar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin-bottom: 0.9rem;
}

/* Pinned variant (job detail). The job page scrolls far — log output, diffs,
   the agent transcript — and the toolbar carries the controls you press WHILE
   reading it ("volgende lopende taak", "afgerond ⏭", vlag). Scrolling back up
   for every skip was the whole complaint, so the row stays put at the top of
   the viewport.

   The window is the scroller (main/.content set no overflow — see
   MainLayout.razor.css), so plain position:sticky against top:0 is enough; no
   JS, no fixed positioning, no manual width maths.

   The negative margins cancel the surrounding padding so the opaque strip
   bleeds a little past the content column — otherwise text scrolling by would
   be visible left and right of the pills. Background is the page background
   (not --surface) so the strip is invisible until content slides under it. */
.dev-toolbar--sticky {
    position: sticky;
    top: 0;
    /* Above the page content (max 50: the flag popover) but below the app-level
       overlays and banners (70+). */
    z-index: 60;
    background: var(--bg);
    border-bottom: 1px solid var(--border);
    padding: 0.6rem 0.6rem 0.5rem;
    margin: -0.6rem -0.6rem 0.9rem;
}

/* On a phone the app header itself is sticky (see .sidebar in
   MainLayout.razor.css), so a second strip pinned at top:0 slides underneath
   it and the toolbar this rule exists for would be invisible exactly while you
   scroll. Park it just below the header instead; 3.5rem is .top-row's
   min-height in NavMenu.razor.css. */
@media (max-width: 640px) {
    .dev-toolbar--sticky {
        top: 3.5rem;
    }
}

/* Eats the slack on a wide screen; collapses on a narrow one (see the media
   query below) so a wrapped toolbar stays left-aligned instead of scattering. */
.dev-toolbar__gap {
    flex: 1 1 auto;
    min-width: 0;
}

.dev-pill-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.35rem;
    min-height: 36px;
    padding: 0 0.85rem;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--surface);
    color: var(--ink-dim);
    font-family: inherit;
    font-size: 0.84rem;
    font-weight: 600;
    line-height: 1;
    white-space: nowrap;
    text-decoration: none;
    cursor: pointer;
    transition: background-color 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}

.dev-pill-btn:hover:not(:disabled) {
    background: var(--border-soft);
    border-color: var(--border-hover);
    color: var(--ink-strong);
    text-decoration: none;
}

.dev-pill-btn:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.dev-pill-btn:disabled {
    opacity: 0.4;
    cursor: default;
}

/* Arrow-only variant: square-ish so a row of them keeps an even rhythm. */
.dev-pill-btn--glyph {
    min-width: 40px;
    padding: 0 0.55rem;
    font-size: 0.95rem;
}

/* The leading ← / ▶ of a labelled pill — slightly dimmer than the label so the
   word carries the meaning and the glyph only points. */
.dev-pill-btn__glyph {
    font-size: 0.9em;
}

/* The one accent pill in the row. */
.dev-pill-btn--accent {
    border-color: var(--accent);
    background: var(--accent-soft);
    color: var(--accent-dark);
}

.dev-pill-btn--accent:hover:not(:disabled) {
    background: var(--accent);
    border-color: var(--accent);
    color: #fff;
}

/* How many running agents there are to walk. */
.dev-pill-btn__count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.25rem;
    height: 1.25rem;
    padding: 0 0.3rem;
    border-radius: 999px;
    background: rgba(0, 0, 0, 0.09);
    font-size: 0.72rem;
    font-weight: 700;
}

[data-theme="dark"] .dev-pill-btn__count {
    background: rgba(255, 255, 255, 0.16);
}

.dev-pill-btn--accent:hover .dev-pill-btn__count {
    background: rgba(255, 255, 255, 0.28);
}

/* The flag picker is a component with its own icon button; this wrapper only
   gives it the same pill footprint as its neighbours so the row stays level. */
.dev-pill-btn--flag {
    padding: 0 0.5rem;
    cursor: default;
}

/* Segmented control: two arrows around a label, drawn as one object. */
.dev-pill-seg {
    display: inline-flex;
    align-items: stretch;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--surface);
    overflow: hidden;
}

/* Inside the group the pills give up their own outline — the group carries it. */
.dev-pill-seg .dev-pill-btn {
    border: 0;
    border-radius: 0;
    background: transparent;
}

/* Hairlines between the segments. Deliberately written at the same specificity
   as the border reset above and placed AFTER it — a plain `> * + *` loses to
   `.dev-pill-seg .dev-pill-btn` and the dividers would silently disappear. */
.dev-pill-seg > * + .dev-pill-btn,
.dev-pill-seg > * + .dev-pill-seg__label {
    border-left: 1px solid var(--border);
}

.dev-pill-seg .dev-pill-btn:focus-visible {
    outline-offset: -2px;
}

.dev-pill-seg__label {
    display: inline-flex;
    align-items: center;
    padding: 0 0.7rem;
    background: var(--border-soft);
    color: var(--ink-muted);
    font-size: 0.78rem;
    font-weight: 600;
    line-height: 1;
    white-space: nowrap;
}

/* Why a press did nothing ("er loopt geen andere taak"). A pill as well, so it
   slots into the row instead of breaking its rhythm, and it sits AFTER the
   buttons so it never pushes them out of place when it appears. */
.dev-toolbar__msg {
    display: inline-flex;
    align-items: center;
    min-height: 36px;
    padding: 0 0.8rem;
    border-radius: 999px;
    background: var(--wait-warn-bg);
    color: var(--wait-warn-ink);
    font-size: 0.8rem;
    font-weight: 600;
}

/* Phone: touch targets grow, and the right-hand cluster stops being pushed
   away — on a wrapped toolbar that would strand it on its own line. */
@media (max-width: 700px) {
    .dev-toolbar {
        gap: 0.35rem;
    }
    .dev-toolbar__gap {
        display: none;
    }
    .dev-pill-btn,
    .dev-toolbar__msg {
        min-height: 48px;
    }
    .dev-pill-btn--glyph {
        min-width: 48px;
    }
}

.development-detail-header {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    flex-wrap: wrap;
}

.development-job-id {
    color: var(--ink-muted);
    font-weight: 400;
}

.development-status-message {
    margin: 0.5rem 0;
    font-style: italic;
}

.development-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 1.25rem;
    color: var(--ink-muted);
    font-size: 0.9rem;
    margin: 0.5rem 0 1rem;
}

.development-detail-actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    margin-bottom: 1rem;
}

.development-section {
    margin-bottom: 1.5rem;
}

.development-section h2 {
    font-size: 1.1rem;
    margin-bottom: 0.5rem;
}

.development-command,
.development-summary {
    background: #f6f8fa;
    border: 1px solid rgba(0, 0, 0, 0.1);
    border-radius: 6px;
    padding: 0.75rem 1rem;
    word-break: break-word;
    margin: 0;
}

/* The command box is a <pre>, which keeps the typed line breaks but does NOT
   wrap by default — a long line ran off the right edge. pre-wrap keeps the
   original breaks AND wraps, so the box grows to exactly the height of its
   text (auto height) instead of scrolling sideways. Not applied to
   .development-summary: that one is rendered Markdown in a <div>, where
   pre-wrap would turn source whitespace into visible blank lines.
   The box grows with its text up to 20rem and scrolls vertically beyond
   that, so a very long command can't push the rest of the page off screen. */
.development-command {
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    height: auto;
    max-height: 20rem;
    overflow-x: hidden;
    overflow-y: auto;
    overscroll-behavior: contain;
}

/* Rendered Markdown (MarkdownView). Shared look for agent results and any
   other Markdown surface. Tuned to read as a document, not a code dump. */
.markdown-body {
    line-height: 1.55;
    overflow-wrap: anywhere;
}

.markdown-body > :first-child {
    margin-top: 0;
}

.markdown-body > :last-child {
    margin-bottom: 0;
}

.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
    margin: 1.1em 0 0.4em;
    line-height: 1.25;
    font-weight: 600;
}

.markdown-body h2 { font-size: 1.35rem; }
.markdown-body h3 { font-size: 1.15rem; }
.markdown-body h4 { font-size: 1.02rem; }
.markdown-body h5,
.markdown-body h6 { font-size: 0.95rem; }

.markdown-body p {
    margin: 0 0 0.6em;
}

.markdown-body ul,
.markdown-body ol {
    margin: 0 0 0.6em;
    padding-left: 1.5rem;
}

.markdown-body li {
    margin: 0.15em 0;
}

.markdown-body li > ul,
.markdown-body li > ol {
    margin: 0.15em 0;
}

.markdown-body code {
    background: rgba(27, 31, 35, 0.06);
    border-radius: 4px;
    padding: 0.12em 0.35em;
    font-size: 0.88em;
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

.markdown-body pre.markdown-code {
    background: #1e1e1e;
    color: #e6e6e6;
    border-radius: 6px;
    padding: 0.75rem 1rem;
    overflow-x: auto;
    margin: 0 0 0.7em;
}

.markdown-body pre.markdown-code code {
    background: transparent;
    padding: 0;
    color: inherit;
    font-size: 0.85rem;
    white-space: pre;
}

.markdown-body blockquote.markdown-quote {
    margin: 0 0 0.7em;
    padding: 0.3em 0.9em;
    border-left: 3px solid #b6c2cf;
    color: #57606a;
    background: rgba(0, 0, 0, 0.02);
}

.markdown-body table.markdown-table {
    border-collapse: collapse;
    margin: 0 0 0.7em;
    width: 100%;
    font-size: 0.92rem;
}

.markdown-body table.markdown-table th,
.markdown-body table.markdown-table td {
    border: 1px solid rgba(0, 0, 0, 0.15);
    padding: 0.35em 0.6em;
    text-align: left;
    vertical-align: top;
}

.markdown-body table.markdown-table th {
    background: rgba(0, 0, 0, 0.04);
    font-weight: 600;
}

.markdown-body hr {
    border: 0;
    border-top: 1px solid rgba(0, 0, 0, 0.12);
    margin: 1em 0;
}

.markdown-body a {
    color: #0969da;
    text-decoration: underline;
}

/* Documents generated during a job, listed under the result. Touch-friendly
   rows (48px min height) because this is read on the phone between meetings. */
.dev-files {
    margin-top: 1rem;
}

.dev-files-title {
    font-weight: 600;
    margin-bottom: 0.5rem;
}

.dev-files-msg {
    margin-top: 1rem;
}

.dev-files-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

.dev-files-item {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem;
    min-height: 48px;
    padding: 0.5rem 0;
    border-bottom: 1px solid rgba(0, 0, 0, 0.08);
}

.dev-files-badge {
    flex: 0 0 auto;
    border-radius: 999px;
    padding: 0.15rem 0.6rem;
    font-size: 0.75rem;
    font-weight: 600;
    color: #fff;
}

.dev-files-badge--added {
    background: #198754;
}

.dev-files-badge--modified {
    background: #0d6efd;
}

.dev-files-badge--deleted {
    background: #dc3545;
}

.dev-files-badge--renamed {
    background: #fd7e14;
}

.dev-files-name {
    flex: 1 1 12rem;
    word-break: break-all;
    font-family: Consolas, "Courier New", monospace;
    font-size: 0.85rem;
}

.dev-files-size {
    flex: 0 0 auto;
    font-size: 0.75rem;
    color: var(--ink-muted);
}

.dev-files-action {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding: 0 0.75rem;
}

/* The documents the agent DELIVERED (its own "Gegenereerde documenten"
   section), carried over from the development machine by VSI Dirigent. Same
   row layout as the git-derived list above it, with room for the remark the
   agent wrote behind a path and for a failed fetch. */
.dev-docs .dev-files-name {
    /* Its own line: a delivered document has a long name AND up to three
       actions behind it, and letting them share a line made one row wrap after
       "Bekijk" and the next after the size. On a phone the actions must line up. */
    flex: 1 1 100%;
    font-weight: 600;
}

.dev-files-note {
    flex: 1 1 100%;
    font-size: 0.75rem;
    color: var(--ink-muted);
}

.dev-files-error {
    flex: 1 1 12rem;
    font-size: 0.75rem;
    color: #dc3545;
}

/* A button that has to read as one of the row's actions, not as a form button. */
.dev-files-fetch {
    background: none;
    border: 1px solid currentColor;
    border-radius: 6px;
    cursor: pointer;
}

.development-live {
    color: #dc3545;
    font-size: 0.8rem;
    margin-left: 0.5rem;
    animation: dev-pulse 1.5s ease-in-out infinite;
}

@keyframes dev-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.3; }
}

/* Live activity log — fixed height, scrolls, newest at bottom. */
.development-log {
    background: #1e1e1e;
    color: #e6e6e6;
    border-radius: 6px;
    padding: 0.5rem 0.75rem;
    height: 420px;
    overflow-y: auto;
    font-family: Consolas, "Courier New", monospace;
    font-size: 0.85rem;
    line-height: 1.5;
}

.development-log-empty {
    color: #a8b2b8;
    font-style: italic;
}

.development-log-line {
    display: flex;
    gap: 0.5rem;
    align-items: baseline;
    padding: 0.1rem 0;
}

.development-log-time {
    color: #a8b2b8;
    flex: 0 0 auto;
}

.development-log-level {
    flex: 0 0 5.5rem;
    font-weight: 600;
    text-transform: uppercase;
    font-size: 0.72rem;
}

.development-log-msg {
    flex: 1 1 auto;
    white-space: pre-wrap;
    word-break: break-word;
}

.dev-log-info { color: #9cdcfe; }
.dev-log-agent { color: #d4d4d4; }
.dev-log-tooluse { color: #c586c0; }
.dev-log-status { color: #4ec9b0; }
.dev-log-warning { color: #dcdcaa; }
.dev-log-error { color: #f48771; }

/* Status/info/warning/error/tool lines must be unmistakable against the wall of
   agent output and code that fills most of the log: a colored left rail, a
   tinted row background, a filled level chip and a bolder message. Plain agent
   output keeps the bare row so the contrast stays meaningful. */
.development-log-line.dev-line-status,
.development-log-line.dev-line-info,
.development-log-line.dev-line-warning,
.development-log-line.dev-line-error,
.development-log-line.dev-line-tooluse {
    border-left: 3px solid transparent;
    border-radius: 4px;
    padding: 0.25rem 0.45rem;
    margin: 0.25rem 0;
}

    .development-log-line.dev-line-status .development-log-level,
    .development-log-line.dev-line-info .development-log-level,
    .development-log-line.dev-line-warning .development-log-level,
    .development-log-line.dev-line-error .development-log-level,
    .development-log-line.dev-line-tooluse .development-log-level {
        flex: 0 0 auto;
        min-width: 5.5rem;
        text-align: center;
        border-radius: 999px;
        padding: 0.05rem 0.45rem;
        font-weight: 700;
        letter-spacing: 0.04em;
    }

    .development-log-line.dev-line-status .development-log-msg,
    .development-log-line.dev-line-warning .development-log-msg,
    .development-log-line.dev-line-error .development-log-msg {
        font-weight: 600;
    }

.dev-line-status {
    border-left-color: #4ec9b0;
    background: rgba(78, 201, 176, 0.14);
}

    .dev-line-status .development-log-level {
        background: #4ec9b0;
        color: #05302b;
    }

    .dev-line-status .development-log-msg { color: #8fe6d4; }

.dev-line-info {
    border-left-color: #9cdcfe;
    background: rgba(156, 220, 254, 0.10);
}

    .dev-line-info .development-log-level {
        background: #9cdcfe;
        color: #0b2b3d;
    }

.dev-line-warning {
    border-left-color: #dcdcaa;
    background: rgba(220, 220, 170, 0.14);
}

    .dev-line-warning .development-log-level {
        background: #dcdcaa;
        color: #3b3a1a;
    }

    .dev-line-warning .development-log-msg { color: #ececc0; }

.dev-line-error {
    border-left-color: #f48771;
    background: rgba(244, 135, 113, 0.16);
}

    .dev-line-error .development-log-level {
        background: #f48771;
        color: #3d1109;
    }

    .dev-line-error .development-log-msg { color: #ffb3a2; }

.dev-line-tooluse {
    border-left-color: #c586c0;
    background: rgba(197, 134, 192, 0.10);
}

    .dev-line-tooluse .development-log-level {
        background: #c586c0;
        color: #33113a;
    }

/* ---- Dictation row under the Opdracht box in the new-job sheet ---- */
.dev-cmd-mic {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex-wrap: wrap;
    margin-top: 0.45rem;
}

.dev-cmd-mic-btn {
    flex: 0 0 auto;
    min-height: 48px; /* thumb target */
    padding: 0.5rem 1rem;
    border-radius: 999px;
    border: 1px solid #0d6efd;
    background: #fff;
    color: #0d6efd;
    font-weight: 600;
    font-size: 0.9rem;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    cursor: pointer;
}

.dev-cmd-mic-btn:hover {
    background: #eef4ff;
}

.dev-cmd-mic-btn:disabled {
    opacity: 0.6;
    cursor: default;
}

/* Recording is the one state that must be unmissable — you're talking to it. */
.dev-cmd-mic-btn.recording {
    background: #dc3545;
    border-color: #dc3545;
    color: #fff;
    animation: dev-pulse 1.2s ease-in-out infinite;
}

.dev-cmd-mic-hint {
    flex: 1 1 12rem;
    font-size: 0.78rem;
    color: var(--ink-muted);
    line-height: 1.3;
}

/* ---- Mic dictation on the create form ---- */
.development-cmd-row {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
}

.development-cmd-row textarea {
    flex: 1 1 auto;
}

.dev-mic-btn {
    flex: 0 0 auto;
    width: 48px;
    height: 48px;
    border-radius: 50%;
    border: 1px solid #0d6efd;
    background: #fff;
    color: #0d6efd;
    font-size: 1.25rem;
    line-height: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
}

.dev-mic-btn:disabled {
    opacity: 0.6;
    cursor: default;
}

.dev-mic-btn.recording {
    background: #dc3545;
    border-color: #dc3545;
    color: #fff;
    animation: dev-pulse 1.2s ease-in-out infinite;
}

.dev-mic-spinner {
    width: 18px;
    height: 18px;
    border: 2px solid rgba(13, 110, 253, 0.3);
    border-top-color: #0d6efd;
    border-radius: 50%;
    animation: dev-spin 0.8s linear infinite;
}

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

.dev-mic-status {
    margin-top: 0.35rem;
    font-size: 0.85rem;
    color: var(--ink-muted);
}

.dev-mic-status.recording {
    color: #dc3545;
    font-weight: 600;
}

/* ---- Compact detail header + Ask status ---- */
.development-compact-header {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    flex-wrap: wrap;
    margin-bottom: 0.5rem;
}

.development-compact-header h1 {
    margin: 0;
    font-size: 1.5rem;
}

.development-init {
    margin: 0.5rem 0 0.75rem;
}

.development-askstatus {
    background: #eef5ff;
    border: 1px solid #cfe2ff;
    border-radius: 6px;
    padding: 0.5rem 0.75rem;
    margin-bottom: 0.75rem;
    font-size: 0.9rem;
}

.development-log-init {
    margin-bottom: 0.5rem;
}

/* ---- Custom tab strip (mobile-friendly) ---- */
.dev-tabs {
    display: flex;
    gap: 0.25rem;
    border-bottom: 2px solid #dee2e6;
    margin-bottom: 1rem;
}

.dev-tab {
    flex: 1 1 0;
    min-height: 48px;
    border: none;
    background: transparent;
    color: #495057;
    font-weight: 600;
    padding: 0.5rem 0.75rem;
    cursor: pointer;
    border-bottom: 3px solid transparent;
    margin-bottom: -2px;
}

.dev-tab.active {
    color: #0b5ed7;
    border-bottom-color: #0b5ed7;
}

.dev-tab .development-live {
    margin-left: 0.25rem;
}

/* ---- Mobile job cards (hidden on desktop) ---- */
.development-cards-mobile {
    display: none;
}

.development-card {
    display: block;
    text-decoration: none;
    color: inherit;
    border: 1px solid rgba(0, 0, 0, 0.12);
    border-radius: 8px;
    padding: 0.75rem;
    margin-bottom: 0.6rem;
    background: #fff;
}

.development-card-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
}

.development-card-app {
    font-weight: 700;
}

/* Job (task) number — shown in the grid and on the mobile card so a job can
   be referenced by its number (the #N used in safety commits and follow-ups). */
.dev-job-nr {
    font-variant-numeric: tabular-nums;
    font-weight: 700;
    color: #868e96;
    /* Also used as an <a> in the grid (a second way into the job detail);
       it should still read as a job number, not as a blue link. */
    text-decoration: none;
}

a.dev-job-nr:hover {
    color: #0d6efd;
}

.development-card-cmd {
    margin-top: 0.35rem;
    color: #333;
    word-break: break-word;
}

.development-card-status {
    margin-top: 0.35rem;
    font-style: italic;
    color: var(--ink-muted);
    font-size: 0.9rem;
}

.development-card-meta {
    display: flex;
    justify-content: space-between;
    margin-top: 0.5rem;
    color: var(--ink-muted);
    font-size: 0.85rem;
}

/* On narrow screens swap the wide grid for the stacked cards. */
@media (max-width: 700px) {
    .development-grid-desktop {
        display: none;
    }
    .development-cards-mobile {
        display: block;
    }
    .development-page {
        padding: 0.75rem;
    }
    .development-create {
        padding: 0.75rem;
    }
    .development-log {
        height: 320px;
        font-size: 0.8rem;
    }
}

/* ---- Job filter bar (search + status pills) ----
   Sits above BOTH the desktop grid and the mobile cards, because it filters
   the data behind them rather than the grid's own filter row. Wraps to two
   rows on a phone: search on top, pills underneath, horizontally scrollable
   so six pills never squash into unreadable stubs. */
.dev-filter-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 0.75rem;
}

.dev-filter-search {
    flex: 1 1 260px;
    max-width: 420px;
    min-height: 44px;
}

.dev-filter-pills {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem;
}

.dev-filter-pills .dev-pill-sm {
    min-height: 38px;
    padding: 0.25rem 0.75rem;
    font-size: 0.85rem;
    border: 1px solid #0d6efd;
    border-radius: 999px;
}

.dev-filter-count {
    display: inline-block;
    margin-left: 0.3rem;
    padding: 0 0.35rem;
    border-radius: 999px;
    background: rgba(13, 110, 253, 0.12);
    font-size: 0.75rem;
    font-weight: 700;
}

.dev-pill-sm.active .dev-filter-count {
    background: rgba(255, 255, 255, 0.25);
}

/* Age filter switch, next to the flag toggle in the filter bar. Same height as
   the pills so the bar stays one straight row. */
.dev-filter-all {
    display: inline-flex;
    align-items: center;
    min-height: 38px;
    margin: 0;
    white-space: nowrap;
}

.dev-filter-all .form-check-input {
    min-width: 2.5em;
    min-height: 1.4em;
    margin-top: 0;
}

.dev-filter-all .form-check-label {
    margin-left: 0.4rem;
    font-size: 0.85rem;
}

.dev-filter-empty {
    margin: 0.5rem 0 0.75rem;
}

@media (max-width: 700px) {
    .dev-filter-search {
        flex: 1 1 100%;
        max-width: none;
    }
    .dev-filter-pills {
        flex-wrap: nowrap;
        overflow-x: auto;
        width: 100%;
        padding-bottom: 0.2rem;
    }
    .dev-filter-pills .dev-pill-sm {
        flex: 0 0 auto;
    }
    .dev-filter-all {
        flex: 0 0 auto;
    }
}

/* ---- Model pills + commit checkbox ---- */
.dev-model-pills {
    display: inline-flex;
    border: 1px solid #0d6efd;
    border-radius: 999px;
    overflow: hidden;
}

.dev-pill {
    min-height: 40px;
    padding: 0.35rem 1.1rem;
    border: none;
    background: #fff;
    color: #0d6efd;
    font-weight: 600;
    cursor: pointer;
}

.dev-pill.active {
    background: #0d6efd;
    color: #fff;
}

.dev-check {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-weight: 500;
    min-height: 44px;
    cursor: pointer;
}

.dev-check input {
    width: 20px;
    height: 20px;
}

/* Small grey hint under a per-job toggle. Indented to align with the
   checkbox label text and given bottom spacing so stacked toggles read
   as separate rows. */
.dev-check-hint {
    margin: 0.15rem 0 0.75rem 1.75rem;
    font-size: 0.8125rem;
    color: var(--bs-secondary-color, #6c757d);
}

/* ---- Attachments ---- */
.development-attach-bar {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
}

.dev-file-hidden {
    display: none;
}

.dev-upload-btn {
    margin: 0;
    cursor: pointer;
}

.dev-attach-hint {
    color: var(--ink-muted);
    font-size: 0.85rem;
}

.development-attach-list {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-top: 0.6rem;
}

.dev-attach-item {
    position: relative;
    display: inline-flex;
    align-items: center;
}

.dev-attach-thumb {
    width: 72px;
    height: 72px;
    object-fit: cover;
    border-radius: 6px;
    border: 1px solid rgba(0, 0, 0, 0.15);
}

.dev-attach-chip {
    display: inline-block;
    max-width: 180px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    padding: 0.4rem 0.6rem;
    background: #f1f3f5;
    border: 1px solid rgba(0, 0, 0, 0.12);
    border-radius: 6px;
    font-size: 0.85rem;
}

.dev-attach-remove {
    position: absolute;
    top: -8px;
    right: -8px;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    border: none;
    background: #dc3545;
    color: #fff;
    font-size: 0.9rem;
    line-height: 1;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
}

/* ---- Dense meta grid (job detail) ---- */
.development-meta-grid {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 0.15rem 0.75rem;
    margin: 0.5rem 0 1rem;
    font-size: 0.9rem;
}

.development-meta-grid dt {
    color: var(--ink-muted);
    font-weight: 600;
}

.development-meta-grid dd {
    margin: 0;
    word-break: break-word;
}

.development-meta-msg {
    margin-left: 0.4rem;
    font-style: italic;
    color: var(--ink-muted);
}

.development-cmd-label {
    font-weight: 600;
    margin-bottom: 0.25rem;
}

/* Keep both tab panels mounted; just hide the inactive one so the live
   log keeps streaming/scrolling while Summary is shown. */
.dev-hidden {
    display: none;
}
/* ─── Development: mobile-first slide-in sheet ──────────────────────────────
   The create/edit form lives in a bottom sheet instead of permanently on the
   page: on a phone the jobs list is what you came for, and the form only
   appears when you ask for it. The sheet is capped at 92vh with its own
   scroll body and a sticky footer, so the confirm button is always reachable
   with a thumb no matter how long the form gets. */
.dev-sheet-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(17, 24, 39, 0.5);
    z-index: 80;
}

.dev-sheet {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 81;
    display: flex;
    flex-direction: column;
    max-height: 92vh;
    background: #fff;
    border-radius: 1rem 1rem 0 0;
    box-shadow: 0 -8px 30px rgba(0, 0, 0, 0.25);
    animation: dev-sheet-up 180ms ease-out;
}

@keyframes dev-sheet-up {
    from { transform: translateY(100%); }
    to { transform: translateY(0); }
}

/* On a wide screen the bottom sheet reads as a centred dialog instead. */
@media (min-width: 720px) {
    .dev-sheet {
        left: 50%;
        right: auto;
        bottom: auto;
        top: 50%;
        transform: translate(-50%, -50%);
        width: 640px;
        max-height: 88vh;
        border-radius: 0.75rem;
        animation: none;
    }
}

.dev-sheet-grip {
    width: 40px;
    height: 4px;
    border-radius: 999px;
    background: #d1d5db;
    margin: 0.5rem auto 0;
}

.dev-sheet-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.5rem 1rem;
    border-bottom: 1px solid #e5e7eb;
}

.dev-sheet-head h2 {
    font-size: 1.1rem;
    margin: 0;
}

.dev-sheet-close {
    border: none;
    background: transparent;
    font-size: 1.6rem;
    line-height: 1;
    cursor: pointer;
    color: var(--ink-muted);
    min-width: 44px;
    min-height: 44px;
}

.dev-sheet-body {
    overflow-y: auto;
    padding: 0.75rem 1rem;
    flex: 1 1 auto;
}

.dev-sheet-foot {
    display: flex;
    gap: 0.5rem;
    justify-content: flex-end;
    padding: 0.75rem 1rem;
    border-top: 1px solid #e5e7eb;
    background: #fff;
    border-radius: 0 0 1rem 1rem;
}

/* ─── Document viewer ───────────────────────────────────────────────────────
   The same sheet, but a document needs the width of a page instead of the
   width of a form: a Markdown table or a PDF is unreadable in 640px. It keeps
   the sheet's own close button, footer and backdrop, which is exactly what the
   old "open in a new tab" lacked in the installed PWA. */
@media (min-width: 720px) {
    .dev-viewer {
        width: min(1100px, 94vw);
    }
}

.dev-viewer-title {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.dev-viewer-body {
    /* A short document should not collapse the dialog to a strip, and a long
       one scrolls inside the body (the .dev-sheet cap already applies). */
    min-height: 40vh;
}

/* Source, log and csv are shown verbatim: wrapped, never a horizontal scroller
   the reader has to hunt in on a phone. */
.dev-viewer-text {
    margin: 0;
    white-space: pre-wrap;
    word-break: break-word;
    font-size: 0.85rem;
}

.dev-viewer-image {
    max-width: 100%;
    height: auto;
}

/* The browser's own PDF viewer, inside the dialog. */
.dev-viewer-frame {
    width: 100%;
    height: 70vh;
    border: 1px solid #e5e7eb;
    border-radius: 0.5rem;
}

/* The AI confirmation banner. Green when the model filled the form in;
   amber when AI was off and you're looking at your raw dictation. */
.dev-ai-banner {
    margin: 0.75rem 1rem 0;
    padding: 0.6rem 0.75rem;
    border-radius: 0.5rem;
    background: #dcfce7;
    border: 1px solid #bbf7d0;
    color: #166534;
    font-size: 0.85rem;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}

.dev-ai-banner.raw {
    background: #fef3c7;
    border-color: #fde68a;
    color: #92400e;
}

/* Follow-up source banner in the create sheet — a blue "info" strip that keeps
   the originating job in view while the new command is written. */
.dev-followup-banner {
    margin: 0.75rem 1rem 0;
    padding: 0.6rem 0.75rem;
    border-radius: 0.5rem;
    background: #dbeafe;
    border: 1px solid #bfdbfe;
    color: #1e40af;
    font-size: 0.85rem;
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
}

.dev-followup-cmd {
    font-style: italic;
    opacity: 0.9;
}

.dev-followup-link {
    align-self: flex-start;
    font-weight: 600;
    color: #1d4ed8;
}

.dev-transcript summary {
    cursor: pointer;
    font-weight: 600;
}

.dev-transcript p {
    margin: 0.35rem 0 0;
    font-style: italic;
}

/* Header action buttons — the mic is the primary way in on a phone. */
.development-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
}

.dev-head-actions {
    display: flex;
    gap: 0.5rem;
    align-items: center;
}

/* On a phone the two FABs leave the header and dock bottom-right — where a
   floating action button belongs and where the thumb already is. The plus sits
   lowest (the primary action), the mic just above it. */
@media (max-width: 700px) {
    .dev-head-actions {
        position: fixed;
        right: 1rem;
        bottom: calc(1rem + env(safe-area-inset-bottom, 0px));
        flex-direction: column-reverse;
        gap: 0.75rem;
        z-index: 70;
    }

    .dev-head-actions .dev-fab {
        min-width: 56px;
        min-height: 56px;
        box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);
    }

    /* Room to scroll the last card clear of the FABs. */
    .development-cards-mobile {
        padding-bottom: 7rem;
    }
}

.dev-fab {
    min-width: 48px;
    min-height: 48px;
    border-radius: 50%;
    border: none;
    font-size: 1.3rem;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.dev-fab-mic {
    background: #ede9fe;
    color: #5b21b6;
    border: 1px solid #ddd6fe;
}

.dev-fab-mic.recording {
    background: #fee2e2;
    color: #991b1b;
    border-color: #fca5a5;
}

.dev-fab-new {
    background: #2563eb;
    color: #fff;
}

.dev-fab:disabled {
    opacity: 0.6;
    cursor: not-allowed;
}

/* Known-application chips — one tap instead of hunting through the dropdown.
   Deliberately smaller than the model/priority pills: there can be a dozen of
   them and they are a shortcut, not the control itself (the dropdown right
   below is), so they trade the 48px target for fitting several per row on a
   phone. */
.dev-app-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem;
    margin-bottom: 0.4rem;
}

.dev-app-chips .dev-pill-sm {
    min-height: 34px;
    padding: 0.2rem 0.6rem;
    font-size: 0.8rem;
    border: 1px solid #0d6efd;
    border-radius: 999px;
}

/* Quick commands, collapsed by default. */
.dev-collapse-toggle {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    background: #f9fafb;
    border: 1px solid #e5e7eb;
    border-radius: 0.5rem;
    padding: 0.6rem 0.75rem;
    font-weight: 600;
    font-size: 0.9rem;
    cursor: pointer;
    min-height: 48px;
}

.dev-collapse-hint {
    font-weight: 400;
    font-size: 0.8rem;
    color: var(--ink-muted);
}

.dev-quick-list {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    margin-top: 0.4rem;
}

.dev-quick-btn {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.15rem;
    text-align: left;
    background: #fff;
    border: 1px solid #e5e7eb;
    border-radius: 0.5rem;
    padding: 0.6rem 0.75rem;
    cursor: pointer;
    min-height: 48px;
}

.dev-quick-btn:hover {
    background: #f3f4f6;
}

.dev-quick-label {
    font-weight: 600;
    font-size: 0.9rem;
}

.dev-quick-cmd {
    font-size: 0.78rem;
    color: var(--ink-muted);
}

/* The small "vul deze waarden eerst in" panel a quick command with {TOKEN}
   placeholders opens (e.g. Figma -> Website). Deliberately inline in the sheet
   rather than a dialog: on a phone a second overlay is only in the way. */
.dev-quick-fields {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin-top: 0.4rem;
    background: #f9fafb;
    border: 1px solid #e5e7eb;
    border-radius: 0.5rem;
    padding: 0.7rem 0.75rem;
}

.dev-quick-fields__title {
    font-weight: 600;
    font-size: 0.9rem;
}

.dev-quick-fields__field {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    font-size: 0.8rem;
    color: var(--ink-muted);
}

.dev-quick-fields__actions {
    display: flex;
    gap: 0.5rem;
}

/* Empty list: the table is the only source, so say where they are maintained
   instead of quietly showing nothing. */
.dev-quick-empty {
    margin-top: 0.4rem;
    font-size: 0.82rem;
    color: var(--ink-muted);
}

/* Destructive zone at the foot of the job-detail page — deliberately quiet
   (outline button, no btn-lg) so rolling the repo back never competes with the
   everyday actions at the top. */
.dev-danger-zone {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    margin-top: 2rem;
    padding-top: 0.75rem;
    border-top: 1px solid #e5e7eb;
}

.dev-danger-hint {
    color: var(--ink-muted);
    font-size: 0.85rem;
}

/* Schakelaars boven de agent-output log ("Details tonen" + automatisch
   scrollen). Wrapt op een telefoon, waar twee labels naast elkaar niet passen. */
.dev-log-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.25rem 1.25rem;
}

/* Auto-scroll switch above the agent-output log. */
.dev-log-scroll {
    margin-bottom: 0.4rem;
    font-size: 0.85rem;
}

.dev-reviewed-note {
    color: #166534;
    font-weight: 600;
    font-size: 0.85rem;
}

html[data-theme="dark"] .dev-sheet,
html[data-theme="dark"] .dev-sheet-foot {
    background: #1f2937;
    color: #e5e7eb;
}

html[data-theme="dark"] .dev-viewer-frame {
    border-color: #374151;
}

html[data-theme="dark"] .dev-quick-btn,
html[data-theme="dark"] .dev-collapse-toggle,
html[data-theme="dark"] .dev-quick-fields {
    background: #111827;
    border-color: #374151;
    color: #e5e7eb;
}

/* Filter pills use the same white background as the model pills, which is
   opaque in dark mode too — only the count badge needs toning down so it
   doesn't glow against it. */
html[data-theme="dark"] .dev-filter-count {
    background: rgba(13, 110, 253, 0.18);
}

/* Dictation playback on the job-detail page. Full width so the scrubber is
   usable with a thumb. */
.dev-voice-player {
    width: 100%;
    max-width: 480px;
    margin-top: 0.25rem;
}

/* Quick note button in the todo card's inline action row (between Next week
   and Done). Neutral/violet so it doesn't compete with the green Done. */
.action-note {
    color: #5b21b6;
    border-color: #ddd6fe;
}

.action-note:hover {
    background: #ede9fe;
}

/* Quick-note modal — reuses the .vc-* shell of the voice-capture modal. */
.qn-subject {
    margin: 0 0 0.5rem;
    font-size: 0.85rem;
    color: var(--ink-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.qn-text {
    width: 100%;
    font-size: 1rem;
}

.qn-actions {
    display: flex;
    gap: 0.5rem;
    align-items: center;
    margin-top: 0.5rem;
}

html[data-theme="dark"] .action-note {
    color: #c4b5fd;
    border-color: #4c1d95;
}

/* Quick-note modal. Its own shell rather than reusing VoiceCaptureModal's
   .vc-* classes: those live in that component's SCOPED stylesheet, so they
   never reach this component and the dialog rendered inline in the page. */
.qn-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(17, 24, 39, 0.5);
    z-index: 90;
}

.qn-modal {
    position: fixed;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    z-index: 91;
    width: min(560px, calc(100vw - 2rem));
    max-height: 88vh;
    overflow-y: auto;
    background: #fff;
    border-radius: 0.75rem;
    padding: 1rem;
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.3);
}

/* On a phone it becomes a bottom sheet — thumb-reachable, no reaching up. */
/* Stays vertically centred on a phone too. It used to drop to a bottom sheet,
   which parked the Save button right on top of the card's action row — after a
   transcription you were reaching for "Notitie opslaan" in the same spot as
   Done/Next week. Centred keeps it clear of everything underneath. */
@media (max-width: 640px) {
    .qn-modal {
        width: calc(100vw - 1.5rem);
        max-height: 80vh;
    }
}

.qn-title {
    font-size: 1.2rem;
    margin: 0 0 0.25rem;
}

.qn-body {
    margin-bottom: 0.75rem;
}

/* Sticky footer: a long transcription grows the body past the modal's
   max-height, and a footer that scrolls with it puts "Notitie opslaan" out of
   reach exactly when you need it. */
.qn-buttons {
    display: flex;
    gap: 0.5rem;
    justify-content: flex-end;
    align-items: center;
    position: sticky;
    bottom: 0;
    background: #fff;
    padding-top: 0.6rem;
    margin-top: 0.25rem;
    border-top: 1px solid #e5e7eb;
}

html[data-theme="dark"] .qn-buttons {
    background: #1f2937;
    border-top-color: #374151;
}

.qn-error {
    color: #b91c1c;
    font-size: 0.85rem;
    margin: 0.5rem 0 0;
}

html[data-theme="dark"] .qn-modal {
    background: #1f2937;
    color: #e5e7eb;
}

/* ── Job colour flags (shared mechanism with VSIDirigent) ──────────────── */
.dev-flag {
    position: relative;
    display: inline-flex;
    align-items: center;
}
.dev-flag__btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: none;
    padding: 0.15rem;
    cursor: pointer;
    border-radius: 4px;
    line-height: 0;
}
.dev-flag__btn:hover { background: rgba(0, 0, 0, 0.06); }
.dev-flag__svg { display: block; }

/* Popover of swatches, anchored under the flag button. It opens to the RIGHT by
   default (the grid puts the flag in the second column, where there is room);
   a caller that pins the picker to a right edge flips it — see
   .development-card-flag below. width:max-content keeps the swatches on one
   row: the popover is absolutely positioned inside a ~21px wide .dev-flag, so
   an auto width is a shrink-to-fit against that, and the moment it is allowed
   to wrap it collapses into a one-swatch column. */
.dev-flag__pop {
    position: absolute;
    top: 100%;
    left: 0;
    z-index: 50;
    margin-top: 0.25rem;
    display: inline-flex;
    width: max-content;
    gap: 0.2rem;
    padding: 0.3rem;
    background: #fff;
    border: 1px solid #d1d5db;
    border-radius: 8px;
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.18);
}
.dev-flag__swatch {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.6rem;
    height: 1.6rem;
    background: none;
    border: 1px solid transparent;
    border-radius: 4px;
    cursor: pointer;
    line-height: 0;
}
.dev-flag__swatch:hover { background: rgba(0, 0, 0, 0.06); }
.dev-flag__swatch--active { border-color: #0d6efd; background: rgba(0, 0, 0, 0.05); }

html[data-theme="dark"] .dev-flag__pop {
    background: #1f2937;
    border-color: #374151;
}
html[data-theme="dark"] .dev-flag__btn:hover,
html[data-theme="dark"] .dev-flag__swatch:hover { background: rgba(255, 255, 255, 0.08); }

/* Phone: the picker in the job-detail toolbar docks to the BOTTOM of the
   VIEWPORT instead of hanging off the flag.
   Reported from an iPhone for the second time ("de vlaggetjes staan buiten de
   viewport"). On the job detail page the flag pill is one control in a
   WRAPPING toolbar, so where it lands depends on the viewport width and on
   which of the optional pills are rendered (Goto parent job, Back to previous
   job, the status message). Measured at a 390px viewport it sat at x=324 and
   the popover ran to x=505: 115px past the right edge, stretching the document
   to 505px so the whole page scrolled sideways.
   Flipping it inward the way the mobile card does (.development-card-flag
   below, where the picker IS at a known corner) only moves the problem: from
   an anchor near the left edge the same 181px popover runs off the other side.
   CSS cannot pick the side an absolutely positioned box grows towards, so on a
   phone the popover stops being anchored to the flag at all and docks to the
   viewport: always fully visible wherever the flag ended up, and under the
   thumb. It clears the home indicator (safe-area-inset) and does not reach the
   FAB column on the right, since the swatches are centred and 181px wide.
   The swatches grow to the 48px touch target the rest of the phone UI uses,
   and may wrap onto a second row on a 320px screen. Wrapping is safe HERE (it
   is explicitly forbidden on the base rule above) because a viewport-docked
   box is no longer shrink-to-fit against a 21px anchor. */
@media (max-width: 700px) {
    .dev-pill-btn--flag .dev-flag__pop {
        position: fixed;
        top: auto;
        left: 50%;
        right: auto;
        bottom: calc(1rem + env(safe-area-inset-bottom, 0px));
        transform: translateX(-50%);
        /* border-box, or the 0.5rem padding and the 1px border are ADDED to
           the clamp and the box is wider than the screen again (measured at
           320px with a 1rem clamp: 304 + 18 = 322 against a 320px screen). */
        box-sizing: border-box;
        max-width: calc(100vw - 1.5rem);
        flex-wrap: wrap;
        justify-content: center;
        margin-top: 0;
        padding: 0.5rem;
        gap: 0.35rem;
        box-shadow: 0 8px 30px rgba(0, 0, 0, 0.28);
    }

    .dev-pill-btn--flag .dev-flag__swatch {
        width: 48px;
        height: 48px;
    }

    /* Only the swatches inside the popover: the trigger icon in the toolbar
       keeps its own size so the pill row stays level. */
    .dev-pill-btn--flag .dev-flag__swatch .dev-flag__svg {
        width: 24px;
        height: 24px;
    }
}

/* Below ~360px six 48px swatches do not fit on one row, and the plain wrap
   leaves a lonely sixth flag on a line of its own. Cap the row at three so it
   breaks into an even 3 x 2 block: three swatches, two gaps, the popover's own
   0.5rem padding on both sides and its 1px border. */
@media (max-width: 359px) {
    .dev-pill-btn--flag .dev-flag__pop {
        max-width: calc((3 * 48px) + (2 * 0.35rem) + 1rem + 2px);
    }
}

/* Tablet and up, same popover, other half of the problem. Here the toolbar
   still has its flexible gap (.dev-toolbar__gap collapses only below 701px),
   which pushes the flag into the right-hand cluster of the row, so the default
   left-anchoring opens it towards the right edge: measured at a 701px viewport
   the flag sat at x=655 and the popover ran to x=836, 135px off screen. Because
   that gap GUARANTEES the flag is right of centre on every width in this
   range, anchoring the popover to the flag's right edge is safe here in a way
   it is not on a phone: it grows back over the toolbar, never past x=0. */
@media (min-width: 701px) {
    .dev-pill-btn--flag .dev-flag__pop {
        left: auto;
        right: 0;
    }
}

/* ── Development grid: one line per row ───────────────────────────────────
   Kendo grid cells already clip with an ellipsis (overflow:hidden +
   text-overflow:ellipsis in the theme), but they do NOT set white-space, so
   long text still WRAPS and only then clips. On a tablet — where the columns
   are squeezed — "Opdracht" and "Laatste status" broke over four or five
   lines, blowing up the row height so a screenful showed only a handful of
   jobs. Forcing nowrap makes every row exactly one line high; the full text
   is still reachable via the cell's title tooltip and the detail page. */
.development-grid-desktop .k-table-td,
.development-grid-desktop .k-table-th {
    white-space: nowrap;
}

/* The ellipsis lives on the CELL, so an inline child would keep its own
   intrinsic width and overflow out of it — the child has to clip too. The
   flag picker is excluded: it is a positioning context for its popover and
   clipping it would cut the colour swatches off. */
.development-grid-desktop .k-table-td > span:not(.dev-flag) {
    display: inline-block;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    vertical-align: middle;
}

/* ----- Opdracht cell -----
   A plain <a> filling the whole cell, so tapping the text opens the job on a
   touch device too (no JavaScript involved). The command wraps over AT MOST
   two lines: enough to see what a job does, bounded enough that the row height
   stays predictable — which is the whole point of the nowrap rule above, kept
   deliberately for this one cell by clamping instead of clipping. */
.dev-jobdesc {
    display: block;
    width: 100%;
    color: inherit;
    text-decoration: none;
    line-height: 1.3;
}

.dev-jobdesc:hover .dev-jobdesc__text {
    color: #0d6efd;
}

.dev-jobdesc__text {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    white-space: normal;
    word-break: break-word;
}

/* Secondary line, only rendered on a narrow screen: carries the values of the
   columns hidden there (model, prioriteit, duur, laatste status). */
.dev-jobdesc__meta {
    display: block;
    margin-top: 0.15rem;
    font-size: 0.72rem;
    color: var(--ink-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Rows carry a tap highlight so a touch actually feels acknowledged, and cells
   centre their content now that the opdracht cell can be two lines tall. */
.development-grid-desktop .k-table-td {
    vertical-align: middle;
}

.development-grid-desktop .k-table-row {
    -webkit-tap-highlight-color: rgba(13, 110, 253, 0.15);
}

/* Tablet: trim the vertical padding and type size as well, so the rows are
   genuinely compact and roughly twice as many jobs fit on screen. Column
   visibility/width at this size is handled in Development.razor via
   TelerikMediaQuery — CSS cannot hide a column of a table-layout:fixed grid
   without breaking its colgroup alignment. Breakpoint matches the
   TelerikMediaQuery there (1250px: iPad landscape is 1180, portrait 820). */
@media (max-width: 1250px) {
    .development-grid-narrow .k-table-td {
        padding-block: 0.25rem;
        padding-inline: 0.4rem;
        font-size: 0.85rem;
    }

    .development-grid-narrow .k-table-th {
        padding-block: 0.35rem;
        padding-inline: 0.4rem;
        font-size: 0.8rem;
    }

    /* The Open link is the tallest thing in a row; shrink it to match. */
    .development-grid-narrow .k-table-td .btn-sm {
        padding: 0.1rem 0.4rem;
        font-size: 0.8rem;
        line-height: 1.35;
    }

    .development-grid-narrow .dev-status,
    .development-grid-narrow .dev-prio {
        font-size: 0.75rem;
        padding: 0.1rem 0.35rem;
    }
}

/* When a flag popover is open inside the Telerik grid, stop the cell/row from
   clipping it so the swatches are fully visible. Scoped with :has() to ONLY
   the cell/row that has an open popover. */
.development-grid-desktop .k-table-td:has(.dev-flag--open) { overflow: visible; }
.development-grid-desktop .k-table-row:has(.dev-flag--open) { position: relative; z-index: 5; }

/* The flag toggle pill in the filter bar. */
.dev-pill-flag { white-space: nowrap; }

/* Mobile card: the flag overlaid top-right, OUTSIDE the navigating anchor. */
.development-card-wrap { position: relative; }
/* Holds the two controls that must sit OUTSIDE the card's <a> (a <button>
   inside an anchor still fires the native navigation on tap): the pause
   toggle and the flag picker, in that order. */
.development-card-flag {
    position: absolute;
    top: 0.5rem;
    right: 0.5rem;
    z-index: 3;
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
}

/* The picker is pinned to the card's RIGHT edge here, so the default popover
   (left:0, growing rightwards) opens straight off the screen: measured at a
   390px viewport it started at x=373 and ran 164px past the edge, stretching
   the document to 554px so the whole list scrolled sideways. Anchor it to the
   flag's right edge instead so the swatches open inward, over the card. */
.development-card-flag .dev-flag__pop {
    left: auto;
    right: 0;
}

/* Because that overlay floats ON TOP of the card, the card's own top row has
   to keep its right edge clear — otherwise the status chip (and the ⚠ stale
   marker beside it) ends up underneath the flag and the pause button, which
   is exactly what happened on a phone. So reserve the overlay's width:
   the flag button is ~21px and sits 0.5rem from the wrap edge while the card
   itself already pads 0.75rem, so ~1.5rem covers it. A card that also shows
   the 44px pause toggle (plus the 0.35rem gap) needs ~4.75rem. :has() applies
   the wider reservation ONLY where a pause button is actually rendered — a job
   that is neither pausable nor unpausable renders no button at all, and would
   otherwise squeeze the status chip for nothing. */
.development-card-top {
    padding-right: 1.5rem;
}

.development-card-wrap:has(.dev-pause) .development-card-top {
    padding-right: 4.75rem;
    /* Horizontal room is only half of it. The 44px toggle starts 0.5rem below
       the WRAP's top edge while the card's content box starts 0.75rem padding
       + 1px border lower, so the button still reaches 44 + 8 - 13 = 39px into
       that content box — past a single-line top row and onto the first line of
       the opdracht text underneath. Giving the row 2.5rem (40px) of height
       hands the button a lane of its own vertically as well. */
    min-height: 2.5rem;
}

/* Without :has() the rule above never matches, the row keeps the narrow 1.5rem
   reservation, and the pause toggle lands straight back on top of the status
   chip — precisely the bug this block exists to prevent. Reserve the wide
   gutter unconditionally on such a browser; the only cost is a slightly
   earlier ellipsis on the application name of a card that shows no toggle. */
@supports not (selector(:has(*))) {
    .development-card-top {
        padding-right: 4.75rem;
        min-height: 2.5rem;
    }
}

/* ── Pause / unpause toggle (JobPauseButton) ──────────────────────────────
   In the grid it stays 32px so it does not push the row height past the
   two-line opdracht cell (the flag next to it is ~21px); on the mobile card
   it grows to a proper touch target. */
.dev-pause { display: inline-flex; align-items: center; }

.dev-pause-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    padding: 0;
    border: 1px solid rgba(0, 0, 0, 0.15);
    border-radius: 8px;
    background: #fff;
    color: #8a5a00;
    cursor: pointer;
}

.dev-pause-btn:hover:not(:disabled) {
    border-color: #d98e04;
    background: #fff8e6;
}

/* Resuming is the "go" action, so it reads blue like the other primaries. */
.dev-pause-btn--resume { color: #0d6efd; }

.dev-pause-btn--resume:hover:not(:disabled) {
    border-color: #0d6efd;
    background: #eff5ff;
}

.dev-pause-btn:disabled {
    opacity: 0.5;
    cursor: default;
}

.dev-pause__svg { display: block; }

/* Mobile card: a real touch target — this is the phone gesture the whole
   feature exists for ("not tonight after all", from the couch). */
.development-card-flag .dev-pause-btn {
    width: 44px;
    height: 44px;
}

/* The verdict line under the filter bar. Neutral by default (a confirmation),
   red only when the server refused the action. */
.dev-pause-msg {
    margin: 0.35rem 0 0.5rem;
    font-size: 0.9rem;
    color: #495057;
}

.dev-pause-msg--error { color: #dc3545; }

/* The job-detail navigation (Prev/Next, the review carousel, the detail flag)
   is styled with the shared pill toolbar — see .dev-toolbar / .dev-pill-btn /
   .dev-pill-seg under "Job detail" above. */

/* ───────────────────────── Deployment page (/deployment) ─────────────────
   Owner-only page that installs the newest published zip of a web
   application on the test web server. Mobile-first: one card per
   application, one big touch target per card. */
.deployment-page {
    padding-bottom: 2rem;
}

/* Live status of the deployment being followed. Three states: busy (upload /
   agent working), ok, error. */
.deploy-banner {
    border: 1px solid #d1d5db;
    border-left-width: 4px;
    border-radius: 8px;
    padding: 0.75rem 1rem;
    margin-bottom: 1rem;
    background: #f9fafb;
}
.deploy-banner--busy { border-left-color: #2563eb; }
.deploy-banner--ok    { border-left-color: #16a34a; background: #f0fdf4; }
.deploy-banner--error { border-left-color: #dc2626; background: #fef2f2; }

.deploy-banner-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    justify-content: space-between;
}
.deploy-banner-msg { margin-top: 0.25rem; }

/* De server antwoordt even niet. Dat is bij een update van VSITodo ZELF de
   normale gang van zaken: deze pagina wordt geserveerd door de site die op dat
   moment vervangen wordt. De melding staat NAAST de status, in een rustige
   kleur, want de deployment loopt gewoon door. */
.deploy-banner-offline {
    margin-top: 0.5rem;
    padding: 0.5rem 0.75rem;
    border-radius: 6px;
    background: #fffbeb;
    border: 1px solid #fcd34d;
    color: #92400e;
    font-size: 0.9rem;
}

/* Na een geslaagde update van VSITodo zelf draait deze browser nog de vorige
   versie. Eén knop, 48px hoog zoals elke andere raakbare knop hier. */
.deploy-banner-reload {
    margin-top: 0.5rem;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
}

.deploy-reload-button {
    min-height: 48px;
    min-width: 48px;
    border: 1px solid #16a34a;
    color: #14532d;
    font-weight: 600;
}

/* "Later" stopt de aftelling zonder iets te installeren, dus hij mag rustiger
   ogen dan de laadknop. Wel dezelfde 48px, want hij wordt op de telefoon
   aangetikt onder tijdsdruk. */
.deploy-reload-later {
    min-height: 48px;
    min-width: 48px;
    color: #4b5563;
    text-decoration: underline;
}

/* De melding staat VASTGEZET bovenaan het scherm in plaats van bovenin de
   pagina. Een deployment duurt minuten en je scrolt ondertussen langs de
   kaarten van de andere applicaties; een melding die meescrolt staat dan
   buiten beeld precies wanneer de voortgang er toe doet, en je moet helemaal
   terug naar boven om te zien hoe ver het is. Zelfde oplossing als
   .dg-deploy-toast op de Applications-pagina van VSI Dirigent.

   Gecentreerd op het VENSTER, niet op de contentkolom: de zijbalk is
   inklapbaar (zie MainLayout.razor.css), dus een vaste 250px in de berekening
   klopt de helft van de tijd niet.

   z-index 70: boven de pagina-inhoud (max 50) en de vastgezette taakbalk (60),
   onder de vastgezette telefoonheader (75) en de sheets/modals (80+). */
.deploy-toast {
    position: fixed;
    top: 0.75rem;
    left: 50%;
    transform: translateX(-50%);
    width: min(52rem, calc(100vw - 1.5rem));
    z-index: 70;
    /* Alleen de melding zelf vangt kliks; de strook eromheen laat de knoppen
       eronder gewoon met rust. */
    pointer-events: none;
}

.deploy-toast .deploy-banner {
    margin-bottom: 0;
    box-shadow: 0 8px 28px rgba(0, 0, 0, 0.22);
    pointer-events: auto;
    /* Een lang agent-log mag het scherm niet opeten: de melding scrollt zelf. */
    max-height: 60vh;
    overflow-y: auto;
}

/* Sluiten verbergt de melding zonder de poll te stoppen, dus de kaarten worden
   daarna nog steeds bijgewerkt. 48px, net als elke andere raakbare knop hier. */
.deploy-toast__close {
    margin-left: auto;
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 48px;
    min-height: 48px;
    padding: 0;
    background: transparent;
    border: none;
    color: inherit;
    font-size: 1.4rem;
    line-height: 1;
    cursor: pointer;
}

/* Onder 641px is de header zelf vastgezet op top:0 met een hoogte van 3.5rem
   (zie .sidebar in MainLayout.razor.css en .dev-toolbar--sticky hierboven), en
   die staat met z-index 75 hier bovenop. Een melding op top:0.75rem zou er dus
   onder verdwijnen: parkeer hem er net onder. */
@media (max-width: 640px) {
    .deploy-toast {
        top: 4rem;
        width: calc(100vw - 1rem);
    }
}

.deploy-status-badge {
    font-size: 0.8rem;
    font-weight: 600;
    padding: 0.1rem 0.5rem;
    border-radius: 999px;
    background: #e5e7eb;
    white-space: nowrap;
}

/* The agent's full install log — collapsed by default, it can be long. */
.deploy-agent-log { margin-top: 0.5rem; }
.deploy-agent-log pre {
    max-height: 18rem;
    overflow: auto;
    font-size: 0.8rem;
    white-space: pre-wrap;
    word-break: break-word;
    background: #111827;
    color: #e5e7eb;
    padding: 0.75rem;
    border-radius: 6px;
}

.deploy-cards {
    display: grid;
    gap: 1rem;
    grid-template-columns: 1fr;
}
@media (min-width: 992px) {
    .deploy-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.deploy-card {
    border: 1px solid #e5e7eb;
    border-radius: 10px;
    padding: 1rem;
    background: #fff;
}
.deploy-card-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.5rem;
    justify-content: space-between;
}
.deploy-card-head h2 {
    font-size: 1.15rem;
    margin: 0;
}

/* Facts grid: label left, value right, wrapping to two lines on a phone. */
.deploy-facts {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 0.15rem 0.75rem;
    margin: 0.75rem 0;
    font-size: 0.9rem;
}
.deploy-facts dt {
    font-weight: 600;
    color: #4b5563;
}
.deploy-facts dd {
    margin: 0;
    overflow-wrap: anywhere;
}
.deploy-facts dd .section-hint {
    display: block;
    font-size: 0.8rem;
}

.deploy-blocker {
    font-size: 0.9rem;
    margin-bottom: 0.5rem;
}

/* One block per server inside a card: the test server and the production
   server are different machines with their own installed version, so each
   gets its own facts and its own buttons. Production carries a red edge, the
   same signal VSI Dirigent uses: it changes the live site. */
.deploy-target {
    border: 1px solid #e5e7eb;
    border-left-width: 4px;
    border-radius: 8px;
    padding: 0.75rem;
    margin-top: 0.75rem;
}
.deploy-target--test       { border-left-color: #2563eb; }
.deploy-target--production { border-left-color: #dc2626; background: #fff7f7; }

.deploy-target-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
}
.deploy-target-head a {
    overflow-wrap: anywhere;
}

.deploy-env-badge {
    font-size: 0.8rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    padding: 0.1rem 0.5rem;
    border-radius: 999px;
    background: #e5e7eb;
    color: #111827;
    white-space: nowrap;
}
.deploy-target--production .deploy-env-badge {
    background: #dc2626;
    color: #fff;
}

/* Install + recycle side by side on a wide screen, stacked on a phone. */
.deploy-actions {
    display: grid;
    gap: 0.5rem;
    grid-template-columns: 1fr;
}
@media (min-width: 576px) {
    .deploy-actions { grid-template-columns: 1fr 1fr; }
}

/* 48px minimum touch target, full width of its grid cell. */
.deploy-button {
    width: 100%;
    min-height: 48px;
}

/* ── De versievergelijking per server ────────────────────────────────────
   Geïnstalleerd en gebouwd NAAST elkaar, met de uitkomst als kleur: groen =
   deze server draait de laatst gebouwde versie, oranje = er staat een nieuwere
   versie klaar. Onder elkaar zetten zou de lezer zelf laten vergelijken, en dat
   is precies de vraag die deze pagina moet beantwoorden. Op een smal scherm
   valt de kolommenrij terug op één kolom en wijst de pijl naar beneden. */
.deploy-versions {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    align-items: start;
    gap: 0.4rem 0.6rem;
    margin-top: 0.6rem;
    padding: 0.55rem 0.7rem;
    border: 1px solid #e5e7eb;
    border-left-width: 4px;
    border-radius: 8px;
    background: #f9fafb;
}

.deploy-version {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.deploy-version__caption {
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--ink-muted);
}

.deploy-version__value {
    font-size: 0.9rem;
    font-weight: 600;
    overflow-wrap: anywhere;
}

/* Geen versie bekend is geen versie: mager en grijs, nooit als getal. */
.deploy-version__value--none {
    font-weight: 400;
    font-style: italic;
    color: var(--ink-muted);
}

.deploy-version__meta {
    font-size: 0.78rem;
    color: var(--ink-muted);
}

.deploy-versions__arrow {
    align-self: center;
    font-size: 1.1rem;
    color: var(--ink-muted);
}

/* Het oordeel in woorden, op zijn eigen regel onder de twee versies: de kleur
   mag nooit de enige drager van de boodschap zijn (kleurenblindheid, print). */
.deploy-version-badge {
    grid-column: 1 / -1;
    justify-self: start;
    font-size: 0.75rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    padding: 0.1rem 0.5rem;
    border-radius: 999px;
    background: #e5e7eb;
    color: #111827;
    white-space: nowrap;
}

.deploy-versions--current  { border-color: #16a34a; border-left-color: #16a34a; background: #f0fdf4; }
.deploy-versions--current  .deploy-version-badge { background: #15803d; color: #fff; }

.deploy-versions--outdated { border-color: #d97706; border-left-color: #d97706; background: #fffbeb; }
.deploy-versions--outdated .deploy-version-badge { background: #b45309; color: #fff; }

/* Er is geen derde kleur: draait deze server een zip die niet ouder is dan de
   laatste build, dan staat er niets nieuwers klaar en is groen het antwoord. */
.deploy-versions--unknown  { border-left-color: #9ca3af; }

@media (max-width: 575.98px) {
    .deploy-versions { grid-template-columns: minmax(0, 1fr); }
    .deploy-versions__arrow { justify-self: start; transform: rotate(90deg); }
}

/* ═══════════════════════════════════════════════════════════════════════
   Dark theme, part 2: readability sweep (2026-08-18)
   ─────────────────────────────────────────────────────────────────────────
   Measured with a headless contrast audit over every screen in both themes.
   Everything below scored under the 4.5:1 norm for body text (3:1 for large
   text) in the dark theme, almost always for one of three reasons:

     1. a panel painted a LIGHT surface with a literal hex while its text
        inherited the light ink from the dark tokens (the agent result box
        was the worst at 1.16:1, near-white letters on #f6f8fa);
     2. an accent colour picked for a white page (Bootstrap's #0d6efd,
        Telerik's #006bb7) has nowhere near enough contrast on #0f172a;
     3. Bootstrap 5.3's dark mode does not restyle its OUTLINE buttons, so
        .btn-outline-secondary kept its #6c757d ink on a dark page.

   The rule is the same one the tokens follow: dark surfaces carry light ink.
   ═══════════════════════════════════════════════════════════════════════ */

/* ─── Links and outline buttons ─────────────────────────────────────────
   Telerik's theme sets a { color: #006bb7 }, which scores 3.22:1 on the
   dark page. These are the app-wide replacements; a link on a coloured
   panel gets its own value further down. */
html[data-theme="dark"] a:not(.btn) {
    color: #6ea8fe;
}

html[data-theme="dark"] .btn-link,
html[data-theme="dark"] .btn-outline-primary {
    --bs-btn-color: #6ea8fe;
    --bs-btn-border-color: #4d8bfd;
    --bs-btn-hover-bg: #1c3d6e;
    --bs-btn-hover-border-color: #6ea8fe;
    --bs-btn-hover-color: #ffffff;
    color: #6ea8fe;
}

html[data-theme="dark"] .btn-outline-secondary {
    --bs-btn-color: #adb5bd;
    --bs-btn-border-color: #6c757d;
    --bs-btn-hover-bg: #343a40;
    --bs-btn-hover-border-color: #adb5bd;
    --bs-btn-hover-color: #ffffff;
    color: #adb5bd;
}

html[data-theme="dark"] .btn-outline-danger {
    --bs-btn-color: #ea868f;
    --bs-btn-border-color: #b02a37;
    --bs-btn-hover-bg: #58151c;
    --bs-btn-hover-border-color: #ea868f;
    --bs-btn-hover-color: #ffffff;
    color: #ea868f;
}

/* ─── Agent result / command box and rendered Markdown ──────────────────
   The single biggest failure in the audit: .development-summary paints
   #f6f8fa and the text inside it is whatever the theme's body ink is, so
   in the dark theme it was #e5e7eb on near-white. Both boxes get the
   surface treatment instead, and the Markdown children that carry their
   own colour follow. */
html[data-theme="dark"] .development-command,
html[data-theme="dark"] .development-summary {
    background: #111827;
    border-color: var(--border);
    color: var(--ink);
}

html[data-theme="dark"] .markdown-body code {
    background: rgba(255, 255, 255, 0.10);
    color: #f0a3c8;
}

html[data-theme="dark"] .markdown-body blockquote.markdown-quote {
    border-left-color: #4b5563;
    color: var(--ink-subtle);
    background: rgba(255, 255, 255, 0.04);
}

html[data-theme="dark"] .markdown-body table.markdown-table th,
html[data-theme="dark"] .markdown-body table.markdown-table td {
    border-color: rgba(255, 255, 255, 0.18);
}

html[data-theme="dark"] .markdown-body table.markdown-table th {
    background: rgba(255, 255, 255, 0.06);
}

html[data-theme="dark"] .markdown-body hr {
    border-top-color: rgba(255, 255, 255, 0.16);
}

html[data-theme="dark"] .markdown-body a {
    color: #79b8ff;
}

/* ─── Development page chrome ──────────────────────────────────────────*/
html[data-theme="dark"] .dev-tabs {
    border-bottom-color: var(--border);
}

html[data-theme="dark"] .dev-tab {
    color: #adb5bd;
}

html[data-theme="dark"] .dev-tab.active {
    color: #6ea8fe;
    border-bottom-color: #6ea8fe;
}

/* The accent pill is a filled dark-blue button; its own accent ink was
   picked against a white page and scored 2.82:1 on that fill. */
html[data-theme="dark"] .dev-pill-btn--accent {
    color: #dbeafe;
}

html[data-theme="dark"] .dev-pill-btn--accent .dev-pill-btn__glyph,
html[data-theme="dark"] .dev-pill-btn--accent .dev-pill-btn__count {
    color: #dbeafe;
}

/* The glyph is dimmed to 0.75, which on a dark surface costs more contrast
   than it does on a white one. */
html[data-theme="dark"] .dev-pill-btn__glyph {
    opacity: 1;
    color: var(--ink-subtle);
}

html[data-theme="dark"] .dev-filter-count {
    background: rgba(110, 168, 254, 0.22);
    color: #dbeafe;
}

html[data-theme="dark"] .dev-pill-sm.active .dev-filter-count {
    background: rgba(0, 0, 0, 0.35);
    color: #ffffff;
}

html[data-theme="dark"] .dev-pill-sm {
    color: #6ea8fe;
}

html[data-theme="dark"] .dev-status-stale {
    color: #ff8f9a;
}

html[data-theme="dark"] .dev-ratelimit--warning {
    color: #f0c060;
}

html[data-theme="dark"] .dev-ratelimit--blocked {
    color: #ff8f9a;
}

/* ─── Dirigent health banner ───────────────────────────────────────────
   Bootstrap's alert palette: light tints with near-black ink. Kept as the
   same three signals, repainted so the banner is a dark panel with light
   ink like everything else. */
html[data-theme="dark"] .dirigent-health,
html[data-theme="dark"] .dirigent-health--stale {
    background: #4a3a10;
    border-color: #7a6218;
    color: #ffe08a;
}

html[data-theme="dark"] .dirigent-health--offline {
    background: #4a1216;
    border-color: #7d2029;
    color: #ffb3ba;
}

html[data-theme="dark"] .dirigent-health--stopped,
html[data-theme="dark"] .dirigent-health--paused,
html[data-theme="dark"] .dirigent-health--unknown {
    background: #10294f;
    border-color: #1c4a86;
    color: #b6d4fe;
}

html[data-theme="dark"] .dirigent-health a {
    color: #9ec5fe;
}

html[data-theme="dark"] .dirigent-health__btn--danger {
    background: #b02a37;
    border-color: #d0424f;
    color: #ffffff;
}

/* ─── Stale-job chip on the job row ───────────────────────────────────*/
html[data-theme="dark"] .dev-status-stale--inline {
    background: #4a1216;
    border-color: #7d2029;
    color: #ffb3ba;
}

/* ─── Light surfaces on the Development page ────────────────────────────
   The model pills and the status filter pills paint a white button with a
   blue border, so in the dark theme they stayed white islands and their
   ink (blue, or the muted token) sat on white at 2.4:1. */
html[data-theme="dark"] .dev-pill {
    background: var(--surface);
    color: var(--ink);
}

html[data-theme="dark"] .dev-pill:hover:not(:disabled):not(.active) {
    background: var(--border);
}

html[data-theme="dark"] .dev-model-pills,
html[data-theme="dark"] .dev-filter-pills .dev-pill-sm {
    border-color: #4d8bfd;
}

/* The dark rules above outweigh .dev-pill.active / .dev-pill-sm.active
   (both are two classes, these carry the attribute as well), so the
   selected pill has to restate its filled look or its white label turns
   pale blue on blue. The fill is the darker blue: white on #0d6efd is
   4.50:1, right on the line. */
html[data-theme="dark"] .dev-pill.active,
html[data-theme="dark"] .dev-pill-sm.active {
    background: #0b5ed7;
    border-color: #0b5ed7;
    color: #ffffff;
}

/* ═══════════════════════════════════════════════════════════════════════
   Light theme: the same readability sweep (2026-08-18)
   ─────────────────────────────────────────────────────────────────────────
   Bootstrap's own accents are tuned for white, and the app draws them on
   --bg (#f3f4f6), which costs just enough to drop them under 4.5:1. These
   are scoped to the light theme; the dark section above owns the other one.
   ═══════════════════════════════════════════════════════════════════════ */

/* Bootstrap 5.3 reads `code` from --bs-code-color and already carries a
   lighter value for its own dark mode, so only the light value moves.
   #d63384 scored 4.09:1 on the page and 3.76:1 inside a grey box, and
   inline code is everywhere in the manual and the agent results. */
html:not([data-theme="dark"]) {
    --bs-code-color: #a52561;
}

html:not([data-theme="dark"]) .btn-outline-primary {
    --bs-btn-color: #0a58ca;
    --bs-btn-border-color: #0a58ca;
    --bs-btn-hover-bg: #0a58ca;
    --bs-btn-hover-border-color: #0a58ca;
}

html:not([data-theme="dark"]) .btn-outline-danger {
    --bs-btn-color: #b02a37;
    --bs-btn-border-color: #b02a37;
    --bs-btn-hover-bg: #b02a37;
    --bs-btn-hover-border-color: #b02a37;
}

/* Links inside the health banner sit on its alert tint, not on the page. */
html:not([data-theme="dark"]) .dirigent-health a {
    color: #0a4f86;
}

/* The filter pill's count badge inherits the pill's blue on a pale blue
   badge; on the selected pill it is white on a translucent white badge. */
.dev-filter-count {
    color: #0a58ca;
}

.dev-pill-sm.active .dev-filter-count {
    background: rgba(0, 0, 0, 0.28);
    color: #ffffff;
}

/* Bootstrap's .text-secondary is the raw #6c757d, not the softened
   --bs-secondary-color that .text-muted uses, so it scored 4.26:1 on the
   light page and 3.81:1 on the dark one. The login page's "OR" divider is
   the most-seen instance. */
.text-secondary {
    /* Bootstrap ships its utilities with !important, so the override
       needs it too. This is the one place in the app where that is
       true; everything else wins on specificity. */
    color: var(--ink-muted) !important;
}

/* ─── Deployment page ───────────────────────────────────────────────────
   The one screen the audit could not open: /deployment is behind the
   SuperAdmin policy, which is the owner's address and nothing else, so a
   throwaway account cannot reach it. These rules were written by reading
   the CSS rather than by measuring the page: the banner, the card and the
   status badge all paint a light surface with a literal hex and take their
   ink from the theme, which is exactly the shape that made the agent result
   box unreadable at 1.16:1. The same treatment is applied here. */
html[data-theme="dark"] .deploy-banner {
    background: #1f2937;
    border-color: var(--border);
}

html[data-theme="dark"] .deploy-banner--ok {
    background: #14532d;
}

html[data-theme="dark"] .deploy-banner--error {
    background: #4a1216;
}

html[data-theme="dark"] .deploy-banner-offline {
    background: #422006;
    border-color: #b45309;
    color: #fde68a;
}

html[data-theme="dark"] .deploy-reload-button {
    background: #14532d;
    border-color: #16a34a;
    color: #dcfce7;
}

html[data-theme="dark"] .deploy-reload-later {
    color: #cbd5e1;
}

html[data-theme="dark"] .deploy-card {
    background: var(--surface);
    border-color: var(--border);
}

html[data-theme="dark"] .deploy-facts dt {
    color: var(--ink-subtle);
}

html[data-theme="dark"] .deploy-status-badge {
    background: var(--border);
    color: var(--ink);
}

html[data-theme="dark"] .deploy-target {
    border-color: var(--border);
}

html[data-theme="dark"] .deploy-target--test       { border-left-color: #60a5fa; }
html[data-theme="dark"] .deploy-target--production { border-left-color: #f87171; background: #3b1113; }

html[data-theme="dark"] .deploy-env-badge {
    background: var(--border);
    color: var(--ink);
}

/* De versievergelijking in de donkere modus. De lichte vlakken van het lichte
   thema (#f0fdf4, #fffbeb) zijn hier bijna wit onder lichte letters, dus elk
   vlak krijgt een donkere variant van dezelfde kleur; de randkleur blijft het
   signaal en houdt groen groen en oranje oranje. */
html[data-theme="dark"] .deploy-versions {
    border-color: var(--border);
    background: var(--surface);
}

html[data-theme="dark"] .deploy-version-badge {
    background: var(--border);
    color: var(--ink);
}

html[data-theme="dark"] .deploy-versions--current  { border-color: #4ade80; border-left-color: #4ade80; background: #10241a; }
html[data-theme="dark"] .deploy-versions--current  .deploy-version-badge { background: #15803d; color: #fff; }

html[data-theme="dark"] .deploy-versions--outdated { border-color: #fbbf24; border-left-color: #fbbf24; background: #2b2110; }
html[data-theme="dark"] .deploy-versions--outdated .deploy-version-badge { background: #b45309; color: #fff; }

html[data-theme="dark"] .deploy-versions--unknown  { border-left-color: #6b7280; }

/* ── "Add more info" (job detail) ───────────────────────────────────────────
   Steering a task whose agent is running right now. Sits ABOVE the tabs, so an
   instruction is never a tab away and the confirmation stays readable whichever
   tab you switch to afterwards. Quiet card, not a warning panel: adding a
   remark is an everyday action, not an exception. */
.dev-addinfo {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    padding: 14px 16px;
    margin-bottom: 16px;
}

.dev-addinfo__title {
    font-family: var(--font-head);
    font-size: 1.05rem;
    font-weight: 600;
    color: var(--ink-strong);
    margin: 0 0 10px;
}

/* Wraps on a phone so the send button keeps its 48px touch target instead of
   sharing a squeezed line with the outcome message. */
.dev-addinfo__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    margin-top: 10px;
}

.dev-addinfo__actions .btn {
    min-height: 48px;
}

.dev-addinfo__ok {
    color: var(--wait-fresh-ink);
    font-weight: 600;
    font-size: 0.85rem;
}

/* Shown instead of the box once the task is no longer running: the history
   below it stays, so what was added remains readable afterwards. */
.dev-addinfo__closed {
    margin: 0 0 10px;
    color: var(--ink-muted);
    font-size: 0.85rem;
}

.dev-addinfo__list {
    list-style: none;
    margin: 12px 0 0;
    padding: 0;
    border-top: 1px solid var(--border-soft);
}

.dev-addinfo__item {
    display: grid;
    /* State chip and timestamp keep their own column so the texts underneath
       each other line up; the text column takes whatever is left and wraps. */
    grid-template-columns: auto auto 1fr;
    align-items: baseline;
    gap: 4px 10px;
    padding: 8px 0;
    border-bottom: 1px solid var(--border-soft);
}

.dev-addinfo__state {
    font-size: 0.75rem;
    font-weight: 600;
    white-space: nowrap;
    border-radius: var(--radius-pill);
    padding: 2px 8px;
}

.dev-addinfo__state--pending {
    background: var(--wait-warn-bg);
    color: var(--wait-warn-ink);
}

.dev-addinfo__state--delivered {
    background: var(--wait-fresh-bg);
    color: var(--wait-fresh-ink);
}

.dev-addinfo__state--failed {
    background: var(--wait-stale-bg);
    color: var(--wait-stale-ink);
}

.dev-addinfo__meta {
    font-size: 0.75rem;
    color: var(--ink-muted);
    white-space: nowrap;
}

.dev-addinfo__spoken {
    margin-left: 4px;
}

/* The instruction itself. pre-wrap keeps the line breaks the user typed or
   dictated, exactly as the agent receives them. */
.dev-addinfo__text {
    white-space: pre-wrap;
    word-break: break-word;
    color: var(--ink);
    font-size: 0.9rem;
}

/* On a phone the three columns do not fit: the chip and the timestamp share a
   line and the text runs full width underneath them. */
@media (max-width: 576px) {
    .dev-addinfo__item {
        grid-template-columns: auto 1fr;
    }

    .dev-addinfo__text {
        grid-column: 1 / -1;
    }
}

/* ───────────────────────── Toggle switches: green on, red off ────────────
   Every switch in the app (a `form-check form-switch` with `role="switch"`)
   carries its state in its colour: green when it is on, red when it is off.
   The Bootstrap default is blue versus grey, which reads as "highlighted"
   rather than "on", so at a glance a switched-off setting looked the same as
   one that was simply not focused. Plain check boxes are deliberately NOT
   touched; only real switches get the colour coding.

   The thumb is the `--bs-form-switch-bg` custom property Bootstrap 5.3.3
   defines on `.form-switch .form-check-input` (verified in
   wwwroot/lib/bootstrap/dist/css/bootstrap.css). It is grey by default, which
   disappears against a red track, so it is overridden to white in both
   positions. The `:focus` and `[data-bs-theme=dark]` variants exist because
   Bootstrap re-declares that same property there with a higher specificity. */
.form-switch .form-check-input {
    --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e");
    background-color: #dc3545;
    border-color: #dc3545;
}

.form-switch .form-check-input:focus {
    --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e");
}

.form-switch .form-check-input:checked {
    --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e");
    background-color: #198754;
    border-color: #198754;
}

[data-bs-theme="dark"] .form-switch .form-check-input:not(:checked):not(:focus) {
    --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e");
}
