/*
 * UserMetric — Metronic layer overrides
 * ---------------------------------------------------------------------------
 * Loaded AFTER plugins.bundle.css and style.bundle.css, so anything here wins.
 *
 * SCOPE RULE: layout and app-specific structure only.
 *
 * The palette is locked to Metronic's stock demo37 tokens (primary #1B84FF,
 * canvas #F9F9F9). Do NOT add brand colour overrides here. If you find yourself
 * writing a hex value for a brand colour, stop — either a Metronic utility
 * already does the job, or the palette decision is being reopened, which is a
 * conversation rather than a CSS edit.
 * See .kiro/steering/metronic-asset-component-map.md
 *
 * Semantic colours, hover states, dark-mode variants and the -light/-clarity
 * families all come from Metronic. Reference them with var(--bs-*) rather than
 * restating values.
 *
 * This file must never be loaded by the admin surface, which stays on
 * Bootstrap 4.
 */

/* ---------------------------------------------------------------------------
 * Auth surface (slice 1)
 * --------------------------------------------------------------------------- */

/* The brand aside on the auth split-screen. Colour comes from Metronic tokens
   so dark mode is handled without a second declaration. */
.um-auth-aside {
    background-color: var(--bs-primary);
    background-image: url("../media/misc/auth-bg.png");
    background-size: cover;
    background-position: center;
}

/* Captcha images and widgets are emitted by the shared Captcha core, which we
   cannot change. These rules keep its output inside Metronic's shape. */
.um-captcha img#captcha {
    border: 1px solid var(--bs-border-color);
}

.um-captcha input[name="captcha"] {
    max-width: 220px;
}

/* The Captcha core hardcodes pixel sizes for the third-party widgets; centre
   them rather than fighting the dimensions. */
.um-captcha .g-recaptcha,
.um-captcha .h-captcha,
.um-captcha .cf-turnstile {
    margin-inline: auto;
}

/* Alerts::output_field_error() emits .invalid-feedback with d-inline-block so it
   shows without a sibling .is-invalid. Give it Metronic's error tone. */
.invalid-feedback.d-inline-block {
    color: var(--bs-danger);
    font-size: 0.925rem;
    font-weight: 500;
    margin-top: 0.35rem;
}

/* ---------------------------------------------------------------------------
 * App shell (slice 2)
 * --------------------------------------------------------------------------- */

/* Fallback brand mark for when no logo is uploaded. Uses Metronic's primary
   token, so it follows the palette and dark mode. */
.um-sidebar-brandmark {
    width: 36px;
    height: 36px;
    border-radius: var(--bs-border-radius-sm);
    background-color: var(--bs-primary);
    color: var(--bs-primary-inverse);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: 1.1rem;
    flex-shrink: 0;
    text-transform: uppercase;
}

/* REMOVED, and the removal matters enough to record here.
 *
 * Slice 2 added these three, back when the shell was a conventional flush admin layout:
 *
 *   .app-sidebar { background-color: var(--bs-body-bg); border-right: 1px solid var(--bs-border-color) }
 *   .app-header  { background-color: var(--bs-body-bg); border-bottom: 1px solid var(--bs-border-color) }
 *   .app-footer  { border-top: 1px solid var(--bs-border-color) }
 *
 * They quietly defeated three separate demo37 behaviours once the chrome was rebuilt on
 * demo37's layout, and cost three rounds of "the sidebar still isn't right":
 *
 * 1. .app-sidebar painted an opaque white SQUARE across the sidebar's whole fixed box, from
 *    top:160px to the bottom of the viewport. .app-sidebar-wrapper sits on top of it with
 *    border-radius: 1.5rem and the same white background, so the rounded card was invisible
 *    against an identical white square and the sidebar read as a flush panel. The border-right
 *    drew the straight vertical edge that made it look welded to the page.
 *
 * 2. .app-header forced the header opaque white with a hard bottom border. demo37 leaves
 *    --bs-app-header-base-bg-color and --bs-app-header-base-box-shadow UNDEFINED on purpose -
 *    zero definitions anywhere in style.bundle.css - so its resting header is transparent, and
 *    only the sticky state paints, via --bs-app-header-sticky-bg-color: #ffffff plus
 *    --bs-app-header-sticky-box-shadow. Overriding the base colour meant the header was always
 *    white and the sticky transition was invisible.
 *
 * 3. .app-footer's top border is not a demo37 detail; its footer is separated by whitespace.
 *
 * The lesson, which is now in the steering file: before adding a rule for a Metronic region,
 * check whether the token it targets is deliberately left undefined. An undefined token IS the
 * design in this theme, not an omission to be filled in.
 */

/* ===========================================================================
 * TEMPORARY: Bootstrap 4 to 5 compatibility shim
 * ===========================================================================
 * Companion to the JS shim in usermetric.js. Maps the Bootstrap 4 utility and
 * component classes that legacy content views still emit onto their Bootstrap 5
 * equivalents, so the app stays usable while the shell lands ahead of the
 * content slices.
 *
 * REMOVAL CRITERIA: delete this block when no non-admin view emits Bootstrap 4
 * classes. The forbidden-pattern grep in
 * .kiro/steering/metronic-migration-workflow.md is the check.
 *
 * Deliberately narrow. This is a bridge, not a translation layer — do not grow
 * it to cover styling, only to keep layout from collapsing.
 * =========================================================================== */

/* Directional spacing: Bootstrap 4 ml-/mr-/pl-/pr- became ms-/me-/ps-/pe-. */
.ml-0 { margin-left: 0 !important; }
.ml-1 { margin-left: 0.25rem !important; }
.ml-2 { margin-left: 0.5rem !important; }
.ml-3 { margin-left: 0.75rem !important; }
.ml-4 { margin-left: 1.25rem !important; }
.ml-5 { margin-left: 1.5rem !important; }
.ml-auto { margin-left: auto !important; }

.mr-0 { margin-right: 0 !important; }
.mr-1 { margin-right: 0.25rem !important; }
.mr-2 { margin-right: 0.5rem !important; }
.mr-3 { margin-right: 0.75rem !important; }
.mr-4 { margin-right: 1.25rem !important; }
.mr-5 { margin-right: 1.5rem !important; }
.mr-auto { margin-right: auto !important; }

.pl-0 { padding-left: 0 !important; }
.pl-1 { padding-left: 0.25rem !important; }
.pl-2 { padding-left: 0.5rem !important; }
.pl-3 { padding-left: 0.75rem !important; }
.pl-4 { padding-left: 1.25rem !important; }
.pl-5 { padding-left: 1.5rem !important; }

.pr-0 { padding-right: 0 !important; }
.pr-1 { padding-right: 0.25rem !important; }
.pr-2 { padding-right: 0.5rem !important; }
.pr-3 { padding-right: 0.75rem !important; }
.pr-4 { padding-right: 1.25rem !important; }
.pr-5 { padding-right: 1.5rem !important; }

@media (min-width: 992px) {
    .ml-lg-0 { margin-left: 0 !important; }
    .ml-lg-3 { margin-left: 0.75rem !important; }
    .mr-lg-0 { margin-right: 0 !important; }
    .mr-lg-3 { margin-right: 0.75rem !important; }
    .pl-lg-5 { padding-left: 1.5rem !important; }
    .pr-lg-5 { padding-right: 1.5rem !important; }
}

/* Form and text utilities dropped in Bootstrap 5. */
.form-group { margin-bottom: 1rem; }
.form-row { display: flex; flex-wrap: wrap; margin-right: -5px; margin-left: -5px; }
.form-row > [class*="col-"] { padding-right: 5px; padding-left: 5px; }
.btn-block { display: block; width: 100%; }
.font-weight-normal { font-weight: 400 !important; }
.font-weight-bold { font-weight: 600 !important; }
.font-weight-600 { font-weight: 600 !important; }
.text-left { text-align: left !important; }
.text-right { text-align: right !important; }

/* Bootstrap 4 custom form controls. Mapped to Bootstrap 5 equivalents so
   checkboxes and switches stay usable rather than rendering as bare inputs. */
.custom-control { position: relative; display: block; min-height: 1.5rem; padding-left: 1.75rem; }
.custom-control-input { position: absolute; left: 0; z-index: -1; width: 1rem; height: 1.25rem; opacity: 0; }
.custom-control-label { position: relative; margin-bottom: 0; vertical-align: top; cursor: pointer; }
.custom-select { display: block; width: 100%; }

/* The dismiss button: .btn-close draws its own X, so the legacy &times; glyph
   inside the button is suppressed to avoid showing two. */


/* Bootstrap 4 input-group append/prepend wrappers. Bootstrap 5 drops them and
   expects siblings, so they are flattened rather than positioned. */
.input-group-append,
.input-group-prepend { display: flex; }
.input-group-append .input-group-text,
.input-group-prepend .input-group-text { height: 100%; }

/* ---------------------------------------------------------------------------
 * Shared partials (slice 3)
 * --------------------------------------------------------------------------- */

/* Floating scroll controls. The legacy version inlined these as a style
   attribute; z-index sits below Metronic's drawer and modal layers so it cannot
   overlap an open dialog. */
.um-scroll-controls {
    position: fixed;
    right: 1rem;
    bottom: 1rem;
    z-index: 90;
    display: flex;
    flex-direction: column;
}

/* ---------------------------------------------------------------------------
 * Websites (slice 4)
 * --------------------------------------------------------------------------- */

/* Replaces the legacy .pre-custom. Used for the tracking snippet and the public
   statistics URL, both of which users copy out, so it wraps rather than
   scrolling horizontally. */
.um-code-block {
    background-color: var(--bs-gray-100);
    border: 1px solid var(--bs-border-color);
    color: var(--bs-gray-800);
    padding: 1rem;
    font-size: 0.875rem;
    line-height: 1.6;
    white-space: pre-wrap;
    word-break: break-all;
    max-height: 240px;
    overflow-y: auto;
}

/* Tooltips carrying a usage breakdown need more room than Metronic's default. */
.tooltip.um-tooltip .tooltip-inner,
.um-tooltip .tooltip-inner {
    max-width: 320px;
    text-align: left;
}

/* Form blackout overlay used by pause_submit_button(). Replaces the legacy
   .loading-overlay, which was defined in the altum stylesheet. */
.um-loading-overlay {
    position: absolute;
    inset: 0;
    z-index: 10;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: rgba(var(--bs-body-bg-rgb), 0.7);
    border-radius: var(--bs-border-radius);
}

/* Replaces the legacy .container-disabled / .container-disabled-simple, used to
   grey out plan-gated controls while leaving their tooltip clickable. */
.um-container-disabled {
    opacity: 0.5;
    pointer-events: none;
    user-select: none;
}

/* Chart watermark. Stands in for the Chart.js watermark plugin the legacy
   js_chart_defaults partial loaded. The image URL arrives as the
   --um-chart-watermark custom property, set by um_chart_watermark() in
   views/metronic/partials/js_chart_defaults.php, because a background image
   cannot carry its own opacity. */
.um-chart-watermark {
    position: relative;
}

.um-chart-watermark::after {
    position: absolute;
    inset: 0;
    z-index: 0;
    content: "";
    background-image: var(--um-chart-watermark);
    background-repeat: no-repeat;
    background-position: right 12px bottom 12px;
    background-size: auto 28px;
    opacity: 0.05;
    pointer-events: none;
}

/* Panel switcher rows on the dashboard hold up to three reports. On a narrow
   card they scroll horizontally rather than wrapping onto a second line, which
   would push the report itself out of alignment across the grid. */
.um-panel-nav {
    flex-wrap: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
    gap: 0.25rem;
}

/* Segmented control rather than nav-line-tabs.
 *
 * These strips previously used nav-line-tabs-2x, whose 2px underline sat inside a card between
 * the card's own header rule above and the ranked rows below - three horizontal lines competing
 * within about 60px. At fs-8 the underline was also nearly as tall as the text it underlined.
 *
 * A pill reads as a control rather than as page navigation, which is what this is: it switches
 * which report the panel body shows, it does not move you anywhere.
 *
 * Colour is deliberately neutral, not family-tinted. The family accent belongs to the rows,
 * where it identifies the data; spending it on a control would make the loudest thing in the
 * panel the least informative. It also keeps this working with the tab JS, which toggles only
 * `active` and `text-muted` - so inactive links take Bootstrap's own muted colour and the
 * active one takes the rule below. */
.um-panel-nav .nav-link {
    padding: 0.35rem 0.7rem;
    border-radius: var(--bs-border-radius-sm);
    color: var(--bs-gray-600);
    white-space: nowrap;
    transition: background-color 0.15s ease, color 0.15s ease;
}

.um-panel-nav .nav-link:hover:not(.active) {
    background-color: var(--bs-gray-100);
    color: var(--bs-gray-800);
}

.um-panel-nav .nav-link.active {
    background-color: var(--bs-gray-200);
    color: var(--bs-gray-900);
}

.um-panel-nav::-webkit-scrollbar {
    display: none;
}

/* Heatmap viewer. The rrweb replay of the page renders into .um-heatmap-container
   and the simpleheat canvas is layered over it. Both rules are carried over from
   the altum stylesheet, which Metronic pages do not load, and renamed to the um-
   prefix so nothing here depends on that file. */
.um-heatmap-container {
    position: relative;
    height: 900px;
    overflow: scroll;
    overflow-x: hidden;
    transform-origin: top left;
}

.um-heatmap-canvas {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 1;
    opacity: 0.75;
}

/* Session replay player.
   rrweb-player has no Metronic equivalent, so the player keeps its own chrome and
   these rules blend it into the surrounding card. Carried over from the inline
   <style> block in views/replay/index.php, with the altum colour variables swapped
   for Metronic's: --white becomes --bs-body-bg, --gray-200 becomes --bs-gray-200,
   --primary becomes --bs-primary and --border-radius becomes --bs-border-radius.
   Doing it here rather than inline means it follows dark mode for free. */
.rr-player {
    background-color: var(--bs-body-bg) !important;
    border-radius: var(--bs-border-radius) !important;
    box-shadow: none !important;
}

.rr-controller {
    background-color: var(--bs-body-bg) !important;
}

.rr-progress {
    background: var(--bs-gray-200) !important;
    border-top: solid 4px var(--bs-body-bg) !important;
    border-bottom: solid 4px var(--bs-body-bg) !important;
}

.rr-progress__handler {
    background: var(--bs-primary) !important;
}

.rr-controller__btns .active {
    background-color: var(--bs-primary) !important;
}

/* The skip-inactive switch and the fullscreen button are hidden, as they were:
   neither behaves correctly inside a scaled container. */
.rr-controller__btns .switch,
.rr-controller__btns button:last-child {
    display: none !important;
}

.replayer-wrapper,
.rr-player iframe {
    border-radius: var(--bs-border-radius) !important;
}

.rr-player__frame {
    overflow: hidden;
}

/* The event timeline beside the player scrolls independently of the page. */
.um-replay-timeline {
    max-height: 320px;
    overflow-y: auto;
}

/* ---------------------------------------------------------------------------
   Third-party brand colours, per element
   ---------------------------------------------------------------------------
   Payment processors carry a brand colour and a dark-mode variant in
   app/includes/payment_processors.php. This is data-driven colour from a config
   file, not a palette override, so it stays out of the token layer: the view
   sets --um-brand-color / --um-brand-color-dark inline and this picks the right
   one for the active theme.

   Replaces `[data-theme-style="dark"] svg[data-custom-colors]` in the legacy
   custom.css, which never fired on these icons - it matches `svg` and they are
   `<i>` - so dark mode kept the light brand colour and near-black processors
   like offline_payment (#393f4a) all but vanished. Metronic also keys dark mode
   off data-bs-theme, not data-theme-style. */
.um-brand-icon {
    color: var(--um-brand-color, currentColor);
}
[data-bs-theme="dark"] .um-brand-icon {
    color: var(--um-brand-color-dark, var(--um-brand-color, currentColor));
}

/* ---------------------------------------------------------------------------
   Printable documents: invoices and credit notes
   ---------------------------------------------------------------------------
   Replaces the .invoice / .invoice-table / .invoice-logo rules that lived in the
   legacy custom.css, which Metronic pages do not load. Those were:

     .invoice-table th  { border-top: 0 }
     .invoice-table td  { vertical-align: baseline }
     @media print {
         .invoice      { page-break-after: always }
         .invoice-logo { filter: grayscale(100%) }
         body, html, .container { height: auto; overflow: visible }
     }

   The two table rules are no longer needed - Metronic's .table-row-dashed and
   .align-middle already do that job - so only the print behaviour carries over. */

/* Each document is its own page, so a payment with three refunds prints as three
   credit notes rather than one long ribbon. */
.um-document-page {
    box-shadow: none;
}

/* CANCELLED stamped across a voided invoice. Was an inline-styled absolutely
   positioned span with a hardcoded 6rem and a rotate transform. */
.um-document-watermark {
    position: absolute;
    inset: 0;
    z-index: 10;
    display: flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;
}
.um-document-watermark span {
    font-size: clamp(3rem, 12vw, 7rem);
    font-weight: 700;
    line-height: 1;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--bs-danger);
    opacity: .28;
    transform: rotate(-24deg);
    white-space: nowrap;
}

@media print {
    /* Metronic's app shell constrains height and hides overflow, which truncates a
       document to one screen's worth. */
    html,
    body.um-document,
    body.um-document .container-fluid {
        height: auto !important;
        overflow: visible !important;
        background: #fff !important;
    }

    body.um-document {
        padding: 0 !important;
    }

    .um-document-page {
        page-break-after: always;
        border: 0 !important;
        box-shadow: none !important;
        margin-bottom: 0 !important;
    }

    /* Last document should not force a trailing blank page. */
    .um-document-page:last-child {
        page-break-after: auto;
    }

    .um-document-page .card-body {
        padding: 0 !important;
    }

    /* Colour logos print poorly on mono printers and waste ink on colour ones. */
    .um-invoice-logo {
        filter: grayscale(100%);
    }

    /* Keep the void stamp, but lighter so the figures underneath stay legible on
       paper, where opacity reads darker than on screen. */
    .um-document-watermark span {
        opacity: .18;
    }
}

/* ---------------------------------------------------------------------------
   Checkout order summary
   ---------------------------------------------------------------------------
   Replaces the legacy `.sticky` class from index-custom.css, which the pay page
   used to pin its summary column. That stylesheet is no longer loaded on Metronic
   pages, and a bare `position: sticky; top: 0` would tuck the card under
   Metronic's fixed app header.

   Only sticky from xl up, which is where the checkout becomes two columns. Below
   that the summary sits in normal flow beneath the choices, so pinning it would
   cover them. */
@media (min-width: 1200px) {
    .um-checkout-summary {
        position: sticky;
        top: calc(var(--bs-app-header-height, 70px) + 1.5rem);
    }
}

/* ---------------------------------------------------------------------------
   um-prose: admin-authored rich text
   ---------------------------------------------------------------------------
   Custom pages and blog posts store HTML written in the admin editor, and it
   arrives as bare tags with no classes on them, so it can only be styled from a
   wrapper. Metronic ships no typography plugin, so this is authored.

   Replaces the two legacy rules from custom.css, which no Metronic page loads:

     .blog-post-content       { line-height: 1.75; word-break: break-word }
     .blog-post-content p     { margin-bottom: 1.5rem }

   Everything else here is new: the legacy theme left headings, lists, tables,
   quotes and code in the content unstyled beyond Bootstrap's defaults.

   All colour and spacing comes from Metronic tokens, so dark mode follows for
   free and a palette change would carry through. */
.um-prose {
    line-height: 1.75;
    word-break: break-word;
    color: var(--bs-gray-800);
}
.um-prose > :first-child {
    margin-top: 0;
}
.um-prose > :last-child {
    margin-bottom: 0;
}
.um-prose p {
    margin-bottom: 1.5rem;
}
.um-prose h1,
.um-prose h2,
.um-prose h3,
.um-prose h4,
.um-prose h5,
.um-prose h6 {
    color: var(--bs-gray-900);
    font-weight: 600;
    margin-top: 2.5rem;
    margin-bottom: 1rem;
    line-height: 1.35;
}
.um-prose h1 { font-size: 1.75rem; }
.um-prose h2 { font-size: 1.5rem; }
.um-prose h3 { font-size: 1.25rem; }
.um-prose h4,
.um-prose h5,
.um-prose h6 { font-size: 1.1rem; }

.um-prose a {
    color: var(--bs-primary);
    text-decoration: underline;
    text-underline-offset: 2px;
}
.um-prose a:hover {
    color: var(--bs-primary-active);
}
.um-prose ul,
.um-prose ol {
    margin-bottom: 1.5rem;
    padding-left: 1.5rem;
}
.um-prose li {
    margin-bottom: .5rem;
}
.um-prose img,
.um-prose video,
.um-prose iframe {
    max-width: 100%;
    height: auto;
    border-radius: var(--bs-border-radius);
    margin: .5rem 0 1.5rem;
}
.um-prose blockquote {
    margin: 0 0 1.5rem;
    padding: .75rem 1.25rem;
    border-left: 3px solid var(--bs-primary);
    background: var(--bs-gray-100);
    border-radius: 0 var(--bs-border-radius) var(--bs-border-radius) 0;
    color: var(--bs-gray-700);
}
.um-prose blockquote > :last-child {
    margin-bottom: 0;
}
.um-prose code {
    background: var(--bs-gray-100);
    color: var(--bs-gray-900);
    padding: .15rem .4rem;
    border-radius: var(--bs-border-radius-sm);
    font-size: .9em;
}
.um-prose pre {
    background: var(--bs-gray-100);
    color: var(--bs-gray-900);
    padding: 1rem 1.25rem;
    border-radius: var(--bs-border-radius);
    margin-bottom: 1.5rem;
    overflow-x: auto;
}
.um-prose pre code {
    background: none;
    padding: 0;
}
.um-prose hr {
    margin: 2rem 0;
    border-color: var(--bs-border-color);
    opacity: 1;
}
.um-prose table {
    width: 100%;
    margin-bottom: 1.5rem;
    border-collapse: collapse;
}
.um-prose th,
.um-prose td {
    padding: .65rem .85rem;
    border: 1px solid var(--bs-border-color);
    text-align: start;
}
.um-prose th {
    background: var(--bs-gray-100);
    color: var(--bs-gray-900);
    font-weight: 600;
}

/* Quill output needs two quirks handled. Emitted only when the page was written in
   the wysiwyg editor, so plain-HTML pages keep the normal paragraph rhythm.

   Quill puts every line in its own <p>, including blank ones, so the 1.5rem
   paragraph margin above compounds into huge gaps. And it marks bullet lists as
   <ol><li data-list="bullet">, which without this renders numbers instead of
   bullets. Both rules are carried over from the legacy custom.css. */
.um-prose.ql-content p {
    margin-bottom: 0;
}
.um-prose.ql-content li[data-list="bullet"] {
    list-style-type: disc;
}
.um-prose.ql-content ol,
.um-prose.ql-content ul {
    margin-bottom: 1rem;
}

/* ---------------------------------------------------------------------------
   Blog
   ---------------------------------------------------------------------------
   Replaces the legacy .blog-post-image rule. The popular-widget thumbnail no
   longer needs one: Metronic has w-40px, h-40px and object-fit-cover, so the
   3rem square from .blog-post-image-popular is expressed in utilities. Only the
   max-height on the full-width post image has no utility equivalent. */
.um-blog-image {
    max-height: 20rem;
}

/* ---------------------------------------------------------------------------
   Blog post rating stars
   ---------------------------------------------------------------------------
   TWO LEGACY RULES WERE DEAD:

     .rating-star svg { color: #ffc107; transition: color .15s }

   targeted an <svg>, but the markup renders <i class="fas fa-star">, so the gold
   never applied and the stars drew in body text colour. And `.rating-star-chosen`,
   the class the click handler adds to mark your own rating, had NO RULE AT ALL -
   so rating a post produced no visible feedback whatsoever.

   Both are fixed here. The dimmed state stays server-rendered from the average,
   and the chosen state is now actually visible. */
.um-rating-star {
    cursor: pointer;
    color: var(--bs-warning);
    transition: transform .15s ease, opacity .15s ease;
    line-height: 1;
}
.um-rating-star:hover {
    transform: scale(1.15);
    opacity: 1;
}
/* Below the average: shown but muted, which is how the average is communicated. */
.um-rating-star-dim {
    opacity: .35;
}
/* The rating this visitor gave. Full strength plus a ring so it reads as "yours"
   rather than just "lit". */
.um-rating-star-chosen {
    opacity: 1;
}
.um-rating-star-chosen::after {
    content: "";
    display: block;
    height: 2px;
    border-radius: 2px;
    background: var(--bs-warning);
    margin-top: 2px;
}

/* ---------------------------------------------------------------------------
   Landing page
   ---------------------------------------------------------------------------
   demo37 is an admin template and ships no marketing pages, so the landing page
   layout is ours. These are the only rules it needs - everything else on that
   page is a stock Metronic utility or component.

   Colour is never hardcoded here. Every value is a Metronic token, which is what
   makes the page follow light/dark and any future rebrand for free. The one place
   that has to stay dark in both themes, the API code sample, does it by nesting
   data-bs-theme="dark" on the element rather than by writing hex values - see the
   terminal block below. */

/* Hero -------------------------------------------------------------------- */
.um-hero {
    position: relative;
    padding-top: 2rem;
}

/* Soft primary glow behind the hero. Deliberately bounded to the content column
   on the left and right: a 100vw bleed would add a horizontal scrollbar on any
   viewport narrower than the container plus the bleed, and nothing in the
   Metronic shell sets overflow-x on <body> to catch it.

   Painted from ::before so it stays below the hero content: both are positioned
   with z-index auto, so they paint in tree order. */
.um-hero::before {
    content: "";
    position: absolute;
    top: -4rem;
    right: 0;
    left: 0;
    height: 34rem;
    pointer-events: none;
    background-image:
        radial-gradient(60% 100% at 50% 0%, rgba(var(--bs-primary-rgb), .10) 0%, rgba(var(--bs-primary-rgb), 0) 70%),
        radial-gradient(40% 60% at 85% 15%, rgba(var(--bs-primary-rgb), .06) 0%, rgba(var(--bs-primary-rgb), 0) 70%);
}

/* Dark canvases swallow a 10% tint, so it is lifted rather than restated. */
[data-bs-theme="dark"] .um-hero::before {
    background-image:
        radial-gradient(60% 100% at 50% 0%, rgba(var(--bs-primary-rgb), .20) 0%, rgba(var(--bs-primary-rgb), 0) 70%),
        radial-gradient(40% 60% at 85% 15%, rgba(var(--bs-primary-rgb), .12) 0%, rgba(var(--bs-primary-rgb), 0) 70%);
}

/* Fluid type scale. Metronic's fs-* utilities are fixed sizes, and a hero
   headline is the one place on the site that needs to scale with the viewport
   instead of stepping at breakpoints. */
.um-hero-title {
    max-width: 46rem;
    font-size: clamp(2rem, 5.2vw, 3.5rem);
    line-height: 1.15;
    letter-spacing: -.02em;
}

.um-hero-stats {
    max-width: 36rem;
    margin-inline: auto;
}

/* The two figures count up from zero, so their glyphs are locked to equal widths.
   Without this the surrounding words shuffle sideways for the whole animation. */
.um-hero-stats [data-kt-countup] {
    font-variant-numeric: tabular-nums;
}

/* Browser-style frame ------------------------------------------------------
   Product screenshots are flat images on a flat page. The frame is what makes
   them read as a product rather than as decoration. Shared by the hero shot and
   all six feature sections. */
.um-frame {
    position: relative;
    overflow: hidden;
    /* --bs-card-bg is declared inside .card, not on :root, so it resolves to
       nothing on a plain div. --bs-body-bg is the root-level token and carries
       the dark value too. */
    background-color: var(--bs-body-bg);
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius-xl);
    box-shadow: var(--bs-box-shadow);
}

.um-frame-bar {
    display: flex;
    align-items: center;
    gap: .375rem;
    padding: .625rem .875rem;
    background-color: var(--bs-gray-100);
    border-bottom: 1px solid var(--bs-border-color);
}

.um-frame-dot {
    width: .5rem;
    height: .5rem;
    border-radius: 50%;
    background-color: var(--bs-gray-400);
}

/* The hero shot carries the page, so it gets the deeper shadow. */
.um-hero-frame {
    box-shadow: var(--bs-box-shadow-lg);
}

/* Cropped card screenshots -------------------------------------------------
   Six feature screenshots at six different proportions have to line up as a
   grid, so each is bled to the card edges and cropped to a fixed height rather
   than scaled to fit. The card itself carries overflow-hidden, which is what
   rounds the corners. */
.um-card-shot {
    position: relative;
    height: 8.5rem;
    overflow: hidden;
    border-top: 1px solid var(--bs-border-color);
}

/* The crop is a hard horizontal cut, which looks like a mistake. Fading the last
   few pixels into the card background makes it read as intentional. */
.um-card-shot:not(.um-card-shot-top)::after {
    content: "";
    position: absolute;
    right: 0;
    bottom: 0;
    left: 0;
    height: 3rem;
    pointer-events: none;
    /* Both stops are the same colour at different alpha rather than a fade to
       `transparent`, which some engines interpolate through grey. */
    background-image: linear-gradient(to bottom, rgba(var(--bs-body-bg-rgb), 0) 0%, rgba(var(--bs-body-bg-rgb), 1) 100%);
}

/* Blog thumbnails sit at the top of the card instead, so the border and the
   fade swap ends - and there is no fade, because the image is the card header
   rather than a cropped afterthought. */
.um-card-shot-top {
    height: 11rem;
    border-top: 0;
    border-bottom: 1px solid var(--bs-border-color);
}

@media (min-width: 992px) {
    .um-card-shot {
        height: 10rem;
    }

    .um-card-shot-top {
        height: 12rem;
    }
}

/* API code sample ---------------------------------------------------------
   A code sample should read as a code sample, so this panel stays dark whether
   the page is light or dark. It gets there by carrying data-bs-theme="dark" in
   the markup: Metronic declares its dark token set on the bare attribute
   selector, not on html[data-bs-theme=dark], so nesting it re-declares every
   --bs-* variable for this subtree only. The rules below are then the same
   token names a light card would use, and no hex value is written anywhere. */
.um-terminal {
    overflow: hidden;
    background-color: var(--bs-gray-100);
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius-xl);
    box-shadow: var(--bs-box-shadow-lg);
}

/* One step lighter than the panel so the chrome separates from the code. */
.um-terminal .um-frame-bar {
    background-color: var(--bs-gray-200);
}

.um-terminal-body {
    padding: 1.25rem 1.5rem;
    overflow-x: auto;
    color: var(--bs-gray-700);
    font-family: var(--bs-font-monospace);
    font-size: .8125rem;
    line-height: 1.9;
    white-space: pre;
}

.um-terminal-value {
    color: var(--bs-primary);
}

/* Closing call to action --------------------------------------------------
   The only saturated block on the page. Both stops are primary tokens, so a
   rebrand carries it. */
.um-cta {
    position: relative;
    overflow: hidden;
    border: 0;
    background-color: var(--bs-primary);
    background-image: linear-gradient(135deg, var(--bs-primary) 0%, var(--bs-primary-active) 100%);
    box-shadow: 0 1rem 2rem rgba(var(--bs-primary-rgb), .25);
}

.um-cta::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background-image: radial-gradient(45% 70% at 85% 0%, rgba(255, 255, 255, .16) 0%, rgba(255, 255, 255, 0) 70%);
}

.um-cta > * {
    position: relative;
}

/* ---------------------------------------------------------------------------
   Help documentation
   ---------------------------------------------------------------------------
   The help pages render inside .um-prose, so the eight content fragments are
   plain semantic HTML with no utility classes on them. Four things prose does
   not cover: pinning the rail, giving an anchored heading room to clear the
   fixed header, positioning a code sample's copy button, and two list shapes
   that have to opt out of the prose list styling. */

/* The rail, pinned from lg up - which is where help becomes two columns. Below
   that it sits above the article in normal flow, so pinning it would cover it.
   The header offset matches .um-checkout-summary: a bare top: 0 would tuck it
   under Metronic's fixed app header.

   RENAMED FROM .um-doc-nav IN SLICE 126, because the element stopped being a
   nav. It is now a rail holding search, the set nav and the within-page index,
   and it is a bare div rather than a card - two panels became one document
   surface. Nothing else referenced the old name; grepped before renaming.

   max-height plus overflow is what keeps this usable as the set grows. Eight
   entries fit any laptop; fifteen plus a search field plus a section index does
   not, and a sticky element taller than the viewport pins its top and hides its
   own tail with no way to reach it - exactly the defect register entry 3 fixed
   on the app sidebar, where a card sized to its content overflowed its own
   container. Scrolling the rail rather than capping the list means a page can
   be added without anyone re-measuring. */
@media (min-width: 992px) {
    .um-doc-rail {
        position: sticky;
        top: calc(var(--bs-app-header-height, 70px) + 1.5rem);
        max-height: calc(100vh - var(--bs-app-header-height, 70px) - 3rem);
        overflow-y: auto;
        /* Room for the scrollbar so it never sits on top of a menu label. */
        padding-right: .25rem;
    }
}

/* ANCHOR OFFSET for the rail's within-page index.
   The app header is fixed, so a fragment jump scrolls the target to y=0 and the
   header covers it - the reader lands on a heading they cannot see and the first
   line of the section reads as the start of the page. scroll-margin-top reserves
   that space, and unlike a JS scroll offset it is the browser's own anchoring, so
   it also applies to a back/forward navigation that restores a fragment.

   SCOPED BY [id], NOT BY A WRAPPER CLASS, and that is the whole trick: only the
   help shell injects ids onto prose headings, so a blog post or an admin-authored
   custom page - which render in the same .um-prose block and have no anchors -
   are untouched by this. No margin changes on any page this slice did not
   redesign, and a future page that gains anchors gets the offset for free. */
.um-prose h2[id] {
    scroll-margin-top: calc(var(--bs-app-header-height, 70px) + 1.5rem);
}

/* Code sample with a copy button.
   The button is positioned rather than in flow, because a sample is one wrapping
   line and a row of chrome above every block would cost more vertical space than
   the block itself. The <pre> then needs room at its top right, or a long first
   line runs underneath the button - the same mistake register entry 201 made by
   putting flatpickr's steppers on top of its own digits.

   Not hover-only. A control nobody can see is a control nobody uses, which is
   register defect 11's lesson about a loading skeleton at 1.06:1, and entry 201
   is the counter-lesson: making a vendor's hover-only control permanent broke
   the layout it sat in. Here the space is reserved for it either way, so
   permanent visibility costs nothing. */
.um-code {
    margin-bottom: 1.5rem;
}
.um-code > pre {
    /* The prose rule supplies 1rem 1.25rem; only the right inset changes, and the
       bottom margin moves to .um-code so the button's box and the sample stay one
       unit. */
    padding-right: 3.25rem;
    margin-bottom: 0;
}
.um-code-copy {
    position: absolute;
    top: .5rem;
    right: .5rem;
    z-index: 1;
}

/* Related-topic chips. Replaces the bare unlabelled <ul> of links that the two
   tracking-mode pages dropped straight after their opening paragraph, where it
   read as a list belonging to that paragraph. No language key exists for a
   "see also" heading, so the layout has to carry the meaning instead.

   Both resets are needed because .um-prose ul and .um-prose li win on
   specificity over Bootstrap's own .list-unstyled. */
.um-doc-links {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
    margin: 0 0 2rem;
    padding: 0;
    list-style: none;
}
.um-doc-links li {
    margin: 0;
}
/* Prose underlines every link; on a chip the badge already reads as a target. */
.um-doc-links a {
    text-decoration: none;
}

/* Icon checklists: the pros and cons lists, and the tracking-exclusion list on
   the FAQ page. The icon replaces the bullet, so the marker is turned off and
   the row becomes a flex line. align-items: flex-start keeps the icon on the
   first line when the text wraps. */
.um-doc-check {
    margin-bottom: 2rem;
    padding: 0;
    list-style: none;
}
.um-doc-check li {
    display: flex;
    align-items: flex-start;
    gap: .75rem;
    margin-bottom: .75rem;
}
/* The glyph is optically centred on the first line of text rather than sitting
   on its baseline, and must not shrink when the label is long. */
.um-doc-check li > i {
    flex-shrink: 0;
    margin-top: .2em;
}

/* ---------------------------------------------------------------------------
   API documentation
   ---------------------------------------------------------------------------
   The code surfaces reuse .um-terminal, .um-frame-bar and .um-frame-dot from the
   landing page, so a curl example looks the same wherever it appears. Only three
   things are specific to these pages. */

/* Placeholders inside a URL, a header or a form field: {website_id}, {api_key}.
   Replaces the bare `text-primary` the legacy pages put on 243 of these. It needs
   its own class because inside a terminal pinned to dark, --bs-primary on a dark
   ground is legible but flat - a touch of weight separates the part the reader has
   to substitute from the part they copy verbatim. */
.um-api-token {
    color: var(--bs-primary);
    font-weight: 600;
}

/* Prism supplies the token colours for the JSON samples; the surface comes from
   .um-terminal so that a response and the curl example above it sit on the same
   ground. Without this Prism paints its own #1e1e3f, which would put two
   different darks on every page.

   Its stock type scale is also built for a documentation hero, not for a panel
   inside an accordion inside a card: 17px monospace with 2em of padding. */
.um-code pre[class*="language-"] {
    margin: 0;
    padding: 1.25rem 1.5rem;
    background: none;
    border-radius: 0;
    font-family: var(--bs-font-monospace);
    font-size: .8125rem;
    line-height: 1.7;
    letter-spacing: 0;
    text-shadow: none;
    overflow-x: auto;
}
.um-code code[class*="language-"] {
    font-family: inherit;
    font-size: inherit;
    line-height: inherit;
    letter-spacing: inherit;
    text-shadow: none;
    background: none;
    padding: 0;
}

/* Parameter descriptions carry inline <code> from the language pack - allowed
   values, date formats, defaults. Metronic styles <code> for prose, which is
   heavier than a table cell wants. */
.um-api-description code {
    padding: .1rem .3rem;
    background: var(--bs-gray-200);
    border-radius: var(--bs-border-radius-sm);
    color: var(--bs-gray-800);
    font-size: .95em;
}

/* ---------------------------------------------------------------------------
   Internal notifications
   ---------------------------------------------------------------------------
   Replaces three altum classes the legacy list relied on, none of which any
   Metronic page defines: bg-gray-100 on the row, bg-gray-50 on the icon tile,
   and text-primary-900 on the icon itself. All three resolved to nothing, so the
   row had no background, the tile had no background, and the icon took body
   colour.

   The unread marker was `border border-info` on the whole row. Under the Metronic
   palette info is purple, so an unread notification read as a hover state rather
   than as something new. A left accent bar makes the cue a shape as well as a
   colour. */
.um-notification {
    padding: 1rem 1.25rem;
    background-color: var(--bs-gray-100);
    border-radius: var(--bs-border-radius-lg);
    border-left: 3px solid transparent;
}

.um-notification-unread {
    border-left-color: var(--bs-primary);
    background-color: rgba(var(--bs-primary-rgb), .06);
}

/* Long titles and descriptions must be allowed to shrink inside the flex row
   rather than pushing the timestamp off the card. Metronic ships no min-w-0
   utility, which is why these carry their own class names rather than borrowing
   one that looks like a Metronic utility but is ours.

   These two predate .um-min-w-0 below and are kept as they are: they name what
   they do to a specific element, which reads better here than a utility, and
   .um-notification-text also needs the overflow-wrap rule underneath. */
.um-notification-body,
.um-notification-text {
    min-width: 0;
}
.um-notification-text > * {
    overflow-wrap: anywhere;
}

/* ---------------------------------------------------------------------------
   um-min-w-0: let a flex item shrink below its content
   ---------------------------------------------------------------------------
   A flex item's automatic minimum size is its CONTENT, so an item holding one
   long unbreakable string refuses to shrink and pushes its siblings out of the
   row - or out of the layout entirely. Metronic ships no utility for this, and
   Bootstrap 5 does not either; the habit comes from Tailwind.

   SIX AUTHORED SITES WROTE `min-w-0` AS THOUGH IT WERE A METRONIC UTILITY AND IT
   IS NOT. Measured across all three served stylesheets it was declared exactly
   once, scoped to `.um-spotlight-row`, so it worked inside a spotlight row and
   was INERT on partials/toolbar.php, three sites in partials/app_menu.php,
   account-api/index.php and visitor/index.php. Register defects 1 and 164 are
   the same failure mode: an invalid class fails completely silently - no console
   error, no PHP notice, nothing in any log.

   WHY THIS IS THE RIGHT FIX, AND WHY THE FIRST ANSWER WAS WRONG. Register entry
   258 deferred it on the grounds that "the safe substitute differs per site,
   because overflow-hidden clips an absolutely-positioned dropdown". That
   conflated two different declarations. `overflow: hidden` clips descendants;
   `min-width: 0` clips NOTHING - it only removes the automatic floor. So there
   is no per-site judgement to make, and reading the six sites confirmed it: all
   six hold either a `text-truncate` child that wants to truncate, or a dropdown
   that must not be clipped. `min-width: 0` is correct in every one and
   `overflow-hidden` would have been actively wrong in four.

   NAMED `um-` RATHER THAN `min-w-0`, because Metronic owns the `min-w-*`
   namespace with its own pixel scale, and defining a bare `.min-w-0` inside it
   would be a collision with the vendor - the trap the notification rules above
   avoided for the same reason. `.um-sizelint.ps1` now fails on any `min-w-*`,
   `w-*` or `h-*` class that does not resolve in the served CSS, so the inert
   spelling cannot come back. */
.um-min-w-0 {
    min-width: 0;
}

/* ---------------------------------------------------------------------------
   Spotlight (Ctrl+K page search)
   ---------------------------------------------------------------------------
   The legacy partial injected these rules as a <style> block through the
   javascript event channel, so the CSS arrived at the end of the body on every
   page that loaded the spotlight. They belong here.

   Four of them were broken on a Metronic page, all silently:

     .spotlight-modal   background: var(--gray-100), radius: var(--border-radius)
                        Both altum variables, so the modal had no background at
                        all and the results floated over the page.
     bg-gray-50         on every result row, same problem.
     rounded-2x         on the rows, so nothing was rounded.
     the dark variant   was scoped to [data-theme-style="dark"], which is altum's
                        attribute. Metronic writes data-bs-theme, so in dark mode
                        the backdrop stayed a 50% white wash over a dark page. */
.um-spotlight {
    position: fixed;
    inset: 0;
    z-index: 2000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
    background: rgba(255, 255, 255, .5);
    backdrop-filter: blur(2px);
    opacity: 0;
    pointer-events: none;
    transition: opacity .15s ease;
}

[data-bs-theme="dark"] .um-spotlight {
    background: rgba(0, 0, 0, .5);
}

.um-spotlight.show {
    opacity: 1;
    pointer-events: auto;
}

.um-spotlight-modal {
    width: 100%;
    max-width: 40rem;
    padding: 1.5rem;
    background-color: var(--bs-body-bg);
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius-xl);
    box-shadow: var(--bs-box-shadow-lg);
}

/* The magnifier sits inside the input rather than beside it, which is where
   Metronic puts a search icon and what the ps-13 padding on the input leaves
   room for. */
.um-spotlight-search-icon {
    position: absolute;
    top: 50%;
    inset-inline-start: 1.25rem;
    transform: translateY(-50%);
    pointer-events: none;
}

.um-spotlight-results {
    max-height: 26rem;
    overflow-y: auto;
}

/* One surface for a result row, the empty state and the loading row. */
.um-spotlight-row {
    padding: .75rem 1rem;
    margin-bottom: .5rem;
    background-color: var(--bs-gray-100);
    border-radius: var(--bs-border-radius-lg);
    border: 1px solid transparent;
}

.um-spotlight-row:last-child {
    margin-bottom: 0;
}

/* Hover and keyboard focus get the same treatment: arrow-key navigation moves
   focus, so a focus-only or hover-only style would leave one of the two ways of
   using this with no feedback. */
a.um-spotlight-row:hover,
a.um-spotlight-row:focus-visible {
    outline: none;
    border-color: var(--bs-primary);
    background-color: rgba(var(--bs-primary-rgb), .06);
}

/* A `.um-spotlight-row .min-w-0 { min-width: 0 }` rule used to live here, and it
   was the ONLY declaration of `min-w-0` anywhere in the three served
   stylesheets - which is exactly how that spelling came to look real to six
   other views, all of which wrote it and got nothing. Retired in favour of the
   global .um-min-w-0 utility, so there is one definition rather than one scoped
   rule plus six inert hopes. The spotlight row uses that utility now. */

/* ---------------------------------------------------------------------------
 * Plan gate cursor.
 *
 * um_plan_gate_disabled() / um_plan_gate_limit_reached() in app/helpers/others.php
 * mark a disabled control that clicks through to the pricing page. They cannot emit
 * class="cursor-pointer" like the Bootstrap 4 originals did, because the fragment is
 * interpolated into tags that already have a class attribute and the browser keeps only
 * the first of a duplicate pair. An attribute selector has nothing to collide with.
 * --------------------------------------------------------------------------- */
[data-um-plan-gate] {
    cursor: pointer;
}

/* ---------------------------------------------------------------------------
 * Image-based icons: favicons, browser marks, OS marks, country flags.
 *
 * These are the only icons on the user surface that are not Keenicons, and they were the
 * one kind with no working size at all. Two faults combined into one very visible bug:
 *
 * 1. Metronic's pixel utility scale is not continuous. It runs 1-10 in single steps and
 *    then jumps in fives: 10, 15, 20, 25, 30 and so on. So w-16px, h-16px, w-14px and
 *    h-14px - the obvious values for a small icon, and what these call sites used - do not
 *    exist in style.bundle.css. Writing one produces no error and no warning. It is inert.
 *
 * 2. The artwork has no intrinsic size to fall back on. 8 of 10 browser SVGs, 6 of 9 OS
 *    SVGs and all 258 flags declare a viewBox but no width attribute. With no CSS size, a
 *    replaced element with no intrinsic dimensions gets the 300x150 default, or stretches
 *    to its container. That is why a browser mark rendered as a Chrome logo hundreds of
 *    pixels across while flags, which happened to land on the one valid value w-20px,
 *    looked correct.
 *
 * WHY em AND NOT PIXELS
 * Sizing in em makes these track the surrounding type exactly the way a Keenicon does
 * through fs-*: one mental model for both icon systems, and they follow browser zoom and
 * any future change to the root font size instead of being pinned to a device pixel count.
 * At the 14px body text these sit in, 1.15em lands on ~16px, which is what the call sites
 * were asking for in the first place.
 *
 * Both axes are always set and object-fit keeps the artwork undistorted, so an asset with
 * no intrinsic size can no longer escape its slot no matter what it declares.
 * --------------------------------------------------------------------------- */
.um-icon-img {
    width: 1.15em;
    height: 1.15em;
    /* contain, not cover: the browser and OS set includes a 2.5:1 Yandex wordmark and a
       portrait Apple mark. Cropping either to a square would mangle it; letterboxing in a
       consistent square slot keeps every row aligned. */
    object-fit: contain;
    flex-shrink: 0;
    vertical-align: -0.15em;
}

.um-icon-img.um-icon-img-sm {
    width: 1em;
    height: 1em;
}

.um-icon-img.um-icon-img-lg {
    width: 1.5em;
    height: 1.5em;
}

/* Flags get their own ratio: 256 of the 258 files are 640x480, so a 4:3 slot fits the set
   without letterboxing. The two outliers, a square placeholder and one 5:3 flag, fall back
   to contain and letterbox rather than distort. */
.um-icon-flag {
    width: 1.5em;
    height: 1.125em;
    object-fit: contain;
    flex-shrink: 0;
    vertical-align: -0.15em;
}

/* ---------------------------------------------------------------------------
 * Sidebar card: keep it inside its own box, and let its bottom corners show.
 *
 * THE SYMPTOM: the card ran off the bottom of the screen with no bottom rounding, so it
 * read as a full-height panel rather than a floating card.
 *
 * THE CAUSE: two stock rules that only conflict once the sidebar has a lot of content.
 *
 *   [data-kt-app-sidebar-fixed=true] .app-sidebar { position: fixed; top: 0; bottom: 0 }
 *   @media (min-width: 992px) { .app-sidebar { top: 160px !important } }
 *
 * gives .app-sidebar a definite height of 100vh - 160px. Inside it, .app-sidebar-wrapper -
 * the element that carries border-radius: 1.5rem and the body background, so the element
 * that IS the card - is a flex item sized to its content. demo37 puts six tiles in there
 * and the content is always shorter than the box, so the card floats with all four corners
 * visible and the conflict never appears. We put thirteen tiles plus a quota block in there,
 * the content is taller than the box, and the card overflowed past bottom: 0 and off the
 * viewport. The rounding was applied the whole time; both bottom corners were simply
 * below the fold.
 *
 * Nothing paints behind the card, incidentally: .app-sidebar declares
 * background-color: var(--bs-app-sidebar-base-bg-color), and that variable is never defined
 * anywhere in style.bundle.css, so the declaration is invalid and discarded. The card is the
 * only visible surface, which is why fixing its geometry is the whole job.
 *
 * THE FIX: min-height: 0 lets the flex item shrink below its content height, so the card is
 * capped by the box and its overflowing child scrolls instead. This keeps BOTH behaviours
 * correct - content-height when the nav is short, exactly like demo37, and capped when it is
 * long - which a fixed max-height would not. The child needs the same release or the shrink
 * cannot propagate. hover-scroll-y already supplies overflow-y: auto, so no overflow is set
 * here.
 *
 * The 30px bottom offset matches demo37's own spacing rhythm: .app-main { margin-left: 310px }
 * is the 280px sidebar plus 30px, and .app-content { padding-top: 30px }. !important is
 * required to beat the attribute-plus-class specificity of the bottom: 0 rule above, and is
 * consistent with demo37's own left: auto !important / top: 160px !important.
 * --------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------
 * Header height, and the sidebar offset that is keyed to it.
 *
 * .app-header{height:var(--bs-app-header-height)} and demo37 sets that token to 160px on
 * :root above 992px, dropping to 90px once stuck. 160px is right for demo37, whose header
 * carries a logo, a horizontal menu, a toolbar row and a page title inside that band. Ours
 * carries one row - brand, website switcher, a few icon buttons - so 160px left a very tall
 * empty bar, and it was doubly obvious while .app-header was still being painted opaque white.
 *
 * 70px fits our single row and is Metronic's own standard single-row header height. Held
 * constant across the sticky state too, so the bar does not resize under the cursor.
 *
 * The sidebar's top offset has to move with it. demo37 keys .app-sidebar{top:160px} to the
 * header height, so leaving it at 160px against a 70px header would float the card 90px below
 * the header with nothing in the gap - demo37 fills that space with its toolbar and we have
 * none. 100px is the 70px header plus the same 30px gap used by .app-content{padding-top:30px}
 * and by the bottom offset below, so the card is inset consistently on all sides.
 *
 * Overrides must sit inside the same min-width:992px query, because that is where demo37
 * declares the token on :root; equal specificity means document order decides, and
 * usermetric.css loads last.
 * --------------------------------------------------------------------------- */
@media (min-width: 992px) {
    :root,
    [data-kt-app-header-sticky="on"] {
        --bs-app-header-height: 70px;
        --bs-app-header-height-actual: 70px;
    }

    .app-sidebar,
    [data-kt-app-header-sticky="on"] .app-sidebar {
        top: 100px !important;
        bottom: 30px !important;
    }

    .app-sidebar-wrapper {
        display: flex;
        flex-direction: column;
        min-height: 0;
    }

    #kt_app_sidebar_nav_wrapper {
        min-height: 0;
    }
}

/* ---------------------------------------------------------------------------
 * Remove demo37's blue band across every surface.
 *
 * The last block of style.bundle.css paints a fixed-height tint behind the top of every
 * page:
 *
 *   --bs-header-bg: linear-gradient(90deg, rgb(228,239,249) 0%, rgb(228,239,249) 100%)
 *   body { background: var(--bs-header-bg); background-repeat: no-repeat }
 *   @media (min-width: 992px)   { body { background-size: 100% 340px } }
 *   @media (max-width: 991.98px) { body { background-size: 100% 200px } }
 *
 * Only the image needs removing, not the colour. That `background` shorthand resets
 * background-color to transparent, but `.app-default, body { background-color: var(--bs-app-bg-color) }`
 * re-declares it at class specificity and wins, and the auth and invoice wrappers use
 * .app-blank { background-color: var(--bs-app-blank-bg-color) } instead. So every surface
 * keeps its proper canvas - #F7F7F7 light, #0F1014 dark - and only the tint goes.
 *
 * Setting background-image rather than the shorthand is deliberate: the shorthand would also
 * wipe those canvas colours and leave the page transparent down to the html element.
 *
 * This also fixes dark mode, where the band was a bug rather than a style: the override sets
 * [data-bs-theme=dark] { --bs-header-bg: rgb(228,239,249) }, the same pale blue, so a dark
 * page had a light blue strip across the top.
 * --------------------------------------------------------------------------- */
body {
    background-image: none !important;
}

/* ===========================================================================
 * DASHBOARD DESIGN SYSTEM
 *
 * The visual language for all 26 analytics report panels. Because the panels share
 * partials/analytics/report_card.php and partials/analytics/ranked_list.php, everything below
 * applies to all of them at once.
 *
 * Contract and rationale: .kiro/steering/metronic-dashboard-design-system.md
 * =========================================================================== */

/* --- Data families -------------------------------------------------------------
 * report_card.php derives the family from result_id and sets it as a data attribute. That is
 * the only place the mapping lives; adding a report never means touching this CSS.
 *
 * All five values are stock Metronic tokens. Colour here carries meaning - it marks which
 * family a panel belongs to, so the page has rhythm and a family can be found without reading
 * headings. It is not decoration, and a sixth family should not be invented for one panel.
 *
 * The variable is declared on the card so that rows, which are injected into the card body over
 * AJAX long after first paint, inherit it through the cascade rather than needing it passed in.
 * ------------------------------------------------------------------------------- */
/* ===========================================================================
 * USERMETRIC BRANDING COLOURS
 *
 * The platform's six identity colours. Declared here as the single source of truth and intended
 * for use anywhere in the product, not only on the dashboard - charts, badges, status, empty
 * states, marketing pages.
 *
 * Each is published twice: a hex value for `color` and `background` and an unbracketed RGB triplet
 * for `rgba()`. The triplet form is not a convenience, it is a requirement - `rgba()` cannot take
 * a hex, so any rule that needs the colour at partial opacity, which is most of them, has to have
 * the components separately.
 *
 * FOUR ARE METRONIC STOCK, TWO ARE AUTHORED
 * Azure, violet, emerald and amber are Metronic's primary, info, success and warning, copied by
 * value rather than aliased to the tokens. Aliasing looked tidier but means a future palette
 * change silently repaints the data families, and worse, it ties a data colour to a semantic one -
 * a row tinted with --bs-danger reads as an error rather than as a browser.
 *
 * Teal and orange are new, and their hues were chosen rather than picked. The four stock hues sit
 * at roughly 47, 145, 213 and 265 degrees, which leaves two usable gaps: 145-213 and 265-47. Teal
 * at 180 sits in the middle of the first, orange at 20 in the second, and both stay clear of
 * Metronic's danger red at 347 so neither can be mistaken for an error state.
 *
 * The full set, in hue order: orange 20, amber 47, emerald 145, teal 180, azure 213, violet 265.
 * =========================================================================== */
:root {
    --um-brand-azure: #1B84FF;
    --um-brand-azure-rgb: 27, 132, 255;

    --um-brand-violet: #7239EA;
    --um-brand-violet-rgb: 114, 57, 234;

    --um-brand-emerald: #17C653;
    --um-brand-emerald-rgb: 23, 198, 83;

    --um-brand-amber: #F6C000;
    --um-brand-amber-rgb: 246, 192, 0;

    --um-brand-teal: #0EA5A5;
    --um-brand-teal-rgb: 14, 165, 165;

    --um-brand-orange: #FF6B2C;
    --um-brand-orange-rgb: 255, 107, 44;
}

.um-report {
    --bs-card-border-width: 0;
}

/* --- Panel identity ------------------------------------------------------------
 * One branding colour per PANEL, and every tab inside that panel shares it.
 *
 * This replaces an earlier per-data-type scheme where the accent lived on each result container,
 * so switching tabs inside a card changed its colour - the OS card went amber for operating
 * systems and grey for visiting hours. A panel that changes colour when you click a tab inside it
 * reads as a different panel, which is exactly wrong: the tabs are views of one subject.
 *
 * Six panels, six colours, so each is identifiable at a glance without reading its heading. The
 * per-report standalone pages use the same map keyed by report, so a report shown inside a card
 * and the same report on its own page are never two different colours.
 *
 * Declared on the attribute alone rather than on .um-report[data-um-family], so the same rule
 * serves the panel cards, the drill-down modals, and anything else that adopts these later.
 *
 * No default is declared here on purpose. A default on .um-rank or .um-rank-row would sit BELOW
 * the card in the tree and beat the inherited value, silently flattening every panel to one
 * colour. The fallback lives inside the var() call at the point of use instead.
 * ------------------------------------------------------------------------------- */
[data-um-family="pages"]     { --um-accent-rgb: var(--um-brand-emerald-rgb); }
[data-um-family="sources"]   { --um-accent-rgb: var(--um-brand-violet-rgb); }
[data-um-family="locations"] { --um-accent-rgb: var(--um-brand-azure-rgb); }
[data-um-family="systems"]   { --um-accent-rgb: var(--um-brand-amber-rgb); }
[data-um-family="devices"]   { --um-accent-rgb: var(--um-brand-teal-rgb); }
[data-um-family="browsers"]  { --um-accent-rgb: var(--um-brand-orange-rgb); }

/* The panel icon, replacing the 40px bg-light-primary badge.
 *
 * Deliberately neutral rather than family-tinted. On a tabbed card the shell rewrites this icon's
 * innerHTML per tab but has no notion of a family, so tinting it would leave the icon showing one
 * family while the rows below showed another. Muted grey is honest and never disagrees with the
 * data. It also stays legible when the shell replaces the glyph with a bare <i class="fs-3">. */
.um-report-icon {
    color: var(--bs-gray-500);
}

/* A lift small enough that nothing shifts around it. Metronic's own card shadow token, so dark
   mode is handled. */
.um-report {
    transition: box-shadow 0.2s ease;
}

.um-report:hover {
    box-shadow: var(--bs-card-box-shadow, 0 3px 14px rgba(0, 0, 0, 0.05));
}

/* --- The row IS the bar --------------------------------------------------------
 * A ranked row is one element whose background is filled to its percentage, instead of a label
 * line plus a separate .progress underneath. --um-fill is set inline per row by
 * ranked_list.php; the gradient turns it into a bar.
 *
 * Two hard stops at the same position, so the edge is crisp rather than a soft ramp - a ramp
 * reads as a gradient effect, a hard stop reads as data.
 *
 * 0.11 is at the 12% ceiling the steering file sets. Labels sit ON this fill, so raising it
 * costs text contrast. If a bolder bar is ever wanted, put the label on a solid chip instead.
 * ------------------------------------------------------------------------------- */
/* Row rhythm.
 *
 * The rows had padding and a corner radius but no vertical spacing, so consecutive fills butted
 * together into one continuous tinted block - two device rows read as a single amber slab, and ten
 * country rows read as a striped column rather than ten bars. The radius was there the whole time
 * and invisible, because a rounded corner needs a gap on the other side of it to be seen.
 *
 * Set as a flex gap on the container rather than a margin on each row. A margin needs a
 * last-child reset, and this list ends in one of three different things depending on the panel -
 * the view-more link, the goals footer, or nothing - so a :last-child rule would be wrong in two
 * of the three cases. A gap is also honest about what it is: rhythm between siblings.
 *
 * 6px, rising to 8px from lg. Below that the cards are narrower and the extra 2px costs a row of
 * data on a phone; above it there is width to spend and the rows read better with more air. Both
 * sit in the 4-8px band that ranked lists in this category settle on.
 */
.um-rank {
    display: flex;
    flex-direction: column;
    gap: 0.375rem;
}

@media (min-width: 992px) {
    .um-rank {
        gap: 0.5rem;
    }
}

.um-rank-row {
    --um-fill: 0%;
    min-height: 2.5rem;
    padding: 0.375rem 0.625rem;
    border-radius: var(--bs-border-radius-sm);
    /* The fallback lives here rather than as a default declaration on this element, because a
       declaration here would beat the family inherited from the result container. */
    background-image: linear-gradient(
        to right,
        rgba(var(--um-accent-rgb, var(--bs-primary-rgb)), 0.11) 0,
        rgba(var(--um-accent-rgb, var(--bs-primary-rgb)), 0.11) var(--um-fill),
        transparent var(--um-fill),
        transparent 100%
    );
    transition: background-color 0.15s ease;
}

/* Hover deepens the whole row rather than the fill, so the bar length stays readable as a
   value and does not appear to change on hover. */
.um-rank-row:hover {
    background-color: rgba(var(--um-accent-rgb, var(--bs-primary-rgb)), 0.05);
}

/* The empty state is a row with no fill and no hover: there is no value to represent. */
.um-rank-row-empty,
.um-rank-row-empty:hover {
    background-image: none;
    background-color: transparent;
}

.um-rank-label {
    min-width: 0;
}

/* --- Value columns ------------------------------------------------------------
 * Widths live here rather than as inline min-width on both the heading and the row, which is
 * how the two previously drifted. tabular-nums makes digits equal width so values line up
 * vertically within a card and across cards, which is the whole point of a ranked list.
 * ------------------------------------------------------------------------------- */
.um-rank-value {
    min-width: 92px;
    font-variant-numeric: tabular-nums;
}

.um-rank-bounce {
    min-width: 68px;
    font-variant-numeric: tabular-nums;
}

/* Both of these now compound with the container's gap, so their own spacing is trimmed to
   compensate. The header still ends up with more air beneath it than the rows have between them -
   0.75rem against 0.5rem - which is the hierarchy we want: a column heading should separate from
   the data more than two data rows separate from each other. */
.um-rank-head {
    padding: 0 0.625rem 0.25rem;
    letter-spacing: 0.04em;
}

.um-rank-more {
    margin: 0.25rem 0 0 0.625rem;
}

/* --- KPI tiles ----------------------------------------------------------------
 * The sparkline these rules used to style is gone. It plotted the same series, in the same
 * colour, that the hero chart plots 40px below, so the same shape appeared four times on one
 * screen. A period-over-period delta occupies the space instead and says something the chart
 * cannot.
 *
 * The delta sits on the number's baseline, not beside its centre, so a two-digit and a
 * five-digit value keep their pills on the same optical line. align-items-baseline on the
 * wrapper does that.
 *
 * The pill itself is Metronic's `badge badge-light-success` / `badge-light-danger`, so padding,
 * radius, size, weight and both palette tones come from the component. Nothing here restates
 * them - an earlier version set font-size and font-weight and was simply fighting `.badge`.
 * Only two things are left, both of which Metronic does not provide:
 * ------------------------------------------------------------------------------- */
.um-kpi-delta {
    /* .badge is inline-block, which stacks the arrow glyph and the digits on the text baseline
       and leaves the arrow sitting low. inline-flex centres them against each other instead. */
    display: inline-flex;
    align-items: center;
    /* Same tabular figures as the ranked rows, so a column of tiles has its deltas aligned
       rather than dancing by a pixel per digit. */
    font-variant-numeric: tabular-nums;
}

/* --- Table values -----------------------------------------------------------------
 * Every numeric cell in a data table, so a column of figures lines up instead of dancing by a pixel
 * per digit. The same reason .um-rank-value and .um-kpi-value carry it - this is the table-shaped
 * member of that set, kept as one class rather than a fourth copy of the declaration so the eight
 * remaining list pages have something to adopt.
 *
 * Metronic ships no tabular-figures utility at all: font-variant-numeric appears zero times in
 * style.bundle.css, which is why this lives here.
 * ------------------------------------------------------------------------------- */
.um-table-value {
    font-variant-numeric: tabular-nums;
}
/* The value keeps tabular figures too. Without it the three volume tiles disagree on digit
   width and their numbers look misaligned against each other across the row. */
.um-kpi-value {
    font-variant-numeric: tabular-nums;
}

/* ---------------------------------------------------------------------------
 * Chart tooltip header.
 *
 * The date line at the top of every chart's hover card, emitted by
 * um_chart_tooltip() in views/metronic/partials/js_chart_defaults.php.
 *
 * WHY THIS NEEDS A CLASS OF OUR OWN, when the renderer deliberately reuses
 * ApexCharts' class names everywhere else. The library's printLabels() runs on
 * the line after a custom tooltip is rendered and does this:
 *
 *     c.tooltipTitle = baseEl.querySelector('.apexcharts-tooltip-title');
 *     c.tooltipTitle.innerHTML = h;
 *
 * It finds that class BY NAME. Our HTML has just replaced the tooltip's
 * contents, so the element it finds is ours, and it overwrites our date with
 * its own x value - empty on these charts - after which the vendor's
 * `.apexcharts-tooltip-title:empty { display: none }` hides the bar. The header
 * was being emitted, blanked and hidden on every hover.
 *
 * The series rows keep the vendor's classes because printLabels reaches those
 * through element references captured at init rather than by class, so it
 * cannot touch them. Only the title is looked up by name; only the title needs
 * renaming.
 *
 * The declarations below are the vendor's own treatment for its tooltip title,
 * copied so the header looks exactly as intended rather than newly designed:
 *
 *     .apexcharts-tooltip-title            { padding: 6px; font-size: 15px; margin-bottom: 4px }
 *     .apexcharts-tooltip .apexcharts-tooltip-title { padding: .5rem 1rem }
 *     .apexcharts-tooltip.apexcharts-theme-light .apexcharts-tooltip-title {
 *         background: var(--bs-body-bg); font-weight: 500;
 *         color: var(--bs-gray-800); border-bottom: 1px solid var(--bs-gray-100) }
 *
 * Tokens rather than the vendor's hardcoded #eceff1 and #ddd, so dark mode
 * follows for free - which is also why there is no dark-mode block here.
 * --------------------------------------------------------------------------- */
.um-chart-tooltip-title {
    padding: 0.5rem 1rem;
    margin-bottom: 0.25rem;
    font-family: inherit;
    font-size: 15px;
    font-weight: 500;
    color: var(--bs-gray-800);
    background: var(--bs-body-bg);
    border-bottom: 1px solid var(--bs-gray-100);
}

/* Nothing to head means no bar, matching how the vendor collapses its own empty
   title. The renderer already omits the element when there is no label; this
   covers a label that is present but blank. */
.um-chart-tooltip-title:empty {
    display: none;
}

/* --- Data tables on a narrow screen -----------------------------------------------
 * Restacks a table into one card per row below md. `.um-table-stack` on the <table>, plus a
 * `data-um-label` on each cell carrying that column's heading.
 *
 * WHY A CSS RESTACK RATHER THAN THE TWO OBVIOUS ALTERNATIVES.
 *
 * Hiding columns with d-none d-md-table-cell is one class per column and no CSS at all, but it takes
 * data away from the smaller screen - and on the websites list the columns that would go are the
 * tracking state and the optional signals, which is exactly what someone checks when a site has
 * stopped reporting.
 *
 * A second markup block - a card grid below md, the table above it - keeps all the data but writes
 * every value twice. Two copies of a row drift, and the renovation has already had to fix three
 * hand-rolled copies of things that existed as shared partials.
 *
 * So: one source of markup, all the data present, and the layout is a stylesheet's problem. The cost
 * is one attribute per cell, which is also what supplies the label, so it pays for itself.
 *
 * WHAT THE VENDOR ALREADY DOES, read rather than assumed:
 *   .table:not(.table-bordered).table-row-dashed tr  puts the row separator on the TR, not the cells,
 *       so the restacked card border replaces it and no cell border has to be unpicked.
 *   .table-responsive  is only overflow-x: auto, so it does nothing once the rows are blocks. It stays
 *       on the wrapper for the md-and-up case.
 *   .table > :not(caption) > * > *  is the cell padding, at the same specificity as the rules below.
 *       usermetric.css loads after style.bundle.css so order would settle it, but padding and borders
 *       carry !important here deliberately: this layout cannot be checked by fetching a page, so it
 *       should not also depend on load order staying as it is.
 *
 * NOT VERIFIED IN A BROWSER. A probe can prove the stylesheet is served and every cell carries its
 * label; it cannot prove what a phone renders. Treat that as outstanding.
 * ------------------------------------------------------------------------------- */
@media (max-width: 767.98px) {
    /* Clipped, not display:none. The headings are the only thing naming these values for a screen
       reader in table mode, and the labels below are decorative pseudo-content that some
       configurations do not announce. */
    .um-table-stack thead {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }

    .um-table-stack,
    .um-table-stack tbody,
    .um-table-stack tr,
    .um-table-stack td {
        display: block;
        width: auto;
    }

    .um-table-stack tr {
        border: 1px solid var(--bs-border-color);
        border-radius: var(--bs-border-radius);
        padding: 1rem;
        margin-bottom: 1rem;
    }

    .um-table-stack tr:last-child {
        margin-bottom: 0;
    }

    .um-table-stack td {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 1rem;
        padding: 0.35rem 0 !important;
        border: 0 !important;
    }

    /* text-nowrap is on several of these cells to stop a desktop column breaking mid-value, and it is
       !important, so a stacked card would overflow sideways instead of wrapping. Beaten on specificity
       rather than removed from the markup: it is still right above md. */
    .um-table-stack td {
        white-space: normal !important;
    }

    /* A badge cluster is flex-nowrap in the table, so the column sizes to the badges exactly and a
       wider floor is not needed to stop them dropping to a second line. In a stacked card the cell is a
       row sharing its width with a label, so there wrapping is the right answer instead of overflowing.
       Two layouts, two behaviours, one markup. */
    .um-table-stack .um-badge-cluster {
        flex-wrap: wrap !important;
    }

    .um-table-stack td::before {
        content: attr(data-um-label);
        flex-shrink: 0;
        font-size: 0.8rem;
        font-weight: 600;
        text-transform: uppercase;
        color: var(--bs-text-muted);
    }

    /* Three cells opt out of the label-and-value shape: the row's identity, which is the card's
       heading; the actions, which are a cluster rather than a value; and the bulk checkbox, which is
       a control and is display:none until bulk mode reveals it. */
    .um-table-stack .um-stack-title,
    .um-table-stack .um-stack-actions,
    .um-table-stack .um-stack-plain {
        display: block;
    }

    .um-table-stack .um-stack-title::before,
    .um-table-stack .um-stack-actions::before,
    .um-table-stack .um-stack-plain::before {
        content: none;
    }

    .um-table-stack .um-stack-title {
        padding-bottom: 0.75rem !important;
        border-bottom: 1px dashed var(--bs-border-color) !important;
    }

    .um-table-stack .um-stack-actions {
        padding-top: 0.75rem !important;
        border-top: 1px dashed var(--bs-border-color) !important;
    }
}

/* --- Card padding on small screens ------------------------------------------------
 * Metronic pads a card at 2.25rem on the left and right AT EVERY WIDTH, and this is the single biggest
 * reason the app feels cramped on a phone. Measured out of style.bundle.css rather than guessed:
 *
 *     .card-header { padding: 0 2.25rem; min-height: 70px }
 *     .card-body   { padding: 2rem 2.25rem }
 *
 * That is 72px of horizontal padding. On a 320px viewport, after the container's own 24px gutter, the
 * card is about 296px wide and 72 of those go to padding before a single control or table cell exists -
 * roughly a quarter of the screen. It is why the websites card header crams "Add website" and four icon
 * buttons against both edges, and why the restacked table rows have so little room for their values.
 *
 * The bundle ships exactly ONE mobile card rule and it is not this one:
 *
 *     @media (max-width: 767.98px) { .card-header:not(.flex-nowrap) { padding-top: .5rem; padding-bottom: .5rem } }
 *
 * Vertical only. So the horizontal step-down does not exist in Metronic and has to be ours. That is not
 * an oversight on their part so much as the demo's context - demo37 is an admin template, shown at
 * desktop widths, where 2.25rem is a good number.
 *
 * APPLIES TO EVERY CARD IN THE APP, deliberately. The complaint was that the whole surface is
 * unresponsive, not one page, and every page in this product is built out of these cards. It only ever
 * gives content MORE room, and 1.25rem still sits above Bootstrap's own 1rem card default.
 *
 * The padding property is overridden directly rather than through --bs-card-spacer-x, because Metronic
 * replaces Bootstrap's token-driven rule with a hardcoded one - setting the token would change nothing.
 * Same specificity as the vendor rule, and usermetric.css loads after style.bundle.css, so document
 * order decides it.
 * ------------------------------------------------------------------------------- */
@media (max-width: 767.98px) {
    .card-header {
        padding-left: 1.25rem;
        padding-right: 1.25rem;
    }

    .card-body,
    .card-footer {
        padding-left: 1.25rem;
        padding-right: 1.25rem;
    }

    .card-body {
        padding-top: 1.25rem;
        padding-bottom: 1.25rem;
    }
}

@media (max-width: 575.98px) {
    .card-header {
        padding-left: 1rem;
        padding-right: 1rem;
    }

    .card-body,
    .card-footer {
        padding-left: 1rem;
        padding-right: 1rem;
    }

    .card-body {
        padding-top: 1rem;
        padding-bottom: 1rem;
    }
}

/* --- Dropdown panels must not be wider than the phone ------------------------------
 * Audited on the served /websites markup: after the card padding fix, every remaining fixed width of
 * 150px or more on the page is a dropdown panel, and each declares a width with no small-screen escape:
 *
 *     dropdown-menu dropdown-menu-end w-350px w-lg-450px    notifications - someone sized it UP for lg
 *                                                           and never down for sm
 *     dropdown-menu w-300px                                 header menu
 *     dropdown-menu dropdown-menu-end w-250px               header menu
 *     menu menu-sub-dropdown w-300px w-md-325px             the list filter dropdown
 *     menu menu-sub-dropdown w-200px                        export, bulk actions
 *
 * On a 320px viewport the 350px panel is wider than the screen. Because these are absolutely positioned
 * they do not squeeze the layout - they hang off the side of it, which is worse: part of the menu is
 * simply unreachable, and on a right-aligned panel the part that goes missing is the left edge with the
 * labels on it.
 *
 * Capped rather than re-sized, and that is the point: one rule holds every panel to the viewport
 * whatever width it declares, so a panel added later inherits the fix instead of needing to remember
 * it. The declared widths still apply above the breakpoint, where they are correct.
 *
 * 100vw minus the container's own 1.5rem gutter, so a panel lines up with the content it belongs to
 * rather than touching the screen edge. Menu items are text and wrap, so a narrower panel is taller
 * rather than clipped.
 * ------------------------------------------------------------------------------- */
@media (max-width: 575.98px) {
    .dropdown-menu,
    .menu-sub-dropdown {
        max-width: calc(100vw - 1.5rem);
    }
}

/* --- ...and a tall one must not be taller than the screen either -------------------
 * The list filter panel is the only dropdown in the product with enough in it to run off the bottom of
 * the viewport, and when it did, nothing could scroll to the rest of it. Three causes, all in the
 * vendor code:
 *
 *   1. KTMenu positions with Popper `strategy: fixed` unless the menu sets
 *      data-kt-menu-overflow="true". A fixed element is out of flow and contributes nothing to the
 *      document's scroll height, so the PAGE has nothing to scroll to - and on a short list there is no
 *      page scrollbar at all.
 *   2. `.menu-sub-dropdown` in style.bundle.css sets display, radius, background, shadow and z-index.
 *      No max-height, no overflow. So the panel could not scroll internally either.
 *   3. KTMenu passes `{name:"flip",options:{enabled:!1}}`, so Popper never flips the panel above the
 *      trigger to find room.
 *
 * Scoped to .um-filter-menu, NOT to .menu-sub-dropdown generally. A blanket overflow rule on every
 * Metronic dropdown would clip the nested accordion submenus the header and sidebar menus use, and
 * that is a far worse regression than the one being fixed.
 *
 * min-height: 0 on both flex levels is the load-bearing part, and it is the same trap as register
 * defect 3: a flex item's automatic min-height is `auto`, which refuses to shrink below its content, so
 * without this the form simply overflows the capped panel instead of the body scrolling inside it.
 *
 * The max-height here is only a fallback for the case where the script has not run. The real value is
 * set by um_init_filter_menu_height() from the panel's measured position, because CSS cannot know where
 * Popper put it - Popper writes a transform, not a top.
 * ------------------------------------------------------------------------------- */
.um-filter-menu {
    max-height: calc(100vh - 8rem);
    /* The scrolling child would otherwise square off the panel's bottom corners. */
    overflow: hidden;
}

.um-filter-menu > .um-filter-form {
    display: flex;
    flex-direction: column;
    min-height: 0;
    overflow: hidden;
}

.um-filter-menu .um-filter-body {
    overflow-y: auto;
    min-height: 0;
    /* Reaching the end of this list must not start scrolling the page behind it. */
    overscroll-behavior: contain;
}

/* --- The bulk delete modal's subject list ------------------------------------------
 * The chips naming the rows about to be deleted. Bounded and scrollable because the selection can be
 * every row on the page - the page size options go up to 1000 - and a modal that grows past the
 * viewport is the defect that was just fixed on the filter panel. There is no "and N more" label
 * because there is no language key for one and inventing copy renders a raw key after a vendor
 * language-pack update; scrolling says the same thing without any words.
 * ------------------------------------------------------------------------------- */
.um-bulk-subject-list {
    max-height: 9rem;
    overflow-y: auto;
    overscroll-behavior: contain;
}

/* --- native date and time fields --------------------------------------------------
 * `um_init_native_pickers()` in usermetric.js makes a click anywhere in one of these fields open the
 * browser's picker, not just a click on the ~16px calendar button at the end. This is the visual half of
 * that: without it the field advertises itself as a text box with one small clickable target, which is
 * exactly the state that got reported.
 *
 * The pointer cursor is honest rather than decorative - clicking really does do something everywhere in
 * the field now. Typing still works, so this is a field that is both, and the pointer is the stronger
 * signal of the two because picking is the primary use.
 *
 * Metronic's .form-control does not set a cursor, so nothing is being fought here. Scoped to the input
 * types that carry a native picker rather than applied to .form-control, since a plain text field must
 * keep its text caret.
 * ------------------------------------------------------------------------------- */
input[type="datetime-local"],
input[type="date"],
input[type="time"],
input[type="month"],
input[type="week"] {
    cursor: pointer;
}
