@model StudyKindleAcademy.ViewModels.MockTestViewModel @ {
    Layout = "~/Views/Shared/_ExamLayout.cshtml";
    ViewData ["Title"] = "Mock Test";
    int progressPct = Model.TotalQuestions > 0 ? (int)System.Math.Round((double)Model.CurrentQuestionNumber / Model.TotalQuestions * 100) : 0;
}

@* ========================================================= StudyKindleAcademy — Exam Runner (premium, calm redesign) Scoped under .sk-exam so nothing leaks into the layout. Includes anti-cheat: tab switch / browser back = expire. ---- FIXES APPLIED (search "FIX #" to find each one) ---- FIX #1 Timer box compacted to a fixed 22mm height. FIX #2 Question palette scrolls independently (100 numbers
           no longer push the timer box around). FIX #3 Save & Next / Submit Test never move — the question card scrolls internally instead of growing the page. FIX #4 DurationMinutes is now read from the Tests table via the ViewModel, not hardcoded to 20 in the timer script. ========================================================= *@ <style> @@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@@400;500;600;700;800&family=Sora:wght@@600;700;800&display=swap');

.sk-exam {
    --bg: #EEF2FA;
    --surface: #ffffff;
    --line: #E6EAF5;
    --ink: #1f2547;
    --muted: #6b73a0;
    --indigo: #4A5AEE;
    --indigo-2: #6B78F5;
    --indigo-soft: #EDEFFE;
    --emerald: #16B67F;
    --amber: #F2A413;
    --slate: #9AA3BE;
    --panel-a: #1B2350;
    --panel-b: #0E1436;
    --gold: #F5B93B;
    font-family: 'Plus Jakarta Sans',system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
    color: var(--ink);
    background: radial-gradient(60% 40% at 100% 0%, rgba(74,90,238,.06), transparent 60%), var(--bg);
    min-height: 100vh;
    padding: 18px 6px 40px;
    margin: -1rem -12px 0;
}

    .sk-exam * {
        box-sizing: border-box;
    }

/* rules banner */
.sk-rules {
    display: flex;
    align-items: center;
    gap: 10px;
    background: linear-gradient(120deg,#fff7e8,#fdeede);
    border: 1px solid #f4dcae;
    color: #8a5a12;
    border-radius: 14px;
    padding: 10px 16px;
    font-weight: 600;
    font-size: .9rem;
    margin-bottom: 16px;
}

    .sk-rules svg {
        flex: 0 0 auto;
        color: #d98a12;
    }

/* ---------- Question card ---------- */
.sk-card {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 22px;
    overflow: hidden;
    box-shadow: 0 18px 44px rgba(20,30,80,.08),0 2px 6px rgba(20,30,80,.04);
}

.sk-qbar {
    height: 5px;
    background: var(--indigo-soft);
}

.sk-qbar__fill {
    height: 100%;
    background: linear-gradient(90deg,var(--indigo),var(--indigo-2));
    transition: width .4s ease;
}

.sk-qhead {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 18px 26px 6px;
}

.sk-qhead__eyebrow {
    font-size: .68rem;
    letter-spacing: .18em;
    text-transform: uppercase;
    color: var(--indigo);
    font-weight: 800;
}

.sk-qhead__count {
    font-family: 'Sora';
    font-weight: 800;
    color: var(--ink);
    font-size: 1.05rem;
    margin-top: 2px;
}

    .sk-qhead__count small {
        color: var(--muted);
        font-weight: 600;
    }

.sk-cardbody {
    padding: 10px 26px 26px;
}

.sk-qtext {
    font-size: 1.18rem;
    font-weight: 600;
    line-height: 1.55;
    color: #20264b;
    margin: 8px 0 6px;
}

    .sk-qtext p {
        margin-bottom: .4rem;
    }

/* Runs of underscores in question/option text (the standard way
               fill-in-the-blank was authored across every mock paper) are
               converted at render time -- see formatBlanks() in the script below
               -- into one of these styled blank-line spans instead of showing
               raw underscore characters bunched together. */
.sk-blank {
    display: inline-block;
    border-bottom: 2px solid #3a3f66;
    margin: 0 3px;
    height: 0.9em;
    vertical-align: -0.12em;
}

.sk-qimg {
    margin: 14px 0 4px;
}

    .sk-qimg img {
        max-height: 400px;
        border-radius: 14px;
        border: 1px solid var(--line);
        box-shadow: 0 8px 20px rgba(20,30,80,.08);
    }

/* ---------- Option tiles ---------- */
.sk-opts {
    display: flex;
    flex-direction: column;
    gap: 12px;
    margin-top: 18px;
}

.sk-opt {
    position: relative;
    display: flex;
    align-items: flex-start;
    gap: 14px;
    padding: 15px 16px;
    border-radius: 15px;
    border: 1.5px solid var(--line);
    background: #fbfcff;
    cursor: pointer;
    transition: border-color .18s,background .18s,box-shadow .18s,transform .12s;
}

    .sk-opt:hover {
        border-color: #c7cef6;
        background: #fff;
        box-shadow: 0 6px 16px rgba(20,30,80,.07);
    }

    .sk-opt:active {
        transform: scale(.997);
    }

.sk-opt__radio {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
}

.sk-opt__marker {
    flex: 0 0 auto;
    width: 34px;
    height: 34px;
    border-radius: 10px;
    display: grid;
    place-items: center;
    font-family: 'Sora';
    font-weight: 800;
    font-size: .95rem;
    color: var(--muted);
    background: #eef1fa;
    border: 1px solid var(--line);
    transition: all .18s;
}

.sk-opt__content {
    flex: 1;
    padding-top: 3px;
    font-weight: 500;
    color: #2a3157;
    line-height: 1.5;
}

    .sk-opt__content img {
        max-height: 180px;
        border-radius: 10px;
        border: 1px solid var(--line);
        margin-top: 6px;
    }

.sk-opt__check {
    flex: 0 0 auto;
    color: var(--indigo);
    opacity: 0;
    transform: scale(.6);
    transition: all .18s;
    align-self: center;
}

.sk-opt:has(.sk-opt__radio:checked) {
    border-color: var(--indigo);
    background: var(--indigo-soft);
    box-shadow: 0 8px 20px rgba(74,90,238,.14);
}

    .sk-opt:has(.sk-opt__radio:checked) .sk-opt__marker {
        background: linear-gradient(150deg,var(--indigo),var(--indigo-2));
        color: #fff;
        border-color: transparent;
        box-shadow: 0 4px 10px rgba(74,90,238,.35);
    }

    .sk-opt:has(.sk-opt__radio:checked) .sk-opt__check {
        opacity: 1;
        transform: scale(1);
    }

.sk-opt:has(.sk-opt__radio:focus-visible) {
    outline: 3px solid rgba(74,90,238,.35);
    outline-offset: 2px;
}

/* ---------- Action buttons ---------- */
.sk-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    gap: 10px;
    margin-top: 20px;
}

.sk-actions__right {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

/* ---------- Small/medium phones: same clean 2-row arrangement used in
       app mode (Previous / Mark For Review / Save & Next, then Skip / Clear
       Response), but here as a plain responsive breakpoint -- this applies
       everywhere, browser and app alike, since an inconsistently-wrapping
       row of 5 differently-sized buttons is a real layout bug on any narrow
       screen, not something specific to the app. Uses the same
       display:contents technique as the app-mode rules further down: the
       actual HTML (Previous standalone + the other four inside
       .sk-actions__right) is untouched, this only "unwraps" that wrapper for
       layout purposes so all five buttons become direct flex children that
       can be reordered predictably. */
@@media (max-width: 575.98px) {
    .sk-actions {
        display: flex;
        flex-wrap: wrap;
        gap: 10px;
    }

    .sk-actions__right {
        display: contents;
    }

    .sk-actions .sk-b {
        flex: 1 1 auto;
        justify-content: center;
        margin: 0;
    }

    .sk-actions .sk-b--ghost {
        order: 1;
        flex-basis: 30%;
    }

    .sk-actions .sk-b--review {
        order: 2;
        flex-basis: 30%;
    }

    .sk-actions .sk-b--primary {
        order: 3;
        flex-basis: 34%;
    }

    .sk-actions .sk-b--skip {
        order: 4;
        flex-basis: 48%;
    }

    .sk-actions .sk-b--danger {
        order: 5;
        flex-basis: 48%;
    }
}

.sk-b {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    font-weight: 700;
    font-size: .92rem;
    border-radius: 12px;
    padding: 11px 18px;
    border: 1.5px solid transparent;
    cursor: pointer;
    transition: transform .16s,box-shadow .16s,background .16s;
    text-decoration: none;
}

    .sk-b:hover {
        transform: translateY(-1px);
    }

.sk-b--ghost {
    background: #fff;
    border-color: var(--line);
    color: #48507e;
}

    .sk-b--ghost:hover {
        border-color: #c7cef6;
        color: var(--indigo);
    }

.sk-b--danger {
    background: #fff;
    border-color: #f3c7cf;
    color: #c33150;
}

    .sk-b--danger:hover {
        background: #fdeef1;
    }

.sk-b--review {
    background: #fff7e8;
    border-color: #f4dcae;
    color: #a5730f;
}

    .sk-b--review:hover {
        background: #fdefcf;
    }

.sk-b--skip {
    background: #eef1fa;
    border-color: var(--line);
    color: #54597e;
}

.sk-b--primary {
    background: linear-gradient(120deg,var(--indigo),var(--indigo-2));
    color: #fff;
    box-shadow: 0 10px 22px rgba(74,90,238,.3);
}

    .sk-b--primary:hover {
        box-shadow: 0 14px 28px rgba(74,90,238,.42);
        color: #fff;
    }

/* Loading state for form-action buttons (Save & Next, Previous, Skip,
           Clear Response, Mark For Review) WHILE an AJAX navigation is in
           flight. Deliberately implemented as a CSS-only visual state --
           pointer-events:none blocks a double-click/double-submit exactly like
           the native `disabled` attribute would -- rather than setting
           .disabled = true. The native `disabled` attribute abruptly yanks
           focus off whatever button was just clicked and pulls it out of the
           tab order, which is a known trigger for odd browser side effects
           (e.g. a spurious full-screen-exit report) on the very button the
           student just used to submit the form. Keeping real DOM focus intact
           avoids that class of bug entirely. */
.sk-b--loading {
    pointer-events: none;
    opacity: .6;
    cursor: default;
}

/* ================= RIGHT PANEL (3D) ================= */
.sk-panel {
    position: sticky;
    top: 16px;
    display: flex;
    flex-direction: column;
    gap: 16px;
}

/* ---- FIX #1: compact timer, locked to 22mm (your 20-25mm ask).
               CSS accepts physical units directly, so this is exact regardless
               of how the fonts inside render. ---- */
.sk-timer {
    border-radius: 22px;
    padding: 8px 16px;
    text-align: center;
    position: relative;
    overflow: hidden;
    background: radial-gradient(120% 60% at 50% -10%, rgba(245,185,59,.10), transparent 60%),linear-gradient(160deg,var(--panel-a),var(--panel-b));
    box-shadow: 0 26px 50px -18px rgba(6,10,40,.8),inset 0 1px 0 rgba(255,255,255,.08);
    height: 22mm;
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
}

.sk-timer__cap {
    font-size: .62rem;
    letter-spacing: .18em;
    text-transform: uppercase;
    color: var(--gold);
    font-weight: 700;
}

.sk-timer__val {
    font-family: 'Sora';
    font-weight: 800;
    font-size: 1.75rem;
    color: #fff;
    line-height: 1;
    margin-top: 4px;
    letter-spacing: .02em;
    font-variant-numeric: tabular-nums;
}

    .sk-timer__val.sk-timer--warn {
        color: #ffd66b;
    }

    .sk-timer__val.sk-timer--danger {
        color: #ff8a8a;
        animation: skPulse 1s ease-in-out infinite;
    }

@@keyframes skPulse {
    0%, 100% {
        opacity: 1;
    }

    50% {
        opacity: .55;
    }
}

.sk-timer__hint {
    color: #9aa3d6;
    font-size: .62rem;
    margin-top: 4px;
}

.sk-palette {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 22px;
    overflow: hidden;
    box-shadow: 0 14px 34px rgba(20,30,80,.07);
}

.sk-palette__head {
    padding: 14px 18px;
    font-family: 'Sora';
    font-weight: 700;
    color: var(--ink);
    border-bottom: 1px solid var(--line);
    background: linear-gradient(120deg,#f7f9ff,#eef2ff);
}

.sk-palette__body {
    padding: 16px 18px;
}

/* NEW: wraps ONLY the number grid, so it can scroll on its own
               while the head above and the legend below stay put. */
.sk-palette__scroll {
}

.sk-pal-grid {
    display: grid;
    grid-template-columns: repeat(5,1fr);
    gap: 9px;
}

.sk-pal {
    display: grid;
    place-items: center;
    height: 46px;
    border-radius: 12px;
    font-family: 'Sora';
    font-weight: 800;
    font-size: .95rem;
    text-decoration: none;
    border: 1.5px solid var(--line);
    color: #3a4173;
    background: #fff;
    transition: transform .15s,box-shadow .15s;
}

    .sk-pal:hover {
        transform: translateY(-2px);
        box-shadow: 0 6px 14px rgba(20,30,80,.15);
    }

.sk-pal--answered {
    background: linear-gradient(150deg,#1cc98d,#12996a);
    color: #fff;
    border-color: transparent;
}

.sk-pal--review {
    background: linear-gradient(150deg,#f7b23c,#e0900f);
    color: #fff;
    border-color: transparent;
}

.sk-pal--skipped {
    background: #e5e8f2;
    color: #5a6188;
    border-color: #d7dcec;
}

.sk-pal.is-current {
    outline: 3px solid var(--indigo);
    outline-offset: 2px;
    box-shadow: 0 0 0 4px rgba(74,90,238,.15);
}

.sk-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    margin-top: 16px;
    padding-top: 14px;
    border-top: 1px solid var(--line);
}

    .sk-legend span {
        display: inline-flex;
        align-items: center;
        gap: 6px;
        font-size: .76rem;
        color: var(--muted);
        font-weight: 600;
    }

    .sk-legend i {
        width: 12px;
        height: 12px;
        border-radius: 4px;
        display: inline-block;
    }

.sk-submit {
    width: 100%;
    padding: 15px;
    font-size: 1rem;
    border-radius: 16px;
    border: 0;
    color: #fff;
    font-weight: 800;
    cursor: pointer;
    background: linear-gradient(120deg,#4CC38A,#2FA97A);
    box-shadow: 0 14px 30px rgba(47,169,122,.44);
    transition: transform .18s,box-shadow .18s;
}

    .sk-submit:hover {
        transform: translateY(-2px);
        box-shadow: 0 18px 36px rgba(90,80,240,.45);
    }

/* ---------- Modals ---------- */
.sk-modal .modal-content {
    border: 0;
    border-radius: 20px;
    overflow: hidden;
    box-shadow: 0 30px 70px rgba(10,15,45,.4);
}

.sk-modal .modal-header {
    border-bottom: 0;
    padding: 20px 24px 6px;
}

.sk-modal .modal-title {
    font-family: 'Sora';
    font-weight: 800;
}

.sk-modal .modal-body {
    padding: 8px 24px 12px;
    color: #454c78;
    line-height: 1.6;
}

.sk-modal .modal-footer {
    border-top: 0;
    padding: 12px 24px 22px;
}

.sk-mhead {
    width: 46px;
    height: 46px;
    border-radius: 14px;
    display: grid;
    place-items: center;
    margin-bottom: 6px;
    color: #fff;
}

.sk-mhead--warn {
    background: linear-gradient(150deg,#f7b23c,#e0900f);
}

.sk-mhead--danger {
    background: linear-gradient(150deg,#F0596B,#c02b45);
}

/* ================= Full-screen enforcement gate =================
           Full-viewport overlay shown until the browser confirms the page
           is actually in full-screen mode. Blocks all interaction with the
           exam underneath until the student explicitly enters full-screen —
           this is the real, guaranteed enforcement point (Instructions.cshtml
           only does a best-effort early attempt). */
.sk-fs-gate {
    position: fixed;
    inset: 0;
    z-index: 2000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    text-align: center;
    background: radial-gradient(120% 90% at 50% 0%, rgba(245,185,59,.12), transparent 60%), linear-gradient(160deg,var(--panel-a),var(--panel-b));
    font-family: 'Plus Jakarta Sans',system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
}

.sk-fs-gate__card {
    max-width: 440px;
}

.sk-fs-gate__ic {
    width: 64px;
    height: 64px;
    margin: 0 auto 18px;
    border-radius: 20px;
    display: grid;
    place-items: center;
    color: #fff;
    background: linear-gradient(150deg,#4A5AEE,#6B78F5);
    box-shadow: 0 14px 30px rgba(74,90,238,.4);
}

.sk-fs-gate__title {
    font-family: 'Sora';
    font-weight: 800;
    font-size: 1.3rem;
    color: #fff;
    margin: 0 0 8px;
}

.sk-fs-gate__body {
    color: #b9c0ea;
    font-size: .94rem;
    line-height: 1.6;
    margin: 0 0 22px;
}

.sk-fs-gate__btn {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    border: 0;
    cursor: pointer;
    font-weight: 800;
    font-size: 1rem;
    color: #fff;
    padding: 14px 26px;
    border-radius: 14px;
    background: linear-gradient(120deg,#4CC38A,#2FA97A);
    box-shadow: 0 14px 30px rgba(47,169,122,.35);
    transition: transform .16s, box-shadow .16s;
}

    .sk-fs-gate__btn:hover {
        transform: translateY(-2px);
        box-shadow: 0 18px 38px rgba(47,169,122,.46);
    }

/* ================= Violation warning (strikes 1 & 2) ================= */
.sk-vio-badge {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: .72rem;
    font-weight: 800;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: #a5730f;
    background: #fff3da;
    border: 1px solid #f4dcae;
    border-radius: 999px;
    padding: 4px 12px;
    margin-bottom: 10px;
}

.sk-vio-remaining {
    display: block;
    margin-top: 10px;
    font-weight: 700;
    color: #c33150;
    font-size: .9rem;
}

/* ================= FIX #2 + FIX #3 (desktop only) =================
               Bounds the two-column row to the visible viewport height (set by
               JS below, see fitExamRow()) and turns both the question card and
               the palette into internally-scrolling regions, so:
                 - the Save & Next / Submit Test buttons never move, and
                 - the 100-button palette scrolls on its own.

               IMPORTANT: Bootstrap's .row defaults to flex-wrap:wrap (needed so
               columns stack on mobile). Left as wrap, a row with an explicit
               height still sizes its single line to the TALLEST child's content
               instead of stretching to that height — align-items:stretch is
               then just matching the wrong (inflated) number. Forcing nowrap
               here, gated to the same >=992px breakpoint the layout already
               uses elsewhere, is what makes the stretch actually take effect.
               Verified directly — see the note at the end of this reply. ===== */
@@media (min-width: 992px) {
    .sk-examrow {
        min-height: 0;
        flex-wrap: nowrap;
    }

        .sk-examrow > [class^="col-"] {
            display: flex;
            flex-direction: column;
            min-height: 0;
        }

    #examForm {
        display: flex;
        flex-direction: column;
        flex: 1 1 auto;
        min-height: 0;
    }

    .sk-card {
        display: flex;
        flex-direction: column;
        flex: 1 1 auto;
        min-height: 0;
    }

    .sk-qbar,
    .sk-qhead {
        flex: 0 0 auto;
    }

    .sk-cardbody {
        flex: 1 1 auto;
        min-height: 0;
        overflow-y: auto;
        overflow-x: hidden;
    }

    .sk-actions {
        flex: 0 0 auto;
    }

    .sk-panel {
        min-height: 0;
        flex: 1 1 auto;
    }

    .sk-palette {
        flex: 1 1 auto;
        min-height: 0;
        display: flex;
        flex-direction: column;
    }

    .sk-palette__head {
        flex: 0 0 auto;
    }

    .sk-palette__body {
        flex: 1 1 auto;
        min-height: 0;
        display: flex;
        flex-direction: column;
    }

    .sk-palette__scroll {
        flex: 1 1 auto;
        min-height: 0;
        overflow-y: auto;
        overflow-x: hidden;
        padding-right: 4px;
    }

    .sk-legend {
        flex: 0 0 auto;
    }

    .sk-submit {
        flex: 0 0 auto;
    }
}

/* slim, unobtrusive scrollbars for the two new scroll regions */
.sk-cardbody::-webkit-scrollbar,
.sk-palette__scroll::-webkit-scrollbar {
    width: 8px;
}

.sk-cardbody::-webkit-scrollbar-thumb,
.sk-palette__scroll::-webkit-scrollbar-thumb {
    background: #d6d9e8;
    border-radius: 8px;
}

@@media (max-width:991.98px) {
    .sk-panel {
        position: static;
        margin-top: 20px;
    }
}

/* ================= COPY PROTECTION ================= *
               Applies only to the question + options area, not the whole page,
               so the timer/palette/buttons stay normally interactive. This is a
               deterrent against casual copy-paste (mouse-select, right-click,
               Ctrl+C) — it does not and cannot stop someone who opens dev tools,
               views page source, or screenshots the screen. See the note at the
               end of this reply for what this does and doesn't cover. */
.sk-cardbody,
.sk-cardbody * {
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
    -webkit-touch-callout: none; /* blocks the iOS long-press copy menu */
}

    .sk-cardbody img {
        -webkit-user-drag: none;
        user-drag: none;
        pointer-events: none; /* blocks right-click "Save image as" */
    }

/* Bootstrap Toast, restyled to match the sk- design system rather
               than the default Bootstrap look (same pattern as .sk-modal). */
.sk-toast {
    background: linear-gradient(160deg,var(--panel-a),var(--panel-b));
    color: #fff;
    border: 0;
    border-radius: 14px;
    box-shadow: 0 20px 44px rgba(10,15,45,.35);
}

    .sk-toast .toast-body {
        display: flex;
        align-items: center;
        gap: 10px;
        font-weight: 600;
        font-size: .88rem;
        padding: 13px 16px;
    }

    .sk-toast svg {
        flex: 0 0 auto;
        color: var(--gold);
    }

@@media (prefers-reduced-motion:reduce) {
    .sk-exam * {
        transition: none !important;
        animation: none !important;
    }
}

/* ================= App-shell mode (Android/iOS app only) =================
       .sk-app is added to <html> by _ExamLayout.cshtml's inline script when
       window.Capacitor is present. Nothing here touches the plain-browser
       experience -- every rule below is scoped under .sk-app. */

/* "Stay on this tab / don't use Back" only makes sense in a browser --
       there's no tab to switch and no Back button on a phone. Tab-switch
       and back-button detection in the anti-cheat script keep running
       regardless (still relevant: home button, app-switcher, etc. via the
       window-blur listener) -- only this specific *notice* is browser talk. */
.sk-app .sk-rules {
    display: none;
}

/* Timer: was a full-width block sitting between the action buttons and
       the question palette, pushing everything else down a whole screen's
       worth on a phone. Turned into a small chip pinned to the top-right,
       out of the document flow entirely, sized to the viewport rather than
       a fixed desktop width. */
.sk-app .sk-timer {
    position: fixed;
    top: calc(10px + env(safe-area-inset-top));
    right: 10px;
    z-index: 900;
    width: auto;
    min-width: 74px;
    padding: 6px 12px;
    border-radius: 12px;
    box-shadow: 0 10px 24px -8px rgba(10,15,45,.5);
}

.sk-app .sk-timer__cap {
    display: none; /* "Time Remaining" label -- the clock icon of a
                               compact chip doesn't need a caption, and there's
                               no room for one at this size */
}

.sk-app .sk-timer__val {
    font-size: clamp(1rem, 4.2vw, 1.35rem);
}

.sk-app .sk-timer__hint {
    display: none; /* "Auto-submits when time runs out" -- same reasoning */
}

/* Give the question card room to breathe now that the timer chip floats
       independently instead of reserving space in the normal flow. */
.sk-app .sk-card {
    margin-top: 46px;
}

/* Action buttons: Previous / Mark For Review / Save & Next on one line,
       Skip / Clear Response on the line below. The HTML markup is untouched
       (Previous is a standalone element, the other four live inside a
       .sk-actions__right wrapper) -- display:contents "unwraps" that wrapper
       for layout purposes only, so all five buttons become direct flex
       children of .sk-actions and can be freely reordered with `order`,
       without risking anything on the desktop/browser layout that still
       relies on the original two-group structure. */
.sk-app .sk-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
}

.sk-app .sk-actions__right {
    display: contents;
}

.sk-app .sk-actions .sk-b {
    flex: 1 1 auto;
    justify-content: center;
    margin: 0;
}

.sk-app .sk-actions .sk-b--ghost {
    order: 1;
    flex-basis: 30%;
}

.sk-app .sk-actions .sk-b--review {
    order: 2;
    flex-basis: 30%;
}

.sk-app .sk-actions .sk-b--primary {
    order: 3;
    flex-basis: 34%;
}

.sk-app .sk-actions .sk-b--skip {
    order: 4;
    flex-basis: 48%;
}

.sk-app .sk-actions .sk-b--danger {
    order: 5;
    flex-basis: 48%;
}

</style >

<div class="sk-exam" >
<!-- ===== Mandatory full-screen gate ===== -->
<!-- Visible by default (inline style) so it blocks the exam from the very
first paint, before any JS has run. Hidden by the anti-cheat script
the moment full-screen is confirmed active; shown again any time the
student exits full-screen mid-exam. On browsers with no Fullscreen
API at all (notably iPhone/iPad Safari), the script hides this
permanently and falls back to tab/back-button/window-blur detection
only — see the script block near the end of this file. -->
<div class="sk-fs-gate" id="fsGateOverlay" style="display:flex;" >
<div class="sk-fs-gate__card" >
<div class="sk-fs-gate__ic" >
<svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" > <path d="M8 3H5a2 2 0 0 0-2 2v3M16 3h3a2 2 0 0 1 2 2v3M8 21H5a2 2 0 0 1-2-2v-3M16 21h3a2 2 0 0 0 2-2v-3" / > </svg >
</div >
<h3 class="sk-fs-gate__title" > Full-screen required</h3 >
<p class="sk-fs-gate__body" >
This exam must be taken in full-screen mode. Click below to continue —
exiting full-screen once the exam has started counts as a violation.
</p >
<button type="button" class="sk-fs-gate__btn" id="fsGateBtn" >
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" > <path d="M8 3H5a2 2 0 0 0-2 2v3M16 3h3a2 2 0 0 1 2 2v3M8 21H5a2 2 0 0 1-2-2v-3M16 21h3a2 2 0 0 0 2-2v-3" / > </svg >
Enter Full-Screen &amp; Start
</button >
</div >
</div >

<div class="container-fluid" >

<div class="sk-rules" >
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" > <path d="M12 9v4M12 17h.01" / > <path d="M10.3 3.3 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.3a2 2 0 0 0-3.4 0Z" / > </svg >
<span > Stay on this tab and in full-screen. Switching tabs, exiting full-screen, or using the browser <strong > Back</strong > button is tracked — after <strong > 3 violations</strong > your exam is submitted automatically.</span >
</div >

@* FIX #2/#3: id + sk-examrow class added so the height-fit script
and the desktop-only CSS above can target this row. *@
<div class="row g-4 sk-examrow" id="examRow" >
<!-- ============ QUESTION AREA ============ -->
<div class="col-lg-9" >

<form id="examForm" method="post" asp-action="SaveAndNext" >

<input type="hidden" asp-for="TestId" / >
<input type="hidden" id="examStartTime" value="@Model.ExamStartTime" / >
@* FIX #4: duration now comes from the ViewModel (populated
                    from the Tests table), not a hardcoded number in JS. *@
<input type="hidden" id="examDurationMinutes" value="@Model.DurationMinutes" / >
<input type="hidden" name="CurrentQuestionIndex" id="hidCurrentQuestionIndex" value="@Model.CurrentQuestionIndex" / >
<input type="hidden" name="CurrentQuestion.QuestionId" id="hidCurrentQuestionId" value="@Model.CurrentQuestion.QuestionId" / >

<div class="sk-card" >
<div class="sk-qbar" > <div class="sk-qbar__fill" id="qbarFill" style="width:@progressPct%" > </div > </div >

<div class="sk-qhead" >
<div >
<div class="sk-qhead__eyebrow" > Mock Test</div >
<div class="sk-qhead__count" id="qHeadCount" > Question @Model.CurrentQuestionNumber <small > of @Model.TotalQuestions</small > </div >
</div >
</div >

<div class="sk-cardbody" >

<div class="sk-qtext" id="qText" >
@Html.Raw(Model.CurrentQuestion.QuestionText)
</div >

<div class="sk-qimg" id="qImgWrap" style="@(string.IsNullOrEmpty(Model.CurrentQuestion.QuestionImagePath) ? "display:none;" : "")" >
<img id="qImg" src="@Model.CurrentQuestion.QuestionImagePath" class="img-fluid" alt="Question image" / >
</div >

<div class="sk-opts" >

<label class="sk-opt" >
<input class="sk-opt__radio" type="radio" name="SelectedOption" value="A" @(Model.SelectedOption == "A" ? "checked" : null) / >
<span class="sk-opt__marker" > A</span >
<span class="sk-opt__content" >
<div id="optAText" > @Html.Raw(Model.CurrentQuestion.OptionA)</div >
<img id="optAImg" src="@Model.CurrentQuestion.OptionAImagePath" class="img-fluid" alt="Option A" style="@(string.IsNullOrEmpty(Model.CurrentQuestion.OptionAImagePath) ? "display:none;" : "")" / >
</span >
<span class="sk-opt__check" > <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round" > <path d="M20 6 9 17l-5-5" / > </svg > </span >
</label >

<label class="sk-opt" >
<input class="sk-opt__radio" type="radio" name="SelectedOption" value="B" @(Model.SelectedOption == "B" ? "checked" : null) / >
<span class="sk-opt__marker" > B</span >
<span class="sk-opt__content" >
<div id="optBText" > @Html.Raw(Model.CurrentQuestion.OptionB)</div >
<img id="optBImg" src="@Model.CurrentQuestion.OptionBImagePath" class="img-fluid" alt="Option B" style="@(string.IsNullOrEmpty(Model.CurrentQuestion.OptionBImagePath) ? "display:none;" : "")" / >
</span >
<span class="sk-opt__check" > <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round" > <path d="M20 6 9 17l-5-5" / > </svg > </span >
</label >

<label class="sk-opt" >
<input class="sk-opt__radio" type="radio" name="SelectedOption" value="C" @(Model.SelectedOption == "C" ? "checked" : null) / >
<span class="sk-opt__marker" > C</span >
<span class="sk-opt__content" >
<div id="optCText" > @Html.Raw(Model.CurrentQuestion.OptionC)</div >
<img id="optCImg" src="@Model.CurrentQuestion.OptionCImagePath" class="img-fluid" alt="Option C" style="@(string.IsNullOrEmpty(Model.CurrentQuestion.OptionCImagePath) ? "display:none;" : "")" / >
</span >
<span class="sk-opt__check" > <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round" > <path d="M20 6 9 17l-5-5" / > </svg > </span >
</label >

<label class="sk-opt" >
<input class="sk-opt__radio" type="radio" name="SelectedOption" value="D" @(Model.SelectedOption == "D" ? "checked" : null) / >
<span class="sk-opt__marker" > D</span >
<span class="sk-opt__content" >
<div id="optDText" > @Html.Raw(Model.CurrentQuestion.OptionD)</div >
<img id="optDImg" src="@Model.CurrentQuestion.OptionDImagePath" class="img-fluid" alt="Option D" style="@(string.IsNullOrEmpty(Model.CurrentQuestion.OptionDImagePath) ? "display:none;" : "")" / >
</span >
<span class="sk-opt__check" > <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round" > <path d="M20 6 9 17l-5-5" / > </svg > </span >
</label >

</div >
</div >
</div >

<div class="sk-actions" >
<button type="submit" formaction="@Url.Action("PreviousQuestion","MockTest")" class="sk-b sk-b--ghost" >
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" > <path d="m15 18-6-6 6-6" / > </svg >
Previous
</button >

<div class="sk-actions__right" >
<button type="submit" formaction="@Url.Action("ClearResponse","MockTest")" class="sk-b sk-b--danger" > Clear Response</button >
<button type="submit" formaction="@Url.Action("MarkForReview","MockTest")" class="sk-b sk-b--review" > Mark For Review</button >
<button type="submit" formaction="@Url.Action("SkipQuestion","MockTest")" class="sk-b sk-b--skip" > Skip</button >
<button type="submit" id="btnSaveNext" class="sk-b sk-b--primary" >
<span id="btnSaveNextLabel" > Save &amp; Next</span >
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" > <path d="m9 18 6-6-6-6" / > </svg >
</button >
</div >
</div >

<button type="submit" id="autoSubmitButton" formaction="@Url.Action("SubmitTest","MockTest")" style="display:none;" > </button >
</form >

</div >
<!-- ============ RIGHT PANEL ============ -->
<div class="col-lg-3" >
<div class="sk-panel" >

<div class="sk-timer" >
<div class="sk-timer__cap" > Time Remaining</div >
@* FIX #4: initial paint now reflects the real duration
instead of a hardcoded "20:00". updateTimer() below
overwrites this within the same tick regardless, but
this removes even the momentary flash of "20:00". *@
<div id="examTimer" class="sk-timer__val" > @(Model.DurationMinutes.ToString().PadLeft(2, '0')):00</div >
<div class="sk-timer__hint" > Auto-submits when time runs out</div >
</div >

<div class="sk-palette" >
<div class="sk-palette__head" > Question Palette</div >
<div class="sk-palette__body" >

@ {
    var answers = ViewBag.Answers as List<StudyKindleAcademy.ViewModels.StudentAnswerViewModel>;
}

@* FIX #2: grid now lives in its own scroll wrapper, separate from the legend below it. *@ <div class="sk-palette__scroll"> <div class="sk-pal-grid"> @for (int i = 0; i < Model.TotalQuestions; i++) {
    string cls = "sk-pal";

    if (answers != null) {
        var answer = answers[i];
        if (answer.Status == "Answered")

{
    cls += " sk-pal--answered";
}

else if (answer.Status == "Review") {
    cls += " sk-pal--review";
}

else if (answer.Status == "Skipped") {
    cls += " sk-pal--skipped";
}

}

if (i == Model.CurrentQuestionIndex) {
    cls += " is-current";
}

<a href="@Url.Action("GoToQuestion","MockTest", new { questionIndex = i, testId = Model.TestId })"
class="@cls" data-index="@i" > @(i + 1)</a >
}

</div >
</div >

<div class="sk-legend" >
<span > <i style="background:linear-gradient(150deg,#1cc98d,#12996a)" > </i > Answered</span >
<span > <i style="background:linear-gradient(150deg,#f7b23c,#e0900f)" > </i > For review</span >
<span > <i style="background:#e5e8f2" > </i > Skipped</span >
<span > <i style="background:#fff;border:1.5px solid #d7dcec" > </i > Not visited</span >
</div >
</div >
</div >

<button type="button" class="sk-submit" data-bs-toggle="modal" data-bs-target="#submitTestModal" >
Submit Test
</button >

</div >
</div >

</div >
<!-- ===== Modals ===== -->
<div class="modal fade" id="lastQuestionModal" tabindex="-1" >
<div class="modal-dialog sk-modal" >
<div class="modal-content" >
<div class="modal-header d-block" >
<div class="sk-mhead sk-mhead--warn" > <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" > <path d="M12 9v4M12 17h.01" / > <circle cx="12" cy="12" r="9" / > </svg > </div >
<h5 class="modal-title" > Last question reached</h5 >
</div >
<div class="modal-body" > You've reached the last question. When you're ready, click <strong > Submit Test</strong > to finish.</div >
<div class="modal-footer" >
<button type="button" class="sk-b sk-b--primary" data-bs-dismiss="modal" > Got it</button >
</div >
</div >
</div >
</div >

<div class="modal fade" id="submitTestModal" tabindex="-1" >
<div class="modal-dialog sk-modal" >
<div class="modal-content" >
<div class="modal-header d-block" >
<div class="sk-mhead sk-mhead--danger" > <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" > <path d="M9 12l2 2 4-4" / > <circle cx="12" cy="12" r="9" / > </svg > </div >
<h5 class="modal-title" > Submit your test?</h5 >
</div >
<div class="modal-body" > Once submitted, your answers are final and can't be changed.</div>
<div class="modal-footer" >
<button type="button" class="sk-b sk-b--ghost" data-bs-dismiss="modal" > Keep going</button >
<form id="submitTestForm" method="post" asp-action="SubmitTest" class="d-inline" >
<input type="hidden" asp-for="TestId" / >
<button type="submit" class="sk-b sk-b--primary" > Yes, submit test</button >
</form >
</div >
</div >
</div >
</div >

<div class="modal fade" id="timeUpModal" tabindex="-1" >
<div class="modal-dialog sk-modal" >
<div class="modal-content" >
<div class="modal-header d-block" >
<div class="sk-mhead sk-mhead--danger" > <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" > <circle cx="12" cy="12" r="9" / > <path d="M12 7v5l3 2" / > </svg > </div >
<h5 class="modal-title" > Time's up</h5>
</div >
<div class="modal-body" > Your exam time has ended. Submitting your test automatically…</div >
</div >
</div >
</div >
<!-- Shown once, on the 3rd (final) violation: exam auto-submits exactly
like the "time's up" flow (same autoSubmitButton -> SubmitTest ->
             Result page), so the student always lands on a real, scored result. -->
<div class="modal fade" id="examExpiredModal" tabindex="-1" data-bs-backdrop="static" data-bs-keyboard="false" >
<div class="modal-dialog sk-modal" >
<div class="modal-content" >
<div class="modal-header d-block" >
<div class="sk-mhead sk-mhead--danger" > <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" > <rect x="4" y="11" width="16" height="10" rx="2" / > <path d="M8 11V7a4 4 0 0 1 8 0v4" / > </svg > </div >
<h5 class="modal-title" id="expiredTitleText" > Exam auto-submitted</h5 >
</div >
<div class="modal-body" id="expiredBodyText" >
You reached the maximum of 3 violations. For test integrity, your exam is being submitted automatically…
</div >
</div >
</div >
</div >
<!-- NEW: shown on violation 1 and 2 (warnings) — the student can resume. -->
<div class="modal fade" id="examViolationModal" tabindex="-1" data-bs-backdrop="static" data-bs-keyboard="false" >
<div class="modal-dialog sk-modal" >
<div class="modal-content" >
<div class="modal-header d-block" >
<div class="sk-mhead sk-mhead--warn" > <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" > <path d="M12 9v4M12 17h.01" / > <path d="M10.3 3.3 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.3a2 2 0 0 0-3.4 0Z" / > </svg > </div >
<span class="sk-vio-badge" id="violationCountBadge" > Violation 1 / 3</span >
<h5 class="modal-title" id="violationTitle" > Warning 1 of 3</h5 >
</div >
<div class="modal-body" >
<span id="violationReasonText" > You left the exam screen.</span >
<span class="sk-vio-remaining" id="violationRemainingText" > 2 attempts left. Repeated violations will submit your exam automatically.</span >
</div >
<div class="modal-footer" >
<button type="button" class="sk-b sk-b--primary" id="violationResumeBtn" data-bs-dismiss="modal" > Resume Exam</button >
</div >
</div >
</div >
</div >
<!-- Hidden form used to expire the session server-side -->
<form id="expireForm" method="post" asp-action="ExpireExam" asp-controller="MockTest" class="d-none" >
<input type="hidden" asp-for="TestId" / >
<input type="hidden" name="reason" id="expireReason" / >
@Html.AntiForgeryToken()
</form >
<!-- Copy-protection notice (Bootstrap Toast, restyled via .sk-toast) -->
<div class="toast-container position-fixed bottom-0 end-0 p-3" style="z-index:1080;" >
<div id="copyBlockedToast" class="toast sk-toast align-items-center" role="alert" aria-live="assertive" aria-atomic="true" data-bs-delay="2200" >
<div class="d-flex" >
<div class="toast-body" >
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" > <rect x="5" y="11" width="14" height="9" rx="2" / > <path d="M8 11V7a4 4 0 0 1 8 0v4" / > </svg >
<span > Copying is disabled during the test.</span >
</div >
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close" > </button >
</div >
</div >
</div >

</div >
</div >

@* ===================== SCRIPTS ===================== *@

<script >
// ---------- Disable browser scroll restoration ----------
// Placed first, before anything else runs: some browsers remember and
// reapply the page's last scroll position on reload, which would land
// the student already scrolled past the header and rules banner
// regardless of any of the JS below. This must run as early as
// possible, ideally before the browser's own restoration logic acts.
if ("scrollRestoration" in history) {
    history .scrollRestoration = "manual";
}

</script >

<script >
// ---------- Countdown timer (start-time based) ----------
// Restructured into a named, idempotent starter (window.__skStartExamTimer)
// instead of an immediately-invoked IIFE. It is deliberately NOT called
// here on page load -- the anti-cheat script (below) calls it at the exact
// moment the mandatory full-screen gate is dismissed (whether that happens
    // instantly, because the browser silently granted full-screen, or only
    // after the student clicks through the gate). This is what makes "the
// timer starts only once the student has actually entered the exam"
// true, instead of the clock silently running down while they're still
// looking at the full-screen prompt. The visible timer element keeps
// showing the full server-rendered duration (e.g. "20:00") until then.
(function () {
        // FIX #4: duration now comes from the hidden input, which the
        // Razor view populates from Model.DurationMinutes (fetched from
        // the Tests table for this TestId server-side). No hardcoded
        // fallback here on purpose — if this ever reads wrong, it should
        // fail loudly (NaN in the console) rather than quietly showing a
        // plausible but incorrect number, which is exactly how the old
        // "hardcoded 20" bug went unnoticed for so long.
        const durationInput = document.getElementById("examDurationMinutes");
        const examDurationMinutes = durationInput ? parseInt(durationInput.value, 10) : NaN;

        if (!examDurationMinutes || examDurationMinutes <= 0) {
            console.error(
                "examDurationMinutes is invalid:",
                durationInput ? durationInput.value : "(hidden input #examDurationMinutes is missing)",
                "— check that Model.DurationMinutes is being set from the Tests table in the controller."
            );
        }

        const timerEl = document.getElementById("examTimer");
        let timeUpFired = false;
        let timerStarted = false;

        function updateTimer(startTime) {
            const now = new Date();
            const elapsed = Math.floor((now - startTime) / 1000);
            const remaining = (examDurationMinutes * 60) - elapsed;

            if (remaining <= 0) {
                timerEl.innerHTML = "00:00";
                timerEl.classList.add("sk-timer--danger");

                if (!timeUpFired) {
                    timeUpFired = true;
                    window.__examLeaving = true; // legitimate auto-submit, not a violation
                    if (window.__skExitFullscreen) window.__skExitFullscreen(); // defined by the anti-cheat script below
                    try {
                        new bootstrap.Modal(document.getElementById('timeUpModal')).show();
                    } catch (e) { }
                    setTimeout(function () {
                        document.getElementById("autoSubmitButton").click();
                    }, 3000);
                }
                return;
            }

            const minutes = Math.floor(remaining / 60);
            const seconds = remaining % 60;
            timerEl.innerHTML = String(minutes).padStart(2, '0') + ":" + String(seconds).padStart(2, '0');

            // calm colour cue as time gets low
            timerEl.classList.toggle("sk-timer--danger", remaining <= 300);
            timerEl.classList.toggle("sk-timer--warn", remaining > 300 && remaining <= 600);
        }

        // Idempotent by design: safe to call multiple times (e.g. once from
        // the "silent full-screen grant" path and again defensively from a
        // fallback) -- only the FIRST call actually starts the clock.
        window.__skStartExamTimer = function () {
            if (timerStarted) return;
            timerStarted = true;

            // The reference point is "now" (the real moment the student
            // entered the exam), not the server-rendered ExamStartTime --
            // that server value is locked in the instant the StartTest page
            // is first requested, which can be earlier than when full-screen
            // is actually confirmed. The exam's duration is a fixed client-
            // enforced budget with no separate server-side deadline check, so
            // using the client "now" here is both simpler and correct.
            const startTime = new Date();
            updateTimer(startTime);
            setInterval(function () { updateTimer(startTime); }, 1000);
        };
    })();
</script >

@if (TempData["LastQuestion"] != null) {
    <script> document.addEventListener("DOMContentLoaded", function () {
            try { new bootstrap.Modal(document.getElementById('lastQuestionModal')).show(); } catch (e) { }
        });
    </script>
}

<script >
// ---------- FIX #2 / FIX #3: bound the two-column row to the visible
// viewport so the internal scroll areas (.sk-cardbody and
    // .sk-palette__scroll) have a finite height to scroll WITHIN, instead
// of the whole page growing. Desktop only — mobile keeps its existing
// natural stacked flow (see the max-width:991.98px rule in the CSS). ----------
(function () {
        const DESKTOP_BREAKPOINT = 992;

        function fitExamRow() {
            const row = document.getElementById("examRow");
            if (!row) return;

            if (window.innerWidth < DESKTOP_BREAKPOINT) {
                row.style.height = ""; // let mobile flow naturally
                return;
            }

            const top = row.getBoundingClientRect().top + window.scrollY;
            const bottomGap = 24; // matches .sk-exam's own bottom padding intent
            const available = window.innerHeight - top - bottomGap;
            row.style.height = Math.max(480, available) + "px";
        }

        window.addEventListener("resize", fitExamRow);
        window.addEventListener("load", fitExamRow);
        fitExamRow();
    })();
</script >

<script >
// ---------- AJAX question navigation (no full page reload) ----------
// Save & Next / Skip / Previous / Mark For Review / Clear Response, and
// clicking any Question Palette number, now update the page in place
// via fetch() instead of a full browser navigation. Submit Test is
// deliberately NOT intercepted here -- finishing the exam should be a
// real navigation to the Result page.
//
// Falls back to a genuine form submission / page navigation if fetch
// fails for any reason (network hiccup, session expiry, JS error), so
// nobody ever gets stuck. The non-AJAX path in the controller is
// completely unchanged, so this fallback always works exactly as it
// did before this feature existed.
//
// IMPORTANT interaction with the anti-cheat script below: it normally
// marks window.__examLeaving = true on any examForm submit or palette
// click, so that the resulting real navigation isn't flagged as a
// tab-switch/back-button violation. Since AJAX means the page never
// actually leaves, that flag must never be set for an AJAX-intercepted
// interaction -- verified by blocking those specific listeners from
// firing at all (stopImmediatePropagation for the form; a capturing-
    // phase listener for the palette links, since the anti-cheat script
    // attaches directly to each anchor). If a fallback to a REAL navigation
// does happen, __examLeaving is set explicitly at that point, exactly
// once, right before the real navigation occurs.
(function () {
        var examForm = document.getElementById("examForm");
        var palGrid = document.querySelector(".sk-pal-grid");
        if (!examForm) return;

        // Same GoToQuestion endpoint the palette already uses for every
        // other question -- used once, right when the exam actually starts
        // (full-screen gate dismissed), to re-fetch and re-render the FIRST
        // question through this exact same AJAX pipeline instead of relying
        // on the server-rendered markup as-is. This guarantees question 1's
        // DOM has gone through the identical code path as question 2, 3, ...
        // so there is no first-question-only difference left for anything
        // (including the full-screen quirk) to key off.
        var initialQuestionUrl =
            "@Html.Raw(Url.Action("GoToQuestion", "MockTest", new { questionIndex = Model.CurrentQuestionIndex, testId = Model.TestId }))";
        var normalized = false;
        window.__skNormalizeFirstQuestion = function () {
            if (normalized || !initialQuestionUrl) return;
            normalized = true;
            ajaxNavigate(initialQuestionUrl, "GET", null, null, null);
        };

        function scrollCurrentIntoView() {
            var current = document.querySelector(".sk-palette__scroll .sk-pal.is-current");
            if (current && current.scrollIntoView) {
                current.scrollIntoView({ block: "start", behavior: "instant" });
                // scrollIntoView can cascade into scrolling the outer page
                // itself (not just the palette's own scroll box) depending
                // on layout state at the moment it runs. This self-corrects
                // every single time this function is called -- both on
                // initial load and after every AJAX question update -- so
                // the header and rules banner can never end up hidden,
                // regardless of when or why the cascade happens.
                if (window.scrollY !== 0) {
                    window.scrollTo(0, 0);
                }
            }
        }

        function setLoading(isLoading) {
            // Exposed globally so the anti-cheat script can suppress
            // window-blur / full-screen-exit detection while an AJAX
            // navigation (Save & Next, Previous, Skip, palette click, etc.)
            // is in flight. Also see the .sk-b--loading CSS class below --
            // this deliberately does NOT set the native `disabled` attribute
            // on the just-clicked button anymore (see that comment for why).
            window.__examAjaxBusy = isLoading;

            examForm.querySelectorAll("button[type=submit]").forEach(function (b) {
                b.classList.toggle("sk-b--loading", isLoading);
                b.setAttribute("aria-disabled", isLoading ? "true" : "false");
            });
        }

        function showLastQuestionModal() {
            try {
                new bootstrap.Modal(document.getElementById("lastQuestionModal")).show();
            } catch (e) { }
        }

        function setImg(imgEl, path) {
            if (!imgEl) return;
            if (path) {
                imgEl.src = path;
                imgEl.style.display = "";
            } else {
                imgEl.removeAttribute("src");
                imgEl.style.display = "none";
            }
        }

        function formatBlanks(html) {
            // Converts runs of 3+ underscores into a styled blank-line
            // span instead of showing raw underscore characters. Applied
            // both here (every AJAX question update) and once on initial
            // page load, so it's consistent no matter how a student
            // navigates to a question.
            if (!html) return html;
            return html.replace(/_{3,}/g, function (match) {
                var width = Math.min(Math.max(match.length * 9, 36), 90);
                return '<span class="sk-blank" style="width:' + width + 'px"></span>';
            });
        }

        function renderQuestion(data) {
            var pct = data.totalQuestions > 0
                ? Math.round(data.currentQuestionNumber / data.totalQuestions * 100)
                : 0;

            var qbarFill = document.getElementById("qbarFill");
            if (qbarFill) qbarFill.style.width = pct + "%";

            var qHeadCount = document.getElementById("qHeadCount");
            if (qHeadCount) {
                qHeadCount.innerHTML = "Question " + data.currentQuestionNumber +
                    " <small>of " + data.totalQuestions + "</small>";
            }

            var hidIndex = document.getElementById("hidCurrentQuestionIndex");
            if (hidIndex) hidIndex.value = data.currentQuestionIndex;
            var hidQid = document.getElementById("hidCurrentQuestionId");
            if (hidQid) hidQid.value = data.questionId;

            var qText = document.getElementById("qText");
            if (qText) qText.innerHTML = formatBlanks(data.questionText) || "";

            var qImgWrap = document.getElementById("qImgWrap");
            var qImg = document.getElementById("qImg");
            if (qImgWrap && qImg) {
                if (data.questionImagePath) {
                    qImg.src = data.questionImagePath;
                    qImgWrap.style.display = "";
                } else {
                    qImgWrap.style.display = "none";
                }
            }

            document.getElementById("optAText").innerHTML = formatBlanks(data.optionA) || "";
            document.getElementById("optBText").innerHTML = formatBlanks(data.optionB) || "";
            document.getElementById("optCText").innerHTML = formatBlanks(data.optionC) || "";
            document.getElementById("optDText").innerHTML = formatBlanks(data.optionD) || "";
            setImg(document.getElementById("optAImg"), data.optionAImagePath);
            setImg(document.getElementById("optBImg"), data.optionBImagePath);
            setImg(document.getElementById("optCImg"), data.optionCImagePath);
            setImg(document.getElementById("optDImg"), data.optionDImagePath);

            examForm.querySelectorAll(".sk-opt__radio").forEach(function (r) {
                r.checked = (r.value === data.selectedOption);
            });

            (data.palette || []).forEach(function (p) {
                var a = palGrid ? palGrid.querySelector('[data-index="' + p.index + '"]') : null;
                if (!a) return;
                a.classList.remove("sk-pal--answered", "sk-pal--review", "sk-pal--skipped", "is-current");
                if (p.status === "Answered") a.classList.add("sk-pal--answered");
                else if (p.status === "Review") a.classList.add("sk-pal--review");
                else if (p.status === "Skipped") a.classList.add("sk-pal--skipped");
                if (p.index === data.currentQuestionIndex) a.classList.add("is-current");
            });

            scrollCurrentIntoView();

            if (data.lastQuestion) {
                showLastQuestionModal();
            }
        }

        function ajaxNavigate(url, method, formData, fallbackForm, fallbackButton) {
            setLoading(true);
            return fetch(url, {
                method: method,
                body: formData,
                headers: { "X-Requested-With": "XMLHttpRequest" }
            }).then(function (res) {
                if (!res.ok) throw new Error("bad status " + res.status);
                return res.json();
            }).then(function (data) {
                renderQuestion(data);
            }).catch(function (err) {
                // Network/server failure -- fall back to a real navigation so
                // the student is never stuck. Mark this as a legitimate exit
                // explicitly, since the interceptors below prevented the
                // anti-cheat script's own allowLeave() from firing for the
                // AJAX attempt that just failed.
                console.error("AJAX navigation failed, falling back to a real page submit. Reason:", err);
                window.__examLeaving = true;
                if (fallbackForm) {
                    window.__ajaxFallbackInProgress = true;
                    if (fallbackForm.requestSubmit) {
                        fallbackForm.requestSubmit(fallbackButton);
                    } else {
                        fallbackForm.action = fallbackButton.formAction || fallbackForm.action;
                        fallbackForm.submit();
                    }
                } else {
                    window.location = url;
                }
            }).finally(function () {
                setLoading(false);
            });
        }

        examForm.addEventListener("submit", function (e) {
            if (window.__ajaxFallbackInProgress) return; // let a fallback resubmission through untouched
            if (window.__examAjaxBusy) { e.preventDefault(); return; } // reentrancy guard: a request is already
            // in flight (e.g. Enter/Space pressed again fast) -- .sk-b--loading no longer uses the native
            // `disabled` attribute (see setLoading()), so this replaces the double-submit protection it used to give.

            var submitter = e.submitter;
            if (!submitter || !submitter.formAction) return; // unknown trigger -> allow normal submission

            e.preventDefault();
            e.stopImmediatePropagation(); // block the anti-cheat script's allowLeave -- this is not a real navigation

            var url = submitter.formAction;
            var fd = new FormData(examForm);
            ajaxNavigate(url, "POST", fd, examForm, submitter);
        });

        if (palGrid) {
            document.addEventListener("click", function (e) {
                var a = e.target.closest && e.target.closest(".sk-pal");
                if (!a || !palGrid.contains(a)) return;
                if (window.__examAjaxBusy) { e.preventDefault(); return; } // same reentrancy guard for palette navigation

                e.preventDefault();
                e.stopPropagation(); // capture-phase stop: runs before the anchor's own
                // bubbling-phase allowLeave listener ever fires

                ajaxNavigate(a.href, "GET", null, null, null);
            }, true); // capture phase
        }

        // Position the current question on first load too (same as every
        // subsequent AJAX render).
        scrollCurrentIntoView();

        // Q1 arrives server-rendered (not through renderQuestion()), so it
        // needs the same underscore-to-blank-line formatting applied once
        // here on load; every question after that goes through the AJAX
        // path above, which already calls formatBlanks() itself.
        var initialQText = document.getElementById("qText");
        if (initialQText) initialQText.innerHTML = formatBlanks(initialQText.innerHTML);
        ["optAText", "optBText", "optCText", "optDText"].forEach(function (id) {
            var el = document.getElementById(id);
            if (el) el.innerHTML = formatBlanks(el.innerHTML);
        });
    })();
</script >

<script >
// =====================================================================
// Anti-cheat: full-screen enforcement + 3-strike tab/back-button/blur
// detection + auto-submit on the 3rd violation.
//
// Design:
// - A single shared violation counter drives BOTH tab-switch/back-button
// AND full-screen-exit detection. All are "the student left the exam
// screen" in one form or another, so they share one 3-strike budget —
// this also closes the loophole where a student could press Esc to
// leave full-screen freely as long as they never switched tabs.
// - Strikes 1 and 2 show a dismissible warning (with remaining-attempts
    //    count) and let the student resume. Strike 3 auto-submits the exam
// by clicking the SAME hidden #autoSubmitButton the "time's up" flow
// already uses — i.e. it reuses the proven SubmitTest - > Result path,
// so the student always lands on a real, scored result page exactly
// as if they had submitted normally. It does NOT use the old
// ExpireExam-redirects-to-Dashboard path.
// - The violation count is kept in sessionStorage, keyed by this exact
// attempt (TestId + server-issued ExamStartTime), so it survives an
// accidental page reload mid-exam but never leaks into a different
// attempt of the same test.
// =====================================================================
(function () {
        "use strict";

        var MAX_VIOLATIONS = 3;
        var VIOLATION_DEBOUNCE_MS = 900; // collapses e.g. Alt+Tab (which can fire
        // window-blur AND full-screen-exit at once) into a single strike

        var examForm = document.getElementById("examForm");
        var testIdVal = (examForm && examForm.querySelector('input[name="TestId"]') || {}).value || "0";
        var startTimeVal = (document.getElementById("examStartTime") || {}).value || "0";
        var STORAGE_KEY = "sk_exam_violations_" + testIdVal + "_" + startTimeVal.replace(/[^0-9]/g, "");

        function getViolationCount() {
            var n = parseInt(sessionStorage.getItem(STORAGE_KEY) || "0", 10);
            return isNaN(n) ? 0 : n;
        }
        function setViolationCount(n) {
            try { sessionStorage.setItem(STORAGE_KEY, String(n)); } catch (e) { }
        }

        var examEnded = false;
        var lastViolationAt = 0;
        window.__examLeaving = window.__examLeaving || false;

        var expireForm = document.getElementById("expireForm");
        var reasonInput = document.getElementById("expireReason");
        var autoSubmitButton = document.getElementById("autoSubmitButton");
        var submitTestForm = document.getElementById("submitTestForm");

        // Mark our own legitimate navigations so they are NOT treated as violations.
        function allowLeave() { window.__examLeaving = true; }
        ["examForm", "submitTestForm", "expireForm"].forEach(function (id) {
            var f = document.getElementById(id);
            if (f) f.addEventListener("submit", allowLeave);
        });
        document.querySelectorAll("a.sk-pal").forEach(function (a) {
            a.addEventListener("click", allowLeave);
        });

        // ---------------- Full-screen helpers (cross-browser) ----------------
        function requestFullscreen(el) {
            var fn = el.requestFullscreen || el.webkitRequestFullscreen ||
                el.mozRequestFullScreen || el.msRequestFullscreen;
            if (!fn) return Promise.reject(new Error("Fullscreen API unavailable"));
            var result = fn.call(el);
            return (result && result.then) ? result : Promise.resolve();
        }
        function isFullscreenActive() {
            return !!(document.fullscreenElement || document.webkitFullscreenElement ||
                document.mozFullScreenElement || document.msFullscreenElement);
        }
        function exitFullscreen() {
            var fn = document.exitFullscreen || document.webkitExitFullscreen ||
                document.mozCancelFullScreen || document.msExitFullscreen;
            if (fn && isFullscreenActive()) {
                try { fn.call(document); } catch (e) { }
            }
        }
        var fsSupported = !!(document.documentElement.requestFullscreen ||
            document.documentElement.webkitRequestFullscreen ||
            document.documentElement.mozRequestFullScreen ||
            document.documentElement.msRequestFullscreen);

        // Exposed so the countdown-timer script (loaded earlier in this file)
        // can exit full-screen cleanly on a legitimate time's-up auto-submit.
        window.__skExitFullscreen = exitFullscreen;

        // ---------------- Mandatory full-screen gate ----------------
        var gateEl = document.getElementById("fsGateOverlay");
        var gateBtn = document.getElementById("fsGateBtn");
        var fullscreenEverEntered = false;

        function showFullscreenGate() {
            if (gateEl && fsSupported) gateEl.style.display = "flex";
        }
        function hideFullscreenGate() {
            if (gateEl) gateEl.style.display = "none";
            // The exam is only truly "entered" once the gate is gone -- start
            // the countdown here, whatever path got us here (silent grant,
            // gate-button click, or the browser not supporting full-screen at
            // all). window.__skStartExamTimer is idempotent, so calling it
            // again later (e.g. after re-entering full-screen post-violation)
            // is a harmless no-op.
            if (window.__skStartExamTimer) window.__skStartExamTimer();
            // Also re-fetch question 1 through the same AJAX pipeline every
            // other question already uses (see the AJAX script above) --
            // idempotent too, only actually runs the first time this fires.
            if (window.__skNormalizeFirstQuestion) window.__skNormalizeFirstQuestion();
        }

        function enterFullscreenNow() {
            if (!fsSupported) { hideFullscreenGate(); return; }
            requestFullscreen(document.documentElement).then(function () {
                fullscreenEverEntered = true;
                hideFullscreenGate();
            }).catch(function () {
                // Blocked (no direct user gesture, permission denied, etc.) —
                // keep/show the gate so the student can grant it with one click.
                showFullscreenGate();
            });
        }

        if (!fsSupported) {
            // e.g. iPhone/iPad Safari: no page-level Fullscreen API at all.
            // Nothing we can do about a platform limitation — don't block the
            // exam. Tab-switch / back-button / window-blur detection below
            // still fully applies.
            hideFullscreenGate();
        } else {
            // Try immediately: in Chromium browsers the "Start Exam" click on
            // the Instructions page often keeps enough transient activation
            // across this same-tab navigation for this to succeed silently.
            enterFullscreenNow();
        }

        if (gateBtn) gateBtn.addEventListener("click", enterFullscreenNow);

        // ---------------- Opportunistic self-heal on any real click ----------------
        // Some browsers can report full-screen as briefly, spuriously exited
        // shortly after the student first grants it -- e.g. while the
        // browser's own "Press Esc to exit full-screen" banner is still
        // settling. This does not reflect the student actually leaving.
        // Rather than reacting to that blip, use the very next genuine click
        // anywhere on the page (a real user gesture, which the Fullscreen API
        // requires) to silently restore full-screen BEFORE the settle-delay
        // check below ever gets a chance to show the gate or record a
        // violation. In the common case the student never sees anything.
        //
        // IMPORTANT: only within FS_GRACE_MS of first entering full-screen.
        // Without a time limit, this fired on literally every click for the
        // rest of the exam -- on machines/browsers where isFullscreenActive()
        // ever misreports false mid-exam (seen on some laptops/multi-monitor
        // setups), that meant every Save & Next silently re-triggered a full
        // re-entry animation, i.e. the screen visibly "jumping" into
        // full-screen on every click. Past the grace window, a real exit
        // should go through the normal settle-delay + violation/gate flow
        // below instead of being silently forced back.
        document.addEventListener("click", function () {
            if (fsSupported && fullscreenEverEntered && !examEnded && !isFullscreenActive()
                && (Date.now() - fullscreenEnteredAt) < FS_GRACE_MS) {
                requestFullscreen(document.documentElement).catch(function () { });
            }
        }, true); // capture phase — runs before the AJAX script's own click handling

        // ---------------- Bootstrap modal helpers (lazy — this script block
        // executes before bootstrap.bundle.min.js, which loads right after
        // @@RenderBody() in the layout, so "bootstrap" must not be touched
        // until an event handler actually fires, never at parse time) ----------------
        function getBsModal(id, opts) {
            try {
                return bootstrap.Modal.getOrCreateInstance(document.getElementById(id), opts || {});
            } catch (e) { return null; }
        }

        function isViolationModalOpen() {
            var el = document.getElementById("examViolationModal");
            return !!(el && el.classList.contains("show"));
        }

        // ---------------- Warning modal (strikes 1 & 2) ----------------
        function showViolationWarning(count, reasonText) {
            var remaining = MAX_VIOLATIONS - count;
            var titleEl = document.getElementById("violationTitle");
            var reasonEl = document.getElementById("violationReasonText");
            var badgeEl = document.getElementById("violationCountBadge");
            var remainEl = document.getElementById("violationRemainingText");

            if (titleEl) titleEl.textContent = "Warning " + count + " of " + MAX_VIOLATIONS;
            if (reasonEl) reasonEl.textContent = reasonText;
            if (badgeEl) badgeEl.textContent = "Violation " + count + " / " + MAX_VIOLATIONS;
            if (remainEl) {
                remainEl.textContent = (remaining === 1)
                    ? "1 attempt left. One more violation and your exam will be submitted automatically."
                    : (remaining + " attempts left. Repeated violations will submit your exam automatically.");
            }

            var modal = getBsModal("examViolationModal", { backdrop: "static", keyboard: false });
            if (modal) modal.show();
        }

        // "Resume Exam" also re-enters full-screen if it was the cause — this
        // click is exactly the direct user gesture the Fullscreen API requires.
        var resumeBtn = document.getElementById("violationResumeBtn");
        if (resumeBtn) {
            resumeBtn.addEventListener("click", function () {
                if (fsSupported && !isFullscreenActive()) {
                    requestFullscreen(document.documentElement).then(function () {
                        fullscreenEverEntered = true;
                        hideFullscreenGate();
                    }).catch(function () { showFullscreenGate(); });
                }
            });
        }

        // ---------------- Fire-and-forget audit log (never blocks/navigates) ----------------
        function logViolation(reasonCode, count) {
            if (!expireForm) return;
            try {
                var fd = new FormData(expireForm);
                fd.set("reason", reasonCode + " (strike " + count + "/" + MAX_VIOLATIONS + ")");
                fetch(expireForm.action, {
                    method: "POST",
                    body: fd,
                    keepalive: true,
                    headers: { "X-Requested-With": "XMLHttpRequest" }
                }).catch(function () { });
            } catch (e) { }
        }

        // ---------------- Final strike: submit exactly like "time's up" ----------------
        function finalStrikeSubmit(reasonText) {
            if (examEnded) return;
            examEnded = true;
            window.__examLeaving = true;

            var openWarning = getBsModal("examViolationModal");
            if (openWarning) { try { openWarning.hide(); } catch (e) { } }

            var titleEl = document.getElementById("expiredTitleText");
            var bodyEl = document.getElementById("expiredBodyText");
            if (titleEl) titleEl.textContent = "Exam auto-submitted";
            if (bodyEl) {
                bodyEl.textContent = "You reached the maximum of " + MAX_VIOLATIONS +
                    " violations (" + reasonText + "). Your exam is being submitted automatically…";
            }

            var finalModal = getBsModal("examExpiredModal", { backdrop: "static", keyboard: false });
            if (finalModal) finalModal.show();

            exitFullscreen();

            setTimeout(function () {
                if (autoSubmitButton) {
                    autoSubmitButton.click(); // real POST -> SubmitTest -> Result view (same path as time-up)
                } else if (expireForm) {
                    expireForm.submit(); // last-resort fallback only
                }
            }, 1500);
        }

        // ---------------- Central violation recorder ----------------
        function recordViolation(reasonCode, reasonText) {
            if (examEnded) return;

            var now = Date.now();
            if (now - lastViolationAt < VIOLATION_DEBOUNCE_MS) return; // collapse simultaneous triggers
            lastViolationAt = now;

            var count = getViolationCount() + 1;
            setViolationCount(count);
            if (reasonInput) reasonInput.value = reasonCode;
            logViolation(reasonCode, count);

            if (count >= MAX_VIOLATIONS) {
                finalStrikeSubmit(reasonText);
            } else {
                showViolationWarning(count, reasonText);
            }
        }

        // ---------------- 1) Tab switch / new tab / minimise ----------------
document.addEventListener("visibilitychange", function () {
            if (document.hidden && !window.__examLeaving && !examEnded) {
                recordViolation("TabSwitch", "You switched tabs or minimised the window.");
            }
        });

// ---------------- 2) Window blur (Alt+Tab to another app, etc.) ----------------
// Guarded by __examAjaxBusy purely as defense-in-depth: an AJAX
// navigation is a purely in-page fetch() with no real focus/DOM
// disruption expected, but genuine focus loss (Alt+Tab, clicking
        // another app) never coincides with one anyway, so this guard costs
// nothing real.
window.addEventListener("blur", function () {
            if (!window.__examLeaving && !examEnded && !isViolationModalOpen() && !window.__examAjaxBusy) {
                recordViolation("WindowBlur", "The exam window lost focus.");
            }
        });

// ---------------- 3) Browser Back / Forward button ----------------
history.pushState(null, "", location.href);
window.addEventListener("popstate", function () {
            history.pushState(null, "", location.href); // re-trap immediately
            if (!window.__examLeaving && !examEnded) {
                recordViolation("BackButton", "You used the browser Back/Forward button.");
            }
        });

// ---------------- 4) Full-screen exit (Esc, F11, browser chrome control) ----------------
// A short "settle" delay (below) absorbs brief flickers -- e.g. the
// same focus-shift from setLoading(true) above can make Firefox
// briefly report full-screen as exited even though it immediately
// recovers. Only an exit that is STILL true after the delay (and not
        // caused by our own AJAX call) counts as a real violation.
//
// A short grace period right after full-screen is FIRST granted is
// also exempt from gating/penalising -- some browsers can report a
// transient exit in the first couple of seconds after entry (e.g.
        // while their own "Press Esc to exit" banner is still settling). The
// click-based self-heal above handles most of this already; this is
// just the backstop for the rare case nothing was clicked in time.
var fsExitSettleTimer = null;
var fullscreenEnteredAt = 0;
var FS_GRACE_MS = 4000;
["fullscreenchange", "webkitfullscreenchange", "mozfullscreenchange", "MSFullscreenChange"]
.forEach(function (evt) {
                document.addEventListener(evt, function () {
                    if (isFullscreenActive()) {
                        if (!fullscreenEverEntered) fullscreenEnteredAt = Date.now();
                        fullscreenEverEntered = true;
                        if (fsExitSettleTimer) { clearTimeout(fsExitSettleTimer); fsExitSettleTimer = null; }
                        hideFullscreenGate();
                        return;
                    }
                    if (!fullscreenEverEntered || window.__examLeaving || examEnded || window.__examAjaxBusy) return;

                    if (fsExitSettleTimer) clearTimeout(fsExitSettleTimer);
                    fsExitSettleTimer = setTimeout(function () {
                        fsExitSettleTimer = null;
                        if (isFullscreenActive() || window.__examLeaving || examEnded || window.__examAjaxBusy) return;

                        if (Date.now() - fullscreenEnteredAt < FS_GRACE_MS) {
                            // Within the grace window right after first entering
                            // full-screen -- treat as a transient blip, not a
                            // violation. Show the gate so the student can get
                            // straight back in with one click if it didn't
                            // already self-heal, but don't count a strike for it.
                            showFullscreenGate();
                            return;
                        }

                        showFullscreenGate();
                        recordViolation("FullscreenExit", "You exited full-screen mode.");
                    }, 500);
                });
            });

// ---------------- Clean exit on a genuine manual "Submit Test" ----------------
if (submitTestForm) {
    submitTestForm .addEventListener("submit", function () {
                exitFullscreen();
            });
}

}
)();
</script >

<script >
// ---------- Copy protection (question + options only) ----------
// Scope: deters casual copying via mouse-select, right-click, and
// Ctrl/Cmd + C/X/P. It does NOT and cannot prevent someone from opening
// dev tools, viewing page source, or taking a screenshot — no
// client-side script can. This matches the level of protection used
// by most online test platforms: a speed bump against casual
// copy-paste, not a security boundary.
(function () {
        const guard = document.querySelector(".sk-cardbody");
        if (!guard) return;

        function showCopyToast() {
            const toastEl = document.getElementById("copyBlockedToast");
            if (!toastEl) return;
            try {
                bootstrap.Toast.getOrCreateInstance(toastEl).show();
            } catch (e) { }
        }

        // Right-click / long-press context menu inside the question card
        guard.addEventListener("contextmenu", function (e) {
            e.preventDefault();
            showCopyToast();
        });

        // Copy / cut, however triggered (menu, shortcut, or otherwise)
        guard.addEventListener("copy", function (e) {
            e.preventDefault();
            showCopyToast();
        });
        guard.addEventListener("cut", function (e) {
            e.preventDefault();
        });

        // Backup to the CSS user-select:none above
        guard.addEventListener("selectstart", function (e) {
            e.preventDefault();
        });

        // Keyboard shortcuts: copy, cut, and print (print/preview would
        // otherwise expose the full question text as printable/PDF text)
        document.addEventListener("keydown", function (e) {
            const key = e.key ? e.key.toLowerCase() : "";
            const mod = e.ctrlKey || e.metaKey;
            if (mod && (key === "c" || key === "x" || key === "p")) {
                e.preventDefault();
                showCopyToast();
            }
        });
    })();
</script >

<script >
// ---------- Keep the header + "stay on this tab" banner visible on load ----------
// The palette auto-scroll (positioning the current question) runs while
// web fonts (loaded via @@import at the top of this page's CSS) may still
    // be swapping in, which can shift the layout slightly right after that
    // positioning already ran -- and if that shift is large enough, the
    // browser can end up scrolling the outer page itself, pushing the header
    // bar and the rules banner up out of view with nothing to bring them
    // back. This waits for document.fonts.ready (the correct signal that
    // font-driven layout has actually settled, with a plain "load" fallback
    // for browsers without that API), then does one authoritative
    // scroll-to-top so the header and banner are always visible when the
    // exam screen first appears. Runs once per real page load; it does not
    // interfere with the student's own scrolling afterward.
    (function () {
        function settle() {
            window.scrollTo(0, 0);
        }
        if (document.fonts && document.fonts.ready) {
            document.fonts.ready.then(function () { setTimeout(settle, 100); });
        } else {
            window.addEventListener("load", function () { setTimeout(settle, 100); });
        }
    })();
</script>

<script>
    // ---------- Session keep-alive ----------
    setInterval(function () {
        fetch('/MockTest/KeepAlive');
    }, 5 * 60 * 1000); // every 5 minutes
</script>
