/*
 * Global admin-panel UX overrides.
 * Loaded after the compiled bundles in layouts/partials/_global-styles.blade.php,
 * so plain (non-!important) rules here win. Keep this file hand-editable.
 */

/* The theme's --kt-warning/--kt-text-warning (#ffc700, bright yellow) is near-invisible on
   both white and the pale-yellow warning background, and its hue (~45°) is also too close to
   the deep-orange brand primary (#E54D38, hue ~9°) to read as a distinct "attention" signal
   once darkened. Re-hued at the variable level (not just .badge-light-warning/.text-warning)
   to the dark gold finalized in the create-entrance-test wizard (--tcs-gold, see below) so
   every element reading these variables - badges, text, icons - picks up the same color
   throughout the admin panel, not just the two classes patched here previously. Background/
   border tokens (--kt-warning-light etc.) are left as the theme's own pale tint; only the
   foreground/text tokens change. A few classes hardcode the old hex directly instead of
   reading these tokens (.btn-warning, .btn-outline-warning, .link-warning, .pulse-ring,
   .explore-label-free) - those get the same color patched directly further down this file. */
:root {
    --kt-warning: #8a6d00;
    --kt-text-warning: #8a6d00;
}

/* A handful of theme classes hardcode the old bright-yellow hex directly instead of reading
   --kt-warning/--bs-warning, so the :root override above doesn't reach them. Same dark-gold
   correction, applied directly since there's no token to redirect. .btn-warning/.btn-outline-
   warning additionally need their text color flipped to white - black text (the theme's
   default pairing for a bright yellow button) is unreadable against this darker gold. */
.btn-warning {
    --bs-btn-color: #ffffff;
    --bs-btn-bg: #8a6d00;
    --bs-btn-border-color: #8a6d00;
    --bs-btn-hover-color: #ffffff;
    --bs-btn-hover-bg: #a3820a;
    --bs-btn-hover-border-color: #a3820a;
    --bs-btn-active-color: #ffffff;
    --bs-btn-active-bg: #705900;
    --bs-btn-active-border-color: #705900;
    --bs-btn-disabled-color: #ffffff;
    --bs-btn-disabled-bg: #8a6d00;
    --bs-btn-disabled-border-color: #8a6d00;
}
.btn-outline-warning {
    --bs-btn-color: #8a6d00;
    --bs-btn-border-color: #8a6d00;
    --bs-btn-hover-color: #ffffff;
    --bs-btn-hover-bg: #8a6d00;
    --bs-btn-hover-border-color: #8a6d00;
    --bs-btn-active-color: #ffffff;
    --bs-btn-active-bg: #8a6d00;
    --bs-btn-active-border-color: #8a6d00;
    --bs-btn-disabled-color: #8a6d00;
}
.link-warning {
    color: #8a6d00 !important;
}
.pulse-ring {
    border-color: #8a6d00;
}
.explore-label-free {
    background-color: #8a6d00;
}

/* Breadcrumb links: enlarge the clickable area so crumbs aren't hard to click. */
.breadcrumb .breadcrumb-item a {
    display: inline-block;
    padding: .25rem .35rem;
}

/* DataTables processing/loading indicator: center it in the viewport instead of
   pinning to the top of a tall table (which forced a scroll-up on pagination). */
div.dataTables_wrapper div.dataTables_processing {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 1000;
}

/* Minimized sidebar logo: the markup fixes the height (h-35px), so let the width
   follow the image's own aspect ratio instead of being stretched to fill the rail.
   object-fit letterboxes a wide whitelabel logo rather than distorting it. */
.app-sidebar-logo .app-sidebar-logo-minimize {
    width: auto;
    max-width: 100%;
    object-fit: contain;
}

/* Give that logo room to breathe: at rest in the 75px rail, px-6 leaves it only
   ~27px. Bootstrap spacing utilities are !important, so overriding needs to be too. */
[data-kt-app-sidebar-minimize=on][data-kt-app-sidebar-hoverable=true] .app-sidebar:not(:hover) .app-sidebar-logo {
    padding-left: .5rem !important;
    padding-right: .5rem !important;
}

/* Warning alerts: Metronic paints these `color: var(--kt-warning)` on
   `background: var(--kt-warning-light)`, which in the light theme is #ffc700 on
   #fff8dd — a contrast ratio of 1.47:1. The sentence is legible only if you already
   know what it says, which on a warning is the wrong way round.

   Only the text colour is touched, so the amber tint and border still carry the
   "this is a warning" signal; the words no longer have to be yellow to mean it.
   --kt-gray-800 flips with the theme (#3F4254 light, #CDCDDE dark), giving 9.31:1
   and 8.31:1 — the dark theme was never the broken one, and does not regress.
   Not scoped to a page: the same four alerts are wrong the same way wherever they
   are — paper-checker subject mapping, the customized-test batch and show screens. */
.alert-warning {
    color: var(--kt-gray-800);
}

/* `code` in one of these keeps --kt-code-color (#b93993), which does not flip with the
   theme: on the dark theme's #392f28 ground that is 2.53:1, and the alert on the
   subject-mapping screen is a sentence whose whole point is the command inside it.
   The monospace face is what marks it as code; the colour was never carrying that. */
.alert-warning code {
    color: inherit;
}

/* Roomy SweetAlert dialogs: opt out of the 200px cap on the message body.

   The bundle caps every SweetAlert body at `max-height: 200px; overflow: auto` and
   pins the container at `overflow-y: hidden !important`. Together those put a long
   message in a 200px scroll box inside a window that cannot scroll either, so
   anything past the first couple of paragraphs is reachable only by scrolling a panel
   that gives no sign it scrolls. On a dialog whose later paragraphs are the warning,
   that is a warning nobody was given.

   Opt-in rather than global: the cap is doing no harm on the one-line confirmations
   most of the panel uses, and lifting it everywhere would change dialogs nobody has
   looked at. Add both classes to a dialog that needs the room —

       Swal.fire({
           customClass: { container: "swal-roomy-container", popup: "swal-roomy" },
           ...
       })

   — the container one included, or the message grows into a window still pinned at
   overflow-y: hidden.

   The container gets its scrolling back rather than being left to overflow, for the
   case the cap was presumably aimed at: a message taller than the viewport. It is now
   the whole dialog that scrolls, which reads as a dialog too big for the window
   rather than as text that stops mid-sentence. */
.swal2-container .swal-roomy .swal2-html-container {
    max-height: none;
    overflow: visible;
}

.swal2-container.swal-roomy-container {
    overflow-y: auto !important;
}

/* Pinned counts get a visible border so a teacher can tell an edited value from an
   auto-distributed one at a glance; both stay theme-token based. Shared with the AQP wizard,
   whose weight input is `.chapter-weight-input` rather than `.chapter-count` (render-chapters.js
   already toggles `.is-pinned` on it the same way index.js does for Customized Test — it just
   never had this rule to render). */
:is(#test-create-stepper .chapter-count, #test-create-stepper .coverage-count-control input, #aqp-test-create-stepper .chapter-weight-input).is-pinned {
    border-color: var(--bs-primary);
    font-weight: 600;
}
/* The pin hangs left of its count without taking width, so pinned and plain rows align. */
#test-create-stepper [data-count-pin] {
    flex-shrink: 0;
    width: 1rem;
    margin-left: -1.25rem;
}
#test-create-stepper .chapter-count[readonly] {
    cursor: default;
}
:is(#test-create-stepper, #aqp-test-create-stepper) .rotate-90-on-expand {
    transition: transform .15s ease;
}
:is(#test-create-stepper, #aqp-test-create-stepper) [aria-expanded="true"] .rotate-90-on-expand {
    transform: rotate(90deg);
}
/* Text color for .badge-light-warning / .text-warning is fixed globally above (near the top
   of this file) — the theme's default warning yellow is near-invisible and too close in hue
   to the brand primary. The wizard additionally strengthens the badge's background/border
   (below), since the theme's own light-warning tint is too pale to register on white here.
   Shared with the AQP wizard, which uses the same badge-light-warning/bg-light-warning/
   border-warning combo (exhausted/expired badges, chapters-summary-warning box, the chapter
   row a subject falls back to when incomplete) and had the same paleness with no reinforcement. */
:is(#test-create-stepper, #aqp-test-create-stepper) {
    --tcs-gold: #8a6d00;
    --tcs-gold-border: #d4a900;
    --tcs-gold-light: #fff4c2;
}
/* Every surface that carries the warning meaning uses the same corrected gold: the tab badge,
   the rail's incomplete-subject rows and note, and the capped chapter rows. The theme's own
   light-warning tint is too pale to register on white, so these get a stronger tint plus a
   gold border, and the JS puts the warning icon next to each of them. */
:is(#test-create-stepper, #aqp-test-create-stepper) .badge-light-warning {
    background-color: var(--tcs-gold-light);
    border: 1px solid var(--tcs-gold-border);
}
:is(#test-create-stepper, #aqp-test-create-stepper) .bg-gold-light,
:is(#test-create-stepper, #aqp-test-create-stepper) tr.bg-light-warning > td,
:is(#test-create-stepper, #aqp-test-create-stepper) .bg-light-warning {
    background-color: var(--tcs-gold-light) !important;
}
:is(#test-create-stepper, #aqp-test-create-stepper) .border-warning {
    border-color: var(--tcs-gold-border) !important;
}
/* Draw the eye to attention icons with a slow, gentle opacity breathe rather than a static mark.
   Shared with AQP — the gold color tokens/badges above stay CT-only, but the pulse itself is a
   generic "needs attention" affordance either wizard can reuse on its own icons. */
:is(#test-create-stepper, #aqp-test-create-stepper) .warning-pulse {
    animation: test-create-stepper-warning-pulse 1.8s ease-in-out infinite;
}
@keyframes test-create-stepper-warning-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: .45; }
}

/* Horizontal stepper header: keep titles compact and clickable-looking only for completed steps.
   Shared with the AQP creation wizard (#aqp-test-create-stepper) — generic to any horizontal
   KTStepper wizard shell, not specific to the Customized Test DOM. */
:is(#test-create-stepper, #aqp-test-create-stepper) .stepper-nav .stepper-item {
    margin-right: 0;
}
:is(#test-create-stepper, #aqp-test-create-stepper) .stepper-nav .stepper-item.completed {
    cursor: pointer;
}
:is(#test-create-stepper, #aqp-test-create-stepper) .stepper-nav .stepper-item.completed:hover .stepper-title {
    color: var(--bs-primary);
}
/* The AQP wizard's headers are jump targets in both directions (see index.js advanceTo), not just
   for completed steps, so every non-current header looks clickable. */
#aqp-test-create-stepper .stepper-nav .stepper-item:not(.current) {
    cursor: pointer;
}
#aqp-test-create-stepper .stepper-nav .stepper-item:not(.current):hover .stepper-title {
    color: var(--bs-primary);
}

/* .stepper-line ships border-left/height for a VERTICAL stepper (see the AQP wizards).
   This header is horizontal, so redraw it as a flex-grown horizontal rule instead. */
:is(#test-create-stepper, #aqp-test-create-stepper).stepper-pills .stepper-nav {
    flex-wrap: nowrap;
}
:is(#test-create-stepper, #aqp-test-create-stepper).stepper-pills .stepper-item {
    flex: 1 1 0;
}
:is(#test-create-stepper, #aqp-test-create-stepper).stepper-pills .stepper-line {
    flex-grow: 1;
    height: 0 !important;
    margin: 0 .75rem !important;
    align-self: center;
    border-left: 0;
    border-top: var(--kt-stepper-line-border);
}
/* The AQP wizard's steps aren't joined by the dotted connector; the sliding underline shows where you are. */
#aqp-test-create-stepper.stepper-pills .stepper-line {
    display: none;
}
/* The connector used to provide the space between steps; keep it so long descriptions don't run into the next number. */
#aqp-test-create-stepper.stepper-pills .stepper-item {
    padding-right: 1.5rem;
}
/* Underline the active step across its full width (icon/label + the connector to the next
   step) — reserve the space on every step so nothing shifts height when it moves. */
:is(#test-create-stepper, #aqp-test-create-stepper).stepper-pills .stepper-item {
    padding-bottom: .75rem;
}
/* A single indicator bar slides/resizes to the current step instead of each item drawing its
   own static border — see .stepper-nav's position:relative + the bar's left/width set inline
   by each wizard's own goTo() (CustomizedTestCreatePage in create/index.js; AqpTestCreatePage
   once built). */
:is(#test-create-stepper, #aqp-test-create-stepper).stepper-pills .stepper-nav {
    position: relative;
}
:is(#test-create-stepper, #aqp-test-create-stepper) .stepper-active-indicator {
    position: absolute;
    bottom: 0;
    height: 3px;
    background-color: var(--bs-primary);
    transition: left .25s ease, width .25s ease;
}

/* Keep the four steps within a phone viewport; scroll the data table, not the page. */
@media (max-width: 767.98px) {
    :is(#test-create-stepper, #aqp-test-create-stepper) .stepper-nav .stepper-item {
        min-width: 0;
    }
    :is(#test-create-stepper, #aqp-test-create-stepper) .stepper-nav .stepper-wrapper {
        flex-direction: column;
        gap: .5rem;
        text-align: center;
    }
    :is(#test-create-stepper, #aqp-test-create-stepper) .stepper-nav .stepper-icon,
    :is(#test-create-stepper, #aqp-test-create-stepper) .stepper-nav .stepper-label {
        margin: 0;
    }
    :is(#test-create-stepper, #aqp-test-create-stepper) .stepper-nav .stepper-desc,
    :is(#test-create-stepper, #aqp-test-create-stepper) .stepper-nav .stepper-line {
        display: none;
    }
    :is(#test-create-stepper, #aqp-test-create-stepper) .stepper-nav .stepper-title {
        font-size: .9rem;
    }
    #test-create-stepper #coverage-card {
        margin-inline: 0;
    }
    #test-create-stepper #coverage-card > div {
        min-width: 0;
        padding-inline: 0;
    }
    #test-create-stepper #chapter-table {
        min-width: 640px;
    }
    #test-create-stepper #chapter-table th:nth-child(2) {
        min-width: 200px;
    }
}

/* Allocation feedback spans the table without intercepting edits or changing its geometry.
   Keep this outside the responsive scroller so its status sticks to the viewport on long tables. */
#test-create-stepper .coverage-table-progress {
    position: absolute;
    inset: 0;
    z-index: 2;
    pointer-events: none;
    background-color: rgba(128, 139, 156, .06);
}
#test-create-stepper .coverage-table-progress-label {
    position: sticky;
    top: 90px;
    width: max-content;
    max-width: calc(100% - 1rem);
    margin: .5rem auto;
    display: flex;
    align-items: center;
}
@media (prefers-reduced-motion: reduce) {
    #test-create-stepper .coverage-table-progress .spinner-border {
        animation: none;
    }
}

/* Coverage counts: stable buttons around directly editable numbers, including table rows. */
#test-create-stepper .coverage-count-control {
    width: 108px;
    max-width: 100%;
}
#test-create-stepper .coverage-total-control {
    width: 180px;
}
#test-create-stepper .coverage-count-control .btn {
    width: 28px;
    height: 30px;
    flex-shrink: 0;
    padding: 0;
    font-size: 1.25rem;
}
#test-create-stepper .coverage-total-control .btn {
    width: 44px;
    height: 44px;
    font-size: 1.75rem;
}
#test-create-stepper .coverage-count-control .form-control-sm {
    min-height: 30px;
    height: 30px;
    padding-block: 0;
}
#test-create-stepper .coverage-count-control .btn[aria-disabled="true"] {
    opacity: .45;
    cursor: default;
}
/* The theme's own input focus colour: primary red here read as an error on the field being typed in. */
#test-create-stepper .coverage-count-control :focus-visible {
    outline: 2px solid var(--kt-input-focus-border-color);
    outline-offset: -2px;
}
#test-create-stepper .coverage-count-control input {
    min-width: 0;
    padding-inline: .25rem;
    border: 0;
    background-color: transparent;
    appearance: textfield;
    -moz-appearance: textfield;
}
/* Count errors are shown only as text (the row's message and the Live summary), never as a red
   frame, icon or focus glow on the box. */
#test-create-stepper .coverage-count-control input.is-invalid {
    background-image: none;
}
#test-create-stepper .coverage-count-control input.is-invalid:focus {
    box-shadow: none;
}
#test-create-stepper .coverage-count-control input::-webkit-inner-spin-button,
#test-create-stepper .coverage-count-control input::-webkit-outer-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

/* .form-check-solid checkboxes drop their border and fill with #eff2f5, near-identical to the
   bg-light chapter-table header they sit in — the unchecked "select all" box was invisible.
   AQP has the same bg-light header/checkbox pairing, one per subject pane, as a class rather
   than Customized Test's single-table id. */
:is(#test-create-stepper #chapter-select-all, #aqp-test-create-stepper .chapter-select-all) {
    border: 1px solid var(--bs-gray-400);
}
/* This theme's .form-check-solid only themes the :checked state (fills it primary-colored);
   :indeterminate falls back to Bootstrap's default, which keeps a white fill under the white
   minus-line, so the "select all" box goes indeterminate but reads as blank. Match :checked. */
:is(#test-create-stepper #chapter-select-all, #aqp-test-create-stepper .chapter-select-all):indeterminate {
    background-color: var(--bs-primary);
    border-color: var(--bs-primary);
}

/* flatpickr's altInput (the visible Starts/Ends text field in the Smart test block) is
   readonly by design — the value only ever comes from the picker popup — but the theme's
   `.form-control[readonly]` rule leaves its background transparent, so it shows the
   bg-light-primary panel color behind it and reads as a disabled field next to Duration and
   Show result, which are genuinely-editable plain white fields on the same panel. */
#test-create-stepper #schedule-block input[readonly] {
    background-color: var(--bs-white);
    border-color: var(--bs-gray-300);
    opacity: 1;
}

/* A disabled Select2 (Batch when Smart isn't turned on for the standard, Standard paper with
   none available) stays a white, pointer-cursor box: the theme greys a native `.form-select:disabled`
   but has no rule for the Select2 box that replaces it, so a dead dropdown looked clickable. */
#test-create-stepper .select2-container--disabled .form-select {
    background-color: var(--kt-form-select-disabled-bg);
    border-color: var(--kt-form-select-disabled-border-color);
    cursor: default;
}

/* Count change flash on rebalance: when a confirmed preview moves an automatic count to make
   room for a teacher-owned one, briefly tint that input so the ripple is visible instead of the
   number just silently jumping. index.js's flashRebalancedCounts() adds/removes this class and
   tweens the number itself, on chapter, topic and MCQ/NVT inputs alike (and on the AQP wizard's
   own chapter-weight-input); pinned/owned cells are never given the class, which is what visually
   reads as "this one didn't move". */
:is(#test-create-stepper, #aqp-test-create-stepper) input.count-rebalanced {
    animation: test-create-stepper-count-flash .9s ease-out;
}
@keyframes test-create-stepper-count-flash {
    0% { background-color: var(--bs-white); border-color: var(--bs-gray-300); }
    25% { background-color: var(--kt-primary-light, #FCF4F4); border-color: var(--bs-primary); }
    100% { background-color: var(--bs-white); border-color: var(--bs-gray-300); }
}

/* Live summary per-subject row: when a subject flips from incomplete to complete, fade its
   background/border from the warning tint to the success tint instead of the row just silently
   swapping color on next render, and pop the new check icon in rather than have it appear inert.
   `innerHTML` replaces these rows wholesale every render, so a plain CSS `transition` never has
   an old value to animate from on a freshly-inserted node — an `animation` (which always plays on
   mount, regardless of whether the element is new) is what actually produces the fade here. */
#test-create-stepper .subject-row-just-completed {
    animation: test-create-stepper-row-complete .8s ease forwards;
}
@keyframes test-create-stepper-row-complete {
    0% { background-color: var(--tcs-gold-light); border-color: var(--tcs-gold-border); }
    100% { background-color: var(--kt-success-light); border-color: var(--bs-success); }
}
:is(#test-create-stepper, #aqp-test-create-stepper) .subject-check-pop {
    display: inline-block;
    animation: test-create-stepper-check-pop .5s ease-out;
}
@keyframes test-create-stepper-check-pop {
    0% { transform: scale(.3); opacity: 0; }
    60% { transform: scale(1.3); opacity: 1; }
    100% { transform: scale(1); }
}

/* Step pane transitions: the underline bar already slides between nav items (see
   .stepper-active-indicator above), so the content pane just snapping into place on Back/Next
   read as inconsistent with it. Fade + a short slide from whichever side matches the direction
   travelled. Only the *incoming* pane animates — KTStepper hides the outgoing one immediately via
   display:none, so there's nothing to cross-fade against; see goTo() in index.js for how the
   class gets applied fresh on every navigation, including repeated same-direction moves. */
:is(#test-create-stepper, #aqp-test-create-stepper) [data-kt-stepper-element="content"].step-pane-enter-forward {
    animation: test-create-stepper-pane-enter-forward .35s ease;
}
:is(#test-create-stepper, #aqp-test-create-stepper) [data-kt-stepper-element="content"].step-pane-enter-backward {
    animation: test-create-stepper-pane-enter-backward .35s ease;
}
@keyframes test-create-stepper-pane-enter-forward {
    0% { opacity: 0; transform: translateX(24px); }
    100% { opacity: 1; transform: translateX(0); }
}
@keyframes test-create-stepper-pane-enter-backward {
    0% { opacity: 0; transform: translateX(-24px); }
    100% { opacity: 1; transform: translateX(0); }
}

/* Scale-in the checkmark on the stepper nav item you just completed (Start/Set up/Coverage),
   distinct from the Live summary's per-subject check pop above — goTo() in index.js diffs
   before/after each navigation and only applies this to a checkmark that's newly appeared. */
:is(#test-create-stepper, #aqp-test-create-stepper) .stepper-check.stepper-check-pop {
    animation: test-create-stepper-nav-check-pop .4s ease-out;
}
@keyframes test-create-stepper-nav-check-pop {
    0% { transform: scale(0); }
    60% { transform: scale(1.3); }
    100% { transform: scale(1); }
}

/* Ui.collapse() helper (helpers/ui.js): the transition it animates max-height/opacity/margin
   toward. Not scoped to this wizard — any page using Ui.collapse() gets it. */
.ui-collapsible {
    overflow: hidden;
    transition: max-height .3s ease, opacity .3s ease, margin-bottom .3s ease;
}

/* Smart block toggling on (Ui.collapse on #schedule-block) also brings in the "Smart · <batch>"
   toolbar badge — sliding it in at the same time is what makes switching into Smart mode read as
   one connected change rather than two unrelated UI updates happening to occur together. Only
   applied on the actual on-transition (see renderBadges() in index.js), so it doesn't replay on
   every render while Smart stays selected. */
#badge-smart.badge-smart-slide-in {
    animation: test-create-stepper-badge-slide-in .35s ease;
}
@keyframes test-create-stepper-badge-slide-in {
    0% { opacity: 0; transform: translateX(16px); }
    100% { opacity: 1; transform: translateX(0); }
}

/* Topic rows (expanded chapter): real <tr>s in the chapter table so the count lands in the
   Questions column. No full-width fill — a left accent on the first cell marks them as children
   of the chapter above, so they read lighter than the chapter rows rather than heavier. */
#test-create-stepper #chapter-table tr.topic-row > td:first-child {
    box-shadow: inset 3px 0 0 var(--bs-primary);
}
#test-create-stepper #chapter-table tr.topic-row > td {
    border-bottom-style: solid;
    border-bottom-color: var(--bs-gray-200);
}
/* On expand the chevron already flips; the topic rows fade in beneath the chapter row (opacity
   only — a <tr> is a poor transform target) and the chapter's own count field cross-fades into
   its readonly/"locked" look (form-control-solid text-muted). Both are triggered explicitly from
   the 'expand' click handler in index.js on only the toggled chapter — render() rebuilds every
   row every time, so gating by class here is what stops other rows replaying these. */
#test-create-stepper tr.topic-row.topics-slide-in > td {
    animation: test-create-stepper-topics-slide-in .3s ease;
}
@keyframes test-create-stepper-topics-slide-in {
    0% { opacity: 0; }
    100% { opacity: 1; }
}
#test-create-stepper .chapter-count.chapter-count-crossfade {
    animation: test-create-stepper-input-crossfade .3s ease;
}
@keyframes test-create-stepper-input-crossfade {
    0% { opacity: 0; }
    100% { opacity: 1; }
}

/* Subject icons (chips, mark cards, coverage tabs, rail, review): one class, one size, so the
   same SVG never renders at a different weight from one step to the next. */
body :is(#test-create-stepper, #aqp-test-create-stepper) .subject-icon {
    width: 24px;
    height: 24px;
    flex: 0 0 24px;
    object-fit: contain;
}
/* On a selected (btn-primary) chip the multi-colour SVG sits dark on the primary fill; render it
   as a white glyph there so the selected chip still reads as the same icon at the same weight. */
#test-create-stepper .btn-primary .subject-icon {
    filter: brightness(0) invert(1);
}

/* Subject chips and mark cards (Set up step): a quick press-scale confirms the chip click
   itself, independent of whether that click is turning the subject on or off. index.js applies
   this to the actual clicked button on select (persists through render(), so it's a live element
   already in the DOM, not a freshly-mounted one) and to the freshly-rendered chip on deselect. */
:is(#test-create-stepper, #aqp-test-create-stepper) .chip-press-scale {
    animation: test-create-stepper-chip-press .25s ease;
}
@keyframes test-create-stepper-chip-press {
    0% { transform: scale(1); }
    40% { transform: scale(.92); }
    100% { transform: scale(1); }
}
/* New mark card fades and slides in when its subject is selected. Removal instead goes through
   Ui.collapseOut() (helpers/ui.js) on click, before the card leaves the grid — see the
   toggle-subject handler in index.js — so there's no matching "exit" keyframe here. */
#test-create-stepper [data-mark-card].marks-card-enter {
    animation: test-create-stepper-marks-card-enter .3s ease;
}
@keyframes test-create-stepper-marks-card-enter {
    0% { opacity: 0; transform: translateY(-8px); }
    100% { opacity: 1; transform: translateY(0); }
}

/* Board's Weightage bars fill in from zero, staggered per row, the first time a subject's
   coverage tab renders (see isFirstRenderForSubject in renderCoverage(), index.js) — reads as
   "here is the shape of this subject's syllabus" rather than the bars just appearing pre-filled.
   Every value the animation needs (target width, per-row delay) is set as an inline custom
   property per bar, so one keyframe covers every row regardless of chapter count. */
#test-create-stepper .progress-bar.weight-bar-fill {
    animation: test-create-stepper-weight-bar-fill .5s ease forwards;
    animation-delay: var(--weight-bar-delay, 0ms);
}
@keyframes test-create-stepper-weight-bar-fill {
    from { width: 0; }
    to { width: var(--weight-bar-target); }
}

/* Coverage shortfall alert: slides in from below the chapter table instead of just appearing —
   only on the actual on-transition (see shortfallWasVisible in index.js), not every render while
   it stays visible. */
#test-create-stepper #coverage-shortfall.shortfall-slide-in {
    animation: test-create-stepper-shortfall-slide-in .35s ease;
}
@keyframes test-create-stepper-shortfall-slide-in {
    0% { opacity: 0; transform: translateY(14px); }
    100% { opacity: 1; transform: translateY(0); }
}

/* Footer status line: cross-fades whenever its message actually changes (setFooterStatus() in
   index.js only adds this on a real text change, not on every render()). */
:is(#test-create-stepper, #aqp-test-create-stepper) #footer-status.footer-status-crossfade {
    animation: test-create-stepper-footer-crossfade .3s ease;
}
@keyframes test-create-stepper-footer-crossfade {
    0% { opacity: 0; }
    100% { opacity: 1; }
}

/* Next button: kept natively enabled even while blocked (see renderFooter() in index.js) so a
   click can still be caught and answered with a shake, rather than the browser silently
   swallowing the click the way it would for a truly `disabled` button. This class only supplies
   the visual "disabled" look. Generate uses the same look while the test name blocks it, and so
   does the AQP Paper step's own Continue (#btn-preview-continue, render-paper.js), which just
   forwards its click to #btn-next and mirrors its blocked state. */
:is(#test-create-stepper, #aqp-test-create-stepper) :is(#btn-next, #btn-preview-continue, #btn-generate).is-soft-disabled {
    opacity: .5;
    cursor: not-allowed;
}
:is(#test-create-stepper, #aqp-test-create-stepper) :is(#btn-next, #btn-generate).shake-invalid {
    animation: test-create-stepper-shake .4s ease;
}
@keyframes test-create-stepper-shake {
    0%, 100% { transform: translateX(0); }
    20% { transform: translateX(-6px); }
    40% { transform: translateX(5px); }
    60% { transform: translateX(-4px); }
    80% { transform: translateX(3px); }
}

/* Generate: the checkmark Ui.morphToCheck() (helpers/ui.js) swaps in for the spinner scales in
   rather than just appearing, so the "done" moment reads as a deliberate confirmation. */
.generate-check-pop {
    display: inline-block;
    animation: test-create-stepper-check-pop .4s ease-out;
}

/* Suggested test name: a single highlight fade when the auto-suggestion changes to a different
   string, deliberately not a typewriter effect — see the diff check in renderReview() (index.js)
   that gates this to real changes only, not every render while the step is open. */
:is(#test-create-stepper, #aqp-test-create-stepper) #test_name.name-suggestion-refresh {
    animation: test-create-stepper-name-highlight .6s ease;
}
@keyframes test-create-stepper-name-highlight {
    0% { background-color: var(--kt-primary-light, #FCF4F4); }
    100% { background-color: var(--bs-white); }
}

/* ---- Micro-animations the AQP wizard adds to match the Customized Test wizard's ----------------
   Reuses the keyframes above wherever the motion is the same (badge slide, marks-card enter,
   shortfall slide, check pop); everything here is gated by the JS on a real change so a re-render
   never replays it, and the reduced-motion block below switches all of it off. */

/* Toolbar badges (standard / mode / template) slide in when they appear or their text changes.
   They sit in the page toolbar, outside the stepper, so they get their own reduced-motion guard
   (the shared blanket reduced-motion rule further down only covers each stepper's own container). */
.aqp-badge-slide-in {
    animation: test-create-stepper-badge-slide-in .35s ease;
}
@media (prefers-reduced-motion: reduce) {
    .aqp-badge-slide-in { animation: none !important; }
}

/* A newly added subject tab / Manual subject card eases in (Customized's mark-card enter). */
#aqp-test-create-stepper .item-enter {
    animation: test-create-stepper-marks-card-enter .3s ease;
}

/* The Set up sections that wait on a curriculum fade up once it has loaded. */
#aqp-test-create-stepper .reveal-in {
    animation: test-create-stepper-marks-card-enter .4s ease;
}

/* Inline alerts (loading / expired / Manual unavailable) slide in from below, like the coverage
   shortfall alert, only when they actually appear or change. */
#aqp-test-create-stepper .alert-slide-in {
    animation: test-create-stepper-shortfall-slide-in .35s ease;
}

/* Paper step (Template mode): master-detail. A slim scrolling list of templates beside the paper
   viewer, both inside one fixed-height frame so switching papers never moves the page. Below xl the
   two stack (list capped at 300px, viewer below it). */
#aqp-test-create-stepper .paper-master-detail {
    display: flex;
    flex-direction: column;
    background: var(--bs-white);
}
#aqp-test-create-stepper .paper-list-col {
    position: relative;
    flex: 0 0 auto;
    max-height: 300px;
    overflow-y: auto;
    border-bottom: 1px solid var(--bs-gray-200);
}
#aqp-test-create-stepper .paper-viewer-col {
    display: flex;
    flex-direction: column;
    min-width: 0;
    height: 85vh;
    min-height: 520px;
}
/* overscroll-behavior was "contain" - meant to stop this panel's own scroll from rubber-banding
   the page behind it, but "contain" also blocks scroll *chaining*: once you hit the bottom of a
   long paper, scrolling further just dead-ends instead of continuing to scroll the page, which
   read as the scroll wheel "doing nothing". Left at the default (chains to the page once this
   panel is exhausted) - the rubber-band concern doesn't apply anyway once .is-expanded takes the
   panel full-screen, since there's no page behind it to bounce. */
#aqp-test-create-stepper .paper-viewer-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 1.25rem;
}
@media (min-width: 1200px) {
    #aqp-test-create-stepper .paper-master-detail {
        flex-direction: row;
        /* Nearly the full screen: the frame sits just under the 70px app header once you scroll to it,
           so a long (80 marks) paper needs far less scrolling inside it. */
        height: max(600px, calc(100vh - 100px));
    }
    #aqp-test-create-stepper .paper-list-col {
        flex: 0 0 340px;
        max-height: none;
        border-bottom: 0;
        border-right: 1px solid var(--bs-gray-200);
    }
    #aqp-test-create-stepper .paper-viewer-col {
        flex: 1 1 0;
        height: auto;
        min-height: 0;
    }
}
/* Expanded: the viewer covers the whole screen (above the app header and sidebar) for reading a long
   paper in one go; the paper keeps a comfortable reading width in the middle. */
#aqp-test-create-stepper .paper-viewer-col.is-expanded {
    position: fixed;
    inset: 0;
    z-index: 1090;
    height: 100vh !important;
    min-height: 0;
    background: var(--bs-white);
}
#aqp-test-create-stepper .paper-viewer-col.is-expanded .paper-viewer-body .paper-sheet {
    max-width: 1100px;
    margin: 0 auto;
}
#aqp-test-create-stepper .paper-list {
    outline: none;
}
/* The list column's own title, matching the right pane's "Template preview" header bar height/
   feel (px-6 py-4 border-bottom in the blade markup) so both columns read as having a header,
   not just one of them. Sticky and above .paper-group-heading's z-index so it stays pinned at the
   very top of the list while both the group headings and the cards scroll underneath it. */
#aqp-test-create-stepper .paper-list-heading {
    position: sticky;
    top: 0;
    z-index: 2;
    padding: 1rem 1.25rem;
    background: var(--bs-white);
    border-bottom: 1px solid var(--bs-gray-200);
    color: var(--bs-gray-900);
    font-size: 1rem;
    font-weight: 600;
}
#aqp-test-create-stepper .paper-group-heading {
    position: sticky;
    top: 0;
    z-index: 1;
    padding: .4rem 1rem;
    background: var(--bs-gray-100);
    border-bottom: 1px solid var(--bs-gray-200);
    color: var(--bs-gray-600);
    font-size: .75rem;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
}
#aqp-test-create-stepper .template-card {
    display: flex;
    align-items: center;
    gap: .75rem;
    padding: .7rem 1rem;
    border-bottom: 1px solid var(--bs-gray-100);
    cursor: pointer;
    transition: background-color .12s ease, box-shadow .12s ease;
}
/* Same .form-check-solid bug as #chapter-select-all above: it only themes the :checked state, so
   an unselected row's radio fills with #eff2f5 - near-identical to the card's own background -
   and reads as not being a control at all (confirmed live: unreadable as a radio on first view
   of the step, before any row had been picked). */
#aqp-test-create-stepper .template-radio:not(:checked) {
    border: 1px solid var(--bs-gray-400);
}
/* .template-name is a flex item with text-truncate, which needs min-width:0 to ellipsise. Without it
   the name never shrinks and the master-detail frame clips it mid-word, so let the title wrap instead. */
#aqp-test-create-stepper .template-card .template-name {
    min-width: 0;
    white-space: normal !important;
    overflow: visible !important;
    text-overflow: clip;
    overflow-wrap: anywhere;
    text-wrap: balance;
}
#aqp-test-create-stepper .template-card:not(.opacity-50):hover,
#aqp-test-create-stepper .template-card.is-previewing {
    background-color: var(--bs-gray-100);
}
#aqp-test-create-stepper .template-card.is-previewing .template-name {
    font-weight: 700 !important;
}
/* Selected is shown by the tint (bg-light-primary, set by the JS) and the filled radio. The JS also
   adds the theme's border-primary, whose blue would otherwise colour the row's bottom rule, so the
   rule keeps its normal colour. */
#aqp-test-create-stepper .template-card.border-primary {
    border-bottom-color: var(--bs-gray-100) !important;
}

/* Paper step's Preview modal: the server-rendered template paper (admin-panel.aqp.json-template.template)
   sits on a white "sheet". Section titles get an accent underline, each question heading is a soft
   band with its marks in the brand colour, and the numbered parts under it are ruled lightly. The
   markup itself is the shared template view's, so this only restyles it and never changes its grid. */
#template-preview-body .paper-sheet {
    background: var(--bs-white);
    border: 1px solid var(--bs-gray-200);
    border-radius: .75rem;
    padding: 1rem 1.25rem 1.25rem;
    font-size: .875rem;
    line-height: 1.35;
    color: var(--bs-gray-800);
}
#template-preview-body .paper-sheet .section-grid {
    margin: 1rem 0 .35rem;
    padding-bottom: .25rem;
    border-bottom: 2px solid var(--bs-primary);
    font-size: .8rem;
    letter-spacing: .04em;
    text-transform: uppercase;
}
#template-preview-body .paper-sheet .section-grid:first-child {
    margin-top: 0;
}
#template-preview-body .paper-sheet .grid-container.full-with-one-frame:not(.question-row-tr) {
    margin-top: .6rem;
    padding: .35rem .75rem;
    background: var(--bs-gray-100);
    border-radius: .5rem;
    align-items: center;
}
/* Explicit markers (qn-number/qn-marks/qn-label, set in question-number.blade.php and the
   question-group/paragraph blade partials) instead of the old "last <p> in its div" heuristic: that
   accidentally coloured a heading's NAME text red only when its marks weren't also merged in at the
   same level (nothing else left to be :last-child of), so the same kind of heading - number, name
   and marks together - read as fully colored for one question and half plain-black for the next
   with no visible reason. Number, name and marks of a question-group/paragraph heading are always
   coloured together now, regardless of whether that particular level happens to carry its own
   merged marks. (Section dividers - "SECTION 3 : ..." - are a different, deliberately plainer
   treatment: see .section-grid below, not given qn-label.) The .question-row-tr > p rule above
   still wins for a sub-item's own qn-number (higher specificity: one more element in the selector),
   so this doesn't recolour the gray detail rows. */
#template-preview-body .paper-sheet .qn-number,
#template-preview-body .paper-sheet .qn-marks,
#template-preview-body .paper-sheet .qn-label {
    color: var(--bs-primary);
}
/* The shared paper style reserves 75px for the number column (Q.15, "iii", "Q.21 (A)"), which left a
   wide empty gap before every short label in this narrower pane. 54px still fits the longest label,
   and every row uses the same width so the text stays aligned down the sheet. */
#template-preview-body .paper-sheet .grid-container.full-with-one-frame,
#template-preview-body .paper-sheet .grid-container.default-grid {
    grid-template-columns: 54px 1fr;
    column-gap: .75rem;
}
/* A paper with (a)/(b) parts (see tidyPaperLabels): every row gets the same wider label area — the
   usual 54px number column plus 28px for the part — so the text starts at one position throughout. */
#template-preview-body .paper-sheet .has-parts .grid-container.full-with-one-frame,
#template-preview-body .paper-sheet .has-parts .grid-container.default-grid {
    grid-template-columns: 82px 1fr;
}
#template-preview-body .paper-sheet .has-parts .grid-container.question-row-tr.qn-tight {
    grid-template-columns: 54px 1fr;
}
#template-preview-body .paper-sheet .question-row-tr > p.qn-split {
    display: grid;
    grid-template-columns: 54px 1fr;
    padding-right: 0;
    justify-content: normal;
}
#template-preview-body .paper-sheet .qn-split .qn-main {
    padding-right: .5rem;
    text-align: right;
}
#template-preview-body .paper-sheet .qn-split .qn-main.qn-main-left {
    padding-left: .5rem;
    text-align: left;
}
#template-preview-body .paper-sheet .qn-split .qn-part {
    text-align: left;
}
#template-preview-body .paper-sheet .question-row-tr {
    padding: .18rem .75rem;
    border-bottom: 1px dashed var(--bs-gray-200);
}
#template-preview-body .paper-sheet .question-row-tr > p {
    justify-content: flex-end;
    padding-right: .5rem;
    color: var(--bs-gray-600);
}
/* question-row-tr-main (see question-details.blade.php): a standalone top-level question is the same
   visual level as a question-group/paragraph heading - same soft band and brand-colour number/marks,
   instead of the dashed-ruled gray treatment plain .question-row-tr gets - so every top-level question
   reads the same whether or not it happens to bundle sub-questions underneath it. */
#template-preview-body .paper-sheet .question-row-tr.question-row-tr-main {
    margin-top: .6rem;
    padding: .35rem .75rem;
    background: var(--bs-gray-100);
    border-radius: .5rem;
    border-bottom: none;
    align-items: center;
}
#template-preview-body .paper-sheet .question-row-tr.question-row-tr-main > p {
    justify-content: flex-start;
    padding-right: 0;
    color: var(--bs-primary);
}
#template-preview-body .paper-sheet .question-row-tr.question-row-tr-main .marks {
    color: var(--bs-primary);
}
/* AqpOptionRenderer / AqpQuestionRenderer emit bare <p> tags (no m-0) for each option and stem, so
   Bootstrap's 1rem bottom margin stacked on top of the option row's own my-1 and made the preview
   read double-spaced. Scoped to this pane so the real Question Paper / PDFs keep their spacing. */
#template-preview-body .paper-sheet .question-container p {
    margin-bottom: 0;
}
#template-preview-body .paper-sheet .options-container .option {
    margin-top: .1rem !important;
    margin-bottom: .1rem !important;
}

/* Number boxes in the chapters section (difficulty mix, bulk setter, weights): no spinner arrows —
   values are typed, and a stray click on the arrows or the wheel used to change a percentage. */
#aqp-test-create-stepper .mix-panel input[type="number"],
#aqp-test-create-stepper .chapter-weight-input {
    -moz-appearance: textfield;
    appearance: textfield;
}
#aqp-test-create-stepper .mix-panel input[type="number"]::-webkit-outer-spin-button,
#aqp-test-create-stepper .mix-panel input[type="number"]::-webkit-inner-spin-button,
#aqp-test-create-stepper .chapter-weight-input::-webkit-outer-spin-button,
#aqp-test-create-stepper .chapter-weight-input::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

/* Review: the Back / Generate bar stays on screen while you read the summary above it. */
#aqp-test-create-stepper.on-review .aqp-footer {
    position: sticky;
    bottom: 0;
    z-index: 5;
    margin-top: 1.5rem !important;
    padding-bottom: .9rem;
    background: var(--kt-card-bg, #fff);
    box-shadow: 0 -8px 16px -12px rgba(0, 0, 0, .15);
}

/* Loading overlay for the Set up pane (Ui.block on #setup-block, as the Customized wizard does for its
   setup card). That card is short so the theme's default centred overlay works there; this pane runs
   ~1,100px, which put the spinner off-screen and left a faint lighter box with hard edges. Instead the
   pane dims with the card's own colour and the spinner stays pinned near the top of the viewport. */
#aqp-test-create-stepper #setup-block > .blockui-overlay {
    background-color: color-mix(in srgb, var(--kt-card-bg, #fff) 72%, transparent);
    align-items: flex-start;
    border-radius: .5rem;
    /* Above the sticky Summary rail (z-index 5), which otherwise showed through undimmed; !important
       because the block plugin sets its own inline z-index. */
    z-index: 10 !important;
}
#aqp-test-create-stepper #setup-block > .blockui-overlay .blockui-message {
    position: sticky;
    top: 40vh;
    margin-top: 6rem;
}

/* Custom mix row fades in beneath its chapter (opacity only — a <tr> is a poor transform target). */
#aqp-test-create-stepper tr.chapter-mix-row td.mix-slide-in {
    animation: test-create-stepper-topics-slide-in .3s ease;
}

/* Summary rail: a subject that just became complete eases into its success tint from the warning
   tint it had a moment ago. Was previously the other way round (success-light fading to
   transparent) with no `forwards`, so the row's own always-on inline background-color (rowStyle in
   renderSummaryRail) snapped it back to solid green the instant the 0.8s animation ended - typing a
   weight value that flips complete/incomplete/complete a few times in a row (e.g. "1" -> "10" ->
   "100") retriggered that fade-then-snap on every keystroke, reading as the card flickering
   green/white/green continuously. Animating into the resting colour, with `forwards`, removes the
   snap entirely - retriggering it mid-flight just restarts the same fade-in, never a flash to white. */
#aqp-test-create-stepper .aqp-summary-row-complete {
    animation: aqp-summary-row-complete .8s ease-out forwards;
}
@keyframes aqp-summary-row-complete {
    0% { background-color: var(--kt-warning-light, #FFF8DD); }
    100% { background-color: var(--kt-success-light, #E8FFF3); }
}

/* Reduced motion: every CSS animation/transition either creation wizard adds under its own
   stepper id — count flash, rail check-pop, step-pane slide, the active-step underline, Smart
   block collapse, topic slide, chip press, weight bars, shortfall/footer cross-fade, shake, name
   highlight, and Ui.collapse()'s max-height/opacity transition (shared with any other page using
   that helper) — is purely decorative motion layered on top of state that's already reflected in
   the DOM (a class, a value, visibility). None of it gates content reveal, so turning it off
   can't strand anything half-shown; it just makes the state change land instantly instead of
   animating there. The one thing left after this — Ui.collapseOut()'s width collapse and any
   JS-driven number tweens (e.g. tweenNumberInput/tweenTextNumber in the Customized Test
   create/index.js) — aren't CSS and so aren't reachable from a media query; those check
   prefers-reduced-motion in JS directly (see ui.js and each wizard's own index.js). */
@media (prefers-reduced-motion: reduce) {
    :is(#test-create-stepper, #aqp-test-create-stepper) *,
    :is(#test-create-stepper, #aqp-test-create-stepper) *::before,
    :is(#test-create-stepper, #aqp-test-create-stepper) *::after,
    .ui-collapsible {
        animation: none !important;
        transition: none !important;
    }
}

/* Dark sidebar active item: the theme's active background (#2a2a3c) sits only ~12 RGB units
   above the sidebar's own background (#1e1e2d), so the "you are here" highlight (e.g. Tests
   under Entrance Test) is nearly invisible against its neighbors. Render the active link as a
   soft white pill with bright bold text - deliberately NOT a primary tint: on whitelabels whose
   primary is red/orange that read as an error state. The only primary accent is the bullet /
   icon, which is small enough to act as a marker rather than a warning. Hover stays a fainter
   white tint so the two states never look alike. */
[data-kt-app-layout=dark-sidebar] .app-sidebar .menu .menu-item .menu-link.active {
    background-color: rgba(255, 255, 255, .14);
    border-radius: .475rem;
    box-shadow: none;
}
[data-kt-app-layout=dark-sidebar] .app-sidebar .menu .menu-item .menu-link.active .menu-title {
    color: #fff;
    font-weight: 600;
}
[data-kt-app-layout=dark-sidebar] .app-sidebar .menu .menu-item .menu-link.active .menu-icon,
[data-kt-app-layout=dark-sidebar] .app-sidebar .menu .menu-item .menu-link.active .menu-icon i,
[data-kt-app-layout=dark-sidebar] .app-sidebar .menu .menu-item .menu-link.active .menu-icon .svg-icon,
[data-kt-app-layout=dark-sidebar] .app-sidebar .menu .menu-item .menu-link.active .menu-icon .svg-icon svg [fill]:not(.permanent):not(g) {
    color: var(--bs-primary);
    fill: var(--bs-primary);
}
[data-kt-app-layout=dark-sidebar] .app-sidebar .menu .menu-item .menu-link.active .menu-bullet .bullet {
    background-color: var(--bs-primary);
    width: 6px;
    height: 6px;
}
[data-kt-app-layout=dark-sidebar] .app-sidebar .menu .menu-item .menu-link:not(.active):hover {
    background-color: rgba(255, 255, 255, .04);
    border-radius: .475rem;
}

/* DataTables (server-side) renders its "No data available in table" placeholder row the
   instant it's initialized, synchronously, before the first ajax request is even dispatched —
   so it can paint before any JS-driven "processing" class has a chance to apply. Rather than
   chase that timing, default the empty-state text to hidden always, and only reveal it once a
   draw has actually completed AND the server confirmed zero total records (see drawCallback in
   datatable.js's initCustomizedTestDatatable, which adds `dt-empty-confirmed` in that case, and
   only that case). This guarantees the text can never show during any loading/transient state —
   only for a genuinely empty result set. */
.dataTables_empty {
    visibility: hidden;
}
.table-responsive.dt-empty-confirmed .dataTables_empty {
    visibility: visible;
}

/* Same confirmed-empty state: the length dropdown, "Showing 0 to 0" text and pager are noise
   under an empty-state message, so drop the whole footer row until there are records. */
.table-responsive.dt-empty-confirmed .dataTables_length,
.table-responsive.dt-empty-confirmed .dataTables_info,
.table-responsive.dt-empty-confirmed .dataTables_paginate {
    display: none;
}

/* Reusable "soft pill" active-tab styling for Bootstrap tabs/pills. Opt in by adding the
   `nav-pill-tabs` class to any `<ul class="nav">` alongside its usual classes (drop
   `nav-tabs`/`nav-line-tabs`/`nav-pills` - this replaces their visuals). Works with
   Bootstrap's own tab-switching JS (`data-bs-toggle="tab"`) unchanged, since that only
   depends on `.nav-link`/`.active`, not on which visual nav-* class is present.
   If a tab-link has a leading icon in Metronic's `.svg-icon` span, its color is handled
   below too, since `.svg-icon` sets its own color directly rather than inheriting it. */
.nav-pill-tabs {
    border-bottom: none;
}
.nav-pill-tabs .nav-link {
    border: none;
    border-radius: 50rem;
    padding: 0.65rem 1.25rem;
    color: var(--kt-gray-600);
    transition: background-color 0.2s ease, color 0.2s ease;
}
.nav-pill-tabs .nav-link:hover,
.nav-pill-tabs .nav-link:hover .svg-icon {
    color: var(--kt-primary);
}
.nav-pill-tabs .nav-link.active,
.nav-pill-tabs .nav-link.active .svg-icon {
    color: var(--kt-primary);
}
.nav-pill-tabs .nav-link.active {
    background-color: var(--kt-primary-light);
}
#chapters-subject-tabs {
    gap: .75rem;
}
/* Scoped override, not a change to .nav-pill-tabs itself - that class is shared by other pages'
   tab navigation (e.g. the JSON test Show page's own _tabs.blade.php), which should keep the
   full pill shape. Here specifically, each tab carries a subject name plus a status icon
   (renderTabs, render-chapters.js), and the full 50rem pill read as oversized for that - the
   theme's own standard button radius (same as "Set difficulty for all" alongside it) instead. */
#chapters-subject-tabs .nav-link {
    border-radius: var(--bs-border-radius);
}

/* Customized Test detail page - Coverage tab's subject/chapter/topic drill-down tree
   (see initCustomizedTestCoverageTree). Only the expand/collapse chevron itself is
   clickable, not the whole row, since the row also renders selectable badge text. */
.coverage-chevron {
    cursor: pointer;
    transition: transform 0.15s ease;
}
.coverage-chevron.rotate-90 svg {
    transform: rotate(90deg);
}

/* Coverage tree: each hierarchy level's icon (subjectLevelIconHtml etc. in datatable.js)
   sits on a small rounded colored chip rather than a row-edge border bar - the color lives
   on the icon, not the row's boundary. */
.coverage-level-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    flex-shrink: 0;
}

/* Coverage tree loading row: a fixed height on the "Loading..." cell so the row
   occupies roughly the same space a real row would, avoiding a layout jump once it's
   replaced by the (differently-sized) real content. min-height doesn't apply reliably
   to table-cell elements in Chrome, so use height instead. */
.coverage-loading-cell {
    height: 56px;
}

/* Coverage summary stats: bold numbers with muted labels, separated by dividers. */
.coverage-summary-stat {
    display: inline-flex;
    align-items: baseline;
    gap: .375rem;
}

.coverage-summary-stat + .coverage-summary-stat {
    padding-left: 1rem;
    margin-left: 1rem;
    border-left: 1px solid var(--kt-gray-300);
}

.coverage-summary-stat .coverage-summary-number {
    font-size: 1.35rem;
    font-weight: 700;
    line-height: 1;
}

.coverage-summary-stat .coverage-summary-label {
    font-size: 0.925rem;
}

/* Row-actions dropdown items (e.g. Download Result before a test ends): the theme's
   default .disabled state on .menu-link is too subtle to read as "blocked" next to
   enabled items in the same menu, so gray out text/icon explicitly and swap the cursor. */
.menu-link.disabled {
    opacity: 0.5;
    color: var(--kt-text-muted) !important;
    cursor: not-allowed;
    pointer-events: none;
}

.menu-link.disabled .svg-icon svg [fill] {
    fill: var(--kt-text-muted) !important;
}

/* Replace the DataTables default red/green +/- badge on the customized-test user-tests and
   paper-checker credits/batches child-row toggles with a plain chevron that rotates open,
   matching the rest of the admin panel's icon language instead of the plugin's stock
   traffic-light styling. The batches table is scoped by its pane: AQP reuses that id. */
#user-test-datatable td.dt-control,
#transactions-datatable td.dt-control,
#batches-pane #batches-datatable td.dt-control {
    cursor: pointer;
}

#user-test-datatable td.dt-control:before,
#transactions-datatable td.dt-control:before,
#batches-pane #batches-datatable td.dt-control:before {
    height: auto;
    width: auto;
    margin-top: 0;
    display: inline-block;
    color: var(--kt-primary);
    border: none;
    border-radius: 0;
    box-shadow: none;
    background-color: transparent;
    font-family: inherit;
    font-size: 1rem;
    line-height: 1;
    content: "\276F"; /* ❯ */
    transition: transform 0.15s ease-in-out;
}

#user-test-datatable tr.dt-hasChild td.dt-control:before,
#transactions-datatable tr.dt-hasChild td.dt-control:before,
#batches-pane #batches-datatable tr.dt-hasChild td.dt-control:before {
    background-color: transparent;
    transform: rotate(90deg);
}

/* Shared modal styling (admin-ui-design skill §8.1/8.4): a tinted header with an icon chip
   instead of Bootstrap's bare default header, and a tightened footer for compact modal bodies.
   Reused across admin-panel modals rather than re-declared per modal id - see
   .modal-header-icon-chip usage in resources/views/admin-panel/**/partials/_*-modal.blade.php. */
.modal-header-tinted {
    background-color: var(--kt-gray-100);
    border-top-left-radius: 0.475rem;
    border-top-right-radius: 0.475rem;
    padding-top: 1.25rem;
    padding-bottom: 1.25rem;
}

.modal-header-icon-chip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border-radius: 0.475rem;
    background-color: var(--kt-primary-light);
    color: var(--kt-primary);
}

.modal-footer-tight {
    padding-top: .75rem;
    padding-bottom: .75rem;
}

/* AQP show-page question rows, in-flight/just-updated state (docs/revamp/aqp-2026-09-16.md D12).
   Same visual language as datatable-cards.css's .row-flipping/.row-just-updated, but AQP renders
   a question as a div.question-row-tr grid (see resources/views/admin-panel/aqp/json-test/
   partials/manual/_simple-question.blade.php and json-template/partials/question-details.blade.php),
   not a <tr>/<td>, so those table-scoped rules can't apply directly — this is the div equivalent.
   Also covers .paragraph-td (json-template paragraph row) and .paragraph-row-tr / a whole
   .manual-meta-data-block (manual mode paragraph row / question-type block) — same grid-or-plain-
   block shape, same setAqpRowBusy()/markAqpRowUpdated() helpers (show.js). Hard-scoped to
   #aqp-test-paper (the wrapper the Show page's paper container gets) rather than a bare class, since
   this markup is reused verbatim inside the question-paper PDF/print view, which must never carry
   this state. */
#aqp-test-paper .question-row-busy-content {
    opacity: .3;
    pointer-events: none;
    transition: opacity .2s ease;
}
/* An in-flow bar prepended above the (now dimmed) content, not a pill centred over the row: a manual
   meta-data block can run several questions tall, and a vertically-centred badge landed wherever that
   midpoint happened to fall — on top of whatever line of text was there. A bar at the top never
   overlaps content, at any row height. grid-column spans every .question-row-tr/.paragraph-td/
   .paragraph-row-tr grid it's prepended into (grid-auto-flow then pushes the existing cells onto the
   row below); it's a harmless no-op on .manual-meta-data-block, which isn't a grid. */
#aqp-test-paper .question-row-busy-indicator {
    grid-column: 1 / -1;
    display: flex;
    align-items: center;
    padding: .5rem .9rem;
    margin-bottom: .5rem;
    border-radius: .475rem;
    background-color: var(--kt-primary-light);
    font-size: .9rem;
}

/* Success green, not primary — same reasoning as dt-card-row-updated in datatable-cards.css: a
   primary flash reads as an error on whitelabels whose primary is red/orange, and green matches
   the success toast fired at the same moment. */
#aqp-test-paper .question-row-tr.is-just-updated,
#aqp-test-paper .paragraph-td.is-just-updated,
#aqp-test-paper .paragraph-row-tr.is-just-updated,
#aqp-test-paper .manual-meta-data-block.is-just-updated {
    animation: aqp-question-row-updated 3s ease-out;
}
#aqp-test-paper .question-row-tr.is-just-updated > *,
#aqp-test-paper .paragraph-td.is-just-updated > *,
#aqp-test-paper .paragraph-row-tr.is-just-updated > *,
#aqp-test-paper .manual-meta-data-block.is-just-updated > * {
    animation: aqp-question-row-cell-in .35s ease-out;
}
@keyframes aqp-question-row-updated {
    0% { background-color: color-mix(in srgb, var(--bs-success, #50cd89) 16%, #fff); }
    100% { background-color: transparent; }
}
@keyframes aqp-question-row-cell-in {
    0% { opacity: 0; transform: translateY(4px); }
    100% { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
    #aqp-test-paper .question-row-tr.is-just-updated,
    #aqp-test-paper .paragraph-td.is-just-updated,
    #aqp-test-paper .paragraph-row-tr.is-just-updated,
    #aqp-test-paper .manual-meta-data-block.is-just-updated,
    #aqp-test-paper .is-just-updated > *,
    #aqp-test-paper .question-row-busy-content {
        animation: none !important;
        transition: none !important;
    }
}

/* Manual mode's Paper-step question-type rows (create/render-paper.js), dragged via Shopify
   Draggable's Sortable — ported from the production (pre-revamp) create page's own
   _manual-template.blade.php <style> block, values and all, not invented here. initSortables()
   restricts `draggable` to `.is-selected` rows and cancels a sort landing on an unselected one
   (same restriction as production's manual-mode.js), so these rules only ever apply to a row that
   was never `.opacity-50` to begin with — !important is defensive, not load-bearing, kept in case
   that restriction is ever loosened again. */
.question-type-row.draggable-source--is-dragging {
    opacity: 0.35 !important;
}
.question-type-row.draggable-mirror {
    background-color: var(--kt-body-bg);
    border: 1px solid var(--kt-border-color);
    border-radius: 0.475rem;
    box-shadow: var(--kt-box-shadow-lg);
    opacity: 0.9 !important;
    z-index: 1000;
}
/* Draggable hides the true original with an inline `display: none`, but every row carries
   Bootstrap's `.d-flex` (`display: flex !important`), which beats an inline style — so the
   original stayed fully visible at its old spot while the placeholder and mirror showed elsewhere:
   the "same row three times" while dragging. Production's rows are a plain grid with no
   !important display, which is why it never showed there. */
.question-type-row.draggable--original {
    display: none !important;
}
/* Only a selected row is draggable (initSortables()), so an unselected row's handle would be dead
   weight — hidden rather than removed so the row's columns stay aligned, matching production. */
.question-type-row:not(.is-selected) .draggable-handle {
    visibility: hidden;
}

/* Q1, Q2… numbering column. Selected rows read in the primary colour, like production's SR No. */
.question-type-row .qt-order-number {
    width: 2rem;
    text-align: center;
    font-weight: 600;
    font-size: 0.85rem;
    color: var(--kt-text-muted);
    flex-shrink: 0;
}
.question-type-row.is-selected .qt-order-number {
    color: var(--kt-primary);
}
/* The marks filter hides rows with .d-none, but .d-flex is !important and is emitted after it, so
   on its own d-none lost and filtered rows stayed visible. */
.question-type-row.d-none {
    display: none !important;
}

/* AQP question-paper/answer-key PDF generation loader (aqp/partials/_pdf-generation-loader.blade.php),
   shown only on that flow's own two view-paper pages - not the shared, app-wide #page-loader every
   other page still uses. */
.aqp-pdf-loader {
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: rgba(0, 0, 0, .5);
}
.aqp-pdf-loader-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    width: 300px;
    padding: 2.25rem 2rem 2rem;
    border-radius: 1rem;
    background-color: var(--kt-body-bg);
    box-shadow: var(--kt-box-shadow-lg);
    text-align: center;
}
/* A static document icon inside a ring that pulses outward, rather than a spinner - reads as "your
   file is being put together", not a generic wait state. */
.aqp-pdf-loader-icon {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 64px;
    height: 64px;
    border-radius: 50%;
    background-color: var(--kt-primary-light);
    margin-bottom: 1.25rem;
}
.aqp-pdf-loader-icon-pulse {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    border: 2px solid var(--kt-primary);
    animation: aqp-pdf-loader-pulse 1.6s ease-out infinite;
}
@keyframes aqp-pdf-loader-pulse {
    0% { transform: scale(1); opacity: .6; }
    100% { transform: scale(1.45); opacity: 0; }
}
.aqp-pdf-loader-title {
    font-size: 1.15rem;
    font-weight: 600;
    color: var(--kt-gray-900);
}
.aqp-pdf-loader-subtitle {
    margin-top: .25rem;
    font-size: .95rem;
    color: var(--kt-gray-600);
}
/* Indeterminate progress bar: a short filled segment sweeps left-to-right on loop. */
.aqp-pdf-loader-progress {
    position: relative;
    width: 100%;
    height: 6px;
    margin-top: 1.5rem;
    border-radius: 50rem;
    background-color: var(--kt-gray-200);
    overflow: hidden;
}
.aqp-pdf-loader-progress span {
    position: absolute;
    top: 0;
    left: 0;
    height: 100%;
    width: 40%;
    border-radius: 50rem;
    background-color: var(--kt-primary);
    animation: aqp-pdf-loader-progress-sweep 1.3s ease-in-out infinite;
}
@keyframes aqp-pdf-loader-progress-sweep {
    0% { left: -40%; }
    100% { left: 100%; }
}
@media (prefers-reduced-motion: reduce) {
    .aqp-pdf-loader-icon-pulse,
    .aqp-pdf-loader-progress span {
        animation: none;
    }
    .aqp-pdf-loader-icon-pulse {
        opacity: .6;
    }
    .aqp-pdf-loader-progress span {
        left: 0;
        width: 100%;
    }
}

/* AQP create-wizard "Generate test" loader (aqp/partials/_test-generation-loader.blade.php) - same
   visual recipe as the PDF loader above (pulsing ring + indeterminate progress bar reads as "still
   working" rather than a static icon that can look stuck), kept as its own class/id set so the two
   flows' show/hide helpers can't collide with each other. */
.aqp-test-generation-loader {
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: rgba(0, 0, 0, .5);
}
.aqp-test-generation-loader-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    width: 300px;
    padding: 2.25rem 2rem 2rem;
    border-radius: 1rem;
    background-color: var(--kt-body-bg);
    box-shadow: var(--kt-box-shadow-lg);
    text-align: center;
}
.aqp-test-generation-loader-icon {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 64px;
    height: 64px;
    border-radius: 50%;
    background-color: var(--kt-primary-light);
    margin-bottom: 1.25rem;
}
.aqp-test-generation-loader-icon-pulse {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    border: 2px solid var(--kt-primary);
    animation: aqp-test-generation-loader-pulse 1.6s ease-out infinite;
}
@keyframes aqp-test-generation-loader-pulse {
    0% { transform: scale(1); opacity: .6; }
    100% { transform: scale(1.45); opacity: 0; }
}
.aqp-test-generation-loader-title {
    font-size: 1.15rem;
    font-weight: 600;
    color: var(--kt-gray-900);
}
.aqp-test-generation-loader-subtitle {
    margin-top: .25rem;
    font-size: .95rem;
    color: var(--kt-gray-600);
}
.aqp-test-generation-loader-progress {
    position: relative;
    width: 100%;
    height: 6px;
    margin-top: 1.5rem;
    border-radius: 50rem;
    background-color: var(--kt-gray-200);
    overflow: hidden;
}
.aqp-test-generation-loader-progress span {
    position: absolute;
    top: 0;
    left: 0;
    height: 100%;
    width: 40%;
    border-radius: 50rem;
    background-color: var(--kt-primary);
    animation: aqp-test-generation-loader-progress-sweep 1.3s ease-in-out infinite;
}
@keyframes aqp-test-generation-loader-progress-sweep {
    0% { left: -40%; }
    100% { left: 100%; }
}
@media (prefers-reduced-motion: reduce) {
    .aqp-test-generation-loader-icon-pulse,
    .aqp-test-generation-loader-progress span {
        animation: none;
    }
    .aqp-test-generation-loader-icon-pulse {
        opacity: .6;
    }
    .aqp-test-generation-loader-progress span {
        left: 0;
        width: 100%;
    }
}

/* Small uppercase section title that groups a cluster of fields - same treatment everywhere it's
   used (AQP/Customized Test PDF modals, AQP print-settings pages, AQP creation wizard). Those pages
   used to each scope this class to their own modal id (#question-paper-details-modal etc.); the
   plain page views (view-paper.blade.php's "Spacing"/"Layout options") never had a matching rule at
   all and rendered as unstyled text. One global rule covers every current and future use. */
.generate-modal-section-title {
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--kt-gray-600);
    margin-bottom: 0.75rem;
}

/* AQP creation wizard's duration preset pills (1h/1h 30m/2h/3h) - smaller than .btn-sm's own
   padding/font-size, since these are a compact quick-pick row, not a primary action. */
.duration-preset {
    padding: 0.25rem 0.75rem;
    font-size: 0.8125rem;
}

/* Stream/Medium/Board/Standard cascading selects (helpers/standard-metadata.js) briefly block
   their own .fv-row with KTBlockUI while a dependent dropdown's options load. The theme's default
   rule is `.blockui .blockui-overlay { background-color: var(--kt-blockui-overlay-bg) }` (two
   classes - KTBlockUI adds "blockui" to the blocked element itself), which paints a visible
   gray/dark tint across the label + field while that's happening, reading as a UI glitch rather
   than a loading state. Matching its specificity (two classes on the overlay itself) rather than
   `.metadata-cascade-overlay` alone, since a single-class rule loses that tie regardless of
   stylesheet order - this keeps just the spinner. */
.blockui-overlay.metadata-cascade-overlay {
    background-color: transparent;
}

/* Manual paper: running totals in the subject header. A light panel holds the two totals; a value flashes
   primary when it changes, and turns red only while it is over its cap. */
#aqp-test-create-stepper .card-toolbar:has(> .manual-marks-totals) {
    align-items: stretch !important;
}
#aqp-test-create-stepper .manual-marks-totals {
    display: flex;
    align-items: center;
    background: var(--bs-gray-100);
    border-radius: .5rem;
    padding: 0 .9rem;
}
#aqp-test-create-stepper .manual-marks-total-item {
    display: inline-flex;
    align-items: center;
    gap: .5rem;
}
#aqp-test-create-stepper .manual-marks-total-item + .manual-marks-total-item {
    padding-left: 1.25rem;
    border-left: 1px solid var(--bs-gray-300);
}
#aqp-test-create-stepper .manual-marks-total-label {
    font-size: .8rem;
    color: var(--bs-gray-600);
}
#aqp-test-create-stepper .manual-marks-total-value {
    display: inline-block;
    min-width: 2.25rem;
    padding: .15rem .5rem;
    border-radius: .375rem;
    background: var(--bs-white);
    font-size: .95rem;
    font-weight: 700;
    line-height: 1.4;
    text-align: center;
    color: var(--bs-gray-900);
}
#aqp-test-create-stepper .manual-marks-total-value.text-danger {
    color: var(--bs-danger) !important;
}
#aqp-test-create-stepper .manual-marks-total-value.is-changed {
    animation: manual-marks-total-highlight 1.2s ease-out;
}
@keyframes manual-marks-total-highlight {
    0% { background: var(--bs-primary-light, #fde4df); color: var(--bs-primary); }
    100% { background: var(--bs-white); }
}

/* Reported-question indicator (json-template/partials/question-details.blade.php): the warning tint
   that used to live on its own report button now lives on the menu-link inside the row-actions
   dropdown, so tint the toggle too - otherwise a reported question is invisible until the dropdown
   is opened. */
.question-actions-td:has(.btn-report-aqp-question.btn-light-warning) .question-actions-toggle {
    color: var(--kt-warning);
    border-color: var(--kt-warning-light);
    background-color: var(--kt-warning-light);
}

/* JSON test create wizard, Set up step (_setup.blade.php): Batch/Duration/Question-pool row. Items hug
   their own content width and wrap as a normal flex row - only .aqp-setup-field-grow (Batch) stretches.
   Equal-width grid/flex tracks were tried first and rejected: Duration's and Question pool's content is
   much narrower than a full track, so forcing them to equal-width tracks left a wide dead gap between
   Duration's content and Question pool's content even though the tracks themselves were adjacent. */
.aqp-setup-fields-row {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 1.5rem 3rem;
}
.aqp-setup-fields-row > .aqp-setup-field-grow {
    flex: 1 1 280px;
    /* Grows to fill leftover space, but a batch-select doesn't need to be nearly as wide as the
       whole remaining row on a big screen - stop it well short of that. */
    max-width: 420px;
}

/* Multi-subject paper view (manual/template.blade.php's manual-subject-group, json-template/
   template.blade.php's per-section card): both sit inside the show page's own white .card, so
   .card-bordered's default border (--bs-gray-200, #eff2f5) is nearly the same shade as that
   ancestor's white background and reads as no border at all. Bump it to --bs-gray-300 and add a
   faint shadow so each subject's box is actually visible, not just present in the DOM. */
.manual-subject-group.card-bordered,
#template-grid-container > .card.card-bordered {
    border-color: var(--bs-gray-300, #e4e6ef);
    box-shadow: 0 1px 2px rgba(0, 0, 0, .04);
}

/* Subjects picker dropdown (create/index.js's renderSubjectOption): a selected row gets the
   theme's own checkmark, an absolutely-positioned icon (.select2-results__option--selected:after,
   plugins.bundle.css) that sat on top of the combine-rule text on the right of each row -
   reserving padding for it didn't clear the overlap reliably, so the icon is dropped entirely for
   this dropdown instead. Redundant anyway: a selected row already reads as selected from its own
   highlighted background and bold/coloured text, same as every other option. */
.select2-container--bootstrap5 .select2-dropdown.aqp-subject-dropdown .select2-results__option {
    /* Keep the theme's checkmark (absolute, right: 1.25rem, .75rem wide) but reserve its space so the
       right-aligned combine-rule text stops short of it instead of running underneath. */
    padding-right: 2.75rem;
}

/* Search matches marked in DataTables rows (assets/js/helpers/datatable-search-highlight.js).
   Bootstrap's own `mark` uses --bs-highlight-bg, which stays pale yellow in dark mode under light
   text. The theme's warning yellow at 22% is a shade deeper than --kt-warning-light (too faint to
   spot in a long row) and stays a muted dark yellow on the dark theme. The cell keeps its own
   text colour. */
mark.dt-search-highlight {
    padding: 0;
    color: inherit;
    background-color: rgba(var(--kt-warning-rgb), 0.22);
    border-radius: 0.25rem;
}

/* No sort arrow on a column that can't be sorted. The theme draws the arrow for whichever column
   is the table's current order (.sorting_asc/.sorting_desc), even when DataTables also marks it
   .sorting_disabled - e.g. a table with ordering: false that still names an initial order, like
   the Sr. No. column of a test's questions. More specific than the theme's !important rule. */
table.dataTable > thead > tr > th.sorting_disabled::before,
table.dataTable > thead > tr > th.sorting_disabled::after {
    display: none !important;
}

/* Review step's "Edit ..." buttons (create/index.js editLink): white with a primary border, solid primary
   with white text on hover. Written out instead of utility classes because .text-primary is !important,
   which kept the label orange on the orange hover fill, and .border-primary uses this theme's blue
   --bs-primary-rgb. */
.btn.btn-outline-edit {
    background-color: var(--bs-white, #fff);
    color: var(--bs-primary);
    border: 1px solid var(--bs-primary);
}
.btn.btn-outline-edit:hover,
.btn.btn-outline-edit:focus-visible {
    background-color: var(--bs-primary);
    color: var(--bs-white, #fff);
    border-color: var(--bs-primary);
}
/* The theme colours an <i> inside .btn grey on its own, so the pen would stay grey on the orange hover fill. */
.btn.btn-outline-edit i {
    color: inherit !important;
}

/* Manual mode question type counts (create wizard, render-paper.js): a typed number above the limit stays visible but
   is marked while it is over, then counts down to the limit and flashes once so the change is noticed. */
.question-type-row .qt-total.is-over-limit,
.question-type-row .qt-required.is-over-limit {
    color: var(--bs-danger);
    border-color: var(--bs-danger);
    font-weight: 600;
}
@keyframes qt-clamped-flash {
    0%   { background-color: rgba(241, 65, 108, .28); box-shadow: 0 0 0 3px rgba(241, 65, 108, .28); }
    100% { background-color: transparent;             box-shadow: 0 0 0 0 rgba(241, 65, 108, 0); }
}
.question-type-row .qt-clamped {
    animation: qt-clamped-flash 1.6s ease-out;
}
@media (prefers-reduced-motion: reduce) {
    .question-type-row .qt-clamped { animation: none; background-color: rgba(241, 65, 108, .12); }
}

/* Manual mode question type rows: Total / Required as labelled steppers (_paper.blade.php #tpl-question-type-row). */
.question-type-row .qt-pair {
    display: flex;
    gap: .65rem;
    flex: none;
}
.question-type-row .qt-lab {
    margin-bottom: 3px;
    color: var(--bs-gray-500);
    font-size: .65rem;
    font-weight: 700;
    letter-spacing: .05em;
    text-transform: uppercase;
}
.question-type-row .qt-step {
    display: flex;
    align-items: stretch;
    height: 34px;
    overflow: hidden;
    border: 1px solid var(--bs-gray-200);
    border-radius: .55rem;
    background: var(--bs-white);
}
.question-type-row .qt-step:focus-within {
    border-color: var(--bs-primary);
}
.question-type-row .qt-step:has(.is-over-limit) {
    border-color: var(--bs-danger);
}
.question-type-row .qt-step-btn {
    flex: 0 0 30px;
    border: 0;
    background: var(--bs-gray-100);
    color: var(--bs-gray-700);
    font-size: 1rem;
    font-weight: 600;
    line-height: 1;
    cursor: pointer;
    transition: background-color .15s ease, color .15s ease;
}
.question-type-row .qt-step-btn:hover:not(:disabled) {
    background: var(--bs-primary);
    color: var(--bs-white);
}
.question-type-row .qt-step-btn:disabled {
    opacity: .4;
    cursor: not-allowed;
}
.question-type-row .qt-step-input {
    width: 46px;
    min-width: 0;
    padding: 0;
    border: 0;
    background: transparent;
    text-align: center;
    font-weight: 700;
    color: var(--bs-gray-900);
    -moz-appearance: textfield;
    appearance: textfield;
}
.question-type-row .qt-step-input:focus {
    outline: 0;
}
.question-type-row .qt-step-input:disabled {
    background: transparent;
    color: var(--bs-gray-400);
}
.question-type-row .qt-step-input::-webkit-outer-spin-button,
.question-type-row .qt-step-input::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}
/* Counts only mean something on a picked row; hidden (not removed) so selecting a row doesn't change its height. */
.question-type-row:not(.is-selected) .qt-pair {
    visibility: hidden;
}

/* Picked row in the Change Question / Change Paragraph modals: the solid primary fill with
   inverse text that the Customized change-question table gets from DataTables' Select
   extension (table.dataTable > tbody > tr.selected), so both pickers read the same. */
#change-aqp-questions-datatable > tbody > tr,
#change-aqp-paragraphs-datatable > tbody > tr {
    cursor: pointer;
}
#change-aqp-questions-datatable > tbody > tr.is-picked > td,
#change-aqp-paragraphs-datatable > tbody > tr.is-picked > td {
    box-shadow: inset 0 0 0 9999px var(--bs-primary);
    color: var(--bs-primary-inverse, #fff);
    border-color: var(--bs-primary) !important;
}

/* AQP PDF modal "Reload data": spin the icon while the re-fetch is in flight */
.aqp-reload-spin svg {
    animation: aqp-reload-spin 0.8s linear infinite;
}
@keyframes aqp-reload-spin {
    to { transform: rotate(360deg); }
}

/* AQP create wizard on a phone: the question type rows wrap Total/Required onto their own line instead of
   sitting beside the name (the step header itself follows the Entrance Test wizard's block above). */
@media (max-width: 767.98px) {
    .question-type-row {
        flex-wrap: wrap;
    }
    .question-type-row .qt-pair {
        flex: 1 1 100%;
        justify-content: flex-end;
    }
    /* The name column is marked min-w-0, which this theme does not define, so a long name could not shrink. */
    .question-type-row > .flex-grow-1 {
        min-width: 0;
    }
}

/* The paper on the AQP test page (template or manual) is laid out in fixed-width rows. On a phone it was wider
   than its card and pushed the whole page sideways, so scroll it inside the card, as the Entrance Test tables do. */
@media (max-width: 767.98px) {
    #aqp-test-paper {
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
    }
    /* Keep a readable width, like a DataTable's min-width: squeezed into the phone's width the question text
       got ~90px and wrapped every other word. */
    #aqp-test-paper > .w-100 {
        min-width: 640px;
    }
}
