/*
DFA style guide colors layered onto django-jazzmin (AdminLTE 4 / Bootstrap 5.3).
The vendored bootswatch build has no CSS custom properties for its "default"
theme, and several components (buttons, sidebar, navbar) bake their color
directly into component-scoped `--bs-*` variables rather than the global
`--bs-primary`, so those are re-declared per component below instead of
relying on a single root override.
https://styleguide.dataforall.org/v5/about.html
*/

:root {
    --dfa-primary: #983365;
    --dfa-primary-dark: #6B1B43;
    --dfa-accent: #EF9F38;
}

/* --- Navbar --- */
.app-header.navbar-primary {
    background-color: var(--dfa-primary-dark) !important;
}
.app-header .nav-link {
    color: rgba(255, 255, 255, 0.85) !important;
}
.app-header .nav-link:hover {
    color: #fff !important;
}

/* "Projects" is the one-stop hub (list + view + edit + add project) that
   replaces hunting across each app's own admin pages, so it gets a filled
   pill treatment to stand out from the plain Dashboard/View Site links. */
.app-header .nav-link[href*="/projects/overview/"] {
    /* AdminLTE fixes .app-header .nav-link at height:2.5rem, which left the
       pill's background taller than its own content with the icon/text
       stuck near the top — inline-flex centers both against that box. */
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    height: auto !important;
    align-self: center;
    background-color: var(--dfa-accent) !important;
    color: var(--dfa-primary-dark) !important;
    border-radius: 999px;
    padding: 0.35rem 0.9rem !important;
    font-weight: 600;
    line-height: 1;
}
.app-header .nav-link[href*="/projects/overview/"]:hover {
    background-color: #fff !important;
    color: var(--dfa-primary-dark) !important;
}
.app-header .nav-link[href*="/projects/overview/"]::before {
    font-family: "Font Awesome 6 Free";
    font-weight: 900;
    content: "\f542";
    font-size: 0.9em;
    line-height: 1;
}

/* --- Navbar user label (static/admin/js/dfa_jazzmin.js) ---
   jazzmin's avatar toggle is icon-only; the JS injects the user's email as
   a visible label so it reads as "icon + email" in the top-right corner
   instead of only showing up in a native title-attribute tooltip. */
.dfa-user-toggle {
    display: inline-flex !important;
    align-items: center;
    gap: 0.45rem;
}
.dfa-user-label {
    display: none;
    font-size: 0.85rem;
    max-width: 14rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
@media (min-width: 576px) {
    .dfa-user-label {
        display: inline-block;
    }
}

/* --- Sidebar --- */
/* The user's email now shows next to the navbar avatar icon (top right,
   see .dfa-user-label above), so the sidebar's own avatar+email block is
   redundant here. `!important` is required: jazzmin renders it as
   `<div class="user-panel d-flex">` and Bootstrap's `.d-flex` utility is
   itself `display: flex !important`. */
.app-sidebar .user-panel {
    display: none !important;
}
.app-sidebar.sidebar-dark-primary {
    background-color: var(--dfa-primary-dark) !important;
}
.sidebar-dark-primary .nav-sidebar > .nav-item > .nav-link.active,
.sidebar-dark-primary .nav-sidebar .nav-link:hover {
    background-color: var(--dfa-primary) !important;
    color: #fff !important;
}
.sidebar-brand.navbar-primary {
    background-color: var(--dfa-primary-dark) !important;
}

/* --- Buttons --- */
.btn-primary {
    --bs-btn-bg: var(--dfa-primary);
    --bs-btn-border-color: var(--dfa-primary);
    --bs-btn-hover-bg: var(--dfa-primary-dark);
    --bs-btn-hover-border-color: var(--dfa-primary-dark);
    --bs-btn-active-bg: var(--dfa-primary-dark);
    --bs-btn-active-border-color: var(--dfa-primary-dark);
    --bs-btn-disabled-bg: var(--dfa-primary);
    --bs-btn-disabled-border-color: var(--dfa-primary);
}
.btn-warning {
    --bs-btn-color: #3a2308;
    --bs-btn-bg: var(--dfa-accent);
    --bs-btn-border-color: var(--dfa-accent);
    --bs-btn-hover-color: #3a2308;
}

/* Outline buttons (Save and keep editing, Save and add another, ...) were
   Bootstrap's stock blue beside the maroon Save. */
.btn-outline-primary {
    --bs-btn-color: var(--dfa-primary);
    --bs-btn-border-color: var(--dfa-primary);
    --bs-btn-hover-bg: var(--dfa-primary);
    --bs-btn-hover-border-color: var(--dfa-primary);
    --bs-btn-hover-color: #fff;
    --bs-btn-active-bg: var(--dfa-primary-dark);
    --bs-btn-active-border-color: var(--dfa-primary-dark);
    --bs-btn-active-color: #fff;
    --bs-btn-focus-shadow-rgb: 152, 51, 101;
}

/* --- Links, badges, pagination, focus rings --- */
a {
    color: var(--dfa-primary);
}
a:hover {
    color: var(--dfa-primary-dark);
}
.bg-primary,
.text-bg-primary {
    background-color: var(--dfa-primary) !important;
}
.text-primary {
    color: var(--dfa-primary) !important;
}
.page-item.active .page-link {
    background-color: var(--dfa-primary) !important;
    border-color: var(--dfa-primary) !important;
}
.form-control:focus,
.form-select:focus {
    border-color: var(--dfa-primary) !important;
    box-shadow: 0 0 0 0.2rem rgba(152, 51, 101, 0.25) !important;
}

/* =====================================================================
   Dashboard (common/templates/admin/dashboard.html)
   =====================================================================
   Built on Bootstrap 5.3's own semantic variables (--bs-body-bg,
   --bs-border-color, --bs-secondary-color, ...) rather than fixed greys:
   jazzmin ships a light/dark colour-scheme switcher that stamps
   data-bs-theme on <html> (jazzmin/templates/admin/base.html), so any
   hardcoded surface or ink here would survive the switch and stop being
   legible. Only the *data marks* carry fixed colour, and those are
   re-stepped for the dark surface at the end of this block.

   Two colour rules, and they are the whole system:
     - ONE hue (the brand maroon) for every count/percentage mark. Every
       chart on this page is a single series, so a second hue would
       encode nothing — bar length already says what a value-ramp would
       repeat.
     - The four reserved status tones (good/warning/serious/critical) for
       *state* only, never as a series colour, and never on their own:
       each renders as `--tone` on a small mark (dot, rail, icon) with
       `--tone-ink` on the text beside it, so the meaning survives both
       colour-blindness and a greyscale print. `--tone-ink` is a darkened
       step of the same hue purely so label text clears 4.5:1 — the marks
       themselves keep the canonical status value.
   ===================================================================== */
.dfa-dash {
    --dfa-series: #983365;
    --dfa-series-soft: rgba(152, 51, 101, 0.1);
    --dfa-radius: 0.75rem;
    --dfa-gap: 1rem;
}
.dfa-dash .tone-good {
    --tone: #0ca30c;
    --tone-ink: #076b07;
}
.dfa-dash .tone-warning {
    --tone: #fab219;
    --tone-ink: #8a5a00;
}
.dfa-dash .tone-serious {
    --tone: #ec835a;
    --tone-ink: #9a4318;
}
.dfa-dash .tone-critical {
    --tone: #d03b3b;
    --tone-ink: #a12525;
}

/* --- Page header ---
   A soft brand-tinted band, not a solid fill: the header sits directly
   above data-dense cards, so it reads as a backdrop rather than another
   panel competing for attention. Contained (not bled to the viewport
   edge) since the surrounding content wrapper's own padding isn't this
   template's to assume. */
.dfa-dash-head {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: 0.75rem;
    margin-bottom: 1.25rem;
    padding: 1.25rem 1.25rem 1.1rem;
    background: linear-gradient(135deg, var(--dfa-series-soft), transparent 70%);
    border: 1px solid var(--bs-border-color);
    border-radius: var(--dfa-radius);
}
.dfa-eyebrow {
    display: block;
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--bs-secondary-color);
}
.dfa-dash-title {
    margin: 0.15rem 0 0.35rem;
    font-size: 1.6rem;
    font-weight: 700;
    line-height: 1.2;
}
.dfa-scope-banner {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.8rem;
    color: var(--bs-secondary-color);
}
.dfa-scope-banner i {
    color: var(--dfa-series);
}
.dfa-quick-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}
/* Styled here rather than reused from .btn-outline-primary: `primary` in
   an outline button resolves to the bootswatch theme's own blue, not the
   brand maroon (only .btn-primary is re-declared above), and six filled
   maroon buttons in a row would out-shout the numbers they sit beside.
   Scoped to the dashboard so no other page's buttons move. */
.dfa-quick-actions .dfa-action {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.35rem 0.75rem;
    font-size: 0.78rem;
    font-weight: 500;
    text-decoration: none;
    border: 1px solid var(--bs-border-color);
    border-radius: 999px;
    background: var(--bs-body-bg);
    color: var(--bs-body-color);
    transition: border-color 0.15s ease, color 0.15s ease;
}
.dfa-quick-actions .dfa-action i {
    color: var(--dfa-series);
    font-size: 0.75rem;
}
.dfa-quick-actions .dfa-action:hover,
.dfa-quick-actions .dfa-action:focus-visible {
    border-color: var(--dfa-series);
    color: var(--dfa-series);
}

/* --- Section headings --- */
.dfa-section {
    margin-top: 1.75rem;
}
.dfa-section-head {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin-bottom: 0.75rem;
}
.dfa-section-head .dfa-eyebrow {
    flex: 0 0 auto;
}
/* A hairline running out from the label to the edge of the row — lighter
   than a rule above it, and it keeps the eye on the label. */
.dfa-section-head::after {
    content: "";
    flex: 1 1 auto;
    height: 1px;
    background: var(--bs-border-color);
}

/* --- Panels --- */
.dfa-panel {
    display: flex;
    flex-direction: column;
    height: 100%;
    background: var(--bs-body-bg);
    border: 1px solid var(--bs-border-color);
    border-radius: var(--dfa-radius);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
    overflow: hidden;
}
.dfa-panel-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.75rem 1rem;
    border-bottom: 1px solid var(--bs-border-color);
}
.dfa-panel-title {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0;
    font-size: 0.9rem;
    font-weight: 600;
}
.dfa-panel-title i {
    color: var(--dfa-series);
    font-size: 0.85em;
}
.dfa-panel-body {
    flex: 1 1 auto;
    padding: 1rem;
}
.dfa-panel-body.is-flush {
    padding: 0;
}
.dfa-panel-foot {
    padding: 0.55rem 1rem;
    border-top: 1px solid var(--bs-border-color);
    font-size: 0.78rem;
}
.dfa-count-pill {
    flex: 0 0 auto;
    padding: 0.1rem 0.5rem;
    font-size: 0.72rem;
    font-weight: 600;
    border-radius: 999px;
    background: var(--bs-secondary-bg);
    color: var(--bs-secondary-color);
}

/* --- Stat cards ---
   A fixed 4-up grid, not auto-fit: there are eight tiles, so any column
   count that isn't a divisor of eight leaves a ragged last row (auto-fit
   picked five at a typical desktop width, stranding three). Two rows of
   four also keep each pair of related counts — client/project,
   server/database — on the same line as each other. */
.dfa-stat-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--dfa-gap);
}
@media (min-width: 768px) {
    .dfa-stat-grid {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }
}
.dfa-stat {
    display: flex;
    align-items: flex-start;
    gap: 0.7rem;
    padding: 0.9rem;
    background: var(--bs-body-bg);
    border: 1px solid var(--bs-border-color);
    border-radius: var(--dfa-radius);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
    color: inherit;
    text-decoration: none;
    transition: border-color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
}
.dfa-stat:hover,
.dfa-stat:focus-visible {
    color: inherit;
    border-color: var(--dfa-series);
    transform: translateY(-1px);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
}
.dfa-stat-icon {
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    width: 2.1rem;
    height: 2.1rem;
    border-radius: 0.55rem;
    background: var(--dfa-series-soft);
    color: var(--dfa-series);
    font-size: 0.85rem;
}
.dfa-stat-body {
    min-width: 0;
}
.dfa-stat-top {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
}
/* Proportional figures, deliberately: equal-width digits make a large
   standalone number look loose, and nothing here aligns in a column. */
.dfa-stat-value {
    font-size: 1.6rem;
    font-weight: 600;
    line-height: 1.05;
}
/* The one spot on the page where the good tone appears without a link of
   its own: growth is never bad news here (these are cumulative totals),
   so there is no second colour to weigh it against. */
.dfa-stat-growth {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.05rem 0.4rem;
    font-size: 0.68rem;
    font-weight: 600;
    border-radius: 999px;
    color: #076b07;
    background: rgba(12, 163, 12, 0.12);
}
[data-bs-theme="dark"] .dfa-stat-growth {
    color: #4fd44f;
}
.dfa-stat-growth i {
    font-size: 0.85em;
}
.dfa-stat-label {
    font-size: 0.8rem;
    font-weight: 500;
    color: var(--bs-body-color);
}
.dfa-stat-sub {
    font-size: 0.72rem;
    color: var(--bs-secondary-color);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* --- "Needs attention" tiles ---
   A tone rail down the left edge rather than a tinted card: the point of
   the row is that the eye finds the one tile that is not green, and a
   full wash of colour at this size reads as an error state even when the
   number is zero. */
.dfa-alert-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(11.5rem, 1fr));
    gap: var(--dfa-gap);
}
.dfa-alert {
    position: relative;
    display: block;
    padding: 0.8rem 0.9rem 0.8rem 1.1rem;
    background: var(--bs-body-bg);
    border: 1px solid var(--bs-border-color);
    border-radius: var(--dfa-radius);
    color: inherit;
    text-decoration: none;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.dfa-alert::before {
    content: "";
    position: absolute;
    inset: 0.55rem auto 0.55rem 0;
    width: 3px;
    border-radius: 0 3px 3px 0;
    background: var(--tone);
}
.dfa-alert:hover,
.dfa-alert:focus-visible {
    color: inherit;
    border-color: var(--tone);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
}
.dfa-alert-top {
    display: flex;
    align-items: baseline;
    gap: 0.45rem;
}
.dfa-alert-top i {
    color: var(--tone);
    font-size: 0.85rem;
}
.dfa-alert-value {
    font-size: 1.35rem;
    font-weight: 600;
    line-height: 1.1;
    color: var(--tone-ink);
}
.dfa-alert-label {
    font-size: 0.8rem;
    font-weight: 500;
}
.dfa-alert-hint {
    font-size: 0.72rem;
    color: var(--bs-secondary-color);
}

/* --- Meters (one bar per row: distributions and coverage) ---
   Thin mark, hairline-recessive track, and the value beside the label
   rather than inside the bar so a 1%-wide fill never has to hold text.
   The fill is square where it meets the baseline and rounded at the data
   end. */
.dfa-meters {
    display: grid;
    gap: 0.7rem;
    margin: 0;
}
.dfa-meter {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: baseline;
    gap: 0.15rem 0.75rem;
}
.dfa-meter-label {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.8rem;
    min-width: 0;
}
.dfa-meter-label i {
    color: var(--bs-secondary-color);
    font-size: 0.75rem;
}
.dfa-meter-label span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* tabular-nums here and only here: these values do line up vertically,
   one per row, so equal-width digits stop the column jittering. */
.dfa-meter-value {
    font-size: 0.8rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.dfa-meter-value small {
    font-weight: 500;
    color: var(--bs-secondary-color);
}
.dfa-meter-track {
    grid-column: 1 / -1;
    display: block;
    height: 6px;
    border-radius: 999px;
    background: var(--bs-secondary-bg);
    overflow: hidden;
}
/* display:block is load-bearing, not tidiness: the fill is a <span>, and
   width/height are ignored on a non-replaced inline box — without it the
   bar renders at zero size and every meter looks like an empty track. */
.dfa-meter-fill {
    display: block;
    height: 100%;
    min-width: 2px;
    border-radius: 0 999px 999px 0;
    background: var(--dfa-series);
}

/* --- Record rows (lists inside a flush panel body) --- */
.dfa-rows {
    list-style: none;
    margin: 0;
    padding: 0;
}
.dfa-row {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.6rem 1rem;
    border-bottom: 1px solid var(--bs-border-color);
}
.dfa-row:last-child {
    border-bottom: 0;
}
.dfa-row-main {
    min-width: 0;
}
.dfa-row-title {
    font-size: 0.85rem;
    font-weight: 500;
    /* Row labels are user data — a long domain or incident title has no
       break opportunity of its own, and .dfa-panel clips its overflow, so
       without this the end of the name simply disappears. */
    overflow-wrap: anywhere;
}
.dfa-row-meta {
    font-size: 0.73rem;
    color: var(--bs-secondary-color);
}
.dfa-row-side {
    flex: 0 0 auto;
    text-align: right;
    font-size: 0.73rem;
    color: var(--bs-secondary-color);
}

/* --- Status chip: `--tone` mark + `--tone-ink` text + a real label --- */
.dfa-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.12rem 0.5rem;
    font-size: 0.7rem;
    font-weight: 600;
    line-height: 1.5;
    white-space: nowrap;
    border-radius: 999px;
    border: 1px solid color-mix(in srgb, var(--tone) 35%, transparent);
    background: color-mix(in srgb, var(--tone) 10%, transparent);
    color: var(--tone-ink);
}
.dfa-chip i {
    color: var(--tone);
    font-size: 0.9em;
}
.dfa-chip-plain {
    --tone: var(--bs-secondary-color);
    --tone-ink: var(--bs-secondary-color);
}

/* --- Timeline (deploys, activity) ---
   The rail is the same hairline as every other divider; the marker is
   8px with a 2px surface ring, so it reads as separate from the rail
   without a border being drawn around it. */
.dfa-timeline {
    list-style: none;
    margin: 0;
    padding: 0.25rem 0 0.25rem 0.4rem;
}
.dfa-timeline-item {
    position: relative;
    padding: 0 0 0.9rem 1.15rem;
    border-left: 1px solid var(--bs-border-color);
}
.dfa-timeline-item:last-child {
    padding-bottom: 0;
    border-left-color: transparent;
}
.dfa-timeline-item::before {
    content: "";
    position: absolute;
    top: 0.3rem;
    left: -5px;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--dfa-series);
    box-shadow: 0 0 0 2px var(--bs-body-bg);
}
.dfa-timeline-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.4rem;
    font-size: 0.85rem;
    font-weight: 500;
    line-height: 1.3;
}
.dfa-timeline-meta {
    font-size: 0.73rem;
    color: var(--bs-secondary-color);
}

/* --- Column chart (cost trend) ---
   The card grows with its content instead of being pinned to a height
   that excludes the axis band, so the month labels can never be the part
   that gets cropped or spawns a nested scrollbar. */
.dfa-columns {
    display: flex;
    align-items: stretch;
    gap: 0.5rem;
}
.dfa-column {
    flex: 1 1 0;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    gap: 0.35rem;
    min-width: 0;
    text-align: center;
}
.dfa-column-plot {
    display: flex;
    align-items: flex-end;
    justify-content: center;
    height: 4.5rem;
}
.dfa-column-bar {
    display: block;
    width: 100%;
    max-width: 2.25rem;
    min-height: 2px;
    border-radius: 4px 4px 0 0;
    background: var(--dfa-series);
}
.dfa-column-bar.is-empty {
    background: var(--bs-secondary-bg);
}
.dfa-column-value {
    font-size: 0.72rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}
.dfa-column-label {
    font-size: 0.68rem;
    color: var(--bs-secondary-color);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    /* The axis band, drawn as a hairline exactly like the gridlines:
       solid, one shade off the surface, never dashed. */
    border-top: 1px solid var(--bs-border-color);
    padding-top: 0.3rem;
}

/* --- Hero figure (cost headline) --- */
.dfa-figure {
    display: flex;
    align-items: baseline;
    gap: 0.35rem;
    margin-bottom: 0.15rem;
}
.dfa-figure-unit {
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--bs-secondary-color);
}
.dfa-figure-value {
    font-size: 1.7rem;
    font-weight: 600;
    line-height: 1.05;
}

/* --- Footprint chips (counts that don't earn a whole tile) --- */
.dfa-footprint {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-bottom: var(--dfa-gap);
}
.dfa-footprint-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.3rem 0.7rem;
    font-size: 0.78rem;
    border: 1px solid var(--bs-border-color);
    border-radius: 999px;
    background: var(--bs-body-bg);
    color: inherit;
    text-decoration: none;
}
.dfa-footprint-chip:hover {
    color: inherit;
    border-color: var(--dfa-series);
}
.dfa-footprint-chip i {
    color: var(--dfa-series);
    font-size: 0.8rem;
}
.dfa-footprint-chip b {
    font-weight: 600;
}

/* --- Empty states ---
   Sparse data is the normal state of a fresh install, so a card with
   nothing to show says what it would show and keeps the same height as
   its neighbours rather than collapsing the row. */
.dfa-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.35rem;
    padding: 1.25rem 1rem;
    text-align: center;
    color: var(--bs-secondary-color);
    font-size: 0.8rem;
}
.dfa-empty i {
    font-size: 1.1rem;
    opacity: 0.5;
}
.dfa-empty.is-good i {
    color: #0ca30c;
    opacity: 1;
}

/* --- Dark colour scheme ---
   The marks only. Everything else on the page is drawn with Bootstrap's
   semantic variables, which jazzmin's switcher re-points on its own. The
   brand hue is re-stepped (not merely reused) so it clears 3:1 against
   the dark surface, and the four status tones keep their canonical
   values — they all clear 3:1 there — with their ink lightened instead
   of darkened. */
[data-bs-theme="dark"] .dfa-dash {
    --dfa-series: #c85b91;
    --dfa-series-soft: rgba(200, 91, 145, 0.16);
}
[data-bs-theme="dark"] .dfa-dash .tone-good {
    --tone-ink: #4fd44f;
}
[data-bs-theme="dark"] .dfa-dash .tone-warning {
    --tone-ink: #fac151;
}
[data-bs-theme="dark"] .dfa-dash .tone-serious {
    --tone-ink: #f0a487;
}
[data-bs-theme="dark"] .dfa-dash .tone-critical {
    --tone-ink: #ef8080;
}

/* --- Dashboard: small screens --- */
@media (max-width: 575.98px) {
    .dfa-dash {
        --dfa-gap: 0.75rem;
    }
    .dfa-stat-value {
        font-size: 1.35rem;
    }
}

/* Below ~400px two tiles across leaves about 58px beside the icon chip,
   which is narrower than the word "Applications" — and a one-word label
   has no break opportunity, so it spilled over the tile's own border.
   Stacking the chip above the text hands the label the full tile width
   instead of dropping to one tile per row and eight rows of scrolling. */
@media (max-width: 400px) {
    .dfa-stat {
        flex-direction: column;
        gap: 0.5rem;
    }
    .dfa-stat-label {
        overflow-wrap: anywhere;
    }
    /* Six columns at ~40px each fit the card, but the month labels
       underneath do not — let the row scroll rather than squeezing "Sep"
       into three clipped pixels. */
    .dfa-columns {
        overflow-x: auto;
        padding-bottom: 0.25rem;
    }
    .dfa-column {
        min-width: 2.75rem;
    }
}

/* --- Password field show/hide toggle (static/admin/js/dfa_jazzmin.js) ---
   Two placements, no JS pixel math (that broke across browsers/timing):
   - Login form: input sits in a Bootstrap `.input-group` (flex) next to a
     lock icon; the toggle is inserted as a normal flex child right before
     that icon, so flex layout positions it.
   - Everywhere else (e.g. the User change form): the JS wraps input and
     button in `.dfa-password-wrap`, an inline-flex row that shrinks to
     them regardless of its (often much wider) parent column. The toggle
     sits next to the input, not over it, so the input keeps exactly the
     geometry of the vTextField boxes above it — it has no CSS width to
     absorb padding, so making room for the icon inside made it wider. */
.dfa-password-wrap {
    display: inline-flex;
    align-items: center;
    gap: 0.15rem;
    max-width: 100%;
}
.dfa-password-toggle {
    display: flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: none;
    padding: 0 0.5rem;
    line-height: 1;
    color: #6c757d;
    cursor: pointer;
}
.dfa-password-toggle:hover {
    color: var(--dfa-primary);
}

/* --- Project overview tabs (common/templates/admin/project_overview_detail.html) ---
   Surfaces and hairlines use Bootstrap's semantic variables (same values
   as the old hex codes in the light theme) so jazzmin's dark-mode switch
   carries over, as on the dashboard. */
.dfa-tabs {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    gap: 0.25rem;
    border-bottom: 2px solid var(--bs-border-color);
    padding: 0 0.25rem;
    scrollbar-width: thin;
}
.dfa-tabs::-webkit-scrollbar {
    height: 6px;
}
.dfa-tabs::-webkit-scrollbar-thumb {
    background: #ced4da;
    border-radius: 3px;
}
.dfa-tabs .nav-item {
    flex: 0 0 auto;
}
.dfa-tabs .nav-link {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    white-space: nowrap;
    border: none;
    border-bottom: 2px solid transparent;
    margin-bottom: -2px;
    padding: 0.6rem 0.9rem;
    font-weight: 500;
    font-size: 0.875rem;
    color: #6c757d;
    border-radius: 0.375rem 0.375rem 0 0;
    transition: color 0.15s ease, background-color 0.15s ease, border-color 0.15s ease;
}
.dfa-tabs .nav-link i {
    font-size: 0.8rem;
    color: #adb5bd;
    transition: color 0.15s ease;
}
.dfa-tabs .nav-link:hover {
    color: var(--dfa-primary-dark);
    background-color: rgba(152, 51, 101, 0.06);
}
.dfa-tabs .nav-link:hover i {
    color: var(--dfa-primary);
}
.dfa-tabs .nav-link.active {
    color: var(--dfa-primary-dark);
    background-color: var(--bs-body-bg);
    border-color: var(--dfa-primary);
}
.dfa-tabs .nav-link.active i {
    color: var(--dfa-primary);
}
.dfa-tab-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.35rem;
    padding: 0.1rem 0.4rem;
    font-size: 0.7rem;
    font-weight: 600;
    border-radius: 1rem;
    background-color: var(--bs-secondary-bg) !important;
    color: #495057 !important;
}
.dfa-tabs .nav-link.active .dfa-tab-badge {
    background-color: var(--dfa-primary) !important;
    color: #fff !important;
}
.dfa-tab-badge-empty {
    background-color: transparent !important;
    color: #adb5bd !important;
}
.dfa-tab-content {
    background-color: var(--bs-body-bg);
    border: 1px solid var(--bs-border-color);
    border-top: none;
    border-radius: 0 0 0.5rem 0.5rem;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.04);
}

/* --- Effective permissions (read-only summary on the user change page) ---
   A plain table rather than jazzmin's form-row styling: it's derived
   output, not an editable field, so it should read as a report of what
   the roles below already grant. */
.dfa-permission-summary {
    width: 100%;
    max-width: 60rem;
    border-collapse: collapse;
    font-size: 0.85rem;
}
.dfa-permission-summary th,
.dfa-permission-summary td {
    padding: 0.4rem 0.6rem;
    text-align: left;
    vertical-align: top;
    border-bottom: 1px solid var(--bs-border-color);
}
.dfa-permission-summary thead th {
    font-weight: 600;
    color: var(--dfa-primary-dark);
    border-bottom: 2px solid var(--dfa-primary);
    white-space: nowrap;
}
.dfa-permission-summary tbody td:first-child,
.dfa-permission-summary tbody td:nth-child(2) {
    white-space: nowrap;
    font-weight: 500;
}
/* Jazzmin wraps every readonly field in a grey, bordered, muted-text box
   (.readonly in jazzmin/css/main.css). That reads as "disabled input",
   which is wrong for a derived summary — it's a small report, so it gets
   the plain page background and normal text colour. */
.field-effective_permissions .readonly {
    padding: 0;
    background-color: transparent;
    border: 0;
    color: inherit;
}
.dfa-permission-chip {
    display: inline-block;
    white-space: nowrap;
    margin: 0 0.25rem 0.25rem 0;
    padding: 0.1rem 0.45rem;
    font-size: 0.75rem;
    border-radius: 0.75rem;
    background-color: rgba(152, 51, 101, 0.08);
    color: var(--dfa-primary-dark);
}

/* --- Role guide (user add/change page, Org memberships form) ---
   Collapsed by default: it's reference text for whoever is picking a role,
   not something to read on every visit. */
.dfa-field-readonly.field-role_guide_display {
    /* The cards need the whole row, and the section title ("Choosing
       roles" / "Permissions") already names it, so the field label is
       just noise. */
    grid-column: 1 / -1;
}
.dfa-field-readonly.field-role_guide_display > .dfa-label {
    display: none;
}
.field-role_guide_display .readonly {
    padding: 0;
    background-color: transparent;
    border: 0;
    color: inherit;
    overflow: visible;
}
.dfa-role-guide summary {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.35rem 0.75rem;
    border: 1px solid rgba(152, 51, 101, 0.25);
    border-radius: 999px;
    background-color: rgba(152, 51, 101, 0.06);
    color: var(--dfa-primary-dark);
    font-size: 0.85rem;
    font-weight: 600;
    cursor: pointer;
    list-style: none;
    user-select: none;
}
.dfa-role-guide summary::-webkit-details-marker {
    display: none;
}
.dfa-role-guide summary::after {
    content: "\25BE"; /* ▾ */
    transition: transform 0.15s ease;
}
.dfa-role-guide[open] summary::after {
    transform: rotate(180deg);
}
.dfa-role-guide summary:hover,
.dfa-role-guide summary:focus-visible {
    border-color: var(--dfa-primary);
    background-color: rgba(152, 51, 101, 0.1);
}
.dfa-role-guide-count {
    padding: 0 0.45rem;
    border-radius: 999px;
    background-color: var(--bs-body-bg);
    color: var(--bs-secondary-color);
    font-size: 0.72rem;
    font-weight: 500;
}
.dfa-role-cards {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
    gap: 0.6rem;
    margin: 0.75rem 0 0;
    padding: 0;
    list-style: none;
}
.dfa-role-card {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    padding: 0.6rem 0.75rem;
    border: 1px solid var(--bs-border-color);
    border-left: 3px solid rgba(152, 51, 101, 0.35);
    border-radius: 0.5rem;
    background-color: var(--bs-body-bg);
    font-size: 0.8rem;
    line-height: 1.4;
    color: var(--bs-secondary-color);
    white-space: normal;
    transition: border-color 0.15s ease, background-color 0.15s ease;
}
.dfa-role-card strong {
    font-size: 0.85rem;
    color: var(--bs-emphasis-color);
}
/* A role already picked in the Roles table below (dfa_forms.js). */
.dfa-role-card.is-selected {
    border-color: rgba(152, 51, 101, 0.35);
    border-left-color: var(--dfa-primary);
    background-color: rgba(152, 51, 101, 0.06);
}
.dfa-role-card.is-selected strong::after {
    content: " \2713";
    color: var(--dfa-primary);
}

/* --- Roles & permissions matrix (PermissionAdmin.matrix_view) ---
   The grid scrolls inside its own box, with the header row and the
   permission column pinned, so on a narrow screen the role names and the
   row being ticked stay in view and the page itself never scrolls
   sideways. Colours are Bootstrap's semantic variables, which jazzmin's
   dark theme re-points. */
.dfa-matrix-scroll {
    overflow: auto;
    max-height: 70vh;
}
.dfa-matrix {
    border-collapse: separate;
    border-spacing: 0;
    font-size: 0.85rem;
}
.dfa-matrix th,
.dfa-matrix td {
    border-bottom: 1px solid var(--bs-border-color);
    vertical-align: middle;
    background-color: var(--bs-body-bg);
}
.dfa-matrix thead th {
    position: sticky;
    top: 0;
    z-index: 2;
    border-bottom: 2px solid var(--dfa-primary);
}
.dfa-matrix .dfa-matrix-perm {
    position: sticky;
    left: 0;
    z-index: 1;
    min-width: 14rem;
    font-weight: normal;
}
.dfa-matrix thead .dfa-matrix-perm {
    z-index: 3;
    font-weight: 600;
}
.dfa-matrix-role {
    text-align: center;
    white-space: nowrap;
    cursor: help;
    font-weight: 600;
}
.dfa-matrix-group th {
    background-color: var(--bs-tertiary-bg);
    font-weight: 600;
    position: sticky;
    left: 0;
}
.dfa-matrix-action {
    display: block;
    font-weight: 500;
}
.dfa-matrix-desc {
    display: block;
    font-size: 0.75rem;
    color: var(--bs-secondary-color);
}
.dfa-matrix-cell {
    text-align: center;
}
.dfa-matrix-cell .form-check-input {
    float: none;
    margin: 0;
    width: 1.15rem;
    height: 1.15rem;
    cursor: pointer;
}
.dfa-matrix tbody tr:not(.dfa-matrix-group):hover td,
.dfa-matrix tbody tr:not(.dfa-matrix-group):hover th {
    background-color: var(--bs-tertiary-bg);
}
@media (max-width: 575.98px) {
    .dfa-matrix .dfa-matrix-perm {
        min-width: 9rem;
    }
}

/* --- Related-object popups (JAZZMIN_SETTINGS.related_modal_active) ---
   The "add another" / "change" / "lookup" icons beside a related field
   (static/admin/js/RelatedObjectLookups.js) normally open the target
   admin page in a bare, unstyled `window.open()` popup. jazzmin can show
   that same page in an in-page Bootstrap modal instead
   (jazzmin/js/related-modal.js), but the installed jazzmin version's own
   main.css dropped the sizing rules that feature needs — the modal shows
   at Bootstrap's default width with a 0-height iframe unless they're
   redeclared here. */
/* jazzmin names each modal instance "related-modal-<n>" (a running counter,
   for nested popup-inside-popup stacking) rather than a shared "related-
   modal" class — an `iframeInternalModalClass` var with that shared name
   is computed in related-modal.js but never actually applied to anything,
   so `.modal-wide` (which IS applied) is what every rule below keys off. */
.modal.modal-wide .modal-dialog {
    width: min(720px, 92vw);
    max-width: inherit;
}
.modal-wide .modal-body {
    padding: 0;
    overflow: hidden;
}
iframe.related-iframe {
    display: block;
    width: 100%;
    height: min(80vh, 640px);
    border: 0;
}
.modal-wide .modal-content {
    border: 0;
    border-radius: var(--dfa-radius);
    box-shadow: 0 12px 36px rgba(0, 0, 0, 0.18);
    overflow: hidden;
}
.modal-wide .modal-header {
    padding: 0.85rem 1.25rem;
    background: var(--dfa-primary-dark);
    border-bottom: 0;
}
.modal-wide .modal-title {
    font-size: 1rem;
    font-weight: 600;
    color: #fff;
}
.modal-wide .modal-header .btn-close {
    filter: invert(1) grayscale(1) brightness(2);
    opacity: 0.85;
}
.modal-wide .modal-header .btn-close:hover {
    opacity: 1;
}
/* A popup opened from inside another popup (e.g. "Parent client" opened
   from within "Add client", itself opened from "Add project") stacks a
   second modal on top of the first — sized a step down so its own header/
   border read as a distinct layer above the modal underneath rather than
   the same box redrawn. `nth-of-type` rather than a nesting class since
   related-modal.js gives each stacked instance only its own counter
   suffix, never a shared marker of depth. */
div[class*="related-modal-"] ~ div[class*="related-modal-"] .modal-dialog {
    width: min(640px, 88vw);
}
/* Backdrop is disabled in the JS ($.showModal({backdrop:false}), so
   several stacked popups don't dim the one behind them into
   illegibility) — a blur on the page behind still separates the modal
   from the admin UI it floats over. */
.modal-open .app-wrapper {
    filter: blur(1px);
}
/* The pencil / plus beside a dropdown. Django draws them as yellow and
   green clip-art SVGs; these are quiet square icon buttons in the brand
   colour instead, the same size as the dropdown beside them. Class names,
   ids and hrefs are untouched — RelatedObjectLookups.js and jazzmin's
   related-modal.js key off them. */
.related-widget-wrapper {
    display: flex;
    align-items: center;
    gap: 0.35rem;
}
.related-widget-wrapper > select,
.related-widget-wrapper > .select2,
.related-widget-wrapper > div {
    flex: 1 1 auto;
    min-width: 0;
}
.related-widget-wrapper-link {
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    width: 2.4rem;
    height: 2.4rem;
    border: 1px solid var(--bs-border-color);
    border-radius: 0.5rem;
    background: var(--bs-body-bg);
    color: var(--dfa-primary);
    text-decoration: none;
    cursor: pointer;
    transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.related-widget-wrapper-link img {
    display: none;
}
.related-widget-wrapper-link::before {
    font-family: "Font Awesome 6 Free";
    font-weight: 900;
    font-size: 0.8rem;
}
.related-widget-wrapper-link.change-related::before {
    content: "\f304"; /* pen */
}
.related-widget-wrapper-link.add-related::before {
    content: "\f067"; /* plus */
}
.related-widget-wrapper-link.view-related::before {
    content: "\f06e"; /* eye */
}
.related-widget-wrapper-link:hover,
.related-widget-wrapper-link:focus-visible {
    border-color: var(--dfa-primary);
    background: var(--dfa-primary);
    color: #fff;
}
/* Edit with nothing selected: Django removes the href until a value is
   picked. Shown, but plainly unavailable. */
.related-widget-wrapper-link:not([href]) {
    opacity: 0.35;
    pointer-events: none;
}
/* The project workspace's small (form-select-sm) dropdowns. */
.dfa-ws .related-widget-wrapper-link {
    width: 1.95rem;
    height: 1.95rem;
    border-radius: 0.375rem;
}

/* Inside the modal's iframe. jazzmin renders a popup page with the same
   `sidebar-expand-lg sidebar-open` body classes as a full page, and the
   iframe (720px) is narrower than the lg breakpoint, so AdminLTE treats it
   as a phone with the sidebar slid open and shows .sidebar-overlay. A popup
   has no sidebar, so that overlay was just a grey sheet over the whole form
   that also swallowed every click, leaving the form unusable. */
body.popup .sidebar-overlay,
body.popup .app-sidebar {
    display: none !important;
}

/* =====================================================================
   Responsive behaviour
   =====================================================================
   AdminLTE/Bootstrap already handle the sidebar, the grid and the
   changelist (jazzmin wraps #result_list in .card-body.table-responsive,
   so those scroll on their own). What it does not handle is content that
   is wider than the viewport with no scroll affordance anywhere above it:
   the page itself never overflows, so the surplus is simply clipped and
   the rightmost columns become unreachable rather than merely offscreen.
   Everything below is either a scroll container for one of those cases or
   a spacing/visibility correction at small widths. */

/* --- Read-only permission summary (user change page) ---
   .dfa-permission-summary keeps its first two columns on one line by
   design, so its min-content width exceeds a phone viewport. The readonly
   wrapper it sits in is the only box that can scroll it — without this the
   "Can do" column was cut off with no way to reach it. */
.field-effective_permissions .readonly {
    overflow-x: auto;
}

/* --- Admin inlines (e.g. Roles on the user page) ---
   Jazzmin renders inline formsets as `.table.table-hover.text-nowrap`
   inside `fieldset.module`. text-nowrap guarantees the table is wider than
   a phone, and nothing in the chain scrolls, so the Delete?/Remove column
   was unreachable. fieldset.module is the closest box that can own the
   scroll without also clipping the "Add another" link below the table. */
.inline-group .inline-related > fieldset.module {
    overflow-x: auto;
}

@media (max-width: 575.98px) {
    /* Jazzmin puts .p-5 (3rem each side) on change-form card bodies, which
       spends 96px of a 360px viewport on padding alone and was pushing the
       form controls themselves out of the card. */
    .card-body.p-5 {
        padding: 1rem !important;
    }

    /* The tab strip already scrolls horizontally (.dfa-tabs above); trim
       its content padding so the panes get the width back. */
    .dfa-tab-content.p-3 {
        padding: 0.75rem !important;
    }

    /* Jazzmin marks every top-menu item `d-none d-sm-inline-block`, so
       below 576px the navbar lost "Manage Projects" — the hub this whole
       app is built around, and the reason it is first in topmenu_links.
       Bring just that one back as an icon-only pill (the ::before glyph
       defined above): there isn't room for both labels at this width, and
       "View Site" is the more expendable of the two on a phone. font-size:0
       hides the label visually while leaving it in the accessibility tree,
       so the link is still announced as "Manage Projects". */
    .app-header .nav-item:has(> .nav-link[href*="/projects/overview/"]) {
        display: inline-block !important;
    }
    .app-header .nav-link[href*="/projects/overview/"] {
        font-size: 0;
        padding: 0.35rem 0.65rem !important;
    }
    .app-header .nav-link[href*="/projects/overview/"]::before {
        font-size: 0.95rem;
    }
}

/* Between sm and md the navbar carries the hamburger, both top-menu links
   and the user's email at once; 14rem of email is enough to push the row
   into a second line. */
@media (max-width: 767.98px) {
    .dfa-user-label {
        max-width: 8rem;
    }
}

/* --- Admin widgets with intrinsic HTML sizing ---
   Django's admin ships its form widgets with hardcoded dimensions
   (cols=40 on vLargeTextField, size=N on the vTextField family) that take
   no account of the viewport, so at ~320px the description textarea on a
   change form reached past the right edge of the page. Capping with
   max-width rather than setting width leaves those intrinsic widths intact
   wherever they already fit — which the password-toggle layout above
   deliberately depends on (see .dfa-password-wrap). */
textarea,
.vTextField,
.vLargeTextField,
.vURLField,
.vIntegerField,
.vBigIntegerField,
.vForeignKeyRawIdAdminField {
    max-width: 100%;
}

/* --- Change forms on a phone ---
   Below 576px every field already sits in a single full-width column, but
   the text inputs kept their intrinsic ~185px width while the selects
   beside them stretched edge to edge, leaving a ragged column of short
   boxes. Full width matches them up and gives long values (hostnames,
   URLs) room. The project workspace form manages its own field layout,
   so it is left out. The password toggle wrapper becomes a full-width
   flex row so its input can grow the same way. */
@media (max-width: 575.98px) {
    form:not(#dfa-project-builder) :is(
        .vTextField,
        .vURLField,
        .vEmailField,
        .vIntegerField,
        .vBigIntegerField,
        .vLargeTextField
    ) {
        width: 100%;
    }
    form:not(#dfa-project-builder) .dfa-password-wrap {
        display: flex;
        width: 100%;
    }
    form:not(#dfa-project-builder) .dfa-password-wrap input {
        flex: 1 1 auto;
        min-width: 0;
    }
}

/* --- Changelists on small screens ---
   jazzmin already wraps #result_list in a scrolling .table-responsive, but
   the table first squeezes every column to fit, so a hostname such as
   "ncert-web-dev-01" broke across four lines and a timestamp across five.
   Keeping cells on one line lets the existing scroll container do its job
   and keeps each row one line tall. */
@media (max-width: 767.98px) {
    #result_list th,
    #result_list td {
        white-space: nowrap;
    }
}
/* The date drill-down above a date_hierarchy changelist (Audit logs) is a
   single btn-group, which ran 8px past a 320px viewport with four months
   in it. */
.date-hierarchy {
    flex-wrap: wrap;
    max-width: 100%;
}

/* --- Project list (common/templates/admin/project_overview_list.html) --- */
.dfa-search {
    max-width: 28rem;
}

/* Below 576px each project row becomes a small labelled card instead of a
   five-column table squeezed into a phone, which pushed the View/Edit
   buttons off the edge. The column heading travels with each cell as its
   data-label, so nothing a reader saw in the header is lost. */
@media (max-width: 575.98px) {
    .dfa-stack-table thead {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip: rect(0 0 0 0);
        white-space: nowrap;
    }
    .dfa-stack-table,
    .dfa-stack-table tbody,
    .dfa-stack-table tr,
    .dfa-stack-table td {
        display: block;
        width: 100%;
    }
    .dfa-stack-table tr {
        border-bottom: 1px solid var(--bs-border-color);
    }
    .dfa-stack-table tr:last-child {
        border-bottom: 0;
    }
    /* Padding lives on the cells, not the row, so the striped background
       still fills the card edge to edge. The selector out-ranks AdminLTE's
       `.card-body.p-0 .table tbody > tr > td:first-of-type` 1.5rem indent
       only on :first-child (equal specificity, later in the cascade), so
       that rule restates padding-left — otherwise the first line of every
       card sat 8px further in than the rest. */
    .card-body .dfa-stack-table.table > tbody > tr > td {
        display: flex;
        gap: 0.75rem;
        padding: 0.2rem 1rem;
        border: 0;
        text-align: left;
        overflow-wrap: anywhere;
    }
    .card-body .dfa-stack-table.table > tbody > tr > td:first-child {
        padding-top: 0.6rem;
        padding-left: 1rem;
    }
    .card-body .dfa-stack-table.table > tbody > tr > td:last-child {
        padding-bottom: 0.6rem;
    }
    .dfa-stack-table td[data-label]::before {
        content: attr(data-label);
        flex: 0 0 6.5rem;
        font-weight: 600;
        color: var(--bs-secondary-color);
    }
    .card-body .dfa-stack-table.table > tbody > tr > td.dfa-stack-actions {
        justify-content: flex-start;
        gap: 0.4rem;
        padding-top: 0.5rem;
    }
}

/* --- Project overview header (project_overview_detail.html) --- */
.dfa-project-title {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.25rem 0.5rem;
    overflow-wrap: anywhere;
}
.dfa-project-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 1rem;
}
.dfa-project-meta > span {
    overflow-wrap: anywhere;
}

/* =====================================================================
   Phone / tablet fixes (2026-10-09 responsiveness pass)
   ===================================================================== */

/* jazzmin renders every page with `sidebar-open`, and AdminLTE only
   collapses it once its script runs at the end of <body> — until then a
   phone shows the menu over the left 250px of the page. Start it closed
   below lg; AdminLTE takes over once loaded (it adds .app-loaded). */
@media (max-width: 991.98px) {
    body.sidebar-expand-lg:not(.app-loaded):not(.sidebar-collapse) .app-sidebar {
        margin-left: calc(-1 * var(--lte-sidebar-width, 250px));
    }
}

/* iOS Safari zooms the page on focus of any control under 16px, and the
   page then pans sideways. */
@media (max-width: 767.98px) and (pointer: coarse) {
    .dfa-field input,
    .dfa-field textarea,
    .dfa-field select,
    .dfa-ws .form-control,
    .dfa-ws .form-select,
    .select2-search__field,
    #searchbar,
    .dfa-search .form-control {
        font-size: 16px !important;
    }
}

/* jazzmin gives date/time inputs min-width:200px, which beats any
   max-width and pushes the date+time row past a 375px screen. */
.dfa-field .vDateField,
.dfa-field .vTimeField,
.dfa-ws .vDateField,
.dfa-ws .vTimeField {
    min-width: 0 !important;
}

/* Touch targets: at least ~40px on touch screens. */
@media (pointer: coarse) {
    .dfa-builder-row-remove {
        min-width: 2.5rem;
        min-height: 2.5rem;
        padding: 0 0.5rem !important;
    }
    .dfa-builder-row-bar {
        gap: 0.25rem;
    }
    .dfa-ws-pane-tools .btn-link,
    .dfa-ws-chip,
    .dfa-password-toggle {
        min-height: 2.5rem;
    }
    .dfa-ws .related-widget-wrapper-link,
    .dfa-ws .datetimeshortcuts a {
        min-width: 2.5rem;
        min-height: 2.5rem;
    }
}
@media (max-width: 991.98px) {
    .dfa-ws-step {
        min-height: 2.75rem;
    }
    /* The workspace Save bar is sticky at the bottom; with the on-screen
       keyboard up it sat on top of the field being typed into. */
    body:has(#dfa-project-builder input:focus, #dfa-project-builder textarea:focus, #dfa-project-builder select:focus) .dfa-builder-actions {
        position: static;
    }
}
@media (max-width: 575.98px) {
    /* The change summary wraps to its own line here and doubles the bar's
       height; the Review step shows the same thing in full. */
    .dfa-builder-actions [data-dfa-summary] {
        display: none !important;
    }
}

/* Permission matrix: the whole cell is the hit area, not a 16px box. */
.dfa-matrix-hit {
    display: grid;
    place-items: center;
    min-width: 2.75rem;
    min-height: 2.75rem;
    margin: 0;
    cursor: pointer;
}
.dfa-matrix-cell:has(.dfa-matrix-hit) {
    padding: 0 !important;
}

/* Long names, hostnames and URLs wrap instead of widening the page. */
.dfa-title-name,
.dfa-ws-review-item,
.dfa-ws-review-item > * {
    overflow-wrap: anywhere;
    min-width: 0;
}
.dfa-ws-hero .me-auto {
    min-width: 0;
    overflow-wrap: anywhere;
}
.dfa-tab-content td a[target="_blank"],
.dfa-stack-table td {
    overflow-wrap: anywhere;
}

/* Changelist toolbar on phones: search full width, filters two per row
   instead of one each, so the results aren't pushed a screen down. */
@media (max-width: 767.98px) {
    #changelist-search > .form-group {
        flex: 1 1 calc(50% - 0.5rem);
        min-width: 0;
        margin: 0;
    }
    #changelist-search .select2-container {
        width: 100% !important;
    }
    #searchbar {
        min-width: 0 !important;
        flex: 1 1 100%;
    }
}

/* Bootstrap only centres modals from sm up. */
.modal.modal-wide .modal-dialog {
    margin-inline: auto;
}

/* Small print never below 12px on phones. */
@media (max-width: 575.98px) {
    .dfa-eyebrow,
    .dfa-stat-growth,
    .dfa-column-label,
    .dfa-chip,
    .dfa-tab-badge,
    .dfa-count-pill,
    .dfa-stat-sub,
    .dfa-alert-hint,
    .dfa-section-count,
    .dfa-row-meta,
    .dfa-row-side,
    .dfa-timeline-meta {
        font-size: 0.75rem !important;
    }
}

/* Visible keyboard focus (the default ring here was ~15% opacity). */
.dfa-field :is(input, select, textarea):focus-visible,
.dfa-ws :is(.form-control, .form-select):focus-visible {
    box-shadow: 0 0 0 0.2rem rgba(152, 51, 101, 0.45) !important;
}
a:focus-visible,
.btn:focus-visible,
.nav-link:focus-visible {
    outline: 2px solid var(--dfa-primary);
    outline-offset: 2px;
}

/* Reduced motion: no smooth scrolling or animated transitions. */
@media (prefers-reduced-motion: reduce) {
    html {
        scroll-behavior: auto !important;
    }
    *,
    *::before,
    *::after {
        transition-duration: 0.01ms !important;
        animation-duration: 0.01ms !important;
    }
}

/* Dark mode (if a stored theme turns it on): the brand maroon on a dark
   background was unreadable on active tabs/steps. */
[data-bs-theme="dark"] .dfa-tabs .nav-link.active,
[data-bs-theme="dark"] .dfa-ws-step.active,
[data-bs-theme="dark"] .dfa-ws-pane-head h5 {
    color: #f3d4e4 !important;
}
[data-bs-theme="dark"] .dfa-tab-badge {
    background: var(--bs-secondary-bg);
    color: var(--bs-emphasis-color);
}
