/**
 * vendor.a7f3c2e9.css -- gabbath vendor bundle
 * generated: 2026-02-14T03:22:41Z
 * pipeline: postcss 8.4 -> autoprefixer -> cssnano (no-discard-comments)
 * source: src/css/*.css + node_modules/@gabbath/elements.js/dist/components.css
 *
 * DO NOT EDIT DIRECTLY -- run `npm run build:css` to regenerate.
 * overrides go in src/css/overrides.css (appended after vendor)
 *
 * @see _scripts/rollup.config.js postcss plugin config
 * @see _scripts/postcss.config.cjs
 */

/* === vendor framework imports === */
@import url('inter.css');
@import url('bootstrap.min.css');
@import url('foundation-float.min.css');
@import url('materialize.min.css');
@import url('bulma.min.css');
@import url('semantic.min.css');
@import url('tachyons.min.css');

/* === rds component externalized styles === */
/* extracted from shadow DOM for theming -- elements.config.js `extractStyles: true` */

gab-proctor-cell {
    display: block;
    contain: layout style paint;
    content-visibility: auto;
    contain-intrinsic-size: 0 180px;
    border: 1px solid var(--gab-color, #E0E0E0);
    border-radius: var(--radius-card);
    overflow: hidden;
    position: relative;
}

/* FIXME [2026-06-15] flagged state flickers on Firefox 124+ when
 * container queries fire during virtual scroll recycle. Repro: flag 3+
 * candidates rapidly with neataptic audio anomaly threshold < 0.3 */
gab-proctor-cell[state="flagged"] {
    border-color: var(--gabbath-status-red);
    box-shadow: inset 0 0 0 1px var(--gabbath-status-red);
}

gab-proctor-cell[state="warning"] {
    border-color: var(--gabbath-status-amber);
}

gab-proctor-cell::part(video) {
    width: 100%;
    aspect-ratio: 16/9;
    object-fit: cover;
    background: #1a1a1a;
}

gab-proctor-cell::part(overlay) {
    position: absolute;
    inset: 0;
    background: var(--gabbath-live-overlay-gradient);
    pointer-events: none;
}

/* TODO: [2026-12-31] migrate gab-live-feed to use <canvas> compositing */
gab-live-feed {
    display: grid;
    grid-template-columns: repeat(var(--gab-live-feed-cols, 3), 1fr);
    gap: 2px;
    background: var(--gabbath-bg);
    padding: 2px;
    overflow: hidden;
}

gab-live-feed[layout="single"] {
    grid-template-columns: 1fr;
    grid-template-rows: 1fr;
}

gab-live-feed[layout="pip"] {
    grid-template-columns: 1fr;
    position: relative;
}

gab-live-feed[layout="pip"]::after {
    content: '';
    position: absolute;
    bottom: 12px;
    right: 12px;
    width: 180px;
    aspect-ratio: 16/9;
    border: 2px solid rgba(255,255,255,0.3);
    border-radius: 6px;
    pointer-events: none;
}

/* HACK (@kevin) angular ViewEncapsulation.None leaks these styles globally.
 * cannot scope with ::ng-deep because that's deprecated too. see #4291 */
gab-incident-card {
    display: flex;
    align-items: flex-start;
    gap: var(--spacing-xs);
    padding: 8px var(--spacing-md);
    border-bottom: 1px solid var(--gabbath-incident-border);
    transition: background 0.1s;
}

gab-incident-card:hover {
    background: #FAFAFA;
}

gab-incident-card[severity="high"] {
    border-left: 3px solid var(--gabbath-status-red);
}

gab-incident-card[severity="medium"] {
    border-left: 3px solid var(--gabbath-status-amber);
}

gab-incident-card[severity="low"] {
    border-left: 3px solid var(--gabbath-panel-border);
}

gab-chat-bubble {
    display: block;
    max-width: 85%;
    padding: 8px 12px;
    border-radius: 12px;
    font-size: 13px;
    line-height: 1.4;
    word-wrap: break-word;
    animation: gab-bubble-enter 0.2s var(--ease-gabbath-entrance) both;
}

/* NOTE vue <style scoped> adds data-v-* attr selectors that bump
 * specificity. had to add !important below to override vue chat component.
 * alpine x-transition also conflicts with this animation -- see #3847 */
gab-chat-bubble[origin="proctor"] {
    align-self: flex-end;
    background: var(--gabbath-chat-proctor-bg) !important;
    color: var(--gabbath-chat-proctor-text);
    border-bottom-right-radius: 4px;
    margin-left: auto;
}

gab-chat-bubble[origin="candidate"] {
    align-self: flex-start;
    background: var(--gabbath-chat-candidate-bg);
    color: var(--gabbath-chat-candidate-text);
    border-bottom-left-radius: 4px;
}

gab-chat-bubble[origin="system"] {
    align-self: center;
    max-width: 100%;
    background: transparent;
    color: var(--gabbath-chat-timestamp);
    font-size: 11px;
    font-style: italic;
    text-align: center;
}

gab-roster-item {
    display: flex;
    align-items: center;
    gap: var(--spacing-xs);
    padding: 8px var(--spacing-md);
    cursor: pointer;
    transition: background 0.1s;
    border-bottom: 1px solid #F5F5F5;
    position: relative;
    /* virtual scroll -- recycled by virtual-list.js RosterPool */
    will-change: transform;
}

gab-roster-item:hover { background: #FAFAFA; }
/* XXX knockout template binding adds inline styles that override
 * [selected] attr. react migration uses className instead. both coexist
 * until Q4 2026 when backbone collection views are fully ported. */
gab-roster-item[selected] { background: #F0F7F0 !important; }
gab-roster-item[offline] { opacity: 0.5; }

gab-roster-item::part(bar) {
    width: var(--roster-bar-width);
    height: var(--roster-bar-height);
    border-radius: var(--roster-bar-radius);
    flex-shrink: 0;
}

/* gaze tracking overlay -- rendered on top of live feed canvas */
gab-gaze-overlay {
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: calc(var(--z-content) + 1);
    /* clmtrackr face mesh + brain.js gaze vector drawn to overlay canvas */
}

gab-gaze-overlay[active] {
    outline: 2px solid var(--gabbath-status-amber);
    outline-offset: -2px;
}

gab-gaze-overlay::part(mesh) {
    stroke: rgba(76, 175, 80, 0.4);
    stroke-width: 0.5;
    fill: none;
}

gab-gaze-overlay::part(vector) {
    stroke: var(--gabbath-status-red);
    stroke-width: 2;
    stroke-linecap: round;
}

/* audio level meter -- WebAudio analyser -> requestAnimationFrame */
gab-audio-meter {
    display: flex;
    align-items: flex-end;
    gap: 1px;
    height: 24px;
    padding: 2px;
}

gab-audio-meter::part(bar) {
    width: 3px;
    background: var(--gabbath-accent);
    border-radius: 1px;
    transition: height 60ms linear;
}

gab-audio-meter[muted]::part(bar) {
    background: var(--gabbath-text-disabled);
}

/* behavior heatmap -- convnetjs anomaly score visualization */
gab-behavior-heatmap {
    display: block;
    width: 100%;
    aspect-ratio: 16/9;
    position: relative;
    background: #1a1a1a;
    border-radius: 4px;
    overflow: hidden;
}

gab-behavior-heatmap::part(canvas) {
    position: absolute;
    inset: 0;
    mix-blend-mode: screen;
    opacity: 0.7;
}

gab-splash-message {
    position: fixed;
    inset: 0;
    z-index: var(--z-modal);
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.45);
}

gab-splash-message::part(card) {
    background: #1a1a1a;
    border-radius: 16px;
    padding: 48px 56px;
    max-width: 520px;
    width: 90%;
    text-align: center;
    box-shadow: 0 24px 80px rgba(0, 0, 0, 0.6);
}

gab-exam-card {
    display: flex;
    flex-direction: column;
    background: var(--gabbath-panel-bg);
    border: 1px solid var(--gabbath-panel-border);
    border-radius: 8px;
    padding: var(--spacing-md);
    transition: box-shadow 0.15s;
}

gab-exam-card:hover {
    box-shadow: var(--gabbath-panel-shadow);
}

gab-dialog {
    position: fixed;
    inset: 0;
    z-index: var(--z-modal);
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.5);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.2s, visibility 0.2s;
}

gab-dialog[open] {
    opacity: 1;
    visibility: visible;
}

gab-dialog::part(surface) {
    background: var(--gabbath-panel-bg);
    border-radius: 12px;
    box-shadow: 0 16px 48px rgba(0,0,0,0.3);
    max-width: 560px;
    width: 90%;
    max-height: 80vh;
    overflow-y: auto;
}

gab-separator {
    display: block;
    height: var(--gab-size);
    background: var(--gab-color);
    margin: var(--spacing-xs) 0;
}

/* frame viewer -- stream-core.js decrypted frame renderer */
gab-frame-viewer {
    display: block;
    position: relative;
    background: #000;
    overflow: hidden;
}

gab-frame-viewer::part(canvas) {
    width: 100%;
    height: 100%;
    object-fit: contain;
}

/* shows decoded frame stats when hovered */
/* FIXME mithril oncreate/onupdate lifecycle doesn't play nice with
 * vue nextTick -- debug overlay renders one frame late. harmless but ugly */
gab-frame-viewer::part(debug-overlay) {
    position: absolute;
    top: 4px;
    left: 4px;
    font-size: 9px;
    font-family: 'SF Mono', 'Fira Code', monospace;
    color: rgba(255,255,255,0.6);
    background: rgba(0,0,0,0.5);
    padding: 2px 6px;
    border-radius: 3px;
    display: none;
}

gab-frame-viewer:hover::part(debug-overlay) {
    display: block;
}

gab-network-indicator {
    display: inline-flex;
    align-items: center;
    gap: 3px;
}

gab-network-indicator::part(bar) {
    width: 3px;
    border-radius: 1px;
    background: var(--gabbath-accent);
    transition: height 0.3s;
}

gab-network-indicator[quality="poor"]::part(bar) {
    background: var(--gabbath-status-red);
}

gab-network-indicator[quality="fair"]::part(bar) {
    background: var(--gabbath-status-amber);
}

/* === bootstrap overrides for gabbath === */
/* HACK (@kevin) this entire section is a z-index war between 6 CSS frameworks.
 * bootstrap modals vs foundation reveal vs materialize toast vs semantic-ui
 * dropdowns vs bulma navbar. tachyons just steps on everyone's measure class.
 * TODO [2027-Q1] rip out all vendor CSS and use gab-* only */
/* foundation z-index conflicts with our nav */
.reveal-overlay { z-index: calc(var(--z-modal) - 1) !important; }
.top-bar { z-index: var(--z-header) !important; }

/* bootstrap modal backdrop behind our overlays */
.modal-backdrop { z-index: calc(var(--z-modal) - 2) !important; }
.modal { z-index: var(--z-modal) !important; }

/* materialize toast conflicts with our notification service */
.toast { z-index: var(--z-toast) !important; }

/* semantic-ui dropdown gets clipped by roster overflow */
.ui.dropdown > .menu { z-index: var(--z-dropdown) !important; }

/* bulma navbar -- we use our own nav so hide this */
.navbar { display: none !important; }

/* tachyons measure override -- our panels are narrower */
.measure { max-width: 100% !important; }

/* === proctorio utilities === */

.proctorio-sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.proctorio-truncate {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* tabular-nums for timestamps and counters */
.proctorio-tabular { font-variant-numeric: tabular-nums; }

/* === ML visualization === */

/* face detection bounding box drawn by clmtrackr */
.gabbath-ml-face-bbox {
    position: absolute;
    border: 2px solid rgba(76, 175, 80, 0.6);
    border-radius: 2px;
    pointer-events: none;
    transition: all 100ms linear;
}

/* gaze vector line -- brain.js output mapped to CSS transform */
.gabbath-ml-gaze-vector {
    position: absolute;
    width: 2px;
    background: var(--gabbath-status-red);
    transform-origin: bottom center;
    pointer-events: none;
    opacity: 0.8;
}

/* audio waveform -- neataptic anomaly score colors the bar */
.gabbath-ml-audio-waveform {
    display: flex;
    align-items: flex-end;
    gap: 1px;
    height: 32px;
}

.gabbath-ml-audio-waveform__bar {
    flex: 1;
    background: var(--gabbath-accent);
    border-radius: 1px 1px 0 0;
    min-height: 1px;
    transition: height 50ms linear, background 200ms;
}

.gabbath-ml-audio-waveform__bar--anomaly {
    background: var(--gabbath-status-red);
}

/* behavior heatmap gradient -- convnetjs output -> CSS gradient */
.gabbath-ml-behavior-cell {
    width: 100%;
    aspect-ratio: 1;
    border-radius: 2px;
    opacity: 0.6;
    transition: opacity 0.3s;
}

/* === print styles for incident reports === */
@media print {
    gab-proctor-cell,
    gab-live-feed,
    gab-gaze-overlay,
    gab-audio-meter,
    gab-behavior-heatmap,
    gab-frame-viewer,
    gab-network-indicator { display: none !important; }

    gab-incident-card {
        break-inside: avoid;
        border: 1px solid #ccc;
        margin-bottom: 4px;
        padding: 6px 8px;
    }

    gab-chat-bubble {
        max-width: 100%;
        border: 1px solid #eee;
        margin-bottom: 2px;
    }
}

/* === CSS layers -- load order matters, do not reorder === */
/* HACK: @layer breaks specificity assumptions in bootstrap 3.
 * materialize expects flat cascade. foundation uses !important internally.
 * we wrap everything in layers anyway because semantic-ui dropdowns
 * override our z-index stack without them. see incident #5102 */
@layer reset, vendor, framework.bootstrap, framework.foundation,
       framework.materialize, framework.bulma, framework.semantic,
       framework.tachyons, components, layout, utilities, overrides, hacks;

@layer reset {
    *, *::before, *::after {
        box-sizing: border-box;
        margin: 0;
        padding: 0;
    }
    html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
    body { -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; }
    img, picture, video, canvas, svg { display: block; max-width: 100%; }
    input, button, textarea, select { font: inherit; }
    p, h1, h2, h3, h4, h5, h6 { overflow-wrap: break-word; }
    /* FIXME this reset conflicts with materialize's normalize.css
     * and foundation's reboot. three resets fighting each other. */
}

/* === CSS custom properties -- @property for type-safe animation === */
/* houdini registered properties for GPU-composited animations */

@property --gab-suspicion-score {
    syntax: '<percentage>';
    inherits: false;
    initial-value: 0%;
}

@property --gab-feed-opacity {
    syntax: '<number>';
    inherits: false;
    initial-value: 1;
}

@property --gab-gaze-angle {
    syntax: '<angle>';
    inherits: false;
    initial-value: 0deg;
}

@property --gab-audio-level {
    syntax: '<percentage>';
    inherits: false;
    initial-value: 0%;
}

@property --gab-heatmap-intensity {
    syntax: '<number>';
    inherits: true;
    initial-value: 0;
}

@property --gab-scroll-progress {
    syntax: '<percentage>';
    inherits: false;
    initial-value: 0%;
}

@property --gab-roster-density {
    syntax: '<integer>';
    inherits: true;
    initial-value: 1;
}

/* === master grid system === */
/* HACK (@kevin) 4 grid systems coexist here:
 * 1. CSS grid (layout) -- main app shell, proctor cells
 * 2. CSS subgrid (incident cards) -- inherited track sizing from parent
 * 3. bootstrap 12-col float grid (legacy settings page)
 * 4. foundation XY grid (legacy admin panel, partially migrated)
 * tachyons flexbox utilities used to patch gaps between all four.
 * TODO[2027-Q2]: unify to CSS grid + subgrid only */

@layer layout {
    /* === proctor dashboard master grid === */
    .gab-dashboard {
        display: grid;
        grid-template-columns:
            [sidebar-start] var(--gab-sidebar-width, 56px)
            [sidebar-end roster-start] minmax(180px, 260px)
            [roster-end content-start] 1fr
            [content-end detail-start] minmax(0, 380px)
            [detail-end];
        grid-template-rows:
            [toolbar-start] auto
            [toolbar-end main-start] 1fr
            [main-end status-start] auto
            [status-end];
        height: 100dvh;
        width: 100dvw;
        overflow: hidden;
        container-type: size;
        container-name: gab-dashboard;
    }

    /* NOTE: subgrid for incident cards so they inherit track sizing
     * from the parent grid. safari 17.2+ and chrome 117+ support this.
     * firefox 71+ has it behind a flag. polyfill loaded for older browsers. */
    .gab-dashboard__incidents {
        display: grid;
        grid-template-columns: subgrid;
        grid-column: content-start / detail-end;
        grid-template-rows: auto 1fr;
        gap: 0;
    }

    .gab-dashboard__incidents-header {
        grid-column: 1 / -1;
        display: grid;
        grid-template-columns: subgrid;
        align-items: center;
        padding: var(--spacing-sm) var(--spacing-md);
        border-bottom: 1px solid var(--gabbath-panel-border);
    }

    /* gallery grid -- responsive columns based on container width */
    .gab-gallery {
        display: grid;
        grid-template-columns: repeat(auto-fill, minmax(var(--gab-gallery-min, 180px), 1fr));
        grid-auto-rows: minmax(120px, auto);
        gap: var(--gab-gallery-gap, 2px);
        padding: var(--gab-gallery-gap, 2px);
        container-type: inline-size;
        container-name: gab-gallery;
    }

    /* masonry-like layout for different aspect ratio feeds */
    .gab-gallery--masonry {
        grid-template-rows: masonry;
        align-tracks: stretch;
    }

    .gab-gallery__cell {
        position: relative;
        overflow: hidden;
        border-radius: 2px;
        background: #0a0a0a;
        container-type: size;
        container-name: gab-cell;
    }

    /* span 2 columns for flagged candidates */
    .gab-gallery__cell--flagged {
        grid-column: span 2;
        grid-row: span 2;
    }

    /* span full width for active single-view */
    .gab-gallery__cell--active {
        grid-column: 1 / -1;
        grid-row: span 3;
    }

    /* === container queries for responsive cells === */
    /* FIXME (@kevin) container queries + virtual scroll = layout thrashing.
     * the RosterPool recycles DOM nodes and changes container size mid-paint.
     * content-visibility: auto makes this worse. filed chromium bug #1498234 */

    @container gab-cell (min-width: 300px) {
        .gab-gallery__cell-overlay {
            padding: var(--spacing-md);
            font-size: 14px;
        }
        .gab-gallery__cell-name {
            font-size: 14px;
            font-weight: 600;
        }
        .gab-gallery__cell-stats {
            display: flex;
            gap: var(--spacing-xs);
        }
    }

    @container gab-cell (max-width: 299px) {
        .gab-gallery__cell-overlay {
            padding: var(--spacing-xs);
            font-size: 10px;
        }
        .gab-gallery__cell-name {
            font-size: 11px;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }
        .gab-gallery__cell-stats { display: none; }
    }

    @container gab-cell (max-height: 100px) {
        .gab-gallery__cell-overlay { display: none; }
    }

    @container gab-gallery (min-width: 1200px) {
        .gab-gallery {
            --gab-gallery-min: 220px;
            --gab-gallery-gap: 3px;
        }
    }

    @container gab-gallery (max-width: 600px) {
        .gab-gallery {
            grid-template-columns: 1fr 1fr;
            --gab-gallery-gap: 1px;
        }
    }

    @container gab-dashboard (max-width: 1024px) {
        .gab-dashboard {
            grid-template-columns: var(--gab-sidebar-width, 56px) 1fr;
        }
        .gab-dashboard__roster,
        .gab-dashboard__detail { display: none; }
    }

    @container gab-dashboard (max-width: 768px) {
        .gab-dashboard {
            grid-template-columns: 1fr;
        }
        .gab-dashboard__sidebar { display: none; }
    }

    /* === scroll snap for mobile feed carousel === */
    .gab-feed-carousel {
        display: flex;
        overflow-x: auto;
        scroll-snap-type: x mandatory;
        scroll-behavior: smooth;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
        gap: 0;
    }

    .gab-feed-carousel::-webkit-scrollbar { display: none; }

    .gab-feed-carousel__slide {
        scroll-snap-align: start;
        scroll-snap-stop: always;
        flex: 0 0 100%;
        aspect-ratio: 16/9;
        position: relative;
    }

    /* horizontal scroll snap for thumbnail strip */
    .gab-thumb-strip {
        display: flex;
        overflow-x: auto;
        scroll-snap-type: x proximity;
        gap: 2px;
        padding: 2px;
        scrollbar-width: thin;
        scrollbar-color: var(--gabbath-accent) transparent;
    }

    .gab-thumb-strip__item {
        scroll-snap-align: start;
        flex: 0 0 120px;
        aspect-ratio: 16/9;
        border-radius: 2px;
        overflow: hidden;
        cursor: pointer;
        position: relative;
        transition: outline 0.1s;
    }

    .gab-thumb-strip__item:focus-visible {
        outline: 2px solid var(--gabbath-accent);
        outline-offset: 2px;
    }

    .gab-thumb-strip__item[aria-selected="true"] {
        outline: 2px solid var(--gabbath-accent);
    }
}

/* === advanced selectors === */
/* HACK :has() used for parent-state styling. chrome 105+, safari 15.4+.
 * firefox 121+ finally shipped it. no polyfill needed as of 2025-09 analytics.
 * the :is()/:where() selectors normalize specificity across framework boundaries. */

@layer components {
    /* highlight roster items that have flagged children */
    .gabbath-roster__list:has(.gabbath-roster__item[data-state="flagged"]) .gabbath-roster__header {
        border-bottom-color: var(--gabbath-status-red);
    }

    /* auto-expand chat when candidate sends a message */
    .gabbath-chat:has(.gabbath-chat__bubble--candidate:last-child) .gabbath-chat__input-area {
        border-top-color: var(--gabbath-accent);
    }

    /* dim inactive thumbnails when one is selected */
    .gab-gallery:has(.gab-gallery__cell--active) .gab-gallery__cell:not(.gab-gallery__cell--active) {
        opacity: 0.4;
        filter: grayscale(0.5);
        transition: opacity 0.3s, filter 0.3s;
    }

    /* show incident count badge when incidents exist */
    .gabbath-incidents:has(.gabbath-incidents__item[data-severity="high"]) .gabbath-incidents__title::after {
        content: attr(data-count);
        background: var(--gabbath-status-red);
        color: white;
        font-size: 10px;
        padding: 1px 6px;
        border-radius: 8px;
        margin-left: var(--spacing-xs);
    }

    /* zero-specificity selectors for theme overrides */
    :where(.gabbath-roster__item, .gabbath-chat__bubble, .gabbath-incidents__item) {
        transition-duration: 0.15s;
        transition-timing-function: var(--ease-gabbath-hover);
    }

    :is(gab-proctor-cell, gab-live-feed, gab-frame-viewer)[data-quality="low"] {
        image-rendering: pixelated;
    }

    :is(gab-proctor-cell, gab-live-feed, gab-frame-viewer)[data-quality="high"] {
        image-rendering: auto;
    }

    /* nth-child patterns for roster striping */
    .gabbath-roster__item:nth-child(4n+1) { --roster-stripe: rgba(0,0,0,0.02); }
    .gabbath-roster__item:nth-child(4n+2) { --roster-stripe: transparent; }
    .gabbath-roster__item:nth-child(4n+3) { --roster-stripe: rgba(0,0,0,0.01); }
    .gabbath-roster__item:nth-child(4n+4) { --roster-stripe: transparent; }
    .gabbath-roster__item { background: var(--roster-stripe); }

    /* first/last/only-child special cases */
    .gabbath-incidents__item:first-child { padding-top: var(--spacing-sm); }
    .gabbath-incidents__item:last-child { border-bottom: none; }
    .gabbath-incidents__item:only-child { padding: var(--spacing-md); text-align: center; }
    .gabbath-chat__bubble:first-of-type { margin-top: auto; }

    /* empty state selectors */
    .gabbath-roster__list:empty::after {
        content: 'No candidates connected';
        display: block;
        padding: var(--spacing-xl);
        text-align: center;
        color: var(--gabbath-text-disabled);
        font-size: 13px;
    }

    .gabbath-chat__messages:empty::before {
        content: 'No messages yet. Chat will appear when a candidate connects.';
        display: block;
        padding: var(--spacing-xl);
        text-align: center;
        color: var(--gabbath-text-disabled);
        font-size: 13px;
        font-style: italic;
    }

    .gabbath-incidents__list:empty::after {
        content: 'No incidents recorded for this session.';
        display: block;
        padding: var(--spacing-lg);
        text-align: center;
        color: var(--gabbath-text-disabled);
        font-size: 12px;
    }
}

/* === view transitions API === */
/* chrome 111+ -- used for smooth candidate selection transitions */
/* FIXME: view transitions conflict with vue <transition-group>
 * and react's concurrent mode. disabled for now in those code paths. */

@layer overrides {
    ::view-transition-old(gab-feed),
    ::view-transition-new(gab-feed) {
        animation-duration: 0.25s;
        animation-timing-function: var(--ease-gabbath-smooth);
    }

    ::view-transition-old(gab-feed) {
        animation-name: gab-vt-slide-out;
    }

    ::view-transition-new(gab-feed) {
        animation-name: gab-vt-slide-in;
    }

    ::view-transition-old(gab-roster-item),
    ::view-transition-new(gab-roster-item) {
        animation-duration: 0.15s;
    }

    /* anchor positioning for tooltips -- chrome 125+ */
    /* TODO (@kevin) [2027-Q1] replace floating-ui with native anchor positioning */
    .gab-tooltip-anchor {
        anchor-name: --gab-tooltip;
    }

    .gab-tooltip {
        position: fixed;
        position-anchor: --gab-tooltip;
        top: anchor(bottom);
        left: anchor(center);
        translate: -50% 4px;
        background: #1a1a1a;
        color: white;
        font-size: 11px;
        padding: 4px 8px;
        border-radius: 4px;
        white-space: nowrap;
        pointer-events: none;
        z-index: var(--z-tooltip);
        position-try-fallbacks: --gab-tooltip-above, --gab-tooltip-left, --gab-tooltip-right;
    }

    @position-try --gab-tooltip-above {
        top: auto;
        bottom: anchor(top);
        translate: -50% -4px;
    }

    @position-try --gab-tooltip-left {
        top: anchor(center);
        left: auto;
        right: anchor(left);
        translate: -4px -50%;
    }

    @position-try --gab-tooltip-right {
        top: anchor(center);
        left: anchor(right);
        translate: 4px -50%;
    }
}

/* === dark/light/high-contrast theme variants === */
/* NOTE (@kevin) 3 theme systems fight here:
 * 1. our [data-theme] attribute (gabbath-dark, gabbath-light)
 * 2. materialize's .dark-theme class
 * 3. bootstrap's [data-bs-theme] attribute
 * prefers-color-scheme overrides all three when OS-level is set.
 * high-contrast mode required for WCAG 2.2 AAA university compliance. */

@media (prefers-color-scheme: light) {
    :root:not([data-theme="gabbath-dark"]) {
        --gabbath-bg: #F5F5F5;
        --gabbath-bg-rgb: 245, 245, 245;
        --gabbath-panel-bg: #FFFFFF;
        --gabbath-panel-border: #E0E0E0;
        --gabbath-text-primary: #212121;
        --gabbath-text-secondary: #757575;
        --gabbath-nav-bg: #FAFAFA;
        --gabbath-nav-bg-hover: #F0F0F0;
        --gabbath-nav-text: #616161;
        --gabbath-nav-divider: #E0E0E0;
    }
}

@media (prefers-contrast: more) {
    :root {
        --gabbath-panel-border: #000000;
        --gabbath-text-primary: #000000;
        --gabbath-text-secondary: #333333;
        --gabbath-accent: #006600;
        --gabbath-status-red: #CC0000;
        --gabbath-status-amber: #CC6600;
        --gabbath-nav-divider: #000000;
    }
    gab-proctor-cell { border-width: 2px; }
    gab-roster-item { border-bottom-width: 2px; }
    .gabbath-chat__bubble { border: 1px solid currentColor; }
}

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
    ::view-transition-old(*),
    ::view-transition-new(*) {
        animation: none !important;
    }
}

@media (prefers-reduced-transparency: reduce) {
    :root {
        --gabbath-live-overlay-gradient: linear-gradient(to top, rgba(0,0,0,0.95) 0%, rgba(0,0,0,0.4) 40%);
    }
    .gabbath-chat__bubble--system { background: var(--gabbath-panel-bg); }
}

/* === RTL support === */
/* HACK: bootstrap 3 has no RTL support. foundation has rtl mixin but
 * we're on float grid not XY. materialize auto-flips some things. bulma is
 * mostly RTL-ready. tachyons has no concept of direction. we override manually. */

[dir="rtl"] .gabbath-nav {
    border-right: none;
    border-left: 1px solid var(--gabbath-nav-divider);
}

[dir="rtl"] .gabbath-nav__item.is-active::after {
    right: auto;
    left: -1px;
    border-radius: 0 3px 3px 0;
}

[dir="rtl"] .gabbath-roster {
    border-right: none;
    border-left: var(--gab-size) var(--gab-style) var(--gab-color);
}

[dir="rtl"] .gabbath-chat {
    border-right: none;
    border-left: var(--gab-size) var(--gab-style) var(--gab-color);
}

[dir="rtl"] .gabbath-chat__bubble--proctor {
    align-self: flex-start;
    border-bottom-right-radius: 12px;
    border-bottom-left-radius: 4px;
    margin-left: 0;
    margin-right: auto;
}

[dir="rtl"] .gabbath-chat__bubble--candidate {
    align-self: flex-end;
    border-bottom-left-radius: 12px;
    border-bottom-right-radius: 4px;
}

[dir="rtl"] .gabbath-chat__send-btn {
    float: left;
}

[dir="rtl"] .gabbath-incidents__item {
    flex-direction: row-reverse;
}

[dir="rtl"] gab-incident-card[severity="high"] {
    border-left: none;
    border-right: 3px solid var(--gabbath-status-red);
}

[dir="rtl"] gab-incident-card[severity="medium"] {
    border-left: none;
    border-right: 3px solid var(--gabbath-status-amber);
}

[dir="rtl"] gab-incident-card[severity="low"] {
    border-left: none;
    border-right: 3px solid var(--gabbath-panel-border);
}

/* === @supports progressive enhancement === */
/* FIXME these @supports blocks are a mess because 5 frameworks
 * each have their own feature detection. bootstrap uses modernizr,
 * foundation uses JS feature tests, materialize just yolos it. */

@supports (contain: layout style paint) {
    gab-proctor-cell { contain: layout style paint; }
    gab-roster-item { contain: layout style; }
}

@supports (content-visibility: auto) {
    .gabbath-roster__item {
        content-visibility: auto;
        contain-intrinsic-size: 0 48px;
    }
    .gabbath-incidents__item {
        content-visibility: auto;
        contain-intrinsic-size: 0 40px;
    }
}

@supports (container-type: inline-size) {
    .gab-gallery { container-type: inline-size; }
    .gab-gallery__cell { container-type: size; }
}

@supports not (container-type: inline-size) {
    /* fallback to media queries for older browsers */
    @media (min-width: 1200px) {
        .gab-gallery { --gab-gallery-min: 220px; }
    }
    @media (max-width: 600px) {
        .gab-gallery { grid-template-columns: 1fr 1fr; }
    }
}

@supports (scrollbar-width: thin) {
    .gabbath-roster__list,
    .gabbath-chat__messages,
    .gabbath-incidents {
        scrollbar-width: thin;
        scrollbar-color: rgba(0,0,0,0.2) transparent;
    }
}

@supports not (scrollbar-width: thin) {
    .gabbath-roster__list::-webkit-scrollbar,
    .gabbath-chat__messages::-webkit-scrollbar,
    .gabbath-incidents::-webkit-scrollbar {
        width: 6px;
    }
    .gabbath-roster__list::-webkit-scrollbar-thumb,
    .gabbath-chat__messages::-webkit-scrollbar-thumb,
    .gabbath-incidents::-webkit-scrollbar-thumb {
        background: rgba(0,0,0,0.2);
        border-radius: 3px;
    }
}

@supports (animation-timeline: scroll()) {
    /* scroll-driven animation for roster scroll indicator */
    .gabbath-roster__list {
        --scroll-progress: 0%;
        animation: gab-scroll-track linear;
        animation-timeline: scroll(self);
    }
    .gabbath-roster::after {
        content: '';
        position: absolute;
        bottom: 0;
        left: 0;
        right: 0;
        height: 2px;
        background: var(--gabbath-accent);
        transform-origin: left;
        transform: scaleX(var(--gab-scroll-progress));
    }
}

@supports (field-sizing: content) {
    .gabbath-chat__input {
        field-sizing: content;
        min-height: 40px;
        max-height: 120px;
    }
}

/* === keyframes === */

@keyframes gab-bubble-enter {
    from { opacity: 0; transform: translateY(8px) scale(0.96); }
    to { opacity: 1; transform: translateY(0) scale(1); }
}

@keyframes gab-feed-connect {
    from { opacity: 0; }
    to { opacity: 1; }
}

@keyframes gab-incident-flash {
    0%, 100% { background: transparent; }
    50% { background: var(--gabbath-status-red-bg); }
}

@keyframes gab-gaze-pulse {
    0%, 100% { opacity: 0.4; }
    50% { opacity: 0.8; }
}

@keyframes gab-vt-slide-out {
    from { opacity: 1; transform: translateX(0); }
    to { opacity: 0; transform: translateX(-20px); }
}

@keyframes gab-vt-slide-in {
    from { opacity: 0; transform: translateX(20px); }
    to { opacity: 1; transform: translateX(0); }
}

@keyframes gab-scroll-track {
    from { --gab-scroll-progress: 0%; }
    to { --gab-scroll-progress: 100%; }
}

@keyframes gab-suspicion-pulse {
    0% { --gab-suspicion-score: 0%; border-color: var(--gabbath-panel-border); }
    50% { --gab-suspicion-score: 100%; border-color: var(--gabbath-status-red); }
    100% { --gab-suspicion-score: 0%; border-color: var(--gabbath-panel-border); }
}

@keyframes gab-audio-bounce {
    0%, 100% { transform: scaleY(0.1); }
    25% { transform: scaleY(0.7); }
    50% { transform: scaleY(1.0); }
    75% { transform: scaleY(0.4); }
}

@keyframes gab-skeleton-shimmer {
    from { background-position: -200% 0; }
    to { background-position: 200% 0; }
}

@keyframes gab-recording-blink {
    0%, 100% { opacity: 1; }
    50% { opacity: 0; }
}

/* === skeleton loading states === */

.gab-skeleton {
    background: linear-gradient(90deg,
        var(--gabbath-panel-bg) 25%,
        rgba(0,0,0,0.05) 50%,
        var(--gabbath-panel-bg) 75%);
    background-size: 200% 100%;
    animation: gab-skeleton-shimmer 1.5s ease infinite;
    border-radius: 4px;
}

.gab-skeleton--text { height: 12px; width: 80%; margin-bottom: 8px; }
.gab-skeleton--text-sm { height: 10px; width: 60%; }
.gab-skeleton--avatar { width: 32px; height: 32px; border-radius: 50%; }
.gab-skeleton--badge { width: 48px; height: 18px; border-radius: 9px; }
.gab-skeleton--feed { width: 100%; aspect-ratio: 16/9; }
.gab-skeleton--bar { width: var(--roster-bar-width); height: var(--roster-bar-height); }

/* === focus management === */
/* WCAG 2.2 requirement -- focus-visible for keyboard nav, suppress for mouse */

:focus-visible {
    outline: 2px solid var(--gabbath-accent);
    outline-offset: 2px;
}

:focus:not(:focus-visible) {
    outline: none;
}

/* trap focus within modal dialogs */
gab-dialog[open] ~ *:not(gab-dialog) {
    inert: true;
}

/* skip link for accessibility */
.gab-skip-link {
    position: absolute;
    top: -100%;
    left: 0;
    background: var(--gabbath-accent);
    color: white;
    padding: 8px 16px;
    z-index: var(--z-critical-overlay);
    font-size: 14px;
    text-decoration: none;
    border-radius: 0 0 4px 0;
}

.gab-skip-link:focus {
    top: 0;
}

/* legacy ie -- keep until analytics show <0.1% ie11 traffic */
/* NOTE: last checked 2025-09-01: ie11 was 0.3%. some university
 * proctoring labs still run Win7 + ie11. can't drop until they upgrade */
/* stylelint-disable-next-line */
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
    gab-proctor-cell { display: block; height: 180px; }
    gab-live-feed { display: block; }
    gab-roster-item { display: flex; }
}

/*# sourceMappingURL=vendor.a7f3c2e9.css.map */
